Index: pym/_emerge/depgraph.py |
diff --git a/pym/_emerge/depgraph.py b/pym/_emerge/depgraph.py |
index 7db5ae3f34541f505ab648e1b983f045251117e6..69cf3b288a8e9bdbab6ec245f7106b96da990314 100644 |
--- a/pym/_emerge/depgraph.py |
+++ b/pym/_emerge/depgraph.py |
@@ -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 = myopts.get('--rebuild', 'n') != 'n' |
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, \ |