| # Copyright 2013-2014 Gentoo Foundation |
| # Distributed under the terms of the GNU General Public License v2 |
| |
| import logging |
| import sys |
| |
| import portage |
| from portage import os |
| from portage.util import normalize_path, writemsg_level, _recursive_file_list |
| from portage.util._argparse import ArgumentParser |
| from portage.util._async.run_main_scheduler import run_main_scheduler |
| from portage.util._async.SchedulerInterface import SchedulerInterface |
| from portage.util._eventloop.global_event_loop import global_event_loop |
| from .Config import Config |
| from .MirrorDistTask import MirrorDistTask |
| |
| if sys.hexversion >= 0x3000000: |
| # pylint: disable=W0622 |
| long = int |
| |
| seconds_per_day = 24 * 60 * 60 |
| |
| common_options = ( |
| { |
| "longopt" : "--dry-run", |
| "help" : "perform a trial run with no changes made (usually combined " |
| "with --verbose)", |
| "action" : "store_true" |
| }, |
| { |
| "longopt" : "--verbose", |
| "shortopt" : "-v", |
| "help" : "display extra information on stderr " |
| "(multiple occurences increase verbosity)", |
| "action" : "count", |
| "default" : 0, |
| }, |
| { |
| "longopt" : "--ignore-default-opts", |
| "help" : "do not use the EMIRRORDIST_DEFAULT_OPTS environment variable", |
| "action" : "store_true" |
| }, |
| { |
| "longopt" : "--distfiles", |
| "help" : "distfiles directory to use (required)", |
| "metavar" : "DIR" |
| }, |
| { |
| "longopt" : "--jobs", |
| "shortopt" : "-j", |
| "help" : "number of concurrent jobs to run", |
| "type" : int |
| }, |
| { |
| "longopt" : "--load-average", |
| "shortopt" : "-l", |
| "help" : "load average limit for spawning of new concurrent jobs", |
| "metavar" : "LOAD", |
| "type" : float |
| }, |
| { |
| "longopt" : "--tries", |
| "help" : "maximum number of tries per file, 0 means unlimited (default is 10)", |
| "default" : 10, |
| "type" : int |
| }, |
| { |
| "longopt" : "--repo", |
| "help" : "name of repo to operate on" |
| }, |
| { |
| "longopt" : "--config-root", |
| "help" : "location of portage config files", |
| "metavar" : "DIR" |
| }, |
| { |
| "longopt" : "--portdir", |
| "help" : "override the PORTDIR variable (deprecated in favor of --repositories-configuration)", |
| "metavar" : "DIR" |
| }, |
| { |
| "longopt" : "--portdir-overlay", |
| "help" : "override the PORTDIR_OVERLAY variable (deprecated in favor of --repositories-configuration)" |
| }, |
| { |
| "longopt" : "--repositories-configuration", |
| "help" : "override configuration of repositories (in format of repos.conf)" |
| }, |
| { |
| "longopt" : "--strict-manifests", |
| "help" : "manually override \"strict\" FEATURES setting", |
| "choices" : ("y", "n"), |
| "metavar" : "<y|n>", |
| }, |
| { |
| "longopt" : "--failure-log", |
| "help" : "log file for fetch failures, with tab-delimited " |
| "output, for reporting purposes", |
| "metavar" : "FILE" |
| }, |
| { |
| "longopt" : "--success-log", |
| "help" : "log file for fetch successes, with tab-delimited " |
| "output, for reporting purposes", |
| "metavar" : "FILE" |
| }, |
| { |
| "longopt" : "--scheduled-deletion-log", |
| "help" : "log file for scheduled deletions, with tab-delimited " |
| "output, for reporting purposes", |
| "metavar" : "FILE" |
| }, |
| { |
| "longopt" : "--delete", |
| "help" : "enable deletion of unused distfiles", |
| "action" : "store_true" |
| }, |
| { |
| "longopt" : "--deletion-db", |
| "help" : "database file used to track lifetime of files " |
| "scheduled for delayed deletion", |
| "metavar" : "FILE" |
| }, |
| { |
| "longopt" : "--deletion-delay", |
| "help" : "delay time for deletion, measured in seconds", |
| "metavar" : "SECONDS" |
| }, |
| { |
| "longopt" : "--temp-dir", |
| "help" : "temporary directory for downloads", |
| "metavar" : "DIR" |
| }, |
| { |
| "longopt" : "--mirror-overrides", |
| "help" : "file holding a list of mirror overrides", |
| "metavar" : "FILE" |
| }, |
| { |
| "longopt" : "--mirror-skip", |
| "help" : "comma delimited list of mirror targets to skip " |
| "when fetching" |
| }, |
| { |
| "longopt" : "--restrict-mirror-exemptions", |
| "help" : "comma delimited list of mirror targets for which to " |
| "ignore RESTRICT=\"mirror\"" |
| }, |
| { |
| "longopt" : "--verify-existing-digest", |
| "help" : "use digest as a verification of whether existing " |
| "distfiles are valid", |
| "action" : "store_true" |
| }, |
| { |
| "longopt" : "--distfiles-local", |
| "help" : "distfiles-local directory to use", |
| "metavar" : "DIR" |
| }, |
| { |
| "longopt" : "--distfiles-db", |
| "help" : "database file used to track which ebuilds a " |
| "distfile belongs to", |
| "metavar" : "FILE" |
| }, |
| { |
| "longopt" : "--recycle-dir", |
| "help" : "directory for extended retention of files that " |
| "are removed from distdir with the --delete option", |
| "metavar" : "DIR" |
| }, |
| { |
| "longopt" : "--recycle-db", |
| "help" : "database file used to track lifetime of files " |
| "in recycle dir", |
| "metavar" : "FILE" |
| }, |
| { |
| "longopt" : "--recycle-deletion-delay", |
| "help" : "delay time for deletion of unused files from " |
| "recycle dir, measured in seconds (defaults to " |
| "the equivalent of 60 days)", |
| "default" : 60 * seconds_per_day, |
| "metavar" : "SECONDS", |
| "type" : int |
| }, |
| { |
| "longopt" : "--fetch-log-dir", |
| "help" : "directory for individual fetch logs", |
| "metavar" : "DIR" |
| }, |
| { |
| "longopt" : "--whitelist-from", |
| "help" : "specifies a file containing a list of files to " |
| "whitelist, one per line, # prefixed lines ignored", |
| "action" : "append", |
| "metavar" : "FILE" |
| }, |
| ) |
| |
| def parse_args(args): |
| description = "emirrordist - a fetch tool for mirroring " \ |
| "of package distfiles" |
| usage = "emirrordist [options] <action>" |
| parser = ArgumentParser(description=description, usage=usage) |
| |
| actions = parser.add_argument_group('Actions') |
| actions.add_argument("--version", |
| action="store_true", |
| help="display portage version and exit") |
| actions.add_argument("--mirror", |
| action="store_true", |
| help="mirror distfiles for the selected repository") |
| |
| common = parser.add_argument_group('Common options') |
| for opt_info in common_options: |
| opt_pargs = [opt_info["longopt"]] |
| if opt_info.get("shortopt"): |
| opt_pargs.append(opt_info["shortopt"]) |
| opt_kwargs = {"help" : opt_info["help"]} |
| for k in ("action", "choices", "default", "metavar", "type"): |
| if k in opt_info: |
| opt_kwargs[k] = opt_info[k] |
| common.add_argument(*opt_pargs, **opt_kwargs) |
| |
| options, args = parser.parse_known_args(args) |
| |
| return (parser, options, args) |
| |
| def emirrordist_main(args): |
| |
| # The calling environment is ignored, so the program is |
| # completely controlled by commandline arguments. |
| env = {} |
| |
| if not sys.stdout.isatty(): |
| portage.output.nocolor() |
| env['NOCOLOR'] = 'true' |
| |
| parser, options, args = parse_args(args) |
| |
| if options.version: |
| sys.stdout.write("Portage %s\n" % portage.VERSION) |
| return os.EX_OK |
| |
| config_root = options.config_root |
| |
| if options.portdir is not None: |
| writemsg_level("emirrordist: warning: --portdir option is deprecated in favor of --repositories-configuration option\n", |
| level=logging.WARNING, noiselevel=-1) |
| if options.portdir_overlay is not None: |
| writemsg_level("emirrordist: warning: --portdir-overlay option is deprecated in favor of --repositories-configuration option\n", |
| level=logging.WARNING, noiselevel=-1) |
| |
| if options.repositories_configuration is not None: |
| env['PORTAGE_REPOSITORIES'] = options.repositories_configuration |
| elif options.portdir_overlay is not None: |
| env['PORTDIR_OVERLAY'] = options.portdir_overlay |
| |
| if options.portdir is not None: |
| env['PORTDIR'] = options.portdir |
| |
| settings = portage.config(config_root=config_root, |
| local_config=False, env=env) |
| |
| default_opts = None |
| if not options.ignore_default_opts: |
| default_opts = settings.get('EMIRRORDIST_DEFAULT_OPTS', '').split() |
| |
| if default_opts: |
| parser, options, args = parse_args(default_opts + args) |
| |
| settings = portage.config(config_root=config_root, |
| local_config=False, env=env) |
| |
| if options.repo is None: |
| if len(settings.repositories.prepos) == 2: |
| for repo in settings.repositories: |
| if repo.name != "DEFAULT": |
| options.repo = repo.name |
| break |
| |
| if options.repo is None: |
| parser.error("--repo option is required") |
| |
| repo_path = settings.repositories.treemap.get(options.repo) |
| if repo_path is None: |
| parser.error("Unable to locate repository named '%s'" % (options.repo,)) |
| |
| if options.jobs is not None: |
| options.jobs = int(options.jobs) |
| |
| if options.load_average is not None: |
| options.load_average = float(options.load_average) |
| |
| if options.failure_log is not None: |
| options.failure_log = normalize_path( |
| os.path.abspath(options.failure_log)) |
| |
| parent_dir = os.path.dirname(options.failure_log) |
| if not (os.path.isdir(parent_dir) and |
| os.access(parent_dir, os.W_OK|os.X_OK)): |
| parser.error(("--failure-log '%s' parent is not a " |
| "writable directory") % options.failure_log) |
| |
| if options.success_log is not None: |
| options.success_log = normalize_path( |
| os.path.abspath(options.success_log)) |
| |
| parent_dir = os.path.dirname(options.success_log) |
| if not (os.path.isdir(parent_dir) and |
| os.access(parent_dir, os.W_OK|os.X_OK)): |
| parser.error(("--success-log '%s' parent is not a " |
| "writable directory") % options.success_log) |
| |
| if options.scheduled_deletion_log is not None: |
| options.scheduled_deletion_log = normalize_path( |
| os.path.abspath(options.scheduled_deletion_log)) |
| |
| parent_dir = os.path.dirname(options.scheduled_deletion_log) |
| if not (os.path.isdir(parent_dir) and |
| os.access(parent_dir, os.W_OK|os.X_OK)): |
| parser.error(("--scheduled-deletion-log '%s' parent is not a " |
| "writable directory") % options.scheduled_deletion_log) |
| |
| if options.deletion_db is None: |
| parser.error("--scheduled-deletion-log requires --deletion-db") |
| |
| if options.deletion_delay is not None: |
| options.deletion_delay = long(options.deletion_delay) |
| if options.deletion_db is None: |
| parser.error("--deletion-delay requires --deletion-db") |
| |
| if options.deletion_db is not None: |
| if options.deletion_delay is None: |
| parser.error("--deletion-db requires --deletion-delay") |
| options.deletion_db = normalize_path( |
| os.path.abspath(options.deletion_db)) |
| |
| if options.temp_dir is not None: |
| options.temp_dir = normalize_path( |
| os.path.abspath(options.temp_dir)) |
| |
| if not (os.path.isdir(options.temp_dir) and |
| os.access(options.temp_dir, os.W_OK|os.X_OK)): |
| parser.error(("--temp-dir '%s' is not a " |
| "writable directory") % options.temp_dir) |
| |
| if options.distfiles is not None: |
| options.distfiles = normalize_path( |
| os.path.abspath(options.distfiles)) |
| |
| if not (os.path.isdir(options.distfiles) and |
| os.access(options.distfiles, os.W_OK|os.X_OK)): |
| parser.error(("--distfiles '%s' is not a " |
| "writable directory") % options.distfiles) |
| else: |
| parser.error("missing required --distfiles parameter") |
| |
| if options.mirror_overrides is not None: |
| options.mirror_overrides = normalize_path( |
| os.path.abspath(options.mirror_overrides)) |
| |
| if not (os.access(options.mirror_overrides, os.R_OK) and |
| os.path.isfile(options.mirror_overrides)): |
| parser.error( |
| "--mirror-overrides-file '%s' is not a readable file" % |
| options.mirror_overrides) |
| |
| if options.distfiles_local is not None: |
| options.distfiles_local = normalize_path( |
| os.path.abspath(options.distfiles_local)) |
| |
| if not (os.path.isdir(options.distfiles_local) and |
| os.access(options.distfiles_local, os.W_OK|os.X_OK)): |
| parser.error(("--distfiles-local '%s' is not a " |
| "writable directory") % options.distfiles_local) |
| |
| if options.distfiles_db is not None: |
| options.distfiles_db = normalize_path( |
| os.path.abspath(options.distfiles_db)) |
| |
| if options.tries is not None: |
| options.tries = int(options.tries) |
| |
| if options.recycle_dir is not None: |
| options.recycle_dir = normalize_path( |
| os.path.abspath(options.recycle_dir)) |
| if not (os.path.isdir(options.recycle_dir) and |
| os.access(options.recycle_dir, os.W_OK|os.X_OK)): |
| parser.error(("--recycle-dir '%s' is not a " |
| "writable directory") % options.recycle_dir) |
| |
| if options.recycle_db is not None: |
| if options.recycle_dir is None: |
| parser.error("--recycle-db requires " |
| "--recycle-dir to be specified") |
| options.recycle_db = normalize_path( |
| os.path.abspath(options.recycle_db)) |
| |
| if options.recycle_deletion_delay is not None: |
| options.recycle_deletion_delay = \ |
| long(options.recycle_deletion_delay) |
| |
| if options.fetch_log_dir is not None: |
| options.fetch_log_dir = normalize_path( |
| os.path.abspath(options.fetch_log_dir)) |
| |
| if not (os.path.isdir(options.fetch_log_dir) and |
| os.access(options.fetch_log_dir, os.W_OK|os.X_OK)): |
| parser.error(("--fetch-log-dir '%s' is not a " |
| "writable directory") % options.fetch_log_dir) |
| |
| if options.whitelist_from: |
| normalized_paths = [] |
| for x in options.whitelist_from: |
| path = normalize_path(os.path.abspath(x)) |
| if not os.access(path, os.R_OK): |
| parser.error("--whitelist-from '%s' is not readable" % x) |
| if os.path.isfile(path): |
| normalized_paths.append(path) |
| elif os.path.isdir(path): |
| for file in _recursive_file_list(path): |
| if not os.access(file, os.R_OK): |
| parser.error("--whitelist-from '%s' directory contains not readable file '%s'" % (x, file)) |
| normalized_paths.append(file) |
| else: |
| parser.error("--whitelist-from '%s' is not a regular file or a directory" % x) |
| options.whitelist_from = normalized_paths |
| |
| if options.strict_manifests is not None: |
| if options.strict_manifests == "y": |
| settings.features.add("strict") |
| else: |
| settings.features.discard("strict") |
| |
| settings.lock() |
| |
| portdb = portage.portdbapi(mysettings=settings) |
| |
| # Limit ebuilds to the specified repo. |
| portdb.porttrees = [repo_path] |
| |
| portage.util.initialize_logger() |
| |
| if options.verbose > 0: |
| l = logging.getLogger() |
| l.setLevel(l.getEffectiveLevel() - 10 * options.verbose) |
| |
| with Config(options, portdb, |
| SchedulerInterface(global_event_loop())) as config: |
| |
| if not options.mirror: |
| parser.error('No action specified') |
| |
| returncode = os.EX_OK |
| |
| if options.mirror: |
| signum = run_main_scheduler(MirrorDistTask(config)) |
| if signum is not None: |
| sys.exit(128 + signum) |
| |
| return returncode |