emerge: add --rebuild and --norebuild-atoms opts
authorDavid James <davidjames@google.com>
Sun, 1 May 2011 17:21:45 +0000 (10:21 -0700)
committerZac Medico <zmedico@gentoo.org>
Sun, 1 May 2011 17:21:45 +0000 (10:21 -0700)
Rebuild when build-time/run-time deps are upgraded.

If pkgA has been updated, and pkgB depends on pkgA at both
build-time and run-time, pkgB needs to be rebuilt. This
feature ensures that all packages are consistent when
dependencies that are used at both runtime and build time
are changed.

This feature only rebuilds packages one layer deep. That
means that if you upgrade libcros, for example, packages
that depend directly on libcros will be rebuilt and
reinstalled, but indirect dependencies will not be rebuilt.

BUG=chromium-os:14296

TEST=Test whether packages rebuilding a bunch of packages.

Change-Id: Idbc0532b4b1de28fd9e5a0abe3b7dbe1a3abd2c8

Review URL: http://codereview.chromium.org/6905107

13 files changed:
man/emerge.1
pym/_emerge/DepPriority.py
pym/_emerge/DepPriorityNormalRange.py
pym/_emerge/DepPrioritySatisfiedRange.py
pym/_emerge/Dependency.py
pym/_emerge/UnmergeDepPriority.py
pym/_emerge/create_depgraph_params.py
pym/_emerge/depgraph.py
pym/_emerge/help.py
pym/_emerge/main.py
pym/_emerge/resolver/backtracking.py
pym/portage/dbapi/bintree.py
pym/portage/tests/resolver/test_rebuild.py [new file with mode: 0644]

index 56823a8804a64d91074c5b2a7b77c594338d8eb8..fc7ed61a979130d871780c1b3e5e85ad5b8f0c5a 100644 (file)
@@ -489,6 +489,10 @@ terminal device is determined to be a TTY.  This flag disables it regardless.
 A space separated list of package names or slot atoms. Emerge will ignore
 matching binary packages.
 .TP
+.BR "\-\-norebuild\-atoms " ATOMS
+A space separated list of package names or slot atoms. Emerge will not rebuild
+matching packages due to \fB\-\-rebuild\fR.
+.TP
 .BR "\-\-oneshot " (\fB\-1\fR)
 Emerge as normal, but do not add the packages to the world file
 for later updating.
@@ -538,6 +542,10 @@ Disable the warning message that's shown prior to
 to be set in the \fBmake.conf\fR(5)
 \fBEMERGE_DEFAULT_OPTS\fR variable.
 .TP
+.BR "\-\-rebuild [ y | n ]"
+Rebuild packages when dependencies that are used at both build\-time and
+run\-time are upgraded.
+.TP
 .BR "\-\-rebuilt\-binaries [ y | n ]"
 Replace installed packages with binary packages that have
 been rebuilt. Rebuilds are detected by comparison of
index f99b7264f5079ade9bcdfd5b8ef0c8b70caa72a2..b08ffe583f2db5092ffb84d7b1f907131ccc0aad 100644 (file)
@@ -4,7 +4,7 @@
 from _emerge.AbstractDepPriority import AbstractDepPriority
 class DepPriority(AbstractDepPriority):
 
-       __slots__ = ("satisfied", "optional", "rebuild")
+       __slots__ = ("satisfied", "optional", "rebuild", "ignored")
 
        def __int__(self):
                """
@@ -24,17 +24,19 @@ class DepPriority(AbstractDepPriority):
 
                """
 
+               if self.optional:
+                       return -3
                if self.buildtime:
                        return 0
                if self.runtime:
                        return -1
                if self.runtime_post:
                        return -2
-               if self.optional:
-                       return -3
                return -4
 
        def __str__(self):
+               if self.ignored:
+                       return "ignored"
                if self.optional:
                        return "optional"
                if self.buildtime:
index 259a1df6571bc96e3a2baffad6d5cbdddd36a273..808c9504b693d12fda038d3fe8f18f030ccfcc4b 100644 (file)
@@ -33,7 +33,7 @@ class DepPriorityNormalRange(object):
        def _ignore_runtime(cls, priority):
                if priority.__class__ is not DepPriority:
                        return False
-               return not priority.buildtime
+               return bool(priority.optional or not priority.buildtime)
 
        ignore_medium      = _ignore_runtime
        ignore_medium_soft = _ignore_runtime_post
index aa32d8f86635245601447afddd16e56d3b987d40..589afde3caa0ac7c38c76ffd2f09c278756a586d 100644 (file)
@@ -80,6 +80,7 @@ class DepPrioritySatisfiedRange(object):
                if priority.__class__ is not DepPriority:
                        return False
                return bool(priority.satisfied or \
+                       priority.optional or \
                        not priority.buildtime)
 
        ignore_medium      = _ignore_runtime
index 63b2a1b12bc7aa7839005e261a40dfa01e2bffd5..d5d519d97dec5550ceb52d36169980f804f2206d 100644 (file)
@@ -5,11 +5,16 @@ from _emerge.DepPriority import DepPriority
 from _emerge.SlotObject import SlotObject
 class Dependency(SlotObject):
        __slots__ = ("atom", "blocker", "child", "depth",
-               "parent", "onlydeps", "priority", "root")
+               "parent", "onlydeps", "priority", "root",
+               "collapsed_parent", "collapsed_priority")
        def __init__(self, **kwargs):
                SlotObject.__init__(self, **kwargs)
                if self.priority is None:
                        self.priority = DepPriority()
                if self.depth is None:
                        self.depth = 0
+               if self.collapsed_parent is None:
+                       self.collapsed_parent = self.parent
+               if self.collapsed_priority is None:
+                       self.collapsed_priority = self.priority
 
index 0f67f3b78dd6f3d1febd348b65de6369c4c11762..db4836e5428b943de72af90d4dd7b25b58797065 100644 (file)
@@ -3,7 +3,7 @@
 
 from _emerge.AbstractDepPriority import AbstractDepPriority
 class UnmergeDepPriority(AbstractDepPriority):
-       __slots__ = ("optional", "satisfied",)
+       __slots__ = ("ignored", "optional", "satisfied",)
        """
        Combination of properties           Priority  Category
 
@@ -32,6 +32,8 @@ class UnmergeDepPriority(AbstractDepPriority):
                return -2
 
        def __str__(self):
+               if self.ignored:
+                       return "ignored"
                myvalue = self.__int__()
                if myvalue > self.SOFT:
                        return "hard"
index d60259ef251491aa2d0deabd8bf650de1ea50aa4..09863479afa06f397dcf03cefa5aebb30fa503db 100644 (file)
@@ -33,7 +33,7 @@ def create_depgraph_params(myopts, myaction):
        deep = myopts.get("--deep")
        if deep is not None and deep != 0:
                myparams["deep"] = deep
-       if "--complete-graph" in myopts:
+       if "--complete-graph" in myopts or "--rebuild" in myopts:
                myparams["complete"] = True
        if "--emptytree" in myopts:
                myparams["empty"] = True
index 73b81e1d18fc63500c32fe0e9d4558c10edcbcb9..f55d84d4903adb18d274388d7a3ec7f7d8bf2b57 100644 (file)
@@ -9,6 +9,7 @@ import logging
 import re
 import sys
 import textwrap
+from collections import deque
 from itertools import chain
 
 import portage
@@ -127,6 +128,10 @@ class _frozen_depgraph_config(object):
                self.nousepkg_atoms = _wildcard_set(atoms)
                atoms = ' '.join(myopts.get("--useoldpkg-atoms", [])).split()
                self.useoldpkg_atoms = _wildcard_set(atoms)
+               atoms = ' '.join(myopts.get("--norebuild-atoms", [])).split()
+               self.norebuild_atoms = _wildcard_set(atoms)
+
+               self.rebuild = "--rebuild" in myopts
 
 class _depgraph_sets(object):
        def __init__(self):
@@ -139,6 +144,128 @@ class _depgraph_sets(object):
                self.atoms = InternalPackageSet(allow_repo=True)
                self.atom_arg_map = {}
 
+class _rebuild_config(object):
+       def __init__(self, frozen_config, backtrack_parameters):
+               self._graph = digraph()
+               self._frozen_config = frozen_config
+               self.rebuild_list = backtrack_parameters.rebuild_list.copy()
+               self.orig_rebuild_list = self.rebuild_list.copy()
+               self.reinstall_list = backtrack_parameters.reinstall_list.copy()
+
+       def add(self, dep_pkg, dep):
+               parent = dep.collapsed_parent
+               priority = dep.collapsed_priority
+               norebuild_atoms = self._frozen_config.norebuild_atoms
+               if (self._frozen_config.rebuild and isinstance(parent, Package) and
+                       parent.built and (priority.buildtime or priority.runtime) and
+                       isinstance(dep_pkg, Package) and
+                       not norebuild_atoms.findAtomForPackage(parent)):
+                       self._graph.add(dep_pkg, parent, priority)
+
+       def _trigger_rebuild(self, parent, build_deps, runtime_deps):
+               root_slot = (parent.root, parent.slot_atom)
+               if root_slot in self.rebuild_list:
+                       return False
+               trees = self._frozen_config.trees
+               children = set(build_deps).intersection(runtime_deps)
+               reinstall = False
+               for slot_atom in children:
+                       kids = set([build_deps[slot_atom], runtime_deps[slot_atom]])
+                       for dep_pkg in kids:
+                               dep_root_slot = (dep_pkg.root, slot_atom)
+                               if (not dep_pkg.built and
+                                       dep_root_slot not in self.orig_rebuild_list):
+                                       # There's no binary package for dep_pkg, so any binary
+                                       # package for this parent would be invalid. Force rebuild.
+                                       self.rebuild_list.add(root_slot)
+                                       return True
+                               elif ("--usepkg" in self._frozen_config.myopts and
+                                       (dep_root_slot in self.reinstall_list or
+                                       dep_root_slot in self.rebuild_list or
+                                       not dep_pkg.installed)):
+
+                                       # A direct rebuild dependency is being installed. We
+                                       # should update the parent as well to the latest binary,
+                                       # if that binary is valid.
+                                       #
+                                       # To validate the binary, we check whether all of the
+                                       # rebuild dependencies are present on the same binhost.
+                                       #
+                                       # 1) If parent is present on the binhost, but one of its
+                                       #    rebuild dependencies is not, then the parent should
+                                       #    be rebuilt from source.
+                                       # 2) Otherwise, the parent binary is assumed to be valid,
+                                       #    because all of its rebuild dependencies are
+                                       #    consistent.
+                                       bintree = trees[parent.root]["bintree"]
+                                       uri = bintree.get_pkgindex_uri(parent.cpv)
+                                       dep_uri = bintree.get_pkgindex_uri(dep_pkg.cpv)
+                                       bindb = bintree.dbapi
+
+                                       if uri and uri != dep_uri:
+                                               # 1) Remote binary package is invalid because it was
+                                               #    built without dep_pkg. Force rebuild.
+                                               self.rebuild_list.add(root_slot)
+                                               return True
+                                       elif (parent.installed and
+                                               root_slot not in self.reinstall_list):
+                                               inst_build_time = parent.metadata.get("BUILD_TIME")
+                                               try:
+                                                       bin_build_time, = bindb.aux_get(parent.cpv,
+                                                               ["BUILD_TIME"])
+                                               except KeyError:
+                                                       continue
+                                               if bin_build_time != inst_build_time:
+                                                       # 2) Remote binary package is valid, and local package
+                                                       #    is not up to date. Force reinstall.
+                                                       reinstall = True
+               if reinstall:
+                       self.reinstall_list.add(root_slot)
+               return reinstall
+
+       def trigger_rebuilds(self):
+               """
+               Trigger rebuilds where necessary. If pkgA has been updated, and pkgB
+               depends on pkgA at both build-time and run-time, pkgB needs to be
+               rebuilt.
+               """
+               need_restart = False
+               graph = self._graph
+               build_deps = {}
+               runtime_deps = {}
+               leaf_nodes = deque(graph.leaf_nodes())
+
+               # Trigger rebuilds bottom-up (starting with the leaves) so that parents
+               # will always know which children are being rebuilt.
+               while leaf_nodes:
+                       node = leaf_nodes.popleft()
+                       slot_atom = node.slot_atom
+
+                       # Remove our leaf node from the graph, keeping track of deps.
+                       parents = graph.nodes[node][1].items()
+                       graph.remove(node)
+                       for parent, priorities in parents:
+                               for priority in priorities:
+                                       if priority.buildtime:
+                                               build_deps.setdefault(parent, {})[slot_atom] = node
+                                       if priority.runtime:
+                                               runtime_deps.setdefault(parent, {})[slot_atom] = node
+                               if not graph.child_nodes(parent):
+                                       leaf_nodes.append(parent)
+
+                       # Trigger rebuilds for our leaf node. Because all of our children
+                       # have been processed, build_deps and runtime_deps will be
+                       # completely filled in, and self.rebuild_list / self.reinstall_list
+                       # will tell us whether any of our children need to be rebuilt or
+                       # reinstalled.
+                       node_build_deps = build_deps.get(node, {})
+                       node_runtime_deps = runtime_deps.get(node, {})
+                       if self._trigger_rebuild(node, node_build_deps, node_runtime_deps):
+                               need_restart = True
+
+               return need_restart
+
+
 class _dynamic_depgraph_config(object):
 
        def __init__(self, depgraph, myparams, allow_backtracking, backtrack_parameters):
@@ -306,6 +433,7 @@ class depgraph(object):
                self._frozen_config = frozen_config
                self._dynamic_config = _dynamic_depgraph_config(self, myparams,
                        allow_backtracking, backtrack_parameters)
+               self._rebuild = _rebuild_config(frozen_config, backtrack_parameters)
 
                self._select_atoms = self._select_atoms_highest_available
                self._select_package = self._select_pkg_highest_available
@@ -671,6 +799,8 @@ class depgraph(object):
                if dep.blocker:
                        if not buildpkgonly and \
                                not nodeps and \
+                               not dep.collapsed_priority.ignored and \
+                               not dep.collapsed_priority.optional and \
                                dep.parent not in self._dynamic_config._slot_collision_nodes:
                                if dep.parent.onlydeps:
                                        # It's safe to ignore blockers if the
@@ -695,9 +825,9 @@ class depgraph(object):
                                dep.root].get(dep_pkg.slot_atom)
 
                if not dep_pkg:
-                       if dep.priority.optional:
-                               # This could be an unnecessary build-time dep
-                               # pulled in by --with-bdeps=y.
+                       if (dep.collapsed_priority.optional or
+                               dep.collapsed_priority.ignored):
+                               # This is an unnecessary build-time dep.
                                return 1
                        if allow_unsatisfied:
                                self._dynamic_config._unsatisfied_deps.append(dep)
@@ -740,7 +870,10 @@ class depgraph(object):
 
                        return 0
 
-               if not self._add_pkg(dep_pkg, dep):
+               self._rebuild.add(dep_pkg, dep)
+
+               if (not dep.collapsed_priority.ignored and
+                       not self._add_pkg(dep_pkg, dep)):
                        return 0
                return 1
 
@@ -1110,6 +1243,7 @@ class depgraph(object):
                        edepend["RDEPEND"] = ""
                        edepend["PDEPEND"] = ""
 
+               ignore_build_time_deps = False
                if pkg.built and not removal_action:
                        if self._frozen_config.myopts.get("--with-bdeps", "n") == "y":
                                # Pull in build time deps as requested, but marked them as
@@ -1121,11 +1255,10 @@ class depgraph(object):
                                # failing.
                                pass
                        else:
-                               # built packages do not have build time dependencies.
-                               edepend["DEPEND"] = ""
+                               ignore_build_time_deps = True
 
                if removal_action and self._frozen_config.myopts.get("--with-bdeps", "y") == "n":
-                       edepend["DEPEND"] = ""
+                       ignore_build_time_deps = True
 
                if removal_action:
                        depend_root = myroot
@@ -1136,13 +1269,14 @@ class depgraph(object):
                                if root_deps is True:
                                        depend_root = myroot
                                elif root_deps == "rdeps":
-                                       edepend["DEPEND"] = ""
+                                       ignore_build_time_deps = True
 
                deps = (
                        (depend_root, edepend["DEPEND"],
-                               self._priority(buildtime=(not pkg.built),
-                               optional=pkg.built),
-                               pkg.built),
+                               self._priority(buildtime=True,
+                               optional=pkg.built,
+                               ignored=ignore_build_time_deps),
+                               pkg.built or ignore_build_time_deps),
                        (myroot, edepend["RDEPEND"],
                                self._priority(runtime=True),
                                False),
@@ -1266,6 +1400,7 @@ class depgraph(object):
 
                        mypriority = dep_priority.copy()
                        if not atom.blocker:
+                               root_slot = (pkg.root, pkg.slot_atom)
                                inst_pkgs = [inst_pkg for inst_pkg in vardb.match_pkgs(atom)
                                        if not reinstall_atoms.findAtomForPackage(inst_pkg,
                                                        modified_use=self._pkg_use_enabled(inst_pkg))]
@@ -1375,7 +1510,8 @@ class depgraph(object):
                                # same depth as the virtual itself.
                                dep = Dependency(atom=atom,
                                        blocker=atom.blocker, child=child, depth=virt_dep.depth,
-                                       parent=virt_pkg, priority=mypriority, root=dep_root)
+                                       parent=virt_pkg, priority=mypriority, root=dep_root,
+                                       collapsed_parent=pkg, collapsed_priority=dep_priority)
 
                                ignored = False
                                if not atom.blocker and \
@@ -1931,9 +2067,12 @@ class depgraph(object):
                pkgsettings = self._frozen_config.pkgsettings[myroot]
                pprovideddict = pkgsettings.pprovideddict
                virtuals = pkgsettings.getvirtuals()
-               for arg in self._expand_set_args(
-                       self._dynamic_config._initial_arg_list,
-                       add_to_digraph=True):
+               args = self._dynamic_config._initial_arg_list[:]
+               for root, atom in chain(self._rebuild.rebuild_list,
+                       self._rebuild.reinstall_list):
+                       args.append(AtomArg(arg=atom, atom=atom,
+                               root_config=self._frozen_config.roots[root]))
+               for arg in self._expand_set_args(args, add_to_digraph=True):
                        for atom in arg.pset.getAtoms():
                                self._spinner_update()
                                dep = Dependency(atom=atom, onlydeps=onlydeps,
@@ -2049,6 +2188,14 @@ class depgraph(object):
                        self._dynamic_config._success_without_autounmask = True
                        return False, myfavorites
 
+               if self._rebuild.trigger_rebuilds():
+                       backtrack_infos = self._dynamic_config._backtrack_infos
+                       config = backtrack_infos.setdefault("config", {})
+                       config["rebuild_list"] = self._rebuild.rebuild_list
+                       config["reinstall_list"] = self._rebuild.reinstall_list
+                       self._dynamic_config._need_restart = True
+                       return False, myfavorites
+
                # We're true here unless we are missing binaries.
                return (True, myfavorites)
 
@@ -2538,7 +2685,12 @@ class depgraph(object):
                                                                pkg.iuse.is_valid_flag):
                                                                required_use_unsatisfied.append(pkg)
                                                                continue
-                                               if pkg.built and not mreasons:
+                                               root_slot = (pkg.root, pkg.slot_atom)
+                                               if pkg.built and root_slot in self._rebuild.rebuild_list:
+                                                       mreasons = ["need to rebuild from source"]
+                                               elif pkg.installed and root_slot in self._rebuild.reinstall_list:
+                                                       mreasons = ["need to rebuild from source"]
+                                               elif pkg.built and not mreasons:
                                                        mreasons = ["use flag configuration mismatch"]
                                        masked_packages.append(
                                                (root_config, pkgsettings, cpv, repo, metadata, mreasons))
@@ -3216,6 +3368,12 @@ class depgraph(object):
                                        if pkg in self._dynamic_config._runtime_pkg_mask:
                                                # The package has been masked by the backtracking logic
                                                continue
+                                       root_slot = (pkg.root, pkg.slot_atom)
+                                       if pkg.built and root_slot in self._rebuild.rebuild_list:
+                                               continue
+                                       if (pkg.installed and
+                                               root_slot in self._rebuild.reinstall_list):
+                                               continue
 
                                        if not pkg.installed and \
                                                self._frozen_config.excluded_pkgs.findAtomForPackage(pkg, \
index fb1e129e74dc36b3f41c0638c301884a4608698d..bf2437dcef1098a8319f35151ece86e5f9cbb8e9 100644 (file)
@@ -565,6 +565,12 @@ def help(myopts, havecolor=1):
                for line in wrap(desc, desc_width):
                        print(desc_indent + line)
                print()
+               print("       " + green("--norebuild-atoms") + " " + turquoise("ATOMS"))
+               desc = "A space separated list of package names or slot atoms." + \
+                       " Emerge will not rebuild matching packages due to --rebuild."
+               for line in wrap(desc, desc_width):
+                       print(desc_indent + line)
+               print()
                print("       "+green("--oneshot")+" ("+green("-1")+" short option)")
                print("              Emerge as normal, but don't add packages to the world profile.")
                print("              This package will only be updated if it is depended upon by")
@@ -616,6 +622,13 @@ def help(myopts, havecolor=1):
                for line in wrap(desc, desc_width):
                        print(desc_indent + line)
                print()
+               print("       " + green("--rebuild") + " [ %s | %s ]" % \
+                       (turquoise("y"), turquoise("n")))
+               desc = "Rebuild packages when dependencies that are used " + \
+                       "at both build-time and run-time are upgraded."
+               for line in wrap(desc, desc_width):
+                       print(desc_indent + line)
+               print()
                print("       " + green("--rebuilt-binaries") + " [ %s | %s ]" % \
                        (turquoise("y"), turquoise("n")))
                desc = "Replace installed packages with binary packages that have " + \
index e0cd0c024307c03d60b131b8cf30220de374b694..434fd5a5742bdd70bcd9a2ea06972e2732c8a0d6 100644 (file)
@@ -440,6 +440,7 @@ def insert_optional_args(args):
                '--package-moves'        : y_or_n,
                '--quiet'                : y_or_n,
                '--quiet-build'          : y_or_n,
+               '--rebuild'              : y_or_n,
                '--rebuilt-binaries'     : y_or_n,
                '--root-deps'  : ('rdeps',),
                '--select'               : y_or_n,
@@ -741,6 +742,14 @@ def parse_opts(tmpcmdline, silent=False):
                        "action" : "append",
                },
 
+               "--norebuild-atoms": {
+                       "help"   :"A space separated list of package names or slot atoms. " + \
+                               "Emerge will not rebuild these packages due to the " + \
+                               "--rebuild flag. ",
+
+                       "action" : "append",
+               },
+
                "--package-moves": {
                        "help"     : "perform package moves when necessary",
                        "type"     : "choice",
@@ -760,6 +769,13 @@ def parse_opts(tmpcmdline, silent=False):
                        "choices"  : true_y_or_n
                },
 
+               "--rebuild": {
+                       "help"     : "Rebuild packages when dependencies that are " + \
+                               "used at both build-time and run-time are upgraded.",
+                       "type"     : "choice",
+                       "choices"  : true_y_or_n
+               },
+
                "--rebuilt-binaries": {
                        "help"     : "replace installed packages with binary " + \
                                     "packages that have been rebuilt",
@@ -889,7 +905,7 @@ def parse_opts(tmpcmdline, silent=False):
        else:
                myoptions.binpkg_respect_use = None
 
-       if myoptions.complete_graph in true_y:
+       if myoptions.complete_graph in true_y or myoptions.rebuild in true_y:
                myoptions.complete_graph = True
        else:
                myoptions.complete_graph = None
@@ -910,6 +926,12 @@ def parse_opts(tmpcmdline, silent=False):
                        parser.error("Invalid Atom(s) in --reinstall-atoms parameter: '%s' (only package names and slot atoms (with wildcards) allowed)\n" % \
                                (",".join(bad_atoms),))
 
+       if myoptions.norebuild_atoms:
+               bad_atoms = _find_bad_atoms(myoptions.norebuild_atoms)
+               if bad_atoms and not silent:
+                       parser.error("Invalid Atom(s) in --norebuild-atoms parameter: '%s' (only package names and slot atoms (with wildcards) allowed)\n" % \
+                               (",".join(bad_atoms),))
+
        if myoptions.nousepkg_atoms:
                bad_atoms = _find_bad_atoms(myoptions.nousepkg_atoms)
                if bad_atoms and not silent:
@@ -953,6 +975,11 @@ def parse_opts(tmpcmdline, silent=False):
        else:
                myoptions.quiet_build = None
 
+       if myoptions.rebuild in true_y:
+               myoptions.rebuild = True
+       else:
+               myoptions.rebuild = None
+
        if myoptions.rebuilt_binaries in true_y:
                myoptions.rebuilt_binaries = True
 
index 1ffada96f18fb647253e1377d35a131a8683ffc0..f00e6ca194aeab9e1df8e5368562e266a9209728 100644 (file)
@@ -7,6 +7,7 @@ class BacktrackParameter(object):
 
        __slots__ = (
                "needed_unstable_keywords", "runtime_pkg_mask", "needed_use_config_changes", "needed_license_changes",
+               "rebuild_list", "reinstall_list"
        )
 
        def __init__(self):
@@ -14,6 +15,8 @@ class BacktrackParameter(object):
                self.runtime_pkg_mask = {}
                self.needed_use_config_changes = {}
                self.needed_license_changes = {}
+               self.rebuild_list = set()
+               self.reinstall_list = set()
 
        def __deepcopy__(self, memo=None):
                if memo is None:
@@ -27,6 +30,8 @@ class BacktrackParameter(object):
                result.runtime_pkg_mask = copy.copy(self.runtime_pkg_mask)
                result.needed_use_config_changes = copy.copy(self.needed_use_config_changes)
                result.needed_license_changes = copy.copy(self.needed_license_changes)
+               result.rebuild_list = copy.copy(self.rebuild_list)
+               result.reinstall_list = copy.copy(self.reinstall_list)
 
                return result
 
@@ -34,7 +39,9 @@ class BacktrackParameter(object):
                return self.needed_unstable_keywords == other.needed_unstable_keywords and \
                        self.runtime_pkg_mask == other.runtime_pkg_mask and \
                        self.needed_use_config_changes == other.needed_use_config_changes and \
-                       self.needed_license_changes == other.needed_license_changes
+                       self.needed_license_changes == other.needed_license_changes and \
+                       self.rebuild_list == other.rebuild_list and \
+                       self.reinstall_list == other.reinstall_list
 
 
 class _BacktrackNode:
@@ -137,6 +144,10 @@ class Backtracker(object):
                        elif change == "needed_use_config_changes":
                                for pkg, (new_use, new_changes) in data:
                                        para.needed_use_config_changes[pkg] = (new_use, new_changes)
+                       elif change == "rebuild_list":
+                               para.rebuild_list.update(data)
+                       elif change == "reinstall_list":
+                               para.reinstall_list.update(data)
 
                self._add(new_node, explore=explore)
                self._current_node = new_node
index 16b79db4776d94fb288b4c5518cd2575aa66532e..33cd6580c7c7cb49c994f8f3a3661bb9d51fc6bf 100644 (file)
@@ -228,6 +228,7 @@ class binarytree(object):
                        self.invalids = []
                        self.settings = settings
                        self._pkg_paths = {}
+                       self._pkgindex_uri = {}
                        self._populating = False
                        self._all_directory = os.path.isdir(
                                os.path.join(self.pkgdir, "All"))
@@ -874,8 +875,9 @@ class binarytree(object):
                                # Organize remote package list as a cpv -> metadata map.
                                remotepkgs = _pkgindex_cpv_map_latest_build(pkgindex)
                                remote_base_uri = pkgindex.header.get("URI", base_url)
-                               for remote_metadata in remotepkgs.values():
+                               for cpv, remote_metadata in remotepkgs.items():
                                        remote_metadata["BASE_URI"] = remote_base_uri
+                                       self._pkgindex_uri[cpv] = url
                                self._remotepkgs.update(remotepkgs)
                                self._remote_has_index = True
                                for cpv in remotepkgs:
@@ -1225,6 +1227,10 @@ class binarytree(object):
                # package is downloaded, state is updated by self.inject().
                return True
 
+       def get_pkgindex_uri(self, pkgname):
+               """Returns the URI to the Packages file for a given package."""
+               return self._pkgindex_uri.get(pkgname)
+
        def gettbz2(self, pkgname):
                """Fetches the package from a remote site, if necessary.  Attempts to
                resume if the file appears to be partially downloaded."""
diff --git a/pym/portage/tests/resolver/test_rebuild.py b/pym/portage/tests/resolver/test_rebuild.py
new file mode 100644 (file)
index 0000000..2185bf7
--- /dev/null
@@ -0,0 +1,66 @@
+from portage.tests import TestCase
+from portage.tests.resolver.ResolverPlayground import (ResolverPlayground,
+       ResolverPlaygroundTestCase)
+
+class RebuildTestCase(TestCase):
+
+       def testRebuild(self):
+               """
+               Rebuild packages when dependencies that are used at both build-time and
+               run-time are upgraded.
+               """
+
+               ebuilds = {
+                       "sys-libs/x-1": { },
+                       "sys-libs/x-2": { },
+                       "sys-apps/a-1": { "DEPEND"  : "sys-libs/x", "RDEPEND" : "sys-libs/x"},
+                       "sys-apps/a-2": { "DEPEND"  : "sys-libs/x", "RDEPEND" : "sys-libs/x"},
+                       "sys-apps/b-1": { "DEPEND"  : "sys-libs/x", "RDEPEND" : "sys-libs/x"},
+                       "sys-apps/b-2": { "DEPEND"  : "sys-libs/x", "RDEPEND" : "sys-libs/x"},
+                       "sys-apps/c-1": { "DEPEND"  : "sys-libs/x", "RDEPEND" : ""},
+                       "sys-apps/c-2": { "DEPEND"  : "sys-libs/x", "RDEPEND" : ""},
+                       "sys-apps/d-1": { "RDEPEND" : "sys-libs/x"},
+                       "sys-apps/d-2": { "RDEPEND" : "sys-libs/x"},
+                       "sys-apps/e-2": { "DEPEND"  : "sys-libs/x", "RDEPEND" : "sys-libs/x"},
+                       "sys-apps/f-2": { "DEPEND"  : "sys-apps/a", "RDEPEND" : "sys-apps/a"},
+                       }
+
+               installed = {
+                       "sys-libs/x-1": { },
+                       "sys-apps/a-1": { "DEPEND"  : "sys-libs/x", "RDEPEND" : "sys-libs/x"},
+                       "sys-apps/b-1": { "DEPEND"  : "sys-libs/x", "RDEPEND" : "sys-libs/x"},
+                       "sys-apps/c-1": { "DEPEND"  : "sys-libs/x", "RDEPEND" : ""},
+                       "sys-apps/d-1": { "RDEPEND" : "sys-libs/x"},
+                       "sys-apps/e-1": { "DEPEND"  : "sys-libs/x", "RDEPEND" : "sys-libs/x"},
+                       "sys-apps/f-1": { "DEPEND"  : "sys-apps/a", "RDEPEND" : "sys-apps/a"},
+                       }
+
+               world = ["sys-apps/a", "sys-apps/b", "sys-apps/c", "sys-apps/d",
+                       "sys-apps/e", "sys-apps/f"]
+
+               test_cases = (
+                               ResolverPlaygroundTestCase(
+                                       ["sys-libs/x"],
+                                       options = {"--rebuild" : True,
+                                               "--norebuild-atoms" : ["sys-apps/b"]},
+                                       mergelist = ['sys-libs/x-2', 'sys-apps/a-2', 'sys-apps/e-2'],
+                                       ignore_mergelist_order = True,
+                                       success = True),
+
+                               ResolverPlaygroundTestCase(
+                                       ["sys-libs/x"],
+                                       options = {"--rebuild" : True},
+                                       mergelist = ['sys-libs/x-2', 'sys-apps/a-2', 'sys-apps/b-2', 'sys-apps/e-2'],
+                                       ignore_mergelist_order = True,
+                                       success = True),
+                       )
+
+               playground = ResolverPlayground(ebuilds=ebuilds,
+                       installed=installed, world=world)
+
+               try:
+                       for test_case in test_cases:
+                               playground.run_TestCase(test_case)
+                               self.assertEqual(test_case.test_success, True, test_case.fail_msg)
+               finally:
+                       playground.cleanup()