From e3025523f9df787c334bb2792d7a02ca35f5b884 Mon Sep 17 00:00:00 2001 From: Fabian Groffen Date: Fri, 2 May 2008 14:36:59 +0000 Subject: [PATCH] Merged from trunk 10000:10035 | 10001 | Bug #219369 - Enable automatic upgrage or downgrade to a | | zmedico | version with visible KEYWORDS when the installed version is | | | masked by KEYWORDS, but never reinstall the same exact | | | version only due to a KEYWORDS mask. | | 10003 | Never uninstall sys-apps/portage except through replacement. | | zmedico | | | 10005 | * Remove the only remaining sys.exit() calls inside the | | zmedico | depgraph class. * Fix _unknown_internal_error() to work with | | | zero arguments. | | 10006 | Fix the EbuildQuote check to ignore "echo" and the elog | | zmedico | functions since people might not want quotes in those cases. | | 10007 | Bug #218854 - Fix depgraph._iter_atoms_for_pkg() so that | | zmedico | argument atoms only match the highest visible slot. | | 10008 | Add missing slot comparison when looking for higher slot in | | zmedico | _iter_atoms_for_pkg(). | | 10009 | Bug #211833 - When an ebuild removal phase fails, notify the | | zmedico | user that removal of environment.bz2 file will cause the | | | ebuild to be sourced and eclasses from the current portage | | | tree will be used when necessary. | | 10014 | Fix PackageVirtualDbapi.cpv_inject() to make sure packages | | zmedico | with duplicate cpv or slot are handled correctly. | | 10016 | Use write_atomic() for manifest updates. | | zmedico | | | 10018 | Try to format the elog message better for bug #211833, | | zmedico | separating paragraphs for easier reading. | | 10020 | Add comma to ("__weakref__",) so that it's a tuple instead | | zmedico | of a string. Thanks to ferringb. | | 10022 | As suggested by Chr. Schaefer in bug #211833, comment #15, | | zmedico | note that removal of the environment.bz2 file is preferred | | | since it may allow the removal phases to execute | | | successfully. | | 10024 | For bug #211833, be explicit that the pkg_prerm() and | | zmedico | pkg_postrm() removal phases are the ones that are skipped. | | 10026 | Add a Package.pv_split attibute to optimize version | | zmedico | comparison operators. | | 10028 | Fix spelling. | | zmedico | | | 10030 | When a package scheduled for uninstall isn't installed | | zmedico | anymore, move on to the next task. | | 10032 | Handle a potential KeyError in depgraph.display() when in | | zmedico | --resume mode and a package scheduled for uninstall is no | | | longer installed. | | 10034 | Prevent some more visible downgrades from possibly being | | zmedico | missed (bug #219369). | | 10035 | Prevent some more visible downgrades from possibly being | | zmedico | missed (bug #219369). | svn path=/main/branches/prefix/; revision=10087 --- pym/_emerge/__init__.py | 169 ++++++++++++++++++++++++++++------- pym/portage/dbapi/vartree.py | 38 ++++++-- pym/portage/manifest.py | 6 +- pym/repoman/checks.py | 5 +- 4 files changed, 173 insertions(+), 45 deletions(-) diff --git a/pym/_emerge/__init__.py b/pym/_emerge/__init__.py index 8eb69067f..0f88c7c16 100644 --- a/pym/_emerge/__init__.py +++ b/pym/_emerge/__init__.py @@ -742,12 +742,13 @@ class search(object): class RootConfig(object): """This is used internally by depgraph to track information about a particular $ROOT.""" - def __init__(self, trees, setconfig): + def __init__(self, settings, trees, setconfig): self.trees = trees - self.settings = trees["vartree"].settings + self.settings = settings self.root = self.settings["ROOT"] self.setconfig = setconfig self.sets = self.setconfig.getSets() + self.visible_pkgs = PackageVirtualDbapi(self.settings) 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 @@ -839,7 +840,7 @@ def filter_iuse_defaults(iuse): yield flag class SlotObject(object): - __slots__ = ("__weakref__") + __slots__ = ("__weakref__",) def __init__(self, **kwargs): classes = [self.__class__] @@ -1285,12 +1286,13 @@ class Blocker(Task): class Package(Task): __slots__ = ("built", "cpv", "depth", "installed", "metadata", "root", "onlydeps", "type_name", - "cp", "cpv_slot", "slot_atom") + "cp", "cpv_slot", "pv_split", "slot_atom") def __init__(self, **kwargs): Task.__init__(self, **kwargs) self.cp = portage.cpv_getkey(self.cpv) self.slot_atom = "%s:%s" % (self.cp, self.metadata["SLOT"]) self.cpv_slot = "%s:%s" % (self.cpv, self.metadata["SLOT"]) + self.pv_split = portage.catpkgsplit(self.cpv)[1:] def _get_hash_key(self): hash_key = getattr(self, "_hash_key", None) @@ -1303,20 +1305,30 @@ class Package(Task): return self._hash_key def __lt__(self, other): - other_split = portage.catpkgsplit(other.cpv) - self_split = portage.catpkgsplit(self.cpv) - if other_split[:2] != self_split[:2]: + if other.cp != self.cp: + return False + if portage.pkgcmp(self.pv_split, other.pv_split) < 0: + return True + return False + + def __le__(self, other): + if other.cp != self.cp: return False - if portage.pkgcmp(self_split[1:], other_split[1:]) < 0: + if portage.pkgcmp(self.pv_split, other.pv_split) <= 0: return True return False def __gt__(self, other): - other_split = portage.catpkgsplit(other.cpv) - self_split = portage.catpkgsplit(self.cpv) - if other_split[:2] != self_split[:2]: + if other.cp != self.cp: + return False + if portage.pkgcmp(self.pv_split, other.pv_split) > 0: + return True + return False + + def __ge__(self, other): + if other.cp != self.cp: return False - if portage.pkgcmp(self_split[1:], other_split[1:]) > 0: + if portage.pkgcmp(self.pv_split, other.pv_split) >= 0: return True return False @@ -1539,6 +1551,9 @@ class PackageVirtualDbapi(portage.dbapi): return True return False + def match_pkgs(self, atom): + return [self._cpv_map[cpv] for cpv in self.match(atom)] + def _clear_cache(self): if self._categories is not None: self._categories = None @@ -1582,11 +1597,17 @@ class PackageVirtualDbapi(portage.dbapi): if cp_list is None: cp_list = [] self._cp_map[pkg.cp] = cp_list + e_pkg = self._cpv_map.get(pkg.cpv) + if e_pkg is not None: + if e_pkg == pkg: + return + self.cpv_remove(e_pkg) for e_pkg in cp_list: if e_pkg.slot_atom == pkg.slot_atom: if e_pkg == pkg: return self.cpv_remove(e_pkg) + break cp_list.append(pkg) self._cpv_map[pkg.cpv] = pkg self._clear_cache() @@ -1646,9 +1667,15 @@ class depgraph(object): # to the graph. self._graph_trees = {} # All Package instances - self._pkg_cache = {} + self._pkg_cache = self._package_cache(self) for myroot in trees: self.trees[myroot] = {} + # Create a RootConfig instance that references + # the FakeVartree instead of the real one. + self.roots[myroot] = RootConfig( + trees[myroot]["vartree"].settings, + self.trees[myroot], + trees[myroot]["root_config"].setconfig) for tree in ("porttree", "bintree"): self.trees[myroot][tree] = trees[myroot][tree] self.trees[myroot]["vartree"] = \ @@ -1659,10 +1686,6 @@ class depgraph(object): clone=self.trees[myroot]["vartree"].settings) self._slot_pkg_map[myroot] = {} vardb = self.trees[myroot]["vartree"].dbapi - # Create a RootConfig instance that references - # the FakeVartree instead of the real one. - self.roots[myroot] = RootConfig(self.trees[myroot], - trees[myroot]["root_config"].setconfig) preload_installed_pkgs = "--nodeps" not in self.myopts and \ "--buildpkgonly" not in self.myopts # This fakedbapi instance will model the state that the vdb will @@ -2248,11 +2271,27 @@ class depgraph(object): if pkg.root != self.target_root: return atom_arg_map = self._atom_arg_map + root_config = self.roots[pkg.root] for atom in self._set_atoms.iterAtomsForPackage(pkg): atom_cp = portage.dep_getkey(atom) if atom_cp != pkg.cp and \ self._have_new_virt(pkg.root, atom_cp): continue + visible_pkgs = root_config.visible_pkgs.match_pkgs(atom) + visible_pkgs.reverse() # descending order + higher_slot = None + for visible_pkg in visible_pkgs: + if visible_pkg.cp != atom_cp: + continue + if pkg >= visible_pkg: + # This is descending order, and we're not + # interested in any versions <= pkg given. + break + if pkg.slot_atom != visible_pkg.slot_atom: + higher_slot = visible_pkg + break + if higher_slot is not None: + continue for arg in atom_arg_map[(atom, pkg.root)]: if isinstance(arg, PackageArg) and \ arg.package != pkg: @@ -2570,14 +2609,14 @@ class depgraph(object): portage.writemsg("!!! to aid in the detection of malicious intent.\n\n") portage.writemsg("!!! THIS IS A POSSIBLE INDICATION OF TAMPERED FILES -- CHECK CAREFULLY.\n") portage.writemsg("!!! Affected file: %s\n" % (e), noiselevel=-1) - sys.exit(1) + return 0, myfavorites except portage.exception.InvalidSignature, e: portage.writemsg("\n\n!!! An invalid gpg signature is preventing portage from calculating the\n") portage.writemsg("!!! required dependencies. This is a security feature enabled by the admin\n") portage.writemsg("!!! to aid in the detection of malicious intent.\n\n") portage.writemsg("!!! THIS IS A POSSIBLE INDICATION OF TAMPERED FILES -- CHECK CAREFULLY.\n") portage.writemsg("!!! Affected file: %s\n" % (e), noiselevel=-1) - sys.exit(1) + return 0, myfavorites except SystemExit, e: raise # Needed else can't exit except Exception, e: @@ -2772,7 +2811,10 @@ class depgraph(object): # Make --noreplace take precedence over --newuse. if not installed and noreplace and \ cpv in vardb.match(atom): - break + # If the installed version is masked, it may + # be necessary to look at lower versions, + # in case there is a visible downgrade. + continue reinstall_for_flags = None cache_key = (pkg_type, root, cpv, pkg_status) calculated_use = True @@ -2820,6 +2862,26 @@ class depgraph(object): continue except portage.exception.InvalidDependString: continue + + # Enable upgrade or downgrade to a version + # with visible KEYWORDS when the installed + # version is masked by KEYWORDS, but never + # reinstall the same exact version only due + # to a KEYWORDS mask. + if installed and matched_packages and \ + pkgsettings.getMissingKeywords( + pkg.cpv, pkg.metadata): + different_version = None + for avail_pkg in matched_packages: + if not portage.dep.cpvequal( + pkg.cpv, avail_pkg.cpv): + different_version = avail_pkg + break + if different_version is not None: + # Only reinstall for KEYWORDS if + # it's not the same version. + continue + if not built and not calculated_use: # This is avoided whenever possible because # it's expensive. @@ -2910,7 +2972,10 @@ class depgraph(object): if not reinstall_for_flags and \ not must_reinstall and \ cpv in vardb.match(atom): - break + # If the installed version is masked, it may + # be necessary to look at lower versions, + # in case there is a visible downgrade. + continue if not built: myeb = cpv matched_packages.append(pkg) @@ -3558,6 +3623,10 @@ class depgraph(object): continue if "/" == task.root: + # Never uninstall sys-apps/portage + # except through replacement. + if "sys-apps/portage" == task.cp: + continue # For packages in the system set, don't take # any chances. If the conflict can't be resolved # by a normal replacement operation then abort. @@ -3679,7 +3748,7 @@ class depgraph(object): print print "!!! Note that circular dependencies can often be avoided by temporarily" print "!!! disabling USE flags that trigger optional dependencies." - sys.exit(1) + raise self._unknown_internal_error() # At this point, we've succeeded in selecting one or more nodes, so # it's now safe to reset the prefer_asap and accept_root_node flags @@ -4006,19 +4075,30 @@ class depgraph(object): else: blockers.append(addl) else: - pkg = self._pkg_cache[tuple(x)] - metadata = pkg.metadata pkg_status = x[3] pkg_merge = ordered and pkg_status == "merge" if not pkg_merge and pkg_status == "merge": pkg_status = "nomerge" + built = pkg_type != "ebuild" + installed = pkg_type == "installed" + try: + pkg = self._pkg_cache[tuple(x)] + except KeyError: + if pkg_status != "uninstall": + raise + # A package scheduled for uninstall apparently + # isn't installed anymore. Since it's already + # been uninstalled, move on to the next task. + # This case should only be reachable in --resume + # mode, since otherwise the package would have + # been cached. + continue + metadata = pkg.metadata ebuild_path = None if pkg_type == "binary": repo_name = self.roots[myroot].settings.get("PORTAGE_BINHOST") else: repo_name = metadata["repository"] - built = pkg_type != "ebuild" - installed = pkg_type == "installed" if pkg_type == "ebuild": ebuild_path = portdb.findname(pkg_key) if not ebuild_path: # shouldn't happen @@ -4684,7 +4764,11 @@ class depgraph(object): fakedb[myroot].cpv_inject(pkg) self.spinner.update() - class _unknown_internal_error(portage.exception.PortageException): + class _internal_exception(portage.exception.PortageException): + def __init__(self, value=""): + portage.exception.PortageException.__init__(self, value) + + class _unknown_internal_error(_internal_exception): """ Used by the depgraph internally to terminate graph creation. The specific reason for the failure should have been dumped @@ -4692,7 +4776,7 @@ class depgraph(object): may not be known. """ - class _serialize_tasks_retry(portage.exception.PortageException): + class _serialize_tasks_retry(_internal_exception): """ This is raised by the _serialize_tasks() method when it needs to be called again for some reason. The only case that it's currently @@ -4824,6 +4908,17 @@ class depgraph(object): metadata = self._cpv_pkg_map[cpv].metadata return [metadata.get(x, "") for x in wants] + class _package_cache(dict): + def __init__(self, depgraph): + dict.__init__(self) + self._depgraph = depgraph + + def __setitem__(self, k, v): + dict.__setitem__(self, k, v) + root_config = self._depgraph.roots[v.root] + if visible(root_config.settings, v): + root_config.visible_pkgs.cpv_inject(v) + class RepoDisplay(object): def __init__(self, roots): self._shown_repos = {} @@ -5129,6 +5224,8 @@ class MergeTask(object): myroot=x[1] pkg_key = x[2] pkgindex=2 + built = pkg_type != "ebuild" + installed = pkg_type == "installed" portdb = self.trees[myroot]["porttree"].dbapi bindb = self.trees[myroot]["bintree"].dbapi vartree = self.trees[myroot]["vartree"] @@ -5151,10 +5248,16 @@ class MergeTask(object): mydbapi = vardb else: raise AssertionError("Package type: '%s'" % pkg_type) - metadata.update(izip(metadata_keys, - mydbapi.aux_get(pkg_key, metadata_keys))) - built = pkg_type != "ebuild" - installed = pkg_type == "installed" + try: + metadata.update(izip(metadata_keys, + mydbapi.aux_get(pkg_key, metadata_keys))) + except KeyError: + if not installed: + raise + # A package scheduled for uninstall apparently + # isn't installed anymore. Since it's already + # been uninstalled, move on to the next task. + continue if installed: pkg_constructor = Uninstall else: @@ -7897,7 +8000,7 @@ def load_emerge_config(trees=None): for root, root_trees in trees.iteritems(): settings = root_trees["vartree"].settings setconfig = load_default_config(settings, root_trees) - root_trees["root_config"] = RootConfig(root_trees, setconfig) + root_trees["root_config"] = RootConfig(settings, root_trees, setconfig) settings = trees["/"]["vartree"].settings diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py index 0cdf7c8fc..3e941d541 100644 --- a/pym/portage/dbapi/vartree.py +++ b/pym/portage/dbapi/vartree.py @@ -1317,16 +1317,40 @@ class dblink(object): try: if myebuildpath: if retval != os.EX_OK: + msg_lines = [] msg = ("The '%s' " % ebuild_phase) + \ ("phase of the '%s' package " % self.mycpv) + \ - ("has failed with exit value %s. " % retval) + \ - "The problem occurred while executing " + \ - ("the ebuild located at '%s'. " % myebuildpath) + \ - "If necessary, manually remove the ebuild " + \ - "in order to skip the execution of removal phases." - from portage.elog.messages import eerror + ("has failed with exit value %s." % retval) from textwrap import wrap - for l in wrap(msg, 72): + msg_lines.extend(wrap(msg, 72)) + msg_lines.append("") + + ebuild_name = os.path.basename(myebuildpath) + ebuild_dir = os.path.dirname(myebuildpath) + msg = "The problem occurred while executing " + \ + ("the ebuild file named '%s' " % ebuild_name) + \ + ("located in the '%s' directory. " \ + % ebuild_dir) + \ + "If necessary, manually remove " + \ + "the environment.bz2 file and/or the " + \ + "ebuild file located in that directory." + msg_lines.extend(wrap(msg, 72)) + msg_lines.append("") + + msg = "Removal " + \ + "of the environment.bz2 file is " + \ + "preferred since it may allow the " + \ + "removal phases to execute successfully. " + \ + "The ebuild will be " + \ + "sourced and the eclasses " + \ + "from the current portage tree will be used " + \ + "when necessary. Removal of " + \ + "the ebuild file will cause the " + \ + "pkg_prerm() and pkg_postrm() removal " + \ + "phases to be skipped entirely." + msg_lines.extend(wrap(msg, 72)) + from portage.elog.messages import eerror + for l in msg_lines: eerror(l, phase=ebuild_phase, key=self.mycpv) # process logs created during pre/postrm diff --git a/pym/portage/manifest.py b/pym/portage/manifest.py index 910c03d18..9c8ab371a 100644 --- a/pym/portage/manifest.py +++ b/pym/portage/manifest.py @@ -222,10 +222,8 @@ class Manifest(object): else: raise if update_manifest: - fd = open(self.getFullname(), "w") - for myentry in myentries: - fd.write("%s\n" % str(myentry)) - fd.close() + write_atomic(self.getFullname(), + "".join("%s\n" % str(myentry) for myentry in myentries)) if sign: self.sign() except (IOError, OSError), e: diff --git a/pym/repoman/checks.py b/pym/repoman/checks.py index bc1c29115..8f9fd1abc 100644 --- a/pym/repoman/checks.py +++ b/pym/repoman/checks.py @@ -76,7 +76,10 @@ class EbuildQuote(LineCheck): """Ensure ebuilds have valid quoting around things like D,FILESDIR, etc...""" repoman_check_name = 'ebuild.minorsyn' - ignore_line = re.compile(r'(^$)|(^\s*#.*)|(^\s*\w+=.*)|(^\s*(local|export)\s+)') + _ignored_commands = ["echo", "local", "export"] + _ignored_commands += ["eerror", "einfo", "elog", "eqawarn", "ewarn"] + ignore_line = re.compile(r'(^$)|(^\s*#.*)|(^\s*\w+=.*)' + \ + r'|(^\s*(' + "|".join(_ignored_commands) + r')\s+)') var_names = ["D", "DISTDIR", "FILESDIR", "S", "T", "ROOT", "WORKDIR"] # variables for games.eclass -- 2.26.2