diff options
Diffstat (limited to 'pym/_emerge/actions.py')
-rw-r--r-- | pym/_emerge/actions.py | 2725 |
1 files changed, 2725 insertions, 0 deletions
diff --git a/pym/_emerge/actions.py b/pym/_emerge/actions.py new file mode 100644 index 000000000..ca3fca518 --- /dev/null +++ b/pym/_emerge/actions.py @@ -0,0 +1,2725 @@ +import commands +import errno +import logging +import os +import platform +import pwd +import re +import shlex +import signal +import socket +import stat +import sys +import textwrap +import time +from itertools import chain, izip + +try: + import portage +except ImportError: + from os import path as osp + sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym")) + import portage + +from portage import digraph +from portage.cache.cache_errors import CacheError +from portage.const import NEWS_LIB_PATH +from portage.output import blue, bold, colorize, create_color_func, darkgreen, \ + red, yellow +good = create_color_func("GOOD") +bad = create_color_func("BAD") +from portage.sets import load_default_config, SETPREFIX +from portage.sets.base import InternalPackageSet +from portage.util import cmp_sort_key, writemsg, writemsg_level + +from _emerge.clear_caches import clear_caches +from _emerge.countdown import countdown +from _emerge.create_depgraph_params import create_depgraph_params +from _emerge.Dependency import Dependency +from _emerge.depgraph import depgraph, resume_depgraph +from _emerge.DepPrioritySatisfiedRange import DepPrioritySatisfiedRange +from _emerge.emergelog import emergelog +from _emerge.is_valid_package_atom import is_valid_package_atom +from _emerge.MetadataRegen import MetadataRegen +from _emerge.Package import Package +from _emerge.ProgressHandler import ProgressHandler +from _emerge.RootConfig import RootConfig +from _emerge.Scheduler import Scheduler +from _emerge.search import search +from _emerge.SetArg import SetArg +from _emerge.show_invalid_depstring_notice import show_invalid_depstring_notice +from _emerge.unmerge import unmerge +from _emerge.UnmergeDepPriority import UnmergeDepPriority +from _emerge.UseFlagDisplay import UseFlagDisplay +from _emerge.userquery import userquery + +def action_build(settings, trees, mtimedb, + myopts, myaction, myfiles, spinner): + + # validate the state of the resume data + # so that we can make assumptions later. + for k in ("resume", "resume_backup"): + if k not in mtimedb: + continue + resume_data = mtimedb[k] + if not isinstance(resume_data, dict): + del mtimedb[k] + continue + mergelist = resume_data.get("mergelist") + if not isinstance(mergelist, list): + del mtimedb[k] + continue + for x in mergelist: + if not (isinstance(x, list) and len(x) == 4): + continue + pkg_type, pkg_root, pkg_key, pkg_action = x + if pkg_root not in trees: + # Current $ROOT setting differs, + # so the list must be stale. + mergelist = None + break + if not mergelist: + del mtimedb[k] + continue + resume_opts = resume_data.get("myopts") + if not isinstance(resume_opts, (dict, list)): + del mtimedb[k] + continue + favorites = resume_data.get("favorites") + if not isinstance(favorites, list): + del mtimedb[k] + continue + + resume = False + if "--resume" in myopts and \ + ("resume" in mtimedb or + "resume_backup" in mtimedb): + resume = True + if "resume" not in mtimedb: + mtimedb["resume"] = mtimedb["resume_backup"] + del mtimedb["resume_backup"] + mtimedb.commit() + # "myopts" is a list for backward compatibility. + resume_opts = mtimedb["resume"].get("myopts", []) + if isinstance(resume_opts, list): + resume_opts = dict((k,True) for k in resume_opts) + for opt in ("--ask", "--color", "--skipfirst", "--tree"): + resume_opts.pop(opt, None) + + # Current options always override resume_opts. + resume_opts.update(myopts) + myopts.clear() + myopts.update(resume_opts) + + if "--debug" in myopts: + writemsg_level("myopts %s\n" % (myopts,)) + + # Adjust config according to options of the command being resumed. + for myroot in trees: + mysettings = trees[myroot]["vartree"].settings + mysettings.unlock() + adjust_config(myopts, mysettings) + mysettings.lock() + del myroot, mysettings + + ldpath_mtimes = mtimedb["ldpath"] + favorites=[] + merge_count = 0 + buildpkgonly = "--buildpkgonly" in myopts + pretend = "--pretend" in myopts + fetchonly = "--fetchonly" in myopts or "--fetch-all-uri" in myopts + ask = "--ask" in myopts + nodeps = "--nodeps" in myopts + oneshot = "--oneshot" in myopts or "--onlydeps" in myopts + tree = "--tree" in myopts + if nodeps and tree: + tree = False + del myopts["--tree"] + portage.writemsg(colorize("WARN", " * ") + \ + "--tree is broken with --nodeps. Disabling...\n") + debug = "--debug" in myopts + verbose = "--verbose" in myopts + quiet = "--quiet" in myopts + if pretend or fetchonly: + # make the mtimedb readonly + mtimedb.filename = None + if '--digest' in myopts or 'digest' in settings.features: + if '--digest' in myopts: + msg = "The --digest option" + else: + msg = "The FEATURES=digest setting" + + msg += " can prevent corruption from being" + \ + " noticed. The `repoman manifest` command is the preferred" + \ + " way to generate manifests and it is capable of doing an" + \ + " entire repository or category at once." + prefix = bad(" * ") + writemsg(prefix + "\n") + from textwrap import wrap + for line in wrap(msg, 72): + writemsg("%s%s\n" % (prefix, line)) + writemsg(prefix + "\n") + + if "--quiet" not in myopts and \ + ("--pretend" in myopts or "--ask" in myopts or \ + "--tree" in myopts or "--verbose" in myopts): + action = "" + if "--fetchonly" in myopts or "--fetch-all-uri" in myopts: + action = "fetched" + elif "--buildpkgonly" in myopts: + action = "built" + else: + action = "merged" + if "--tree" in myopts and action != "fetched": # Tree doesn't work with fetching + print + print darkgreen("These are the packages that would be %s, in reverse order:") % action + print + else: + print + print darkgreen("These are the packages that would be %s, in order:") % action + print + + show_spinner = "--quiet" not in myopts and "--nodeps" not in myopts + if not show_spinner: + spinner.update = spinner.update_quiet + + if resume: + favorites = mtimedb["resume"].get("favorites") + if not isinstance(favorites, list): + favorites = [] + + if show_spinner: + print "Calculating dependencies ", + myparams = create_depgraph_params(myopts, myaction) + + resume_data = mtimedb["resume"] + mergelist = resume_data["mergelist"] + if mergelist and "--skipfirst" in myopts: + for i, task in enumerate(mergelist): + if isinstance(task, list) and \ + task and task[-1] == "merge": + del mergelist[i] + break + + success = False + mydepgraph = None + try: + success, mydepgraph, dropped_tasks = resume_depgraph( + settings, trees, mtimedb, myopts, myparams, spinner) + except (portage.exception.PackageNotFound, + depgraph.UnsatisfiedResumeDep), e: + if isinstance(e, depgraph.UnsatisfiedResumeDep): + mydepgraph = e.depgraph + if show_spinner: + print + from textwrap import wrap + from portage.output import EOutput + out = EOutput() + + resume_data = mtimedb["resume"] + mergelist = resume_data.get("mergelist") + if not isinstance(mergelist, list): + mergelist = [] + if mergelist and debug or (verbose and not quiet): + out.eerror("Invalid resume list:") + out.eerror("") + indent = " " + for task in mergelist: + if isinstance(task, list): + out.eerror(indent + str(tuple(task))) + out.eerror("") + + if isinstance(e, depgraph.UnsatisfiedResumeDep): + out.eerror("One or more packages are either masked or " + \ + "have missing dependencies:") + out.eerror("") + indent = " " + for dep in e.value: + if dep.atom is None: + out.eerror(indent + "Masked package:") + out.eerror(2 * indent + str(dep.parent)) + out.eerror("") + else: + out.eerror(indent + str(dep.atom) + " pulled in by:") + out.eerror(2 * indent + str(dep.parent)) + out.eerror("") + msg = "The resume list contains packages " + \ + "that are either masked or have " + \ + "unsatisfied dependencies. " + \ + "Please restart/continue " + \ + "the operation manually, or use --skipfirst " + \ + "to skip the first package in the list and " + \ + "any other packages that may be " + \ + "masked or have missing dependencies." + for line in wrap(msg, 72): + out.eerror(line) + elif isinstance(e, portage.exception.PackageNotFound): + out.eerror("An expected package is " + \ + "not available: %s" % str(e)) + out.eerror("") + msg = "The resume list contains one or more " + \ + "packages that are no longer " + \ + "available. Please restart/continue " + \ + "the operation manually." + for line in wrap(msg, 72): + out.eerror(line) + else: + if show_spinner: + print "\b\b... done!" + + if success: + if dropped_tasks: + portage.writemsg("!!! One or more packages have been " + \ + "dropped due to\n" + \ + "!!! masking or unsatisfied dependencies:\n\n", + noiselevel=-1) + for task in dropped_tasks: + portage.writemsg(" " + str(task) + "\n", noiselevel=-1) + portage.writemsg("\n", noiselevel=-1) + del dropped_tasks + else: + if mydepgraph is not None: + mydepgraph.display_problems() + if not (ask or pretend): + # delete the current list and also the backup + # since it's probably stale too. + for k in ("resume", "resume_backup"): + mtimedb.pop(k, None) + mtimedb.commit() + + return 1 + else: + if ("--resume" in myopts): + print darkgreen("emerge: It seems we have nothing to resume...") + return os.EX_OK + + myparams = create_depgraph_params(myopts, myaction) + if "--quiet" not in myopts and "--nodeps" not in myopts: + print "Calculating dependencies ", + sys.stdout.flush() + mydepgraph = depgraph(settings, trees, myopts, myparams, spinner) + try: + retval, favorites = mydepgraph.select_files(myfiles) + except portage.exception.PackageNotFound, e: + portage.writemsg("\n!!! %s\n" % str(e), noiselevel=-1) + return 1 + except portage.exception.PackageSetNotFound, e: + root_config = trees[settings["ROOT"]]["root_config"] + display_missing_pkg_set(root_config, e.value) + return 1 + if show_spinner: + print "\b\b... done!" + if not retval: + mydepgraph.display_problems() + return 1 + + if "--pretend" not in myopts and \ + ("--ask" in myopts or "--tree" in myopts or \ + "--verbose" in myopts) and \ + not ("--quiet" in myopts and "--ask" not in myopts): + if "--resume" in myopts: + mymergelist = mydepgraph.altlist() + if len(mymergelist) == 0: + print colorize("INFORM", "emerge: It seems we have nothing to resume...") + return os.EX_OK + favorites = mtimedb["resume"]["favorites"] + retval = mydepgraph.display( + mydepgraph.altlist(reversed=tree), + favorites=favorites) + mydepgraph.display_problems() + if retval != os.EX_OK: + return retval + prompt="Would you like to resume merging these packages?" + else: + retval = mydepgraph.display( + mydepgraph.altlist(reversed=("--tree" in myopts)), + favorites=favorites) + mydepgraph.display_problems() + if retval != os.EX_OK: + return retval + mergecount=0 + for x in mydepgraph.altlist(): + if isinstance(x, Package) and x.operation == "merge": + mergecount += 1 + + if mergecount==0: + sets = trees[settings["ROOT"]]["root_config"].sets + world_candidates = None + if "--noreplace" in myopts and \ + not oneshot and favorites: + # Sets that are not world candidates are filtered + # out here since the favorites list needs to be + # complete for depgraph.loadResumeCommand() to + # operate correctly. + world_candidates = [x for x in favorites \ + if not (x.startswith(SETPREFIX) and \ + not sets[x[1:]].world_candidate)] + if "--noreplace" in myopts and \ + not oneshot and world_candidates: + print + for x in world_candidates: + print " %s %s" % (good("*"), x) + prompt="Would you like to add these packages to your world favorites?" + elif settings["AUTOCLEAN"] and "yes"==settings["AUTOCLEAN"]: + prompt="Nothing to merge; would you like to auto-clean packages?" + else: + print + print "Nothing to merge; quitting." + print + return os.EX_OK + elif "--fetchonly" in myopts or "--fetch-all-uri" in myopts: + prompt="Would you like to fetch the source files for these packages?" + else: + prompt="Would you like to merge these packages?" + print + if "--ask" in myopts and userquery(prompt) == "No": + print + print "Quitting." + print + return os.EX_OK + # Don't ask again (e.g. when auto-cleaning packages after merge) + myopts.pop("--ask", None) + + if ("--pretend" in myopts) and not ("--fetchonly" in myopts or "--fetch-all-uri" in myopts): + if ("--resume" in myopts): + mymergelist = mydepgraph.altlist() + if len(mymergelist) == 0: + print colorize("INFORM", "emerge: It seems we have nothing to resume...") + return os.EX_OK + favorites = mtimedb["resume"]["favorites"] + retval = mydepgraph.display( + mydepgraph.altlist(reversed=tree), + favorites=favorites) + mydepgraph.display_problems() + if retval != os.EX_OK: + return retval + else: + retval = mydepgraph.display( + mydepgraph.altlist(reversed=("--tree" in myopts)), + favorites=favorites) + mydepgraph.display_problems() + if retval != os.EX_OK: + return retval + if "--buildpkgonly" in myopts: + graph_copy = mydepgraph.digraph.clone() + removed_nodes = set() + for node in graph_copy: + if not isinstance(node, Package) or \ + node.operation == "nomerge": + removed_nodes.add(node) + graph_copy.difference_update(removed_nodes) + if not graph_copy.hasallzeros(ignore_priority = \ + DepPrioritySatisfiedRange.ignore_medium): + print "\n!!! --buildpkgonly requires all dependencies to be merged." + print "!!! You have to merge the dependencies before you can build this package.\n" + return 1 + else: + if "--buildpkgonly" in myopts: + graph_copy = mydepgraph.digraph.clone() + removed_nodes = set() + for node in graph_copy: + if not isinstance(node, Package) or \ + node.operation == "nomerge": + removed_nodes.add(node) + graph_copy.difference_update(removed_nodes) + if not graph_copy.hasallzeros(ignore_priority = \ + DepPrioritySatisfiedRange.ignore_medium): + print "\n!!! --buildpkgonly requires all dependencies to be merged." + print "!!! Cannot merge requested packages. Merge deps and try again.\n" + return 1 + + if ("--resume" in myopts): + favorites=mtimedb["resume"]["favorites"] + mymergelist = mydepgraph.altlist() + mydepgraph.break_refs(mymergelist) + mergetask = Scheduler(settings, trees, mtimedb, myopts, + spinner, mymergelist, favorites, mydepgraph.schedulerGraph()) + del mydepgraph, mymergelist + clear_caches(trees) + + retval = mergetask.merge() + merge_count = mergetask.curval + else: + if "resume" in mtimedb and \ + "mergelist" in mtimedb["resume"] and \ + len(mtimedb["resume"]["mergelist"]) > 1: + mtimedb["resume_backup"] = mtimedb["resume"] + del mtimedb["resume"] + mtimedb.commit() + mtimedb["resume"]={} + # Stored as a dict starting with portage-2.1.6_rc1, and supported + # by >=portage-2.1.3_rc8. Versions <portage-2.1.3_rc8 only support + # a list type for options. + mtimedb["resume"]["myopts"] = myopts.copy() + + # Convert Atom instances to plain str. + mtimedb["resume"]["favorites"] = [str(x) for x in favorites] + + pkglist = mydepgraph.altlist() + mydepgraph.saveNomergeFavorites() + mydepgraph.break_refs(pkglist) + mergetask = Scheduler(settings, trees, mtimedb, myopts, + spinner, pkglist, favorites, mydepgraph.schedulerGraph()) + del mydepgraph, pkglist + clear_caches(trees) + + retval = mergetask.merge() + merge_count = mergetask.curval + + if retval == os.EX_OK and not (buildpkgonly or fetchonly or pretend): + if "yes" == settings.get("AUTOCLEAN"): + portage.writemsg_stdout(">>> Auto-cleaning packages...\n") + unmerge(trees[settings["ROOT"]]["root_config"], + myopts, "clean", [], + ldpath_mtimes, autoclean=1) + else: + portage.writemsg_stdout(colorize("WARN", "WARNING:") + + " AUTOCLEAN is disabled. This can cause serious" + + " problems due to overlapping packages.\n") + trees[settings["ROOT"]]["vartree"].dbapi.plib_registry.pruneNonExisting() + + return retval + +def action_config(settings, trees, myopts, myfiles): + if len(myfiles) != 1: + print red("!!! config can only take a single package atom at this time\n") + sys.exit(1) + if not is_valid_package_atom(myfiles[0]): + portage.writemsg("!!! '%s' is not a valid package atom.\n" % myfiles[0], + noiselevel=-1) + portage.writemsg("!!! Please check ebuild(5) for full details.\n") + portage.writemsg("!!! (Did you specify a version but forget to prefix with '='?)\n") + sys.exit(1) + print + try: + pkgs = trees[settings["ROOT"]]["vartree"].dbapi.match(myfiles[0]) + except portage.exception.AmbiguousPackageName, e: + # Multiple matches thrown from cpv_expand + pkgs = e.args[0] + if len(pkgs) == 0: + print "No packages found.\n" + sys.exit(0) + elif len(pkgs) > 1: + if "--ask" in myopts: + options = [] + print "Please select a package to configure:" + idx = 0 + for pkg in pkgs: + idx += 1 + options.append(str(idx)) + print options[-1]+") "+pkg + print "X) Cancel" + options.append("X") + idx = userquery("Selection?", options) + if idx == "X": + sys.exit(0) + pkg = pkgs[int(idx)-1] + else: + print "The following packages available:" + for pkg in pkgs: + print "* "+pkg + print "\nPlease use a specific atom or the --ask option." + sys.exit(1) + else: + pkg = pkgs[0] + + print + if "--ask" in myopts: + if userquery("Ready to configure "+pkg+"?") == "No": + sys.exit(0) + else: + print "Configuring pkg..." + print + ebuildpath = trees[settings["ROOT"]]["vartree"].dbapi.findname(pkg) + mysettings = portage.config(clone=settings) + vardb = trees[mysettings["ROOT"]]["vartree"].dbapi + debug = mysettings.get("PORTAGE_DEBUG") == "1" + retval = portage.doebuild(ebuildpath, "config", mysettings["ROOT"], + mysettings, + debug=(settings.get("PORTAGE_DEBUG", "") == 1), cleanup=True, + mydbapi=trees[settings["ROOT"]]["vartree"].dbapi, tree="vartree") + if retval == os.EX_OK: + portage.doebuild(ebuildpath, "clean", mysettings["ROOT"], + mysettings, debug=debug, mydbapi=vardb, tree="vartree") + print + +def action_depclean(settings, trees, ldpath_mtimes, + myopts, action, myfiles, spinner): + # Kill packages that aren't explicitly merged or are required as a + # dependency of another package. World file is explicit. + + # Global depclean or prune operations are not very safe when there are + # missing dependencies since it's unknown how badly incomplete + # the dependency graph is, and we might accidentally remove packages + # that should have been pulled into the graph. On the other hand, it's + # relatively safe to ignore missing deps when only asked to remove + # specific packages. + allow_missing_deps = len(myfiles) > 0 + + msg = [] + msg.append("Always study the list of packages to be cleaned for any obvious\n") + msg.append("mistakes. Packages that are part of the world set will always\n") + msg.append("be kept. They can be manually added to this set with\n") + msg.append(good("`emerge --noreplace <atom>`") + ". Packages that are listed in\n") + msg.append("package.provided (see portage(5)) will be removed by\n") + msg.append("depclean, even if they are part of the world set.\n") + msg.append("\n") + msg.append("As a safety measure, depclean will not remove any packages\n") + msg.append("unless *all* required dependencies have been resolved. As a\n") + msg.append("consequence, it is often necessary to run %s\n" % \ + good("`emerge --update")) + msg.append(good("--newuse --deep @system @world`") + \ + " prior to depclean.\n") + + if action == "depclean" and "--quiet" not in myopts and not myfiles: + portage.writemsg_stdout("\n") + for x in msg: + portage.writemsg_stdout(colorize("WARN", " * ") + x) + + xterm_titles = "notitles" not in settings.features + myroot = settings["ROOT"] + root_config = trees[myroot]["root_config"] + getSetAtoms = root_config.setconfig.getSetAtoms + vardb = trees[myroot]["vartree"].dbapi + deselect = myopts.get('--deselect') != 'n' + + required_set_names = ("system", "world") + required_sets = {} + set_args = [] + + for s in required_set_names: + required_sets[s] = InternalPackageSet( + initial_atoms=getSetAtoms(s)) + + + # When removing packages, use a temporary version of world + # which excludes packages that are intended to be eligible for + # removal. + world_temp_set = required_sets["world"] + system_set = required_sets["system"] + + if not system_set or not world_temp_set: + + if not system_set: + writemsg_level("!!! You have no system list.\n", + level=logging.ERROR, noiselevel=-1) + + if not world_temp_set: + writemsg_level("!!! You have no world file.\n", + level=logging.WARNING, noiselevel=-1) + + writemsg_level("!!! Proceeding is likely to " + \ + "break your installation.\n", + level=logging.WARNING, noiselevel=-1) + if "--pretend" not in myopts: + countdown(int(settings["EMERGE_WARNING_DELAY"]), ">>> Depclean") + + if action == "depclean": + emergelog(xterm_titles, " >>> depclean") + + args_set = InternalPackageSet() + if myfiles: + args_set.update(myfiles) + matched_packages = False + for x in args_set: + if vardb.match(x): + matched_packages = True + break + if not matched_packages: + writemsg_level(">>> No packages selected for removal by %s\n" % \ + action) + return + + writemsg_level("\nCalculating dependencies ") + resolver_params = create_depgraph_params(myopts, "remove") + resolver = depgraph(settings, trees, myopts, resolver_params, spinner) + vardb = resolver.trees[myroot]["vartree"].dbapi + + if action == "depclean": + + if args_set: + + if deselect: + world_temp_set.clear() + + # Pull in everything that's installed but not matched + # by an argument atom since we don't want to clean any + # package if something depends on it. + for pkg in vardb: + spinner.update() + + try: + if args_set.findAtomForPackage(pkg) is None: + world_temp_set.add("=" + pkg.cpv) + continue + except portage.exception.InvalidDependString, e: + show_invalid_depstring_notice(pkg, + pkg.metadata["PROVIDE"], str(e)) + del e + world_temp_set.add("=" + pkg.cpv) + continue + + elif action == "prune": + + if deselect: + world_temp_set.clear() + + # Pull in everything that's installed since we don't + # to prune a package if something depends on it. + world_temp_set.update(vardb.cp_all()) + + if not args_set: + + # Try to prune everything that's slotted. + for cp in vardb.cp_all(): + if len(vardb.cp_list(cp)) > 1: + args_set.add(cp) + + # Remove atoms from world that match installed packages + # that are also matched by argument atoms, but do not remove + # them if they match the highest installed version. + for pkg in vardb: + spinner.update() + pkgs_for_cp = vardb.match_pkgs(pkg.cp) + if not pkgs_for_cp or pkg not in pkgs_for_cp: + raise AssertionError("package expected in matches: " + \ + "cp = %s, cpv = %s matches = %s" % \ + (pkg.cp, pkg.cpv, [str(x) for x in pkgs_for_cp])) + + highest_version = pkgs_for_cp[-1] + if pkg == highest_version: + # pkg is the highest version + world_temp_set.add("=" + pkg.cpv) + continue + + if len(pkgs_for_cp) <= 1: + raise AssertionError("more packages expected: " + \ + "cp = %s, cpv = %s matches = %s" % \ + (pkg.cp, pkg.cpv, [str(x) for x in pkgs_for_cp])) + + try: + if args_set.findAtomForPackage(pkg) is None: + world_temp_set.add("=" + pkg.cpv) + continue + except portage.exception.InvalidDependString, e: + show_invalid_depstring_notice(pkg, + pkg.metadata["PROVIDE"], str(e)) + del e + world_temp_set.add("=" + pkg.cpv) + continue + + set_args = {} + for s, package_set in required_sets.iteritems(): + set_atom = SETPREFIX + s + set_arg = SetArg(arg=set_atom, set=package_set, + root_config=resolver.roots[myroot]) + set_args[s] = set_arg + for atom in set_arg.set: + resolver._dep_stack.append( + Dependency(atom=atom, root=myroot, parent=set_arg)) + resolver.digraph.add(set_arg, None) + + success = resolver._complete_graph() + writemsg_level("\b\b... done!\n") + + resolver.display_problems() + + if not success: + return 1 + + def unresolved_deps(): + + unresolvable = set() + for dep in resolver._initially_unsatisfied_deps: + if isinstance(dep.parent, Package) and \ + (dep.priority > UnmergeDepPriority.SOFT): + unresolvable.add((dep.atom, dep.parent.cpv)) + + if not unresolvable: + return False + + if unresolvable and not allow_missing_deps: + prefix = bad(" * ") + msg = [] + msg.append("Dependencies could not be completely resolved due to") + msg.append("the following required packages not being installed:") + msg.append("") + for atom, parent in unresolvable: + msg.append(" %s pulled in by:" % (atom,)) + msg.append(" %s" % (parent,)) + msg.append("") + msg.append("Have you forgotten to run " + \ + good("`emerge --update --newuse --deep @system @world`") + " prior") + msg.append(("to %s? It may be necessary to manually " + \ + "uninstall packages that no longer") % action) + msg.append("exist in the portage tree since " + \ + "it may not be possible to satisfy their") + msg.append("dependencies. Also, be aware of " + \ + "the --with-bdeps option that is documented") + msg.append("in " + good("`man emerge`") + ".") + if action == "prune": + msg.append("") + msg.append("If you would like to ignore " + \ + "dependencies then use %s." % good("--nodeps")) + writemsg_level("".join("%s%s\n" % (prefix, line) for line in msg), + level=logging.ERROR, noiselevel=-1) + return True + return False + + if unresolved_deps(): + return 1 + + graph = resolver.digraph.copy() + required_pkgs_total = 0 + for node in graph: + if isinstance(node, Package): + required_pkgs_total += 1 + + def show_parents(child_node): + parent_nodes = graph.parent_nodes(child_node) + if not parent_nodes: + # With --prune, the highest version can be pulled in without any + # real parent since all installed packages are pulled in. In that + # case there's nothing to show here. + return + parent_strs = [] + for node in parent_nodes: + parent_strs.append(str(getattr(node, "cpv", node))) + parent_strs.sort() + msg = [] + msg.append(" %s pulled in by:\n" % (child_node.cpv,)) + for parent_str in parent_strs: + msg.append(" %s\n" % (parent_str,)) + msg.append("\n") + portage.writemsg_stdout("".join(msg), noiselevel=-1) + + def cmp_pkg_cpv(pkg1, pkg2): + """Sort Package instances by cpv.""" + if pkg1.cpv > pkg2.cpv: + return 1 + elif pkg1.cpv == pkg2.cpv: + return 0 + else: + return -1 + + def create_cleanlist(): + pkgs_to_remove = [] + + if action == "depclean": + if args_set: + + for pkg in sorted(vardb, key=cmp_sort_key(cmp_pkg_cpv)): + arg_atom = None + try: + arg_atom = args_set.findAtomForPackage(pkg) + except portage.exception.InvalidDependString: + # this error has already been displayed by now + continue + + if arg_atom: + if pkg not in graph: + pkgs_to_remove.append(pkg) + elif "--verbose" in myopts: + show_parents(pkg) + + else: + for pkg in sorted(vardb, key=cmp_sort_key(cmp_pkg_cpv)): + if pkg not in graph: + pkgs_to_remove.append(pkg) + elif "--verbose" in myopts: + show_parents(pkg) + + elif action == "prune": + # Prune really uses all installed instead of world. It's not + # a real reverse dependency so don't display it as such. + graph.remove(set_args["world"]) + + for atom in args_set: + for pkg in vardb.match_pkgs(atom): + if pkg not in graph: + pkgs_to_remove.append(pkg) + elif "--verbose" in myopts: + show_parents(pkg) + + if not pkgs_to_remove: + writemsg_level( + ">>> No packages selected for removal by %s\n" % action) + if "--verbose" not in myopts: + writemsg_level( + ">>> To see reverse dependencies, use %s\n" % \ + good("--verbose")) + if action == "prune": + writemsg_level( + ">>> To ignore dependencies, use %s\n" % \ + good("--nodeps")) + + return pkgs_to_remove + + cleanlist = create_cleanlist() + + if len(cleanlist): + clean_set = set(cleanlist) + + # Check if any of these package are the sole providers of libraries + # with consumers that have not been selected for removal. If so, these + # packages and any dependencies need to be added to the graph. + real_vardb = trees[myroot]["vartree"].dbapi + linkmap = real_vardb.linkmap + liblist = linkmap.listLibraryObjects() + consumer_cache = {} + provider_cache = {} + soname_cache = {} + consumer_map = {} + + writemsg_level(">>> Checking for lib consumers...\n") + + for pkg in cleanlist: + pkg_dblink = real_vardb._dblink(pkg.cpv) + provided_libs = set() + + for lib in liblist: + if pkg_dblink.isowner(lib, myroot): + provided_libs.add(lib) + + if not provided_libs: + continue + + consumers = {} + for lib in provided_libs: + lib_consumers = consumer_cache.get(lib) + if lib_consumers is None: + lib_consumers = linkmap.findConsumers(lib) + consumer_cache[lib] = lib_consumers + if lib_consumers: + consumers[lib] = lib_consumers + + if not consumers: + continue + + for lib, lib_consumers in consumers.items(): + for consumer_file in list(lib_consumers): + if pkg_dblink.isowner(consumer_file, myroot): + lib_consumers.remove(consumer_file) + if not lib_consumers: + del consumers[lib] + + if not consumers: + continue + + for lib, lib_consumers in consumers.iteritems(): + + soname = soname_cache.get(lib) + if soname is None: + soname = linkmap.getSoname(lib) + soname_cache[lib] = soname + + consumer_providers = [] + for lib_consumer in lib_consumers: + providers = provider_cache.get(lib) + if providers is None: + providers = linkmap.findProviders(lib_consumer) + provider_cache[lib_consumer] = providers + if soname not in providers: + # Why does this happen? + continue + consumer_providers.append( + (lib_consumer, providers[soname])) + + consumers[lib] = consumer_providers + + consumer_map[pkg] = consumers + + if consumer_map: + + search_files = set() + for consumers in consumer_map.itervalues(): + for lib, consumer_providers in consumers.iteritems(): + for lib_consumer, providers in consumer_providers: + search_files.add(lib_consumer) + search_files.update(providers) + + writemsg_level(">>> Assigning files to packages...\n") + file_owners = real_vardb._owners.getFileOwnerMap(search_files) + + for pkg, consumers in consumer_map.items(): + for lib, consumer_providers in consumers.items(): + lib_consumers = set() + + for lib_consumer, providers in consumer_providers: + owner_set = file_owners.get(lib_consumer) + provider_dblinks = set() + provider_pkgs = set() + + if len(providers) > 1: + for provider in providers: + provider_set = file_owners.get(provider) + if provider_set is not None: + provider_dblinks.update(provider_set) + + if len(provider_dblinks) > 1: + for provider_dblink in provider_dblinks: + pkg_key = ("installed", myroot, + provider_dblink.mycpv, "nomerge") + if pkg_key not in clean_set: + provider_pkgs.add(vardb.get(pkg_key)) + + if provider_pkgs: + continue + + if owner_set is not None: + lib_consumers.update(owner_set) + + for consumer_dblink in list(lib_consumers): + if ("installed", myroot, consumer_dblink.mycpv, + "nomerge") in clean_set: + lib_consumers.remove(consumer_dblink) + continue + + if lib_consumers: + consumers[lib] = lib_consumers + else: + del consumers[lib] + if not consumers: + del consumer_map[pkg] + + if consumer_map: + # TODO: Implement a package set for rebuilding consumer packages. + + msg = "In order to avoid breakage of link level " + \ + "dependencies, one or more packages will not be removed. " + \ + "This can be solved by rebuilding " + \ + "the packages that pulled them in." + + prefix = bad(" * ") + from textwrap import wrap + writemsg_level("".join(prefix + "%s\n" % line for \ + line in wrap(msg, 70)), level=logging.WARNING, noiselevel=-1) + + msg = [] + for pkg, consumers in consumer_map.iteritems(): + unique_consumers = set(chain(*consumers.values())) + unique_consumers = sorted(consumer.mycpv \ + for consumer in unique_consumers) + msg.append("") + msg.append(" %s pulled in by:" % (pkg.cpv,)) + for consumer in unique_consumers: + msg.append(" %s" % (consumer,)) + msg.append("") + writemsg_level("".join(prefix + "%s\n" % line for line in msg), + level=logging.WARNING, noiselevel=-1) + + # Add lib providers to the graph as children of lib consumers, + # and also add any dependencies pulled in by the provider. + writemsg_level(">>> Adding lib providers to graph...\n") + + for pkg, consumers in consumer_map.iteritems(): + for consumer_dblink in set(chain(*consumers.values())): + consumer_pkg = vardb.get(("installed", myroot, + consumer_dblink.mycpv, "nomerge")) + if not resolver._add_pkg(pkg, + Dependency(parent=consumer_pkg, + priority=UnmergeDepPriority(runtime=True), + root=pkg.root)): + resolver.display_problems() + return 1 + + writemsg_level("\nCalculating dependencies ") + success = resolver._complete_graph() + writemsg_level("\b\b... done!\n") + resolver.display_problems() + if not success: + return 1 + if unresolved_deps(): + return 1 + + graph = resolver.digraph.copy() + required_pkgs_total = 0 + for node in graph: + if isinstance(node, Package): + required_pkgs_total += 1 + cleanlist = create_cleanlist() + if not cleanlist: + return 0 + clean_set = set(cleanlist) + + # Use a topological sort to create an unmerge order such that + # each package is unmerged before it's dependencies. This is + # necessary to avoid breaking things that may need to run + # during pkg_prerm or pkg_postrm phases. + + # Create a new graph to account for dependencies between the + # packages being unmerged. + graph = digraph() + del cleanlist[:] + + dep_keys = ["DEPEND", "RDEPEND", "PDEPEND"] + runtime = UnmergeDepPriority(runtime=True) + runtime_post = UnmergeDepPriority(runtime_post=True) + buildtime = UnmergeDepPriority(buildtime=True) + priority_map = { + "RDEPEND": runtime, + "PDEPEND": runtime_post, + "DEPEND": buildtime, + } + + for node in clean_set: + graph.add(node, None) + mydeps = [] + node_use = node.metadata["USE"].split() + for dep_type in dep_keys: + depstr = node.metadata[dep_type] + if not depstr: + continue + try: + portage.dep._dep_check_strict = False + success, atoms = portage.dep_check(depstr, None, settings, + myuse=node_use, trees=resolver._graph_trees, + myroot=myroot) + finally: + portage.dep._dep_check_strict = True + if not success: + # Ignore invalid deps of packages that will + # be uninstalled anyway. + continue + + priority = priority_map[dep_type] + for atom in atoms: + if not isinstance(atom, portage.dep.Atom): + # Ignore invalid atoms returned from dep_check(). + continue + if atom.blocker: + continue + matches = vardb.match_pkgs(atom) + if not matches: + continue + for child_node in matches: + if child_node in clean_set: + graph.add(child_node, node, priority=priority) + + ordered = True + if len(graph.order) == len(graph.root_nodes()): + # If there are no dependencies between packages + # let unmerge() group them by cat/pn. + ordered = False + cleanlist = [pkg.cpv for pkg in graph.order] + else: + # Order nodes from lowest to highest overall reference count for + # optimal root node selection. + node_refcounts = {} + for node in graph.order: + node_refcounts[node] = len(graph.parent_nodes(node)) + def cmp_reference_count(node1, node2): + return node_refcounts[node1] - node_refcounts[node2] + graph.order.sort(key=cmp_sort_key(cmp_reference_count)) + + ignore_priority_range = [None] + ignore_priority_range.extend( + xrange(UnmergeDepPriority.MIN, UnmergeDepPriority.MAX + 1)) + while not graph.empty(): + for ignore_priority in ignore_priority_range: + nodes = graph.root_nodes(ignore_priority=ignore_priority) + if nodes: + break + if not nodes: + raise AssertionError("no root nodes") + if ignore_priority is not None: + # Some deps have been dropped due to circular dependencies, + # so only pop one node in order do minimize the number that + # are dropped. + del nodes[1:] + for node in nodes: + graph.remove(node) + cleanlist.append(node.cpv) + + unmerge(root_config, myopts, "unmerge", cleanlist, + ldpath_mtimes, ordered=ordered) + + if action == "prune": + return + + if not cleanlist and "--quiet" in myopts: + return + + print "Packages installed: "+str(len(vardb.cpv_all())) + print "Packages in world: " + \ + str(len(root_config.sets["world"].getAtoms())) + print "Packages in system: " + \ + str(len(root_config.sets["system"].getAtoms())) + print "Required packages: "+str(required_pkgs_total) + if "--pretend" in myopts: + print "Number to remove: "+str(len(cleanlist)) + else: + print "Number removed: "+str(len(cleanlist)) + +def action_deselect(settings, trees, opts, atoms): + root_config = trees[settings['ROOT']]['root_config'] + world_set = root_config.sets['world'] + if not hasattr(world_set, 'update'): + writemsg_level("World set does not appear to be mutable.\n", + level=logging.ERROR, noiselevel=-1) + return 1 + + vardb = root_config.trees['vartree'].dbapi + expanded_atoms = set(atoms) + from portage.dep import Atom + for atom in atoms: + for cpv in vardb.match(atom): + slot, = vardb.aux_get(cpv, ['SLOT']) + if not slot: + slot = '0' + expanded_atoms.add(Atom('%s:%s' % (portage.cpv_getkey(cpv), slot))) + + pretend = '--pretend' in opts + locked = False + if not pretend and hasattr(world_set, 'lock'): + world_set.lock() + locked = True + try: + discard_atoms = set() + world_set.load() + for atom in world_set: + if not isinstance(atom, Atom): + # nested set + continue + for arg_atom in expanded_atoms: + if arg_atom.intersects(atom) and \ + not (arg_atom.slot and not atom.slot): + discard_atoms.add(atom) + break + if discard_atoms: + for atom in sorted(discard_atoms): + print ">>> Removing %s from \"world\" favorites file..." % \ + colorize("INFORM", str(atom)) + + if '--ask' in opts: + prompt = "Would you like to remove these " + \ + "packages from your world favorites?" + if userquery(prompt) == 'No': + return os.EX_OK + + remaining = set(world_set) + remaining.difference_update(discard_atoms) + if not pretend: + world_set.replace(remaining) + else: + print ">>> No matching atoms found in \"world\" favorites file..." + finally: + if locked: + world_set.unlock() + return os.EX_OK + +def action_info(settings, trees, myopts, myfiles): + print getportageversion(settings["PORTDIR"], settings["ROOT"], + settings.profile_path, settings["CHOST"], + trees[settings["ROOT"]]["vartree"].dbapi) + header_width = 65 + header_title = "System Settings" + if myfiles: + print header_width * "=" + print header_title.rjust(int(header_width/2 + len(header_title)/2)) + print header_width * "=" + print "System uname: "+platform.platform(aliased=1) + + lastSync = portage.grabfile(os.path.join( + settings["PORTDIR"], "metadata", "timestamp.chk")) + print "Timestamp of tree:", + if lastSync: + print lastSync[0] + else: + print "Unknown" + + output=commands.getstatusoutput("distcc --version") + if not output[0]: + print str(output[1].split("\n",1)[0]), + if "distcc" in settings.features: + print "[enabled]" + else: + print "[disabled]" + + output=commands.getstatusoutput("ccache -V") + if not output[0]: + print str(output[1].split("\n",1)[0]), + if "ccache" in settings.features: + print "[enabled]" + else: + print "[disabled]" + + myvars = ["sys-devel/autoconf", "sys-devel/automake", "virtual/os-headers", + "sys-devel/binutils", "sys-devel/libtool", "dev-lang/python"] + myvars += portage.util.grabfile(settings["PORTDIR"]+"/profiles/info_pkgs") + myvars = portage.util.unique_array(myvars) + myvars.sort() + + for x in myvars: + if portage.isvalidatom(x): + pkg_matches = trees["/"]["vartree"].dbapi.match(x) + pkg_matches = [portage.catpkgsplit(cpv)[1:] for cpv in pkg_matches] + pkg_matches.sort(key=cmp_sort_key(portage.pkgcmp)) + pkgs = [] + for pn, ver, rev in pkg_matches: + if rev != "r0": + pkgs.append(ver + "-" + rev) + else: + pkgs.append(ver) + if pkgs: + pkgs = ", ".join(pkgs) + print "%-20s %s" % (x+":", pkgs) + else: + print "%-20s %s" % (x+":", "[NOT VALID]") + + libtool_vers = ",".join(trees["/"]["vartree"].dbapi.match("sys-devel/libtool")) + + if "--verbose" in myopts: + myvars=settings.keys() + else: + myvars = ['GENTOO_MIRRORS', 'CONFIG_PROTECT', 'CONFIG_PROTECT_MASK', + 'PORTDIR', 'DISTDIR', 'PKGDIR', 'PORTAGE_TMPDIR', + 'PORTDIR_OVERLAY', 'USE', 'CHOST', 'CFLAGS', 'CXXFLAGS', + 'ACCEPT_KEYWORDS', 'SYNC', 'FEATURES', 'EMERGE_DEFAULT_OPTS'] + + myvars.extend(portage.util.grabfile(settings["PORTDIR"]+"/profiles/info_vars")) + + myvars = portage.util.unique_array(myvars) + use_expand = settings.get('USE_EXPAND', '').split() + use_expand.sort() + use_expand_hidden = set( + settings.get('USE_EXPAND_HIDDEN', '').upper().split()) + alphabetical_use = '--alphabetical' in myopts + root_config = trees[settings["ROOT"]]['root_config'] + unset_vars = [] + myvars.sort() + for x in myvars: + if x in settings: + if x != "USE": + print '%s="%s"' % (x, settings[x]) + else: + use = set(settings["USE"].split()) + for varname in use_expand: + flag_prefix = varname.lower() + "_" + for f in list(use): + if f.startswith(flag_prefix): + use.remove(f) + use = list(use) + use.sort() + print 'USE="%s"' % " ".join(use), + for varname in use_expand: + myval = settings.get(varname) + if myval: + print '%s="%s"' % (varname, myval), + print + else: + unset_vars.append(x) + if unset_vars: + print "Unset: "+", ".join(unset_vars) + print + + if "--debug" in myopts: + for x in dir(portage): + module = getattr(portage, x) + if "cvs_id_string" in dir(module): + print "%s: %s" % (str(x), str(module.cvs_id_string)) + + # See if we can find any packages installed matching the strings + # passed on the command line + mypkgs = [] + vardb = trees[settings["ROOT"]]["vartree"].dbapi + portdb = trees[settings["ROOT"]]["porttree"].dbapi + for x in myfiles: + mypkgs.extend(vardb.match(x)) + + # If some packages were found... + if mypkgs: + # Get our global settings (we only print stuff if it varies from + # the current config) + mydesiredvars = [ 'CHOST', 'CFLAGS', 'CXXFLAGS', 'LDFLAGS' ] + auxkeys = mydesiredvars + list(vardb._aux_cache_keys) + auxkeys.append('DEFINED_PHASES') + global_vals = {} + pkgsettings = portage.config(clone=settings) + + # Loop through each package + # Only print settings if they differ from global settings + header_title = "Package Settings" + print header_width * "=" + print header_title.rjust(int(header_width/2 + len(header_title)/2)) + print header_width * "=" + from portage.output import EOutput + out = EOutput() + for cpv in mypkgs: + # Get all package specific variables + metadata = dict(izip(auxkeys, vardb.aux_get(cpv, auxkeys))) + pkg = Package(built=True, cpv=cpv, + installed=True, metadata=izip(Package.metadata_keys, + (metadata.get(x, '') for x in Package.metadata_keys)), + root_config=root_config, type_name='installed') + + print "\n%s was built with the following:" % \ + colorize("INFORM", str(pkg.cpv)) + + pkgsettings.setcpv(pkg) + forced_flags = set(chain(pkgsettings.useforce, + pkgsettings.usemask)) + use = set(pkg.use.enabled) + use.discard(pkgsettings.get('ARCH')) + use_expand_flags = set() + use_enabled = {} + use_disabled = {} + for varname in use_expand: + flag_prefix = varname.lower() + "_" + for f in use: + if f.startswith(flag_prefix): + use_expand_flags.add(f) + use_enabled.setdefault( + varname.upper(), []).append(f[len(flag_prefix):]) + + for f in pkg.iuse.all: + if f.startswith(flag_prefix): + use_expand_flags.add(f) + if f not in use: + use_disabled.setdefault( + varname.upper(), []).append(f[len(flag_prefix):]) + + var_order = set(use_enabled) + var_order.update(use_disabled) + var_order = sorted(var_order) + var_order.insert(0, 'USE') + use.difference_update(use_expand_flags) + use_enabled['USE'] = list(use) + use_disabled['USE'] = [] + + for f in pkg.iuse.all: + if f not in use and \ + f not in use_expand_flags: + use_disabled['USE'].append(f) + + for varname in var_order: + if varname in use_expand_hidden: + continue + flags = [] + for f in use_enabled.get(varname, []): + flags.append(UseFlagDisplay(f, True, f in forced_flags)) + for f in use_disabled.get(varname, []): + flags.append(UseFlagDisplay(f, False, f in forced_flags)) + if alphabetical_use: + flags.sort(key=UseFlagDisplay.sort_combined) + else: + flags.sort(key=UseFlagDisplay.sort_separated) + print '%s="%s"' % (varname, ' '.join(str(f) for f in flags)), + print + + for myvar in mydesiredvars: + if metadata[myvar].split() != settings.get(myvar, '').split(): + print "%s=\"%s\"" % (myvar, metadata[myvar]) + print + + if metadata['DEFINED_PHASES']: + if 'info' not in metadata['DEFINED_PHASES'].split(): + continue + + print ">>> Attempting to run pkg_info() for '%s'" % pkg.cpv + ebuildpath = vardb.findname(pkg.cpv) + if not ebuildpath or not os.path.exists(ebuildpath): + out.ewarn("No ebuild found for '%s'" % pkg.cpv) + continue + portage.doebuild(ebuildpath, "info", pkgsettings["ROOT"], + pkgsettings, debug=(settings.get("PORTAGE_DEBUG", "") == 1), + mydbapi=trees[settings["ROOT"]]["vartree"].dbapi, + tree="vartree") + +def action_metadata(settings, portdb, myopts, porttrees=None): + if porttrees is None: + porttrees = portdb.porttrees + portage.writemsg_stdout("\n>>> Updating Portage cache\n") + old_umask = os.umask(0002) + cachedir = os.path.normpath(settings.depcachedir) + if cachedir in ["/", "/bin", "/dev", "/etc", "/home", + "/lib", "/opt", "/proc", "/root", "/sbin", + "/sys", "/tmp", "/usr", "/var"]: + print >> sys.stderr, "!!! PORTAGE_DEPCACHEDIR IS SET TO A PRIMARY " + \ + "ROOT DIRECTORY ON YOUR SYSTEM." + print >> sys.stderr, \ + "!!! This is ALMOST CERTAINLY NOT what you want: '%s'" % cachedir + sys.exit(73) + if not os.path.exists(cachedir): + os.makedirs(cachedir) + + auxdbkeys = [x for x in portage.auxdbkeys if not x.startswith("UNUSED_0")] + auxdbkeys = tuple(auxdbkeys) + + class TreeData(object): + __slots__ = ('dest_db', 'eclass_db', 'path', 'src_db', 'valid_nodes') + def __init__(self, dest_db, eclass_db, path, src_db): + self.dest_db = dest_db + self.eclass_db = eclass_db + self.path = path + self.src_db = src_db + self.valid_nodes = set() + + porttrees_data = [] + for path in porttrees: + src_db = portdb._pregen_auxdb.get(path) + if src_db is None and \ + os.path.isdir(os.path.join(path, 'metadata', 'cache')): + src_db = portdb.metadbmodule( + path, 'metadata/cache', auxdbkeys, readonly=True) + try: + src_db.ec = portdb._repo_info[path].eclass_db + except AttributeError: + pass + + if src_db is not None: + porttrees_data.append(TreeData(portdb.auxdb[path], + portdb._repo_info[path].eclass_db, path, src_db)) + + porttrees = [tree_data.path for tree_data in porttrees_data] + + isatty = sys.stdout.isatty() + quiet = not isatty or '--quiet' in myopts + onProgress = None + if not quiet: + progressBar = portage.output.TermProgressBar() + progressHandler = ProgressHandler() + onProgress = progressHandler.onProgress + def display(): + progressBar.set(progressHandler.curval, progressHandler.maxval) + progressHandler.display = display + def sigwinch_handler(signum, frame): + lines, progressBar.term_columns = \ + portage.output.get_term_size() + signal.signal(signal.SIGWINCH, sigwinch_handler) + + # Temporarily override portdb.porttrees so portdb.cp_all() + # will only return the relevant subset. + portdb_porttrees = portdb.porttrees + portdb.porttrees = porttrees + try: + cp_all = portdb.cp_all() + finally: + portdb.porttrees = portdb_porttrees + + curval = 0 + maxval = len(cp_all) + if onProgress is not None: + onProgress(maxval, curval) + + from portage.cache.util import quiet_mirroring + from portage import eapi_is_supported, \ + _validate_cache_for_unsupported_eapis + + # TODO: Display error messages, but do not interfere with the progress bar. + # Here's how: + # 1) erase the progress bar + # 2) show the error message + # 3) redraw the progress bar on a new line + noise = quiet_mirroring() + + for cp in cp_all: + for tree_data in porttrees_data: + for cpv in portdb.cp_list(cp, mytree=tree_data.path): + tree_data.valid_nodes.add(cpv) + try: + src = tree_data.src_db[cpv] + except KeyError, e: + noise.missing_entry(cpv) + del e + continue + except CacheError, ce: + noise.exception(cpv, ce) + del ce + continue + + eapi = src.get('EAPI') + if not eapi: + eapi = '0' + eapi = eapi.lstrip('-') + eapi_supported = eapi_is_supported(eapi) + if not eapi_supported: + if not _validate_cache_for_unsupported_eapis: + noise.misc(cpv, "unable to validate " + \ + "cache for EAPI='%s'" % eapi) + continue + + dest = None + try: + dest = tree_data.dest_db[cpv] + except (KeyError, CacheError): + pass + + for d in (src, dest): + if d is not None and d.get('EAPI') in ('', '0'): + del d['EAPI'] + + if dest is not None: + if not (dest['_mtime_'] == src['_mtime_'] and \ + tree_data.eclass_db.is_eclass_data_valid( + dest['_eclasses_']) and \ + set(dest['_eclasses_']) == set(src['_eclasses_'])): + dest = None + else: + # We don't want to skip the write unless we're really + # sure that the existing cache is identical, so don't + # trust _mtime_ and _eclasses_ alone. + for k in set(chain(src, dest)).difference( + ('_mtime_', '_eclasses_')): + if dest.get(k, '') != src.get(k, ''): + dest = None + break + + if dest is not None: + # The existing data is valid and identical, + # so there's no need to overwrite it. + continue + + try: + inherited = src.get('INHERITED', '') + eclasses = src.get('_eclasses_') + except CacheError, ce: + noise.exception(cpv, ce) + del ce + continue + + if eclasses is not None: + if not tree_data.eclass_db.is_eclass_data_valid( + src['_eclasses_']): + noise.eclass_stale(cpv) + continue + inherited = eclasses + else: + inherited = inherited.split() + + if tree_data.src_db.complete_eclass_entries and \ + eclasses is None: + noise.corruption(cpv, "missing _eclasses_ field") + continue + + if inherited: + # Even if _eclasses_ already exists, replace it with data from + # eclass_cache, in order to insert local eclass paths. + try: + eclasses = tree_data.eclass_db.get_eclass_data(inherited) + except KeyError: + # INHERITED contains a non-existent eclass. + noise.eclass_stale(cpv) + continue + + if eclasses is None: + noise.eclass_stale(cpv) + continue + src['_eclasses_'] = eclasses + else: + src['_eclasses_'] = {} + + if not eapi_supported: + src = { + 'EAPI' : '-' + eapi, + '_mtime_' : src['_mtime_'], + '_eclasses_' : src['_eclasses_'], + } + + try: + tree_data.dest_db[cpv] = src + except CacheError, ce: + noise.exception(cpv, ce) + del ce + + curval += 1 + if onProgress is not None: + onProgress(maxval, curval) + + if onProgress is not None: + onProgress(maxval, curval) + + for tree_data in porttrees_data: + try: + dead_nodes = set(tree_data.dest_db.iterkeys()) + except CacheError, e: + writemsg_level("Error listing cache entries for " + \ + "'%s': %s, continuing...\n" % (tree_data.path, e), + level=logging.ERROR, noiselevel=-1) + del e + else: + dead_nodes.difference_update(tree_data.valid_nodes) + for cpv in dead_nodes: + try: + del tree_data.dest_db[cpv] + except (KeyError, CacheError): + pass + + if not quiet: + # make sure the final progress is displayed + progressHandler.display() + print + signal.signal(signal.SIGWINCH, signal.SIG_DFL) + + sys.stdout.flush() + os.umask(old_umask) + +def action_regen(settings, portdb, max_jobs, max_load): + xterm_titles = "notitles" not in settings.features + emergelog(xterm_titles, " === regen") + #regenerate cache entries + portage.writemsg_stdout("Regenerating cache entries...\n") + try: + os.close(sys.stdin.fileno()) + except SystemExit, e: + raise # Needed else can't exit + except: + pass + sys.stdout.flush() + + regen = MetadataRegen(portdb, max_jobs=max_jobs, max_load=max_load) + regen.run() + + portage.writemsg_stdout("done!\n") + return regen.returncode + +def action_search(root_config, myopts, myfiles, spinner): + if not myfiles: + print "emerge: no search terms provided." + else: + searchinstance = search(root_config, + spinner, "--searchdesc" in myopts, + "--quiet" not in myopts, "--usepkg" in myopts, + "--usepkgonly" in myopts) + for mysearch in myfiles: + try: + searchinstance.execute(mysearch) + except re.error, comment: + print "\n!!! Regular expression error in \"%s\": %s" % ( mysearch, comment ) + sys.exit(1) + searchinstance.output() + +def action_sync(settings, trees, mtimedb, myopts, myaction): + xterm_titles = "notitles" not in settings.features + emergelog(xterm_titles, " === sync") + portdb = trees[settings["ROOT"]]["porttree"].dbapi + myportdir = portdb.porttree_root + out = portage.output.EOutput() + if not myportdir: + sys.stderr.write("!!! PORTDIR is undefined. Is /etc/make.globals missing?\n") + sys.exit(1) + if myportdir[-1]=="/": + myportdir=myportdir[:-1] + try: + st = os.stat(myportdir) + except OSError: + st = None + if st is None: + print ">>>",myportdir,"not found, creating it." + os.makedirs(myportdir,0755) + st = os.stat(myportdir) + + spawn_kwargs = {} + spawn_kwargs["env"] = settings.environ() + if 'usersync' in settings.features and \ + portage.data.secpass >= 2 and \ + (st.st_uid != os.getuid() and st.st_mode & 0700 or \ + st.st_gid != os.getgid() and st.st_mode & 0070): + try: + homedir = pwd.getpwuid(st.st_uid).pw_dir + except KeyError: + pass + else: + # Drop privileges when syncing, in order to match + # existing uid/gid settings. + spawn_kwargs["uid"] = st.st_uid + spawn_kwargs["gid"] = st.st_gid + spawn_kwargs["groups"] = [st.st_gid] + spawn_kwargs["env"]["HOME"] = homedir + umask = 0002 + if not st.st_mode & 0020: + umask = umask | 0020 + spawn_kwargs["umask"] = umask + + syncuri = settings.get("SYNC", "").strip() + if not syncuri: + writemsg_level("!!! SYNC is undefined. Is /etc/make.globals missing?\n", + noiselevel=-1, level=logging.ERROR) + return 1 + + vcs_dirs = frozenset([".git", ".svn", "CVS", ".hg"]) + vcs_dirs = vcs_dirs.intersection(os.listdir(myportdir)) + + os.umask(0022) + dosyncuri = syncuri + updatecache_flg = False + if myaction == "metadata": + print "skipping sync" + updatecache_flg = True + elif ".git" in vcs_dirs: + # Update existing git repository, and ignore the syncuri. We are + # going to trust the user and assume that the user is in the branch + # that he/she wants updated. We'll let the user manage branches with + # git directly. + if portage.process.find_binary("git") is None: + msg = ["Command not found: git", + "Type \"emerge dev-util/git\" to enable git support."] + for l in msg: + writemsg_level("!!! %s\n" % l, + level=logging.ERROR, noiselevel=-1) + return 1 + msg = ">>> Starting git pull in %s..." % myportdir + emergelog(xterm_titles, msg ) + writemsg_level(msg + "\n") + exitcode = portage.process.spawn_bash("cd %s ; git pull" % \ + (portage._shell_quote(myportdir),), **spawn_kwargs) + if exitcode != os.EX_OK: + msg = "!!! git pull error in %s." % myportdir + emergelog(xterm_titles, msg) + writemsg_level(msg + "\n", level=logging.ERROR, noiselevel=-1) + return exitcode + msg = ">>> Git pull in %s successful" % myportdir + emergelog(xterm_titles, msg) + writemsg_level(msg + "\n") + exitcode = git_sync_timestamps(settings, myportdir) + if exitcode == os.EX_OK: + updatecache_flg = True + elif syncuri[:8]=="rsync://": + for vcs_dir in vcs_dirs: + writemsg_level(("!!! %s appears to be under revision " + \ + "control (contains %s).\n!!! Aborting rsync sync.\n") % \ + (myportdir, vcs_dir), level=logging.ERROR, noiselevel=-1) + return 1 + if not os.path.exists("/usr/bin/rsync"): + print "!!! /usr/bin/rsync does not exist, so rsync support is disabled." + print "!!! Type \"emerge net-misc/rsync\" to enable rsync support." + sys.exit(1) + mytimeout=180 + + rsync_opts = [] + if settings["PORTAGE_RSYNC_OPTS"] == "": + portage.writemsg("PORTAGE_RSYNC_OPTS empty or unset, using hardcoded defaults\n") + rsync_opts.extend([ + "--recursive", # Recurse directories + "--links", # Consider symlinks + "--safe-links", # Ignore links outside of tree + "--perms", # Preserve permissions + "--times", # Preserive mod times + "--compress", # Compress the data transmitted + "--force", # Force deletion on non-empty dirs + "--whole-file", # Don't do block transfers, only entire files + "--delete", # Delete files that aren't in the master tree + "--stats", # Show final statistics about what was transfered + "--timeout="+str(mytimeout), # IO timeout if not done in X seconds + "--exclude=/distfiles", # Exclude distfiles from consideration + "--exclude=/local", # Exclude local from consideration + "--exclude=/packages", # Exclude packages from consideration + ]) + + else: + # The below validation is not needed when using the above hardcoded + # defaults. + + portage.writemsg("Using PORTAGE_RSYNC_OPTS instead of hardcoded defaults\n", 1) + rsync_opts.extend( + shlex.split(settings.get("PORTAGE_RSYNC_OPTS",""))) + for opt in ("--recursive", "--times"): + if opt not in rsync_opts: + portage.writemsg(yellow("WARNING:") + " adding required option " + \ + "%s not included in PORTAGE_RSYNC_OPTS\n" % opt) + rsync_opts.append(opt) + + for exclude in ("distfiles", "local", "packages"): + opt = "--exclude=/%s" % exclude + if opt not in rsync_opts: + portage.writemsg(yellow("WARNING:") + \ + " adding required option %s not included in " % opt + \ + "PORTAGE_RSYNC_OPTS (can be overridden with --exclude='!')\n") + rsync_opts.append(opt) + + if syncuri.rstrip("/").endswith(".gentoo.org/gentoo-portage"): + def rsync_opt_startswith(opt_prefix): + for x in rsync_opts: + if x.startswith(opt_prefix): + return True + return False + + if not rsync_opt_startswith("--timeout="): + rsync_opts.append("--timeout=%d" % mytimeout) + + for opt in ("--compress", "--whole-file"): + if opt not in rsync_opts: + portage.writemsg(yellow("WARNING:") + " adding required option " + \ + "%s not included in PORTAGE_RSYNC_OPTS\n" % opt) + rsync_opts.append(opt) + + if "--quiet" in myopts: + rsync_opts.append("--quiet") # Shut up a lot + else: + rsync_opts.append("--verbose") # Print filelist + + if "--verbose" in myopts: + rsync_opts.append("--progress") # Progress meter for each file + + if "--debug" in myopts: + rsync_opts.append("--checksum") # Force checksum on all files + + # Real local timestamp file. + servertimestampfile = os.path.join( + myportdir, "metadata", "timestamp.chk") + + content = portage.util.grabfile(servertimestampfile) + mytimestamp = 0 + if content: + try: + mytimestamp = time.mktime(time.strptime(content[0], + "%a, %d %b %Y %H:%M:%S +0000")) + except (OverflowError, ValueError): + pass + del content + + try: + rsync_initial_timeout = \ + int(settings.get("PORTAGE_RSYNC_INITIAL_TIMEOUT", "15")) + except ValueError: + rsync_initial_timeout = 15 + + try: + maxretries=int(settings["PORTAGE_RSYNC_RETRIES"]) + except SystemExit, e: + raise # Needed else can't exit + except: + maxretries=3 #default number of retries + + retries=0 + user_name, hostname, port = re.split( + "rsync://([^:/]+@)?([^:/]*)(:[0-9]+)?", syncuri, maxsplit=3)[1:4] + if port is None: + port="" + if user_name is None: + user_name="" + updatecache_flg=True + all_rsync_opts = set(rsync_opts) + extra_rsync_opts = shlex.split( + settings.get("PORTAGE_RSYNC_EXTRA_OPTS","")) + all_rsync_opts.update(extra_rsync_opts) + family = socket.AF_INET + if "-4" in all_rsync_opts or "--ipv4" in all_rsync_opts: + family = socket.AF_INET + elif socket.has_ipv6 and \ + ("-6" in all_rsync_opts or "--ipv6" in all_rsync_opts): + family = socket.AF_INET6 + ips=[] + SERVER_OUT_OF_DATE = -1 + EXCEEDED_MAX_RETRIES = -2 + while (1): + if ips: + del ips[0] + if ips==[]: + try: + for addrinfo in socket.getaddrinfo( + hostname, None, family, socket.SOCK_STREAM): + if socket.has_ipv6 and addrinfo[0] == socket.AF_INET6: + # IPv6 addresses need to be enclosed in square brackets + ips.append("[%s]" % addrinfo[4][0]) + else: + ips.append(addrinfo[4][0]) + from random import shuffle + shuffle(ips) + except SystemExit, e: + raise # Needed else can't exit + except Exception, e: + print "Notice:",str(e) + dosyncuri=syncuri + + if ips: + try: + dosyncuri = syncuri.replace( + "//" + user_name + hostname + port + "/", + "//" + user_name + ips[0] + port + "/", 1) + except SystemExit, e: + raise # Needed else can't exit + except Exception, e: + print "Notice:",str(e) + dosyncuri=syncuri + + if (retries==0): + if "--ask" in myopts: + if userquery("Do you want to sync your Portage tree with the mirror at\n" + blue(dosyncuri) + bold("?"))=="No": + print + print "Quitting." + print + sys.exit(0) + emergelog(xterm_titles, ">>> Starting rsync with " + dosyncuri) + if "--quiet" not in myopts: + print ">>> Starting rsync with "+dosyncuri+"..." + else: + emergelog(xterm_titles, + ">>> Starting retry %d of %d with %s" % \ + (retries,maxretries,dosyncuri)) + print "\n\n>>> Starting retry %d of %d with %s" % (retries,maxretries,dosyncuri) + + if mytimestamp != 0 and "--quiet" not in myopts: + print ">>> Checking server timestamp ..." + + rsynccommand = ["/usr/bin/rsync"] + rsync_opts + extra_rsync_opts + + if "--debug" in myopts: + print rsynccommand + + exitcode = os.EX_OK + servertimestamp = 0 + # Even if there's no timestamp available locally, fetch the + # timestamp anyway as an initial probe to verify that the server is + # responsive. This protects us from hanging indefinitely on a + # connection attempt to an unresponsive server which rsync's + # --timeout option does not prevent. + if True: + # Temporary file for remote server timestamp comparison. + from tempfile import mkstemp + fd, tmpservertimestampfile = mkstemp() + os.close(fd) + mycommand = rsynccommand[:] + mycommand.append(dosyncuri.rstrip("/") + \ + "/metadata/timestamp.chk") + mycommand.append(tmpservertimestampfile) + content = None + mypids = [] + try: + def timeout_handler(signum, frame): + raise portage.exception.PortageException("timed out") + signal.signal(signal.SIGALRM, timeout_handler) + # Timeout here in case the server is unresponsive. The + # --timeout rsync option doesn't apply to the initial + # connection attempt. + if rsync_initial_timeout: + signal.alarm(rsync_initial_timeout) + try: + mypids.extend(portage.process.spawn( + mycommand, env=settings.environ(), returnpid=True)) + exitcode = os.waitpid(mypids[0], 0)[1] + content = portage.grabfile(tmpservertimestampfile) + finally: + if rsync_initial_timeout: + signal.alarm(0) + try: + os.unlink(tmpservertimestampfile) + except OSError: + pass + except portage.exception.PortageException, e: + # timed out + print e + del e + if mypids and os.waitpid(mypids[0], os.WNOHANG) == (0,0): + os.kill(mypids[0], signal.SIGTERM) + os.waitpid(mypids[0], 0) + # This is the same code rsync uses for timeout. + exitcode = 30 + else: + if exitcode != os.EX_OK: + if exitcode & 0xff: + exitcode = (exitcode & 0xff) << 8 + else: + exitcode = exitcode >> 8 + if mypids: + portage.process.spawned_pids.remove(mypids[0]) + if content: + try: + servertimestamp = time.mktime(time.strptime( + content[0], "%a, %d %b %Y %H:%M:%S +0000")) + except (OverflowError, ValueError): + pass + del mycommand, mypids, content + if exitcode == os.EX_OK: + if (servertimestamp != 0) and (servertimestamp == mytimestamp): + emergelog(xterm_titles, + ">>> Cancelling sync -- Already current.") + print + print ">>>" + print ">>> Timestamps on the server and in the local repository are the same." + print ">>> Cancelling all further sync action. You are already up to date." + print ">>>" + print ">>> In order to force sync, remove '%s'." % servertimestampfile + print ">>>" + print + sys.exit(0) + elif (servertimestamp != 0) and (servertimestamp < mytimestamp): + emergelog(xterm_titles, + ">>> Server out of date: %s" % dosyncuri) + print + print ">>>" + print ">>> SERVER OUT OF DATE: %s" % dosyncuri + print ">>>" + print ">>> In order to force sync, remove '%s'." % servertimestampfile + print ">>>" + print + exitcode = SERVER_OUT_OF_DATE + elif (servertimestamp == 0) or (servertimestamp > mytimestamp): + # actual sync + mycommand = rsynccommand + [dosyncuri+"/", myportdir] + exitcode = portage.process.spawn(mycommand, **spawn_kwargs) + if exitcode in [0,1,3,4,11,14,20,21]: + break + elif exitcode in [1,3,4,11,14,20,21]: + break + else: + # Code 2 indicates protocol incompatibility, which is expected + # for servers with protocol < 29 that don't support + # --prune-empty-directories. Retry for a server that supports + # at least rsync protocol version 29 (>=rsync-2.6.4). + pass + + retries=retries+1 + + if retries<=maxretries: + print ">>> Retrying..." + time.sleep(11) + else: + # over retries + # exit loop + updatecache_flg=False + exitcode = EXCEEDED_MAX_RETRIES + break + + if (exitcode==0): + emergelog(xterm_titles, "=== Sync completed with %s" % dosyncuri) + elif exitcode == SERVER_OUT_OF_DATE: + sys.exit(1) + elif exitcode == EXCEEDED_MAX_RETRIES: + sys.stderr.write( + ">>> Exceeded PORTAGE_RSYNC_RETRIES: %s\n" % maxretries) + sys.exit(1) + elif (exitcode>0): + msg = [] + if exitcode==1: + msg.append("Rsync has reported that there is a syntax error. Please ensure") + msg.append("that your SYNC statement is proper.") + msg.append("SYNC=" + settings["SYNC"]) + elif exitcode==11: + msg.append("Rsync has reported that there is a File IO error. Normally") + msg.append("this means your disk is full, but can be caused by corruption") + msg.append("on the filesystem that contains PORTDIR. Please investigate") + msg.append("and try again after the problem has been fixed.") + msg.append("PORTDIR=" + settings["PORTDIR"]) + elif exitcode==20: + msg.append("Rsync was killed before it finished.") + else: + msg.append("Rsync has not successfully finished. It is recommended that you keep") + msg.append("trying or that you use the 'emerge-webrsync' option if you are unable") + msg.append("to use rsync due to firewall or other restrictions. This should be a") + msg.append("temporary problem unless complications exist with your network") + msg.append("(and possibly your system's filesystem) configuration.") + for line in msg: + out.eerror(line) + sys.exit(exitcode) + elif syncuri[:6]=="cvs://": + if not os.path.exists("/usr/bin/cvs"): + print "!!! /usr/bin/cvs does not exist, so CVS support is disabled." + print "!!! Type \"emerge dev-util/cvs\" to enable CVS support." + sys.exit(1) + cvsroot=syncuri[6:] + cvsdir=os.path.dirname(myportdir) + if not os.path.exists(myportdir+"/CVS"): + #initial checkout + print ">>> Starting initial cvs checkout with "+syncuri+"..." + if os.path.exists(cvsdir+"/gentoo-x86"): + print "!!! existing",cvsdir+"/gentoo-x86 directory; exiting." + sys.exit(1) + try: + os.rmdir(myportdir) + except OSError, e: + if e.errno != errno.ENOENT: + sys.stderr.write( + "!!! existing '%s' directory; exiting.\n" % myportdir) + sys.exit(1) + del e + if portage.spawn("cd "+cvsdir+"; cvs -z0 -d "+cvsroot+" co -P gentoo-x86",settings,free=1): + print "!!! cvs checkout error; exiting." + sys.exit(1) + os.rename(os.path.join(cvsdir, "gentoo-x86"), myportdir) + else: + #cvs update + print ">>> Starting cvs update with "+syncuri+"..." + retval = portage.process.spawn_bash( + "cd %s; cvs -z0 -q update -dP" % \ + (portage._shell_quote(myportdir),), **spawn_kwargs) + if retval != os.EX_OK: + sys.exit(retval) + dosyncuri = syncuri + else: + writemsg_level("!!! Unrecognized protocol: SYNC='%s'\n" % (syncuri,), + noiselevel=-1, level=logging.ERROR) + return 1 + + if updatecache_flg and \ + myaction != "metadata" and \ + "metadata-transfer" not in settings.features: + updatecache_flg = False + + # Reload the whole config from scratch. + settings, trees, mtimedb = load_emerge_config(trees=trees) + root_config = trees[settings["ROOT"]]["root_config"] + portdb = trees[settings["ROOT"]]["porttree"].dbapi + + if updatecache_flg and \ + os.path.exists(os.path.join(myportdir, 'metadata', 'cache')): + + # Only update cache for myportdir since that's + # the only one that's been synced here. + action_metadata(settings, portdb, myopts, porttrees=[myportdir]) + + if portage._global_updates(trees, mtimedb["updates"]): + mtimedb.commit() + # Reload the whole config from scratch. + settings, trees, mtimedb = load_emerge_config(trees=trees) + portdb = trees[settings["ROOT"]]["porttree"].dbapi + root_config = trees[settings["ROOT"]]["root_config"] + + mybestpv = portdb.xmatch("bestmatch-visible", + portage.const.PORTAGE_PACKAGE_ATOM) + mypvs = portage.best( + trees[settings["ROOT"]]["vartree"].dbapi.match( + portage.const.PORTAGE_PACKAGE_ATOM)) + + chk_updated_cfg_files("/", settings.get("CONFIG_PROTECT","").split()) + + if myaction != "metadata": + if os.access(portage.USER_CONFIG_PATH + "/bin/post_sync", os.X_OK): + retval = portage.process.spawn( + [os.path.join(portage.USER_CONFIG_PATH, "bin", "post_sync"), + dosyncuri], env=settings.environ()) + if retval != os.EX_OK: + print red(" * ")+bold("spawn failed of "+ portage.USER_CONFIG_PATH + "/bin/post_sync") + + if(mybestpv != mypvs) and not "--quiet" in myopts: + print + print red(" * ")+bold("An update to portage is available.")+" It is _highly_ recommended" + print red(" * ")+"that you update portage now, before any other packages are updated." + print + print red(" * ")+"To update portage, run 'emerge portage' now." + print + + display_news_notification(root_config, myopts) + return os.EX_OK + +def action_uninstall(settings, trees, ldpath_mtimes, + opts, action, files, spinner): + + # For backward compat, some actions do not require leading '='. + ignore_missing_eq = action in ('clean', 'unmerge') + root = settings['ROOT'] + vardb = trees[root]['vartree'].dbapi + valid_atoms = [] + lookup_owners = [] + + # Ensure atoms are valid before calling unmerge(). + # For backward compat, leading '=' is not required. + for x in files: + if is_valid_package_atom(x) or \ + (ignore_missing_eq and is_valid_package_atom('=' + x)): + + try: + valid_atoms.append( + portage.dep_expand(x, mydb=vardb, settings=settings)) + except portage.exception.AmbiguousPackageName, e: + msg = "The short ebuild name \"" + x + \ + "\" is ambiguous. Please specify " + \ + "one of the following " + \ + "fully-qualified ebuild names instead:" + for line in textwrap.wrap(msg, 70): + writemsg_level("!!! %s\n" % (line,), + level=logging.ERROR, noiselevel=-1) + for i in e[0]: + writemsg_level(" %s\n" % colorize("INFORM", i), + level=logging.ERROR, noiselevel=-1) + writemsg_level("\n", level=logging.ERROR, noiselevel=-1) + return 1 + + elif x.startswith(os.sep): + if not x.startswith(root): + writemsg_level(("!!! '%s' does not start with" + \ + " $ROOT.\n") % x, level=logging.ERROR, noiselevel=-1) + return 1 + # Queue these up since it's most efficient to handle + # multiple files in a single iter_owners() call. + lookup_owners.append(x) + + else: + msg = [] + msg.append("'%s' is not a valid package atom." % (x,)) + msg.append("Please check ebuild(5) for full details.") + writemsg_level("".join("!!! %s\n" % line for line in msg), + level=logging.ERROR, noiselevel=-1) + return 1 + + if lookup_owners: + relative_paths = [] + search_for_multiple = False + if len(lookup_owners) > 1: + search_for_multiple = True + + for x in lookup_owners: + if not search_for_multiple and os.path.isdir(x): + search_for_multiple = True + relative_paths.append(x[len(root):]) + + owners = set() + for pkg, relative_path in \ + vardb._owners.iter_owners(relative_paths): + owners.add(pkg.mycpv) + if not search_for_multiple: + break + + if owners: + for cpv in owners: + slot = vardb.aux_get(cpv, ['SLOT'])[0] + if not slot: + # portage now masks packages with missing slot, but it's + # possible that one was installed by an older version + atom = portage.cpv_getkey(cpv) + else: + atom = '%s:%s' % (portage.cpv_getkey(cpv), slot) + valid_atoms.append(portage.dep.Atom(atom)) + else: + writemsg_level(("!!! '%s' is not claimed " + \ + "by any package.\n") % lookup_owners[0], + level=logging.WARNING, noiselevel=-1) + + if files and not valid_atoms: + return 1 + + if action in ('clean', 'unmerge') or \ + (action == 'prune' and "--nodeps" in opts): + # When given a list of atoms, unmerge them in the order given. + ordered = action == 'unmerge' + unmerge(trees[settings["ROOT"]]['root_config'], opts, action, + valid_atoms, ldpath_mtimes, ordered=ordered) + rval = os.EX_OK + elif action == 'deselect': + rval = action_deselect(settings, trees, opts, valid_atoms) + else: + rval = action_depclean(settings, trees, ldpath_mtimes, + opts, action, valid_atoms, spinner) + + return rval + +def adjust_config(myopts, settings): + """Make emerge specific adjustments to the config.""" + + # To enhance usability, make some vars case insensitive by forcing them to + # lower case. + for myvar in ("AUTOCLEAN", "NOCOLOR"): + if myvar in settings: + settings[myvar] = settings[myvar].lower() + settings.backup_changes(myvar) + del myvar + + # Kill noauto as it will break merges otherwise. + if "noauto" in settings.features: + settings.features.remove('noauto') + settings['FEATURES'] = ' '.join(sorted(settings.features)) + settings.backup_changes("FEATURES") + + CLEAN_DELAY = 5 + try: + CLEAN_DELAY = int(settings.get("CLEAN_DELAY", str(CLEAN_DELAY))) + except ValueError, e: + portage.writemsg("!!! %s\n" % str(e), noiselevel=-1) + portage.writemsg("!!! Unable to parse integer: CLEAN_DELAY='%s'\n" % \ + settings["CLEAN_DELAY"], noiselevel=-1) + settings["CLEAN_DELAY"] = str(CLEAN_DELAY) + settings.backup_changes("CLEAN_DELAY") + + EMERGE_WARNING_DELAY = 10 + try: + EMERGE_WARNING_DELAY = int(settings.get( + "EMERGE_WARNING_DELAY", str(EMERGE_WARNING_DELAY))) + except ValueError, e: + portage.writemsg("!!! %s\n" % str(e), noiselevel=-1) + portage.writemsg("!!! Unable to parse integer: EMERGE_WARNING_DELAY='%s'\n" % \ + settings["EMERGE_WARNING_DELAY"], noiselevel=-1) + settings["EMERGE_WARNING_DELAY"] = str(EMERGE_WARNING_DELAY) + settings.backup_changes("EMERGE_WARNING_DELAY") + + if "--quiet" in myopts: + settings["PORTAGE_QUIET"]="1" + settings.backup_changes("PORTAGE_QUIET") + + if "--verbose" in myopts: + settings["PORTAGE_VERBOSE"] = "1" + settings.backup_changes("PORTAGE_VERBOSE") + + # Set so that configs will be merged regardless of remembered status + if ("--noconfmem" in myopts): + settings["NOCONFMEM"]="1" + settings.backup_changes("NOCONFMEM") + + # Set various debug markers... They should be merged somehow. + PORTAGE_DEBUG = 0 + try: + PORTAGE_DEBUG = int(settings.get("PORTAGE_DEBUG", str(PORTAGE_DEBUG))) + if PORTAGE_DEBUG not in (0, 1): + portage.writemsg("!!! Invalid value: PORTAGE_DEBUG='%i'\n" % \ + PORTAGE_DEBUG, noiselevel=-1) + portage.writemsg("!!! PORTAGE_DEBUG must be either 0 or 1\n", + noiselevel=-1) + PORTAGE_DEBUG = 0 + except ValueError, e: + portage.writemsg("!!! %s\n" % str(e), noiselevel=-1) + portage.writemsg("!!! Unable to parse integer: PORTAGE_DEBUG='%s'\n" %\ + settings["PORTAGE_DEBUG"], noiselevel=-1) + del e + if "--debug" in myopts: + PORTAGE_DEBUG = 1 + settings["PORTAGE_DEBUG"] = str(PORTAGE_DEBUG) + settings.backup_changes("PORTAGE_DEBUG") + + if settings.get("NOCOLOR") not in ("yes","true"): + portage.output.havecolor = 1 + + """The explicit --color < y | n > option overrides the NOCOLOR environment + variable and stdout auto-detection.""" + if "--color" in myopts: + if "y" == myopts["--color"]: + portage.output.havecolor = 1 + settings["NOCOLOR"] = "false" + else: + portage.output.havecolor = 0 + settings["NOCOLOR"] = "true" + settings.backup_changes("NOCOLOR") + elif not sys.stdout.isatty() and settings.get("NOCOLOR") != "no": + portage.output.havecolor = 0 + settings["NOCOLOR"] = "true" + settings.backup_changes("NOCOLOR") + +def display_missing_pkg_set(root_config, set_name): + + msg = [] + msg.append(("emerge: There are no sets to satisfy '%s'. " + \ + "The following sets exist:") % \ + colorize("INFORM", set_name)) + msg.append("") + + for s in sorted(root_config.sets): + msg.append(" %s" % s) + msg.append("") + + writemsg_level("".join("%s\n" % l for l in msg), + level=logging.ERROR, noiselevel=-1) + +def getportageversion(portdir, target_root, profile, chost, vardb): + profilever = "unavailable" + if profile: + realpath = os.path.realpath(profile) + basepath = os.path.realpath(os.path.join(portdir, "profiles")) + if realpath.startswith(basepath): + profilever = realpath[1 + len(basepath):] + else: + try: + profilever = "!" + os.readlink(profile) + except (OSError): + pass + del realpath, basepath + + libcver=[] + libclist = vardb.match("virtual/libc") + libclist += vardb.match("virtual/glibc") + libclist = portage.util.unique_array(libclist) + for x in libclist: + xs=portage.catpkgsplit(x) + if libcver: + libcver+=","+"-".join(xs[1:]) + else: + libcver="-".join(xs[1:]) + if libcver==[]: + libcver="unavailable" + + gccver = getgccversion(chost) + unameout=platform.release()+" "+platform.machine() + + return "Portage " + portage.VERSION +" ("+profilever+", "+gccver+", "+libcver+", "+unameout+")" + +def git_sync_timestamps(settings, portdir): + """ + Since git doesn't preserve timestamps, synchronize timestamps between + entries and ebuilds/eclasses. Assume the cache has the correct timestamp + for a given file as long as the file in the working tree is not modified + (relative to HEAD). + """ + cache_dir = os.path.join(portdir, "metadata", "cache") + if not os.path.isdir(cache_dir): + return os.EX_OK + writemsg_level(">>> Synchronizing timestamps...\n") + + from portage.cache.cache_errors import CacheError + try: + cache_db = settings.load_best_module("portdbapi.metadbmodule")( + portdir, "metadata/cache", portage.auxdbkeys[:], readonly=True) + except CacheError, e: + writemsg_level("!!! Unable to instantiate cache: %s\n" % (e,), + level=logging.ERROR, noiselevel=-1) + return 1 + + ec_dir = os.path.join(portdir, "eclass") + try: + ec_names = set(f[:-7] for f in os.listdir(ec_dir) \ + if f.endswith(".eclass")) + except OSError, e: + writemsg_level("!!! Unable to list eclasses: %s\n" % (e,), + level=logging.ERROR, noiselevel=-1) + return 1 + + args = [portage.const.BASH_BINARY, "-c", + "cd %s && git diff-index --name-only --diff-filter=M HEAD" % \ + portage._shell_quote(portdir)] + import subprocess + proc = subprocess.Popen(args, stdout=subprocess.PIPE) + modified_files = set(l.rstrip("\n") for l in proc.stdout) + rval = proc.wait() + if rval != os.EX_OK: + return rval + + modified_eclasses = set(ec for ec in ec_names \ + if os.path.join("eclass", ec + ".eclass") in modified_files) + + updated_ec_mtimes = {} + + for cpv in cache_db: + cpv_split = portage.catpkgsplit(cpv) + if cpv_split is None: + writemsg_level("!!! Invalid cache entry: %s\n" % (cpv,), + level=logging.ERROR, noiselevel=-1) + continue + + cat, pn, ver, rev = cpv_split + cat, pf = portage.catsplit(cpv) + relative_eb_path = os.path.join(cat, pn, pf + ".ebuild") + if relative_eb_path in modified_files: + continue + + try: + cache_entry = cache_db[cpv] + eb_mtime = cache_entry.get("_mtime_") + ec_mtimes = cache_entry.get("_eclasses_") + except KeyError: + writemsg_level("!!! Missing cache entry: %s\n" % (cpv,), + level=logging.ERROR, noiselevel=-1) + continue + except CacheError, e: + writemsg_level("!!! Unable to access cache entry: %s %s\n" % \ + (cpv, e), level=logging.ERROR, noiselevel=-1) + continue + + if eb_mtime is None: + writemsg_level("!!! Missing ebuild mtime: %s\n" % (cpv,), + level=logging.ERROR, noiselevel=-1) + continue + + try: + eb_mtime = long(eb_mtime) + except ValueError: + writemsg_level("!!! Invalid ebuild mtime: %s %s\n" % \ + (cpv, eb_mtime), level=logging.ERROR, noiselevel=-1) + continue + + if ec_mtimes is None: + writemsg_level("!!! Missing eclass mtimes: %s\n" % (cpv,), + level=logging.ERROR, noiselevel=-1) + continue + + if modified_eclasses.intersection(ec_mtimes): + continue + + missing_eclasses = set(ec_mtimes).difference(ec_names) + if missing_eclasses: + writemsg_level("!!! Non-existent eclass(es): %s %s\n" % \ + (cpv, sorted(missing_eclasses)), level=logging.ERROR, + noiselevel=-1) + continue + + eb_path = os.path.join(portdir, relative_eb_path) + try: + current_eb_mtime = os.stat(eb_path) + except OSError: + writemsg_level("!!! Missing ebuild: %s\n" % \ + (cpv,), level=logging.ERROR, noiselevel=-1) + continue + + inconsistent = False + for ec, (ec_path, ec_mtime) in ec_mtimes.iteritems(): + updated_mtime = updated_ec_mtimes.get(ec) + if updated_mtime is not None and updated_mtime != ec_mtime: + writemsg_level("!!! Inconsistent eclass mtime: %s %s\n" % \ + (cpv, ec), level=logging.ERROR, noiselevel=-1) + inconsistent = True + break + + if inconsistent: + continue + + if current_eb_mtime != eb_mtime: + os.utime(eb_path, (eb_mtime, eb_mtime)) + + for ec, (ec_path, ec_mtime) in ec_mtimes.iteritems(): + if ec in updated_ec_mtimes: + continue + ec_path = os.path.join(ec_dir, ec + ".eclass") + current_mtime = long(os.stat(ec_path).st_mtime) + if current_mtime != ec_mtime: + os.utime(ec_path, (ec_mtime, ec_mtime)) + updated_ec_mtimes[ec] = ec_mtime + + return os.EX_OK + +def load_emerge_config(trees=None): + kwargs = {} + for k, envvar in (("config_root", "PORTAGE_CONFIGROOT"), ("target_root", "ROOT")): + v = os.environ.get(envvar, None) + if v and v.strip(): + kwargs[k] = v + trees = portage.create_trees(trees=trees, **kwargs) + + for root, root_trees in trees.iteritems(): + settings = root_trees["vartree"].settings + setconfig = load_default_config(settings, root_trees) + root_trees["root_config"] = RootConfig(settings, root_trees, setconfig) + + settings = trees["/"]["vartree"].settings + + for myroot in trees: + if myroot != "/": + settings = trees[myroot]["vartree"].settings + break + + mtimedbfile = os.path.join("/", portage.CACHE_PATH.lstrip(os.path.sep), "mtimedb") + mtimedb = portage.MtimeDB(mtimedbfile) + + return settings, trees, mtimedb + +def chk_updated_cfg_files(target_root, config_protect): + if config_protect: + #number of directories with some protect files in them + procount=0 + for x in config_protect: + x = os.path.join(target_root, x.lstrip(os.path.sep)) + if not os.access(x, os.W_OK): + # Avoid Permission denied errors generated + # later by `find`. + continue + try: + mymode = os.lstat(x).st_mode + except OSError: + continue + if stat.S_ISLNK(mymode): + # We want to treat it like a directory if it + # is a symlink to an existing directory. + try: + real_mode = os.stat(x).st_mode + if stat.S_ISDIR(real_mode): + mymode = real_mode + except OSError: + pass + if stat.S_ISDIR(mymode): + mycommand = "find '%s' -name '.*' -type d -prune -o -name '._cfg????_*'" % x + else: + mycommand = "find '%s' -maxdepth 1 -name '._cfg????_%s'" % \ + os.path.split(x.rstrip(os.path.sep)) + mycommand += " ! -name '.*~' ! -iname '.*.bak' -print0" + a = commands.getstatusoutput(mycommand) + if a[0] != 0: + sys.stderr.write(" %s error scanning '%s': " % (bad("*"), x)) + sys.stderr.flush() + # Show the error message alone, sending stdout to /dev/null. + os.system(mycommand + " 1>/dev/null") + else: + files = a[1].split('\0') + # split always produces an empty string as the last element + if files and not files[-1]: + del files[-1] + if files: + procount += 1 + print "\n"+colorize("WARN", " * IMPORTANT:"), + if stat.S_ISDIR(mymode): + print "%d config files in '%s' need updating." % \ + (len(files), x) + else: + print "config file '%s' needs updating." % x + + if procount: + print " "+yellow("*")+" See the "+colorize("INFORM","CONFIGURATION FILES")+ \ + " section of the " + bold("emerge") + print " "+yellow("*")+" man page to learn how to update config files." + +def display_news_notification(root_config, myopts): + target_root = root_config.root + trees = root_config.trees + settings = trees["vartree"].settings + portdb = trees["porttree"].dbapi + vardb = trees["vartree"].dbapi + NEWS_PATH = os.path.join("metadata", "news") + UNREAD_PATH = os.path.join(target_root, NEWS_LIB_PATH, "news") + newsReaderDisplay = False + update = "--pretend" not in myopts + + for repo in portdb.getRepositories(): + unreadItems = checkUpdatedNewsItems( + portdb, vardb, NEWS_PATH, UNREAD_PATH, repo, update=update) + if unreadItems: + if not newsReaderDisplay: + newsReaderDisplay = True + print + print colorize("WARN", " * IMPORTANT:"), + print "%s news items need reading for repository '%s'." % (unreadItems, repo) + + + if newsReaderDisplay: + print colorize("WARN", " *"), + print "Use " + colorize("GOOD", "eselect news") + " to read news items." + print + +def getgccversion(chost): + """ + rtype: C{str} + return: the current in-use gcc version + """ + + gcc_ver_command = 'gcc -dumpversion' + gcc_ver_prefix = 'gcc-' + + gcc_not_found_error = red( + "!!! No gcc found. You probably need to 'source /etc/profile'\n" + + "!!! to update the environment of this terminal and possibly\n" + + "!!! other terminals also.\n" + ) + + mystatus, myoutput = commands.getstatusoutput("gcc-config -c") + if mystatus == os.EX_OK and myoutput.startswith(chost + "-"): + return myoutput.replace(chost + "-", gcc_ver_prefix, 1) + + mystatus, myoutput = commands.getstatusoutput( + chost + "-" + gcc_ver_command) + if mystatus == os.EX_OK: + return gcc_ver_prefix + myoutput + + mystatus, myoutput = commands.getstatusoutput(gcc_ver_command) + if mystatus == os.EX_OK: + return gcc_ver_prefix + myoutput + + portage.writemsg(gcc_not_found_error, noiselevel=-1) + return "[unavailable]" + +def checkUpdatedNewsItems(portdb, vardb, NEWS_PATH, UNREAD_PATH, repo_id, + update=False): + """ + Examines news items in repodir + '/' + NEWS_PATH and attempts to find unread items + Returns the number of unread (yet relevent) items. + + @param portdb: a portage tree database + @type portdb: pordbapi + @param vardb: an installed package database + @type vardb: vardbapi + @param NEWS_PATH: + @type NEWS_PATH: + @param UNREAD_PATH: + @type UNREAD_PATH: + @param repo_id: + @type repo_id: + @rtype: Integer + @returns: + 1. The number of unread but relevant news items. + + """ + from portage.news import NewsManager + manager = NewsManager(portdb, vardb, NEWS_PATH, UNREAD_PATH) + return manager.getUnreadItems( repo_id, update=update ) + |