X-Git-Url: http://git.tremily.us/?a=blobdiff_plain;f=pym%2Femerge%2F__init__.py;h=2246d9cf4047c8b53cdcd4e03f7559367dcd707b;hb=383b4fc6f8613dce23e30d141d63622d15ba83f6;hp=3be9614b59fe9cc67b13c6190cc0ae528aad9401;hpb=aa52ccdf6ac76d6f440f565371c0b675478d7271;p=portage.git diff --git a/pym/emerge/__init__.py b/pym/emerge/__init__.py index 3be9614b5..2246d9cf4 100644 --- a/pym/emerge/__init__.py +++ b/pym/emerge/__init__.py @@ -41,6 +41,8 @@ from portage.output import blue, bold, colorize, darkblue, darkgreen, darkred, g from portage.output import create_color_func good = create_color_func("GOOD") bad = create_color_func("BAD") +# white looks bad on terminals with white background +from portage.output import bold as white import portage.dep portage.dep._dep_check_strict = True @@ -50,9 +52,10 @@ import portage.exception from portage.data import secpass from portage.util import normalize_path as normpath from portage.util import writemsg +from portage.sets import InternalPackageSet +from portage.sets.profiles import PackagesSystemSet as SystemSet +from portage.sets.files import WorldSet -if not hasattr(__builtins__, "set"): - from sets import Set as set from itertools import chain, izip from UserDict import DictMixin @@ -135,7 +138,11 @@ def userquery(prompt, responses=None, colours=None): KeyboardInterrupt is converted to SystemExit to avoid tracebacks being printed.""" if responses is None: - responses, colours = ["Yes", "No"], [green, red] + responses = ["Yes", "No"] + colours = [ + create_color_func("PROMPT_CHOICE_DEFAULT"), + create_color_func("PROMPT_CHOICE_OTHER") + ] elif colours is None: colours=[bold] colours=(colours*len(responses))[:len(responses)] @@ -338,27 +345,20 @@ def create_depgraph_params(myopts, myaction): # recurse: go into the dependencies # deep: go into the dependencies of already merged packages # empty: pretend nothing is merged - myparams = ["recurse"] - add=[] - sub=[] + myparams = set(["recurse"]) if "--update" in myopts or \ "--newuse" in myopts or \ + "--reinstall" in myopts or \ "--noreplace" in myopts or \ myaction in ("system", "world"): - add.extend(["selective"]) + myparams.add("selective") if "--emptytree" in myopts: - add.extend(["empty"]) - sub.extend(["selective"]) + myparams.add("empty") + myparams.discard("selective") if "--nodeps" in myopts: - sub.extend(["recurse"]) + myparams.discard("recurse") if "--deep" in myopts: - add.extend(["deep"]) - for x in add: - if (x not in myparams) and (x not in sub): - myparams.append(x) - for x in sub: - if x in myparams: - myparams.remove(x) + myparams.add("deep") return myparams # search functionality @@ -567,8 +567,9 @@ def getlist(settings, mode): def clean_world(vardb, cpv): """Remove a package from the world file when unmerged.""" - world_filename = os.path.join(vardb.root, portage.WORLD_FILE) - worldlist = portage.util.grabfile(world_filename) + world_set = WorldSet("world", vardb.settings["ROOT"]) + world_set.lock() + worldlist = list(world_set) # loads latest from disk mykey = portage.cpv_getkey(cpv) newworldlist = [] for x in worldlist: @@ -586,18 +587,80 @@ def clean_world(vardb, cpv): else: #this doesn't match the package we're unmerging; keep it. newworldlist.append(x) - - newworldlist.sort() - - portage.util.ensure_dirs(os.path.join(vardb.root, portage.PRIVATE_PATH), - gid=portage.portage_gid, mode=02770) - portage.util.write_atomic(world_filename, "\n".join(newworldlist)+"\n") -def genericdict(mylist): - mynewdict={} - for x in mylist: - mynewdict[portage.dep_getkey(x)]=x - return mynewdict + world_set.replace(newworldlist) + world_set.unlock() + + +class RootConfig(object): + """This is used internally by depgraph to track information about a + particular $ROOT.""" + def __init__(self, trees): + self.trees = trees + self.settings = trees["vartree"].settings + self.root = self.settings["ROOT"] + self.sets = {} + world_set = WorldSet("world", self.root) + self.sets["world"] = world_set + system_set = SystemSet("system", self.settings.profiles) + self.sets["system"] = system_set + +def create_world_atom(pkg_key, metadata, args_set, root_config): + """Create a new atom for the world file if one does not exist. If the + argument atom is precise enough to identify a specific slot then a slot + atom will be returned. Atoms that are in the system set may also be stored + in world since system atoms can only match one slot while world atoms can + be greedy with respect to slots. Unslotted system packages will not be + stored in world.""" + arg_atom = args_set.findAtomForPackage(pkg_key, metadata) + cp = portage.dep_getkey(arg_atom) + new_world_atom = cp + sets = root_config.sets + portdb = root_config.trees["porttree"].dbapi + vardb = root_config.trees["vartree"].dbapi + available_slots = set(portdb.aux_get(cpv, ["SLOT"])[0] \ + for cpv in portdb.match(cp)) + slotted = len(available_slots) > 1 or \ + (len(available_slots) == 1 and "0" not in available_slots) + if not slotted: + # check the vdb in case this is multislot + available_slots = set(vardb.aux_get(cpv, ["SLOT"])[0] \ + for cpv in vardb.match(cp)) + slotted = len(available_slots) > 1 or \ + (len(available_slots) == 1 and "0" not in available_slots) + if slotted and arg_atom != cp: + # If the user gave a specific atom, store it as a + # slot atom in the world file. + slot_atom = "%s:%s" % (cp, metadata["SLOT"]) + # First verify the slot is in the portage tree to avoid + # adding a bogus slot like that produced by multislot. + if portdb.match(slot_atom): + # Now verify that the argument is precise enough to identify a + # specific slot. + matches = portdb.match(arg_atom) + matched_slots = set() + for cpv in matches: + matched_slots.add(portdb.aux_get(cpv, ["SLOT"])[0]) + if len(matched_slots) == 1: + new_world_atom = slot_atom + if new_world_atom == sets["world"].findAtomForPackage(pkg_key, metadata): + # Both atoms would be identical, so there's nothing to add. + return None + if not slotted: + # Unlike world atoms, system atoms are not greedy for slots, so they + # can't be safely excluded from world if they are slotted. + system_atom = sets["system"].findAtomForPackage(pkg_key, metadata) + if system_atom: + if not portage.dep_getkey(system_atom).startswith("virtual/"): + return None + # System virtuals aren't safe to exclude from world since they can + # match multiple old-style virtuals but only one of them will be + # pulled in by update or depclean. + providers = portdb.mysettings.getvirtuals().get( + portage.dep_getkey(system_atom)) + if providers and len(providers) == 1 and providers[0] == cp: + return None + return new_world_atom def filter_iuse_defaults(iuse): for flag in iuse: @@ -629,13 +692,15 @@ class DepPriority(object): levels: MEDIUM The upper boundary for medium dependencies. + MEDIUM_SOFT The upper boundary for medium-soft dependencies. SOFT The upper boundary for soft dependencies. MIN The lower boundary for soft dependencies. """ - __slots__ = ("__weakref__", "satisfied", "buildtime", "runtime") + __slots__ = ("__weakref__", "satisfied", "buildtime", "runtime", "runtime_post") MEDIUM = -1 - SOFT = -2 - MIN = -4 + MEDIUM_SOFT = -2 + SOFT = -3 + MIN = -6 def __init__(self, **kwargs): for myattr in self.__slots__: if myattr == "__weakref__": @@ -648,11 +713,15 @@ class DepPriority(object): return 0 if self.runtime: return -1 + if self.runtime_post: + return -2 if self.buildtime: - return -2 - if self.runtime: return -3 - return -4 + if self.runtime: + return -4 + if self.runtime_post: + return -5 + return -6 def __lt__(self, other): return self.__int__() < other def __le__(self, other): @@ -672,8 +741,10 @@ class DepPriority(object): myvalue = self.__int__() if myvalue > self.MEDIUM: return "hard" - if myvalue > self.SOFT: + if myvalue > self.MEDIUM_SOFT: return "medium" + if myvalue > self.SOFT: + return "medium-soft" return "soft" class FakeVartree(portage.vartree): @@ -701,7 +772,7 @@ class FakeVartree(portage.vartree): if os.access(vdb_path, os.W_OK): vdb_lock = portage.locks.lockdir(vdb_path) mykeys = ["SLOT", "COUNTER", "PROVIDE", "USE", "IUSE", - "DEPEND", "RDEPEND", "PDEPEND"] + "RESTRICT", "DEPEND", "RDEPEND", "PDEPEND", "repository"] real_dbapi = real_vartree.dbapi slot_counters = {} for cpv in real_dbapi.cpv_all(): @@ -877,6 +948,13 @@ class BlockerCache(DictMixin): """ return self.BlockerData(*self._cache_data["blockers"][cpv]) + def keys(self): + """This needs to be implemented so that self.__repr__() doesn't raise + an AttributeError.""" + if self._cache_data and "blockers" in self._cache_data: + return self._cache_data["blockers"].keys() + return [] + def show_invalid_depstring_notice(parent_node, depstring, error_msg): from formatter import AbstractFormatter, DumbWriter @@ -933,10 +1011,14 @@ class depgraph(object): self.pkg_node_map = {} # Maps slot atom to digraph node for all nodes added to the graph. self._slot_node_map = {} + # Maps nodes to the reasons they were selected for reinstallation. + self._reinstall_nodes = {} self.mydbapi = {} - self._mydbapi_keys = ["SLOT", "DEPEND", "RDEPEND", "PDEPEND"] + self._mydbapi_keys = ["SLOT", "DEPEND", "RDEPEND", "PDEPEND", + "USE", "IUSE", "PROVIDE", "RESTRICT", "repository"] self.useFlags = {} self.trees = {} + self.roots = {} for myroot in trees: self.trees[myroot] = {} for tree in ("porttree", "bintree"): @@ -949,6 +1031,7 @@ class depgraph(object): self.pkg_node_map[myroot] = {} self._slot_node_map[myroot] = {} vardb = self.trees[myroot]["vartree"].dbapi + self.roots[myroot] = RootConfig(self.trees[myroot]) # This fakedbapi instance will model the state that the vdb will # have after new packages have been installed. fakedb = portage.fakedbapi(settings=self.pkgsettings[myroot]) @@ -977,7 +1060,15 @@ class depgraph(object): self._parent_child_digraph = digraph() self.orderedkeys=[] self.outdatedpackages=[] - self.args_keys = [] + # contains all sets added to the graph + self._sets = {} + # contains atoms given as arguments + self._sets["args"] = InternalPackageSet() + # contains all atoms from all sets added to the graph, including + # atoms given as arguments + self._set_atoms = InternalPackageSet() + # contains all nodes pulled in by self._set_atoms + self._set_nodes = set() self.blocker_digraph = digraph() self.blocker_parents = {} self._unresolved_blocker_parents = {} @@ -1041,6 +1132,24 @@ class depgraph(object): f.end_paragraph(1) f.writer.flush() + def _reinstall_for_flags(self, forced_flags, + orig_use, orig_iuse, cur_use, cur_iuse): + """Return a set of flags that trigger reinstallation, or None if there + are no such flags.""" + if "--newuse" in self.myopts: + flags = orig_iuse.symmetric_difference( + cur_iuse).difference(forced_flags) + flags.update(orig_iuse.intersection(orig_use).symmetric_difference( + cur_iuse.intersection(cur_use))) + if flags: + return flags + elif "changed-use" == self.myopts.get("--reinstall"): + flags = orig_iuse.intersection(orig_use).symmetric_difference( + cur_iuse.intersection(cur_use)) + if flags: + return flags + return None + def create(self, mybigkey, myparent=None, addme=1, myuse=None, priority=DepPriority(), rev_dep=False, arg=None): """ @@ -1093,15 +1202,24 @@ class depgraph(object): # directive, otherwise we add a "merge" directive. mydbapi = self.trees[myroot][self.pkg_tree_map[mytype]].dbapi + metadata = dict(izip(self._mydbapi_keys, + mydbapi.aux_get(mykey, self._mydbapi_keys))) + if mytype == "ebuild": + pkgsettings.setcpv(mykey, mydb=portdb) + metadata["USE"] = pkgsettings["USE"] + myuse = pkgsettings["USE"].split() if not arg and myroot == self.target_root: - cpv_slot = "%s:%s" % (mykey, mydbapi.aux_get(mykey, ["SLOT"])[0]) - arg = portage.best_match_to_list(cpv_slot, self.args_keys) - - if myuse is None: - self.pkgsettings[myroot].setcpv(mykey, mydb=portdb) - myuse = self.pkgsettings[myroot]["USE"].split() + try: + arg = self._set_atoms.findAtomForPackage(mykey, metadata) + except portage.exception.InvalidDependString, e: + if mytype != "installed": + show_invalid_depstring_notice(tuple(mybigkey+["merge"]), + metadata["PROVIDE"], str(e)) + return 0 + del e + reinstall_for_flags = None merging=1 if mytype == "installed": merging = 0 @@ -1114,23 +1232,23 @@ class depgraph(object): """ If we aren't merging, perform the --newuse check. If the package has new iuse flags or different use flags then if --newuse is specified, we need to merge the package. """ - if merging==0 and "--newuse" in self.myopts and \ + if merging == 0 and \ + myroot == self.target_root and \ + ("--newuse" in self.myopts or + "--reinstall" in self.myopts) and \ vardbapi.cpv_exists(mykey): pkgsettings.setcpv(mykey, mydb=mydbapi) forced_flags = set() forced_flags.update(pkgsettings.useforce) forced_flags.update(pkgsettings.usemask) old_use = vardbapi.aux_get(mykey, ["USE"])[0].split() - iuses = set(filter_iuse_defaults( - mydbapi.aux_get(mykey, ["IUSE"])[0].split())) + iuses = set(filter_iuse_defaults(metadata["IUSE"].split())) old_iuse = set(filter_iuse_defaults( vardbapi.aux_get(mykey, ["IUSE"])[0].split())) - if iuses.symmetric_difference( - old_iuse).difference(forced_flags): + reinstall_for_flags = self._reinstall_for_flags( + forced_flags, old_use, old_iuse, myuse, iuses) + if reinstall_for_flags: merging = 1 - elif old_iuse.intersection(old_use) != \ - iuses.intersection(myuse): - merging=1 if addme and merging == 1: mybigkey.append("merge") @@ -1139,16 +1257,15 @@ class depgraph(object): jbigkey = tuple(mybigkey) if addme: - metadata = dict(izip(self._mydbapi_keys, - mydbapi.aux_get(mykey, self._mydbapi_keys))) if merging == 0 and vardbapi.cpv_exists(mykey) and \ mytype != "installed": + mytype = "installed" mybigkey[0] = "installed" mydbapi = vardbapi jbigkey = tuple(mybigkey) metadata = dict(izip(self._mydbapi_keys, mydbapi.aux_get(mykey, self._mydbapi_keys))) - myuse = mydbapi.aux_get(mykey, ["USE"])[0].split() + myuse = metadata["USE"].split() slot_atom = "%s:%s" % (portage.dep_getkey(mykey), metadata["SLOT"]) existing_node = self._slot_node_map[myroot].get( slot_atom, None) @@ -1198,6 +1315,8 @@ class depgraph(object): self._slot_node_map[myroot][slot_atom] = jbigkey self.pkg_node_map[myroot][mykey] = jbigkey self.useFlags[myroot][mykey] = myuse + if reinstall_for_flags: + self._reinstall_nodes[jbigkey] = reinstall_for_flags if rev_dep and myparent: self.digraph.addnode(myparent, jbigkey, @@ -1206,6 +1325,9 @@ class depgraph(object): self.digraph.addnode(jbigkey, myparent, priority=priority) + if arg: + self._set_nodes.add(jbigkey) + # Do this even when addme is False (--onlydeps) so that the # parent/child relationship is always known in case # self._show_slot_collision_notice() needs to be called later. @@ -1229,9 +1351,8 @@ class depgraph(object): edepend={} depkeys = ["DEPEND","RDEPEND","PDEPEND"] - depvalues = mydbapi.aux_get(mykey, depkeys) - for i in xrange(len(depkeys)): - edepend[depkeys[i]] = depvalues[i] + for k in depkeys: + edepend[k] = metadata[k] if mytype == "ebuild": if "--buildpkgonly" in self.myopts: @@ -1266,7 +1387,7 @@ class depgraph(object): # Post Depend -- Add to the list without a parent, as it depends # on a package being present AND must be built after that package. if not self.select_dep(myroot, edepend["PDEPEND"], myparent=mp, - myuse=myuse, priority=DepPriority(), rev_deps=True, + myuse=myuse, priority=DepPriority(runtime_post=True), parent_arg=arg): return 0 except ValueError, e: @@ -1293,6 +1414,7 @@ class depgraph(object): "given a list of .tbz2s, .ebuilds and deps, create the appropriate depgraph and return a favorite list" myfavorites=[] myroot = self.target_root + vardb = self.trees[myroot]["vartree"].dbapi portdb = self.trees[myroot]["porttree"].dbapi bindb = self.trees[myroot]["bintree"].dbapi pkgsettings = self.pkgsettings[myroot] @@ -1314,26 +1436,38 @@ class depgraph(object): else: print "\n\n!!! Binary package '"+str(x)+"' does not exist." print "!!! Please ensure the tbz2 exists as specified.\n" - sys.exit(1) + return 0, myfavorites mytbz2=portage.xpak.tbz2(x) mykey=mytbz2.getelements("CATEGORY")[0]+"/"+os.path.splitext(os.path.basename(x))[0] if os.path.realpath(x) != \ os.path.realpath(self.trees[myroot]["bintree"].getname(mykey)): print colorize("BAD", "\n*** You need to adjust PKGDIR to emerge this package.\n") - sys.exit(1) + return 0, myfavorites if not self.create(["binary", myroot, mykey], - None, "--onlydeps" not in self.myopts): + None, "--onlydeps" not in self.myopts, + myuse=mytbz2.getelements("USE"), arg=x): return (0,myfavorites) - elif not "--oneshot" in self.myopts: - myfavorites.append(mykey) + arg_atoms.append((x, "="+mykey)) elif ext==".ebuild": - x = os.path.realpath(x) - mykey=os.path.basename(os.path.normpath(x+"/../.."))+"/"+os.path.splitext(os.path.basename(x))[0] + ebuild_path = portage.util.normalize_path(os.path.abspath(x)) + pkgdir = os.path.dirname(ebuild_path) + tree_root = os.path.dirname(os.path.dirname(pkgdir)) + cp = pkgdir[len(tree_root)+1:] + e = portage.exception.PackageNotFound( + ("%s is not in a valid portage tree " + \ + "hierarchy or does not exist") % x) + if not portage.isvalidatom(cp): + raise e + cat = portage.catsplit(cp)[0] + mykey = cat + "/" + os.path.basename(ebuild_path[:-7]) + if not portage.isvalidatom("="+mykey): + raise e ebuild_path = portdb.findname(mykey) if ebuild_path: - if os.path.realpath(ebuild_path) != x: + if ebuild_path != os.path.join(os.path.realpath(tree_root), + cp, os.path.basename(ebuild_path)): print colorize("BAD", "\n*** You need to adjust PORTDIR or PORTDIR_OVERLAY to emerge this package.\n") - sys.exit(1) + return 0, myfavorites if mykey not in portdb.xmatch( "match-visible", portage.dep_getkey(mykey)): print colorize("BAD", "\n*** You are emerging a masked package. It is MUCH better to use") @@ -1345,10 +1479,9 @@ class depgraph(object): raise portage.exception.PackageNotFound( "%s is not in a valid portage tree hierarchy or does not exist" % x) if not self.create(["ebuild", myroot, mykey], - None, "--onlydeps" not in self.myopts): + None, "--onlydeps" not in self.myopts, arg=x): return (0,myfavorites) - elif not "--oneshot" in self.myopts: - myfavorites.append(mykey) + arg_atoms.append((x, "="+mykey)) else: if not is_valid_package_atom(x): portage.writemsg("\n\n!!! '%s' is not a valid package atom.\n" % x, @@ -1361,13 +1494,22 @@ class depgraph(object): if "--usepkg" in self.myopts: mykey = portage.dep_expand(x, mydb=bindb, settings=pkgsettings) - if (mykey and not mykey.startswith("null/")) or \ - "--usepkgonly" in self.myopts: + if "--usepkgonly" in self.myopts or \ + (mykey and not portage.dep_getkey(mykey).startswith("null/")): arg_atoms.append((x, mykey)) continue - mykey = portage.dep_expand(x, - mydb=portdb, settings=pkgsettings) + try: + mykey = portage.dep_expand(x, + mydb=portdb, settings=pkgsettings) + except ValueError, e: + mykey = portage.dep_expand(x, + mydb=vardb, settings=pkgsettings) + cp = portage.dep_getkey(mykey) + if cp.startswith("null/") or \ + cp not in e[0]: + raise + del e arg_atoms.append((x, mykey)) except ValueError, errpkgs: print "\n\n!!! The short ebuild name \"" + x + "\" is ambiguous. Please specify" @@ -1426,9 +1568,17 @@ class depgraph(object): greedy_atoms.append((myarg, myslot_atom)) arg_atoms = greedy_atoms + oneshot = "--oneshot" in self.myopts or "--onlydeps" in self.myopts """ These are used inside self.create() in order to ensure packages that happen to match arguments are not incorrectly marked as nomerge.""" - self.args_keys = [x[1] for x in arg_atoms] + args_set = self._sets["args"] + for myarg, myatom in arg_atoms: + if myatom in args_set: + continue + args_set.add(myatom) + self._set_atoms.add(myatom) + if not oneshot: + myfavorites.append(myatom) for myarg, myatom in arg_atoms: try: self.mysd = self.select_dep(myroot, myatom, arg=myarg) @@ -1455,10 +1605,6 @@ class depgraph(object): if not self.mysd: return (0,myfavorites) - elif not "--oneshot" in self.myopts: - mykey = portage.dep_getkey(myatom) - if mykey not in myfavorites: - myfavorites.append(mykey) missing=0 if "--usepkgonly" in self.myopts: @@ -1532,11 +1678,11 @@ class depgraph(object): return 0 mymerge = mycheck[1] - if not mymerge and arg and \ - portage.best_match_to_list(depstring, self.args_keys): + if not mymerge and arg: # A provided package has been specified on the command line. The # package will not be merged and a warning will be displayed. - self._pprovided_args.append(arg) + if depstring in self._set_atoms: + self._pprovided_args.append((arg, depstring)) if myparent: # The parent is added after it's own dep_check call so that it @@ -1549,9 +1695,17 @@ class depgraph(object): if p_status == "merge": # Update old-style virtuals if this package provides any. # These are needed for dep_virtual calls inside dep_check. - p_db = self.trees[p_root][self.pkg_tree_map[p_type]].dbapi + p_db = self.mydbapi[p_root] # contains cached metadata + if myparent in self._slot_collision_nodes: + # The metadata isn't cached due to the slot collision. + p_db = self.trees[p_root][self.pkg_tree_map[p_type]].dbapi try: self.pkgsettings[p_root].setinst(p_key, p_db) + # For consistency, also update the global virtuals. + settings = self.roots[p_root].settings + settings.unlock() + settings.setinst(p_key, p_db) + settings.lock() except portage.exception.InvalidDependString, e: provide = p_db.aux_get(p_key, ["PROVIDE"])[0] show_invalid_depstring_notice(myparent, provide, str(e)) @@ -1596,7 +1750,9 @@ class depgraph(object): if myeb_pkg_matches: myeb_pkg = portage.best(myeb_pkg_matches) - if myeb_pkg and "--newuse" in self.myopts: + if myeb_pkg and \ + ("--newuse" in self.myopts or \ + "--reinstall" in self.myopts): iuses = set(filter_iuse_defaults( bindb.aux_get(myeb_pkg, ["IUSE"])[0].split())) old_use = bindb.aux_get(myeb_pkg, ["USE"])[0].split() @@ -1615,11 +1771,8 @@ class depgraph(object): if "--usepkgonly" not in self.myopts and myeb: cur_iuse = set(filter_iuse_defaults( portdb.aux_get(myeb, ["IUSE"])[0].split())) - if iuses.symmetric_difference( - cur_iuse).difference(forced_flags): - myeb_pkg = None - elif iuses.intersection(old_use) != \ - cur_iuse.intersection(now_use): + if self._reinstall_for_flags( + forced_flags, old_use, iuses, now_use, cur_iuse): myeb_pkg = None if myeb_pkg: binpkguseflags = \ @@ -2029,6 +2182,9 @@ class depgraph(object): self._altlist_cache[reversed] = retlist[:] return retlist mygraph=self.digraph.copy() + for node in mygraph.order[:]: + if node[-1] == "nomerge": + mygraph.remove(node) self._merge_order_bias(mygraph) myblockers = self.blocker_digraph.copy() retlist=[] @@ -2043,32 +2199,34 @@ class depgraph(object): if "portage" == portage.catsplit(portage.dep_getkey(cpv))[-1]: asap_nodes.append(node) break - ignore_priority_range = [None] - ignore_priority_range.extend( - xrange(DepPriority.MIN, DepPriority.MEDIUM + 1)) + ignore_priority_soft_range = [None] + ignore_priority_soft_range.extend( + xrange(DepPriority.MIN, DepPriority.SOFT + 1)) tree_mode = "--tree" in self.myopts + # Tracks whether or not the current iteration should prefer asap_nodes + # if available. This is set to False when the previous iteration + # failed to select any nodes. It is reset whenever nodes are + # successfully selected. + prefer_asap = True while not mygraph.empty(): - ignore_priority = None - nodes = None - if asap_nodes: + selected_nodes = None + if prefer_asap and asap_nodes: """ASAP nodes are merged before their soft deps.""" + asap_nodes = [node for node in asap_nodes \ + if mygraph.contains(node)] for node in asap_nodes: - if not mygraph.contains(node): - asap_nodes.remove(node) - continue if not mygraph.child_nodes(node, ignore_priority=DepPriority.SOFT): - nodes = [node] + selected_nodes = [node] asap_nodes.remove(node) break - if not nodes: - for ignore_priority in ignore_priority_range: + if not selected_nodes and \ + not (prefer_asap and asap_nodes): + for ignore_priority in ignore_priority_soft_range: nodes = get_nodes(ignore_priority=ignore_priority) if nodes: break - selected_nodes = None - if nodes: - if ignore_priority <= DepPriority.SOFT: + if nodes: if ignore_priority is None and not tree_mode: # Greedily pop all of these nodes since no relationship # has been ignored. This optimization destroys --tree @@ -2087,30 +2245,65 @@ class depgraph(object): if not selected_nodes: # settle for a root node selected_nodes = [nodes[0]] - else: + if not selected_nodes: + nodes = get_nodes(ignore_priority=DepPriority.MEDIUM) + if nodes: """Recursively gather a group of nodes that RDEPEND on eachother. This ensures that they are merged as a group and get their RDEPENDs satisfied as soon as possible.""" - def gather_deps(mergeable_nodes, selected_nodes, node): + def gather_deps(ignore_priority, + mergeable_nodes, selected_nodes, node): if node in selected_nodes: return True if node not in mergeable_nodes: return False selected_nodes.add(node) for child in mygraph.child_nodes(node, - ignore_priority=DepPriority.SOFT): - if not gather_deps( + ignore_priority=ignore_priority): + if not gather_deps(ignore_priority, mergeable_nodes, selected_nodes, child): return False return True mergeable_nodes = set(nodes) - for node in nodes: - selected_nodes = set() - if gather_deps( - mergeable_nodes, selected_nodes, node): + if prefer_asap and asap_nodes: + nodes = asap_nodes + for ignore_priority in xrange(DepPriority.SOFT, + DepPriority.MEDIUM_SOFT + 1): + for node in nodes: + selected_nodes = set() + if gather_deps(ignore_priority, + mergeable_nodes, selected_nodes, node): + break + else: + selected_nodes = None + if selected_nodes: break - else: - selected_nodes = None + + if prefer_asap and asap_nodes and not selected_nodes: + # We failed to find any asap nodes to merge, so ignore + # them for the next iteration. + prefer_asap = False + continue + + if selected_nodes and ignore_priority > DepPriority.SOFT: + # Try to merge ignored medium deps as soon as possible. + for node in selected_nodes: + children = set(mygraph.child_nodes(node)) + soft = children.difference( + mygraph.child_nodes(node, + ignore_priority=DepPriority.SOFT)) + medium_soft = children.difference( + mygraph.child_nodes(node, + ignore_priority=DepPriority.MEDIUM_SOFT)) + medium_soft.difference_update(soft) + for child in medium_soft: + if child in selected_nodes: + continue + if child in asap_nodes: + continue + # TODO: Try harder to make these nodes get + # merged absolutely as soon as possible. + asap_nodes.append(child) if not selected_nodes: if not myblockers.is_empty(): @@ -2171,6 +2364,10 @@ class depgraph(object): print "!!! disabling USE flags that trigger optional dependencies." sys.exit(1) + # At this point, we've succeeded in selecting one or more nodes, so + # it's now safe to reset the prefer_asap to it's default state. + prefer_asap = True + for node in selected_nodes: retlist.append(list(node)) mygraph.remove(node) @@ -2208,15 +2405,15 @@ class depgraph(object): return [x for x in mylist \ if x in matches or not portdb.cpv_exists(x)] world_problems = False - if mode=="system": - mylist = getlist(self.settings, "system") - else: - #world mode - worldlist = getlist(self.settings, "world") - mylist = getlist(self.settings, "system") - worlddict=genericdict(worldlist) - for x in worlddict: + root_config = self.roots[self.target_root] + world_set = root_config.sets["world"] + system_set = root_config.sets["system"] + mylist = list(system_set) + self._sets["system"] = system_set + if mode == "world": + self._sets["world"] = world_set + for x in world_set: if not portage.isvalidatom(x): world_problems = True continue @@ -2241,9 +2438,12 @@ class depgraph(object): mykey = portage.dep_getkey(atom) if True: newlist.append(atom) - """Make sure all installed slots are updated when possible. - Do this with --emptytree also, to ensure that all slots are - remerged.""" + if mode == "system" or atom not in world_set: + # only world is greedy for slots, not system + continue + # Make sure all installed slots are updated when possible. + # Do this with --emptytree also, to ensure that all slots are + # remerged. myslots = set() for cpv in vardb.match(mykey): myslots.add(vardb.aux_get(cpv, ["SLOT"])[0]) @@ -2284,7 +2484,10 @@ class depgraph(object): if available: newlist.append(myslot_atom) mylist = newlist - + + for myatom in mylist: + self._set_atoms.add(myatom) + missing_atoms = [] for mydep in mylist: try: @@ -2310,10 +2513,11 @@ class depgraph(object): return 1 - def display(self,mylist,verbosity=None): + def display(self, mylist, favorites=[], verbosity=None): if verbosity is None: verbosity = ("--quiet" in self.myopts and 1 or \ "--verbose" in self.myopts and 3 or 2) + favorites_set = InternalPackageSet(favorites) changelogs=[] p=[] blockers = [] @@ -2385,12 +2589,7 @@ class depgraph(object): ret = '%s="%s" ' % (name, ret) return ret - if verbosity == 3: - # FIXME: account for the possibility of different overlays in - # /etc/make.conf vs. ${PORTAGE_CONFIGROOT}/etc/make.conf - overlays = self.settings["PORTDIR_OVERLAY"].split() - overlays_real = [os.path.realpath(t) \ - for t in self.settings["PORTDIR_OVERLAY"].split()] + repo_display = RepoDisplay(self.roots) tree_nodes = [] display_list = [] @@ -2417,7 +2616,12 @@ class depgraph(object): traversed_nodes = set() # prevent endless circles traversed_nodes.add(graph_key) def add_parents(current_node, ordered): - parent_nodes = mygraph.parent_nodes(current_node) + parent_nodes = None + # Do not traverse to parents if this node is an + # an argument or a direct member of a set that has + # been specified as an argument (system or world). + if current_node not in self._set_nodes: + parent_nodes = mygraph.parent_nodes(current_node) if parent_nodes: child_nodes = set(mygraph.child_nodes(current_node)) selected_parent = None @@ -2474,14 +2678,13 @@ class depgraph(object): from portage import flatten from portage.dep import use_reduce, paren_reduce - display_overlays=False # files to fetch list - avoids counting a same file twice # in size display (verbose mode) myfetchlist=[] - worldlist = set(getlist(self.settings, "world")) for mylist_index in xrange(len(mylist)): x, depth, ordered = mylist[mylist_index] + pkg_node = tuple(x) pkg_type = x[0] myroot = x[1] pkg_key = x[2] @@ -2513,23 +2716,27 @@ class depgraph(object): addl += bad(" (is blocking %s)") % block_parents blockers.append(addl) else: - mydbapi = self.trees[myroot][self.pkg_tree_map[pkg_type]].dbapi pkg_status = x[3] pkg_merge = ordered and pkg_status != "nomerge" - binary_package = True - if "ebuild" == pkg_type: - if "merge" == x[3] or \ - not vartree.dbapi.cpv_exists(pkg_key): - """An ebuild "merge" node or a --onlydeps "nomerge" - node.""" - binary_package = False - pkgsettings.setcpv(pkg_key, mydb=portdb) - if pkg_key not in self.useFlags[myroot]: - self.useFlags[myroot][pkg_key] = \ - pkgsettings["USE"].split() - else: - # An ebuild "nomerge" node, so USE come from the vardb. - mydbapi = vartree.dbapi + if pkg_node in self._slot_collision_nodes or \ + (pkg_status == "nomerge" and pkg_type != "installed"): + # The metadata isn't cached due to a slot collision or + # --onlydeps. + mydbapi = self.trees[myroot][self.pkg_tree_map[pkg_type]].dbapi + else: + mydbapi = self.mydbapi[myroot] # contains cached metadata + metadata = dict(izip(self._mydbapi_keys, + mydbapi.aux_get(pkg_key, self._mydbapi_keys))) + ebuild_path = None + repo_name = metadata["repository"] + if pkg_type == "ebuild": + ebuild_path = portdb.findname(pkg_key) + if not ebuild_path: # shouldn't happen + raise portage.exception.PackageNotFound(pkg_key) + repo_path_real = os.path.dirname(os.path.dirname( + os.path.dirname(ebuild_path))) + else: + repo_path_real = portdb.getRepositoryPath(repo_name) if pkg_key not in self.useFlags[myroot]: """If this is a --resume then the USE flags need to be fetched from the appropriate locations here.""" @@ -2545,7 +2752,7 @@ class depgraph(object): restrict = mydbapi.aux_get(pkg_key, ["RESTRICT"])[0] show_invalid_depstring_notice(x, restrict, str(e)) del e - sys.exit(1) + return 1 restrict = [] if "ebuild" == pkg_type and x[3] != "nomerge" and \ "fetch" in restrict: @@ -2612,9 +2819,9 @@ class depgraph(object): mydbapi.aux_get(pkg_key, ["IUSE"])[0].split())) forced_flags = set() - if not binary_package: - forced_flags.update(pkgsettings.useforce) - forced_flags.update(pkgsettings.usemask) + pkgsettings.setcpv(pkg_key) # for package.use.{mask,force} + forced_flags.update(pkgsettings.useforce) + forced_flags.update(pkgsettings.usemask) cur_iuse = portage.unique_array(cur_iuse) cur_iuse.sort() @@ -2647,7 +2854,8 @@ class depgraph(object): use_expand_hidden = \ pkgsettings["USE_EXPAND_HIDDEN"].lower().split() - def map_to_use_expand(myvals, forcedFlags=False): + def map_to_use_expand(myvals, forcedFlags=False, + removeHidden=True): ret = {} forced = {} for exp in use_expand: @@ -2662,13 +2870,32 @@ class depgraph(object): ret["USE"] = myvals forced["USE"] = [val for val in myvals \ if val in forced_flags] - for exp in use_expand_hidden: - if exp in ret: - del ret[exp] + if removeHidden: + for exp in use_expand_hidden: + ret.pop(exp, None) if forcedFlags: return ret, forced return ret + # Prevent USE_EXPAND_HIDDEN flags from being hidden if they + # are the only thing that triggered reinstallation. + reinst_flags_map = None + reinstall_for_flags = self._reinstall_nodes.get(pkg_node) + if reinstall_for_flags: + reinst_flags_map = map_to_use_expand( + list(reinstall_for_flags), removeHidden=False) + if reinst_flags_map["USE"]: + reinst_flags_map = None + else: + for k in reinst_flags_map.keys(): + if not reinst_flags_map[k]: + del reinst_flags_map[k] + if reinst_flags_map and \ + not set(reinst_flags_map).difference( + use_expand_hidden): + use_expand_hidden = set(use_expand_hidden).difference( + reinst_flags_map) + cur_iuse_map, iuse_forced = \ map_to_use_expand(cur_iuse, forcedFlags=True) cur_use_map = map_to_use_expand(cur_use) @@ -2698,7 +2925,7 @@ class depgraph(object): src_uri = portdb.aux_get(pkg_key, ["SRC_URI"])[0] show_invalid_depstring_notice(x, src_uri, str(e)) del e - sys.exit(1) + return 1 if myfilesdict is None: myfilesdict="[empty/missing/bad digest]" else: @@ -2710,17 +2937,31 @@ class depgraph(object): verboseadd+=format_size(mysize)+" " # overlay verbose - # XXX: Invalid binaries have caused tracebacks here. 'if file_name' - # x = ['binary', '/', 'sys-apps/pcmcia-cs-3.2.7.2.6', 'merge'] - file_name = portdb.findname(pkg_key) - if file_name: # It might not exist in the tree - dir_name=os.path.abspath(os.path.dirname(file_name)+"/../..") - if (overlays_real.count(dir_name)>0): - verboseadd+=teal("["+str(overlays_real.index( - os.path.normpath(dir_name))+1)+"]")+" " - display_overlays=True + # assign index for a previous version in the same slot + has_previous = False + repo_name_prev = None + slot_atom = "%s:%s" % (portage.dep_getkey(pkg_key), + metadata["SLOT"]) + slot_matches = vardb.match(slot_atom) + if slot_matches: + has_previous = True + repo_name_prev = vardb.aux_get(slot_matches[0], + ["repository"])[0] + + # now use the data to generate output + repoadd = None + if pkg_status == "nomerge" or not has_previous: + repoadd = repo_display.repoStr(repo_path_real) else: - verboseadd += "[No ebuild?]" + repo_path_prev = None + if repo_name_prev: + repo_path_prev = portdb.getRepositoryPath( + repo_name_prev) + repoadd = "%s=>%s" % ( + repo_display.repoStr(repo_path_prev), + repo_display.repoStr(repo_path_real)) + if repoadd: + verboseadd += teal("[%s]" % repoadd) xs = list(portage.pkgsplit(x[2])) if xs[2]=="r0": @@ -2750,16 +2991,37 @@ class depgraph(object): myoldbest=blue("["+myoldbest+"]") pkg_cp = xs[0] - pkg_world = pkg_cp in worldlist + root_config = self.roots[myroot] + system_set = root_config.sets["system"] + world_set = root_config.sets["world"] + + pkg_system = False + pkg_world = False + try: + pkg_system = system_set.findAtomForPackage(pkg_key, metadata) + pkg_world = world_set.findAtomForPackage(pkg_key, metadata) + if not pkg_world and myroot == self.target_root and \ + favorites_set.findAtomForPackage(pkg_key, metadata): + # Maybe it will be added to world now. + if create_world_atom(pkg_key, metadata, + favorites_set, root_config): + pkg_world = True + except portage.exception.InvalidDependString: + # This is reported elsewhere if relevant. + pass def pkgprint(pkg): if pkg_merge: - if pkg_world: + if pkg_system: + return colorize("PKG_MERGE_SYSTEM", pkg) + elif pkg_world: return colorize("PKG_MERGE_WORLD", pkg) else: return colorize("PKG_MERGE", pkg) else: - if pkg_world: + if pkg_system: + return colorize("PKG_NOMERGE_SYSTEM", pkg) + elif pkg_world: return colorize("PKG_NOMERGE_WORLD", pkg) else: return colorize("PKG_NOMERGE", pkg) @@ -2837,12 +3099,8 @@ class depgraph(object): if verbosity == 3: print print counters - if overlays and display_overlays: - print "Portage overlays:" - y=0 - for x in overlays: - y=y+1 - print " "+teal("["+str(y)+"]"),x + if p: + sys.stdout.write(str(repo_display)) if "--changelog" in self.myopts: print @@ -2851,6 +3109,14 @@ class depgraph(object): sys.stdout.write(text) if self._pprovided_args: + arg_refs = {} + for arg_atom in self._pprovided_args: + arg, atom = arg_atom + arg_refs[arg_atom] = [] + cp = portage.dep_getkey(atom) + for set_name, atom_set in self._sets.iteritems(): + if atom in atom_set: + arg_refs[arg_atom].append(set_name) msg = [] msg.append(bad("\nWARNING: ")) if len(self._pprovided_args) > 1: @@ -2859,11 +3125,26 @@ class depgraph(object): else: msg.append("A requested package will not be " + \ "merged because it is listed in\n") - msg.append(" package.provided:\n\n") - for arg in self._pprovided_args: - msg.append(" " + arg + "\n") + msg.append("package.provided:\n\n") + problems_sets = set() + for (arg, atom), refs in arg_refs.iteritems(): + ref_string = "" + if refs: + problems_sets.update(refs) + refs.sort() + ref_string = ", ".join(["'%s'" % name for name in refs]) + ref_string = " pulled in by " + ref_string + msg.append(" %s%s\n" % (colorize("INFORM", arg), ref_string)) msg.append("\n") + if "world" in problems_sets: + msg.append("This problem can be solved in one of the following ways:\n\n") + msg.append(" A) Use emaint to clean offending packages from world (if not installed).\n") + msg.append(" B) Uninstall offending packages (cleans them from world).\n") + msg.append(" C) Remove offending entries from package.provided.\n\n") + msg.append("The best course of action depends on the reason that an offending\n") + msg.append("package.provided entry exists.\n\n") sys.stderr.write("".join(msg)) + return os.EX_OK def calc_changelog(self,ebuildpath,current,next): if ebuildpath == None or not os.path.exists(ebuildpath): @@ -2917,8 +3198,132 @@ class depgraph(object): if release.endswith('-r0'): release = release[:-3] - def outdated(self): - return self.outdatedpackages + def saveNomergeFavorites(self): + """Find atoms in favorites that are not in the mergelist and add them + to the world file if necessary.""" + for x in ("--fetchonly", "--fetch-all-uri", + "--oneshot", "--onlydeps", "--pretend"): + if x in self.myopts: + return + root_config = self.roots[self.target_root] + world_set = root_config.sets["world"] + world_set.lock() + world_set.load() # maybe it's changed on disk + args_set = self._sets["args"] + portdb = self.trees[self.target_root]["porttree"].dbapi + added_favorites = set() + for x in self._set_nodes: + pkg_type, root, pkg_key, pkg_status = x + if pkg_status != "nomerge": + continue + metadata = dict(izip(self._mydbapi_keys, + self.mydbapi[root].aux_get(pkg_key, self._mydbapi_keys))) + try: + myfavkey = create_world_atom(pkg_key, metadata, + args_set, root_config) + if myfavkey: + if myfavkey in added_favorites: + continue + added_favorites.add(myfavkey) + world_set.add(myfavkey) + print ">>> Recording",myfavkey,"in \"world\" favorites file..." + except portage.exception.InvalidDependString, e: + writemsg("\n\n!!! '%s' has invalid PROVIDE: %s\n" % \ + (pkg_key, str(e)), noiselevel=-1) + writemsg("!!! see '%s'\n\n" % os.path.join( + root, portage.VDB_PATH, pkg_key, "PROVIDE"), noiselevel=-1) + del e + world_set.unlock() + + def loadResumeCommand(self, resume_data): + """ + Add a resume command to the graph and validate it in the process. This + will raise a PackageNotFound exception if a package is not available. + """ + self._sets["args"].update(resume_data.get("favorites", [])) + mergelist = resume_data.get("mergelist", []) + fakedb = self.mydbapi + trees = self.trees + for x in mergelist: + if len(x) != 4: + continue + pkg_type, myroot, pkg_key, action = x + if pkg_type not in self.pkg_tree_map: + continue + if action != "merge": + continue + mydb = trees[myroot][self.pkg_tree_map[pkg_type]].dbapi + try: + metadata = dict(izip(self._mydbapi_keys, + mydb.aux_get(pkg_key, self._mydbapi_keys))) + except KeyError: + # It does no exist or it is corrupt. + raise portage.exception.PackageNotFound(pkg_key) + fakedb[myroot].cpv_inject(pkg_key, metadata=metadata) + if pkg_type == "ebuild": + pkgsettings = self.pkgsettings[myroot] + pkgsettings.setcpv(pkg_key, mydb=fakedb[myroot]) + fakedb[myroot].aux_update(pkg_key, {"USE":pkgsettings["USE"]}) + self.spinner.update() + +class RepoDisplay(object): + def __init__(self, roots): + self._shown_repos = {} + self._unknown_repo = False + repo_paths = set() + for root_config in roots.itervalues(): + portdir = root_config.settings.get("PORTDIR") + if portdir: + repo_paths.add(portdir) + overlays = root_config.settings.get("PORTDIR_OVERLAY") + if overlays: + repo_paths.update(overlays.split()) + repo_paths = list(repo_paths) + self._repo_paths = repo_paths + self._repo_paths_real = [ os.path.realpath(repo_path) \ + for repo_path in repo_paths ] + + # pre-allocate index for PORTDIR so that it always has index 0. + for root_config in roots.itervalues(): + portdb = root_config.trees["porttree"].dbapi + portdir = portdb.porttree_root + if portdir: + self.repoStr(portdir) + + def repoStr(self, repo_path_real): + real_index = -1 + if repo_path_real: + real_index = self._repo_paths_real.index(repo_path_real) + if real_index == -1: + s = "?" + self._unknown_repo = True + else: + shown_repos = self._shown_repos + repo_paths = self._repo_paths + repo_path = repo_paths[real_index] + index = shown_repos.get(repo_path) + if index is None: + index = len(shown_repos) + shown_repos[repo_path] = index + s = str(index) + return s + + def __str__(self): + output = [] + shown_repos = self._shown_repos + unknown_repo = self._unknown_repo + if shown_repos or self._unknown_repo: + output.append("Portage tree and overlays:\n") + show_repo_paths = list(shown_repos) + for repo_path, repo_index in shown_repos.iteritems(): + show_repo_paths[repo_index] = repo_path + if show_repo_paths: + for index, repo_path in enumerate(show_repo_paths): + output.append(" "+teal("["+str(index)+"]")+" %s\n" % repo_path) + if unknown_repo: + output.append(" "+teal("[?]") + \ + " indicates that the source repository could not be determined\n") + return "".join(output) class PackageCounters(object): @@ -2993,11 +3398,15 @@ class MergeTask(object): if self.target_root != "/": self.pkgsettings["/"] = \ portage.config(clone=trees["/"]["vartree"].settings) + self.curval = 0 def merge(self, mylist, favorites, mtimedb): + from portage.elog import elog_process + from portage.elog.filtering import filter_mergephases failed_fetches = [] fetchonly = "--fetchonly" in self.myopts or \ "--fetch-all-uri" in self.myopts + pretend = "--pretend" in self.myopts mymergelist=[] ldpath_mtimes = mtimedb["ldpath"] xterm_titles = "notitles" not in self.settings.features @@ -3024,7 +3433,6 @@ class MergeTask(object): del mtimedb["resume"]["mergelist"][0] del mylist[0] mtimedb.commit() - validate_merge_list(self.trees, mylist) mymergelist = mylist # Verify all the manifests now so that the user is notified of failure @@ -3055,35 +3463,12 @@ class MergeTask(object): del x, mytype, myroot, mycpv, mystatus, quiet_config del shown_verifying_msg, quiet_settings - #buildsyspkg: I need mysysdict also on resume (moved from the else block) - mysysdict = genericdict(getlist(self.settings, "system")) + root_config = RootConfig(self.trees[self.target_root]) + system_set = root_config.sets["system"] + args_set = InternalPackageSet(favorites) + world_set = root_config.sets["world"] if "--resume" not in self.myopts: - myfavs = portage.grabfile( - os.path.join(self.target_root, portage.WORLD_FILE)) - myfavdict=genericdict(myfavs) - for x in range(len(mylist)): - if mylist[x][3]!="nomerge": - # Add to the mergelist - mymergelist.append(mylist[x]) - else: - myfavkey=portage.cpv_getkey(mylist[x][2]) - if "--onlydeps" in self.myopts: - continue - # Add to the world file. Since we won't be able to later. - if "--fetchonly" not in self.myopts and \ - myfavkey in favorites: - #don't record if already in system profile or already recorded - if (not mysysdict.has_key(myfavkey)) and (not myfavdict.has_key(myfavkey)): - #we don't have a favorites entry for this package yet; add one - myfavdict[myfavkey]=myfavkey - print ">>> Recording",myfavkey,"in \"world\" favorites file..." - if not ("--fetchonly" in self.myopts or \ - "--fetch-all-uri" in self.myopts or \ - "--pretend" in self.myopts): - portage.write_atomic( - os.path.join(self.target_root, portage.WORLD_FILE), - "\n".join(sorted(myfavdict.values())) + "\n") - + mymergelist = mylist mtimedb["resume"]["mergelist"]=mymergelist[:] mtimedb.commit() @@ -3128,9 +3513,13 @@ class MergeTask(object): del fetch_log, logfile, fd_pipes, fetch_env, fetch_args, \ resume_opts + metadata_keys = [k for k in portage.auxdbkeys \ + if not k.startswith("UNUSED_")] + ["USE"] + mergecount=0 for x in mymergelist: mergecount+=1 + pkg_type = x[0] myroot=x[1] pkg_key = x[2] pkgindex=2 @@ -3138,6 +3527,22 @@ class MergeTask(object): bindb = self.trees[myroot]["bintree"].dbapi vartree = self.trees[myroot]["vartree"] pkgsettings = self.pkgsettings[myroot] + metadata = {} + if pkg_type == "blocks": + pass + elif pkg_type == "ebuild": + mydbapi = portdb + metadata.update(izip(metadata_keys, + mydbapi.aux_get(pkg_key, metadata_keys))) + pkgsettings.setcpv(pkg_key, mydb=mydbapi) + metadata["USE"] = pkgsettings["USE"] + else: + if pkg_type == "binary": + mydbapi = bindb + else: + raise AssertionError("Package type: '%s'" % pkg_type) + metadata.update(izip(metadata_keys, + mydbapi.aux_get(pkg_key, metadata_keys))) if x[0]=="blocks": pkgindex=3 y = portdb.findname(pkg_key) @@ -3157,7 +3562,7 @@ class MergeTask(object): #buildsyspkg: Check if we need to _force_ binary package creation issyspkg = ("buildsyspkg" in myfeat) \ and x[0] != "blocks" \ - and mysysdict.has_key(portage.cpv_getkey(x[2])) \ + and system_set.findAtomForPackage(pkg_key, metadata) \ and "--buildpkg" not in self.myopts if x[0] in ["ebuild","blocks"]: if x[0] == "blocks" and "--fetchonly" not in self.myopts: @@ -3179,6 +3584,7 @@ class MergeTask(object): print "!!! Fetch for",y,"failed, continuing..." print failed_fetches.append(pkg_key) + self.curval += 1 continue portage.doebuild_environment(y, "setup", myroot, @@ -3228,6 +3634,9 @@ class MergeTask(object): pkgsettings, self.edebug, mydbapi=portdb, tree="porttree") del pkgsettings["PORTAGE_BINPKG_TMPFILE"] + if retval != os.EX_OK or \ + "--buildpkgonly" in self.myopts: + elog_process(pkg_key, pkgsettings, phasefilter=filter_mergephases) if retval != os.EX_OK: return retval bintree = self.trees[myroot]["bintree"] @@ -3327,6 +3736,7 @@ class MergeTask(object): if "--fetchonly" in self.myopts or \ "--fetch-all-uri" in self.myopts: + self.curval += 1 continue short_msg = "emerge: ("+str(mergecount)+" of "+str(len(mymergelist))+") "+x[pkgindex]+" Merge Binary" @@ -3342,24 +3752,21 @@ class MergeTask(object): #need to check for errors if "--buildpkgonly" not in self.myopts: self.trees[x[1]]["vartree"].inject(x[2]) - myfavkey=portage.cpv_getkey(x[2]) - if "--fetchonly" not in self.myopts and \ - "--fetch-all-uri" not in self.myopts and \ - myfavkey in favorites: - myfavs = portage.grabfile(os.path.join(myroot, portage.WORLD_FILE)) - myfavdict=genericdict(myfavs) - #don't record if already in system profile or already recorded - if (not mysysdict.has_key(myfavkey)) and (not myfavdict.has_key(myfavkey)): - #we don't have a favorites entry for this package yet; add one - myfavdict[myfavkey]=myfavkey + myfavkey = portage.cpv_getkey(x[2]) + if not fetchonly and not pretend and \ + args_set.findAtomForPackage(pkg_key, metadata): + world_set.lock() + world_set.load() # maybe it's changed on disk + myfavkey = create_world_atom(pkg_key, metadata, + args_set, root_config) + if myfavkey: print ">>> Recording",myfavkey,"in \"world\" favorites file..." emergelog(xterm_titles, " === ("+\ str(mergecount)+" of "+\ str(len(mymergelist))+\ ") Updating world file ("+x[pkgindex]+")") - portage.write_atomic( - os.path.join(myroot, portage.WORLD_FILE), - "\n".join(sorted(myfavdict.values()))+"\n") + world_set.add(myfavkey) + world_set.unlock() if "--pretend" not in self.myopts and \ "--fetchonly" not in self.myopts and \ @@ -3434,6 +3841,7 @@ class MergeTask(object): # in the event that portage is not allowed to exit normally # due to power failure, SIGKILL, etc... mtimedb.commit() + self.curval += 1 if "--pretend" not in self.myopts: emergelog(xterm_titles, " *** Finished. Cleaning up...") @@ -3674,9 +4082,13 @@ def unmerge(settings, myopts, vartree, unmerge_action, unmerge_files, if "--pretend" not in myopts and "--ask" not in myopts: countdown(int(settings["EMERGE_WARNING_DELAY"]), colorize("UNMERGE_WARN", "Press Ctrl-C to Stop")) - print "\n "+white(x) + if "--quiet" not in myopts: + print "\n "+white(x) + else: + print white(x)+": ", for mytype in ["selected","protected","omitted"]: - portage.writemsg_stdout((mytype + ": ").rjust(14), noiselevel=-1) + if "--quiet" not in myopts: + portage.writemsg_stdout((mytype + ": ").rjust(14), noiselevel=-1) if pkgmap[x][mytype]: for mypkg in pkgmap[x][mytype]: mysplit=portage.catpkgsplit(mypkg) @@ -3691,7 +4103,10 @@ def unmerge(settings, myopts, vartree, unmerge_action, unmerge_files, portage.writemsg_stdout( colorize("GOOD", myversion + " "), noiselevel=-1) else: - portage.writemsg_stdout("none", noiselevel=-1) + portage.writemsg_stdout("none ", noiselevel=-1) + if "--quiet" not in myopts: + portage.writemsg_stdout("\n", noiselevel=-1) + if "--quiet" in myopts: portage.writemsg_stdout("\n", noiselevel=-1) portage.writemsg_stdout("\n>>> " + colorize("UNMERGE_WARN", "'Selected'") + \ @@ -3986,20 +4401,6 @@ def is_valid_package_atom(x): testatom = x return portage.isvalidatom(testatom) -def validate_merge_list(trees, mergelist): - """Validate the list to make sure all the packages are still available. - This is needed for --resume.""" - for (pkg_type, myroot, pkg_key, action) in mergelist: - if pkg_type == "binary" and \ - not trees[myroot]["bintree"].dbapi.match("="+pkg_key) or \ - pkg_type == "ebuild" and \ - not trees[myroot]["porttree"].dbapi.xmatch( - "match-all", "="+pkg_key): - print red("!!! Error: The resume list contains packages that are no longer") - print red("!!! available to be emerged. Please restart/continue") - print red("!!! the merge operation manually.") - sys.exit(1) - def show_blocker_docs_link(): print print "For more information about " + bad("Blocked Packages") + ", please refer to the following" @@ -4455,9 +4856,8 @@ def action_sync(settings, trees, mtimedb, myopts, myaction): 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 red(" * ")+"Please run 'emerge portage' and then update "+bold("ALL")+" of your" - print red(" * ")+"configuration files." - print red(" * ")+"To update portage, run 'emerge portage'." + print + print red(" * ")+"To update portage, run 'emerge portage' now." print display_news_notification(trees) @@ -4772,7 +5172,12 @@ def action_info(settings, trees, myopts, myfiles): # Loop through each package # Only print settings if they differ from global settings - header_printed = False + 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 pkg in mypkgs: # Get all package specific variables auxvalues = vardb.aux_get(pkg, auxkeys) @@ -4802,16 +5207,6 @@ def action_info(settings, trees, myopts, myfiles): # If a difference was found, print the info for # this package. if diff_values: - - # If we have not yet printed the header, - # print it now - if not header_printed: - header_title = "Package Settings" - print header_width * "=" - print header_title.rjust(int(header_width/2 + len(header_title)/2)) - print header_width * "=" - header_printed = True - # Print package info print "%s was built with the following:" % pkg for myvar in mydesiredvars + ["USE"]: @@ -4820,6 +5215,15 @@ def action_info(settings, trees, myopts, myfiles): mylist.sort() print "%s=\"%s\"" % (myvar, " ".join(mylist)) print + print ">>> Attempting to run pkg_info() for '%s'" % pkg + ebuildpath = vardb.findname(pkg) + if not ebuildpath or not os.path.exists(ebuildpath): + out.ewarn("No ebuild found for '%s'" % pkg) + continue + portage.doebuild(ebuildpath, "info", pkgsettings["ROOT"], + pkgsettings, debug=(settings.get("PORTAGE_DEBUG", "") == 1), + mydbapi=trees[settings["ROOT"]]["vartree"].dbapi, + tree="vartree") def action_search(settings, portdb, vartree, myopts, myfiles, spinner): if not myfiles: @@ -4841,26 +5245,30 @@ def action_depclean(settings, trees, ldpath_mtimes, # Kill packages that aren't explicitly merged or are required as a # dependency of another package. World file is explicit. - warn_prefix = colorize("BAD", "*** WARNING *** ") - print - print warn_prefix + "Depclean may break link level dependencies. Thus, it is" - print warn_prefix + "recommended to use a tool such as " + good("`revdep-rebuild`") + " (from" - print warn_prefix + "app-portage/gentoolkit) in order to detect such breakage." - print warn_prefix - print warn_prefix + "Also study the list of packages to be cleaned for any obvious" - print warn_prefix + "mistakes. Packages that are part of the world set will always" - print warn_prefix + "be kept. They can be manually added to this set with" - print warn_prefix + good("`emerge --noreplace `") + ". Packages that are listed in" - print warn_prefix + "package.provided (see portage(5)) will be removed by" - print warn_prefix + "depclean, even if they are part of the world set." - print warn_prefix - print warn_prefix + "As a safety measure, depclean will not remove any packages" - print warn_prefix + "unless *all* required dependencies have been resolved. As a" - print warn_prefix + "consequence, it is often necessary to run " - print warn_prefix + good("`emerge --update --newuse --deep world`") + " prior to depclean." + msg = [] + msg.append("Depclean may break link level dependencies. Thus, it is\n") + msg.append("recommended to use a tool such as " + good("`revdep-rebuild`") + " (from\n") + msg.append("app-portage/gentoolkit) in order to detect such breakage.\n") + msg.append("\n") + msg.append("Also 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 `") + ". 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\n") + msg.append(good("`emerge --update --newuse --deep world`") + " prior to depclean.\n") + + portage.writemsg_stdout("\n") + for x in msg: + portage.writemsg_stdout(colorize("BAD", "*** WARNING *** ") + x) xterm_titles = "notitles" not in settings.features myroot = settings["ROOT"] + portdb = trees[myroot]["porttree"].dbapi dep_check_trees = {} dep_check_trees[myroot] = {} dep_check_trees[myroot]["vartree"] = \ @@ -4868,10 +5276,10 @@ def action_depclean(settings, trees, ldpath_mtimes, vardb = dep_check_trees[myroot]["vartree"].dbapi # Constrain dependency selection to the installed packages. dep_check_trees[myroot]["porttree"] = dep_check_trees[myroot]["vartree"] - syslist = getlist(settings, "system") - worldlist = getlist(settings, "world") - system_world_dict = genericdict(worldlist) - system_world_dict.update(genericdict(syslist)) + system_set = SystemSet("system", settings.profiles) + syslist = list(system_set) + world_set = WorldSet("world", myroot) + worldlist = list(world_set) fakedb = portage.fakedbapi(settings=settings) myvarlist = vardb.cpv_all() @@ -4900,6 +5308,7 @@ def action_depclean(settings, trees, ldpath_mtimes, remaining_atoms += [(atom, 'system', hard) for atom in syslist if vardb.match(atom)] unresolveable = {} aux_keys = ["DEPEND", "RDEPEND", "PDEPEND"] + metadata_keys = ["PROVIDE", "SLOT", "USE"] while remaining_atoms: atom, parent, priority = remaining_atoms.pop() @@ -4908,11 +5317,17 @@ def action_depclean(settings, trees, ldpath_mtimes, if not atom.startswith("!") and priority == hard: unresolveable.setdefault(atom, []).append(parent) continue - if portage.dep_getkey(atom) not in system_world_dict: + if len(pkgs) > 1 and parent != "world": # Prune all but the best matching slot, since that's all that a - # deep world update would pull in. Don't prune if the cpv is in - # system or world though, since those sets trigger greedy update - # of all slots. + # deep world update would pull in. Don't prune if this atom comes + # directly from world though, since world atoms are greedy when + # they don't specify a slot. + visible_in_portdb = [cpv for cpv in pkgs if portdb.match("="+cpv)] + if visible_in_portdb: + # For consistency with the update algorithm, keep the highest + # visible version and prune any versions that are either masked + # or no longer exist in the portage tree. + pkgs = visible_in_portdb pkgs = [portage.best(pkgs)] for pkg in pkgs: if fakedb.cpv_exists(pkg): @@ -4997,6 +5412,12 @@ def action_build(settings, trees, mtimedb, myopts, myaction, myfiles, spinner): ldpath_mtimes = mtimedb["ldpath"] favorites=[] + merge_count = 0 + pretend = "--pretend" in myopts + fetchonly = "--fetchonly" in myopts or "--fetch-all-uri" in myopts + if pretend or fetchonly: + # make the mtimedb readonly + mtimedb.filename = None if "--quiet" not in myopts and \ ("--pretend" in myopts or "--ask" in myopts or \ "--tree" in myopts or "--verbose" in myopts): @@ -5031,16 +5452,6 @@ def action_build(settings, trees, mtimedb, mtimedb["resume"] = mtimedb["resume_backup"] del mtimedb["resume_backup"] mtimedb.commit() - # XXX: "myopts" is a list for backward compatibility. - myresumeopts = dict([(k,True) for k in mtimedb["resume"]["myopts"]]) - - for opt in ("--skipfirst", "--ask", "--tree"): - myresumeopts.pop(opt, None) - - for myopt, myarg in myopts.iteritems(): - if myopt not in myresumeopts: - myresumeopts[myopt] = myarg - myopts=myresumeopts # Adjust config according to options of the command being resumed. for myroot in trees: @@ -5050,17 +5461,38 @@ def action_build(settings, trees, mtimedb, mysettings.lock() del myroot, mysettings - myparams = create_depgraph_params(myopts, myaction) - if "--quiet" not in myopts and "--nodeps" not in myopts: + # "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 ("--skipfirst", "--ask", "--tree"): + resume_opts.pop(opt, None) + myopts.update(resume_opts) + show_spinner = "--quiet" not in myopts and "--nodeps" not in myopts + if not show_spinner: + spinner.update = spinner.update_quiet + if show_spinner: print "Calculating dependencies ", + myparams = create_depgraph_params(myopts, myaction) mydepgraph = depgraph(settings, trees, myopts, myparams, spinner) - if "--quiet" not in myopts and "--nodeps" not in myopts: + try: + mydepgraph.loadResumeCommand(mtimedb["resume"]) + except portage.exception.PackageNotFound: + if show_spinner: + print + from portage.output import EOutput + out = EOutput() + out.eerror("Error: The resume list contains packages that are no longer") + out.eerror(" available to be emerged. Please restart/continue") + out.eerror(" the merge operation manually.") + return 1 + if show_spinner: print "\b\b... done!" else: if ("--resume" in myopts): print darkgreen("emerge: It seems we have nothing to resume...") - sys.exit(0) + return os.EX_OK myparams = create_depgraph_params(myopts, myaction) if myaction in ["system","world"]: @@ -5070,7 +5502,7 @@ def action_build(settings, trees, mtimedb, mydepgraph = depgraph(settings, trees, myopts, myparams, spinner) if not mydepgraph.xcreate(myaction): print "!!! Depgraph creation failed." - sys.exit(1) + return 1 if "--quiet" not in myopts and "--nodeps" not in myopts: print "\b\b... done!" else: @@ -5082,9 +5514,9 @@ def action_build(settings, trees, mtimedb, retval, favorites = mydepgraph.select_files(myfiles) except portage.exception.PackageNotFound, e: portage.writemsg("\n!!! %s\n" % str(e), noiselevel=-1) - sys.exit(1) + return 1 if not retval: - sys.exit(1) + return 1 if "--quiet" not in myopts and "--nodeps" not in myopts: print "\b\b... done!" @@ -5095,25 +5527,30 @@ def action_build(settings, trees, mtimedb, for x in mydepgraph.missingbins: sys.stderr.write(" "+str(x)+"\n") sys.stderr.write("\nThese are required by '--usepkgonly' -- Terminating.\n\n") - sys.exit(1) + 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: - validate_merge_list(trees, mtimedb["resume"]["mergelist"]) mymergelist = mtimedb["resume"]["mergelist"] if "--skipfirst" in myopts: mymergelist = mymergelist[1:] if len(mymergelist) == 0: print colorize("INFORM", "emerge: It seems we have nothing to resume...") - sys.exit(0) - mydepgraph.display(mymergelist) + return os.EX_OK + favorites = mtimedb["resume"]["favorites"] + retval = mydepgraph.display(mymergelist, favorites=favorites) + if retval != os.EX_OK: + return retval prompt="Would you like to resume merging these packages?" else: - mydepgraph.display( - mydepgraph.altlist(reversed=("--tree" in myopts))) + retval = mydepgraph.display( + mydepgraph.altlist(reversed=("--tree" in myopts)), + favorites=favorites) + if retval != os.EX_OK: + return retval mergecount=0 for x in mydepgraph.altlist(): if x[0] != "blocks" and x[3] != "nomerge": @@ -5124,7 +5561,7 @@ def action_build(settings, trees, mtimedb, print "!!! at the same time on the same system." if "--quiet" not in myopts: show_blocker_docs_link() - sys.exit(1) + return 1 if mergecount==0: if "--noreplace" in myopts and favorites: print @@ -5137,7 +5574,7 @@ def action_build(settings, trees, mtimedb, print print "Nothing to merge; quitting." print - sys.exit(0) + 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: @@ -5147,34 +5584,39 @@ def action_build(settings, trees, mtimedb, print print "Quitting." print - sys.exit(0) + 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): - validate_merge_list(trees, mtimedb["resume"]["mergelist"]) mymergelist = mtimedb["resume"]["mergelist"] if "--skipfirst" in myopts: mymergelist = mymergelist[1:] if len(mymergelist) == 0: print colorize("INFORM", "emerge: It seems we have nothing to resume...") - sys.exit(0) - mydepgraph.display(mymergelist) + return os.EX_OK + favorites = mtimedb["resume"]["favorites"] + retval = mydepgraph.display(mymergelist, favorites=favorites) + if retval != os.EX_OK: + return retval else: - mydepgraph.display( - mydepgraph.altlist(reversed=("--tree" in myopts))) + retval = mydepgraph.display( + mydepgraph.altlist(reversed=("--tree" in myopts)), + favorites=favorites) + if retval != os.EX_OK: + return retval if "--buildpkgonly" in myopts and \ not mydepgraph.digraph.hasallzeros(ignore_priority=DepPriority.MEDIUM): print "\n!!! --buildpkgonly requires all dependencies to be merged." print "!!! You have to merge the dependencies before you can build this package.\n" - sys.exit(1) + return 1 else: if ("--buildpkgonly" in myopts): if not mydepgraph.digraph.hasallzeros(ignore_priority=DepPriority.MEDIUM): print "\n!!! --buildpkgonly requires all dependencies to be merged." print "!!! Cannot merge requested packages. Merge deps and try again.\n" - sys.exit(1) + return 1 if ("--resume" in myopts): favorites=mtimedb["resume"]["favorites"] @@ -5187,8 +5629,7 @@ def action_build(settings, trees, mtimedb, del mydepgraph retval = mergetask.merge( mtimedb["resume"]["mergelist"], favorites, mtimedb) - if retval != os.EX_OK: - sys.exit(retval) + merge_count = mergetask.curval else: if "resume" in mtimedb and \ "mergelist" in mtimedb["resume"] and \ @@ -5221,23 +5662,28 @@ def action_build(settings, trees, mtimedb, pkglist.append(pkg) else: pkglist = mydepgraph.altlist() + if favorites: + mydepgraph.saveNomergeFavorites() del mydepgraph mergetask = MergeTask(settings, trees, myopts) retval = mergetask.merge(pkglist, favorites, mtimedb) - if retval != os.EX_OK: - sys.exit(retval) + merge_count = mergetask.curval + + if retval == os.EX_OK and not (pretend or fetchonly): + mtimedb.pop("resume", None) + if "yes" == settings.get("AUTOCLEAN"): + portage.writemsg_stdout(">>> Auto-cleaning packages...\n") + vartree = trees[settings["ROOT"]]["vartree"] + unmerge(settings, myopts, vartree, "clean", ["world"], + ldpath_mtimes, autoclean=1) + else: + portage.writemsg_stdout(colorize("WARN", "WARNING:") + + " AUTOCLEAN is disabled. This can cause serious" + + " problems due to overlapping packages.\n") - if mtimedb.has_key("resume"): - del mtimedb["resume"] - if settings["AUTOCLEAN"] and "yes"==settings["AUTOCLEAN"]: - portage.writemsg_stdout(">>> Auto-cleaning packages...\n") - vartree = trees[settings["ROOT"]]["vartree"] - unmerge(settings, myopts, vartree, "clean", ["world"], - ldpath_mtimes, autoclean=1) - else: - portage.writemsg_stdout(colorize("WARN", "WARNING:") - + " AUTOCLEAN is disabled. This can cause serious" - + " problems due to overlapping packages.\n") + if merge_count and not (pretend or fetchonly): + post_emerge(trees, mtimedb, retval) + return retval def multiple_actions(action1, action2): sys.stderr.write("\n!!! Multiple actions requested... Please choose one only.\n") @@ -5266,6 +5712,11 @@ def parse_opts(tmpcmdline, silent=False): "help":"include unnecessary build time dependencies", "type":"choice", "choices":("y", "n") + }, + "--reinstall": { + "help":"specify conditions to trigger package reinstallation", + "type":"choice", + "choices":["changed-use"] } } @@ -5540,7 +5991,6 @@ def emerge_main(): for x in myfiles: ext = os.path.splitext(x)[1] if (ext == ".ebuild" or ext == ".tbz2") and os.path.exists(os.path.abspath(x)): - print "emerging by path implies --oneshot... adding --oneshot to options." print colorize("BAD", "\n*** emerging by path is broken and may not always work!!!\n") break @@ -5623,10 +6073,6 @@ def emerge_main(): emerge.help.help(myaction, myopts, portage.output.havecolor) sys.exit(0) - if portage.wheelgid == portage.portage_gid: - print "emerge: wheel group use is being deprecated. Please update group and passwd to" - print " include the portage user as noted above, and then use group portage." - if "--debug" in myopts: print "myaction", myaction print "myopts", myopts @@ -5756,12 +6202,11 @@ def emerge_main(): validate_ebuild_environment(trees) if "--pretend" not in myopts: display_news_notification(trees) - action_build(settings, trees, mtimedb, + retval = action_build(settings, trees, mtimedb, myopts, myaction, myfiles, spinner) - if "--pretend" not in myopts: - post_emerge(trees, mtimedb, os.EX_OK) - else: + if "--pretend" in myopts: display_news_notification(trees) + return retval if __name__ == "__main__": retval = emerge_main()