summaryrefslogtreecommitdiffstats
path: root/pym/_emerge/depgraph.py
diff options
context:
space:
mode:
authorDavid James <davidjames@google.com>2011-05-01 10:21:45 -0700
committerZac Medico <zmedico@gentoo.org>2011-05-01 10:21:45 -0700
commit470871eeafa89a05486d4eb6f3f7626c1f813e4d (patch)
treeaeb8eb32a862b2a0e271755372ba4c9994b50800 /pym/_emerge/depgraph.py
parentdf15d8c67d6c9d821a5fada0063c04da9bb2bed6 (diff)
downloadportage-470871eeafa89a05486d4eb6f3f7626c1f813e4d.tar.gz
portage-470871eeafa89a05486d4eb6f3f7626c1f813e4d.tar.bz2
portage-470871eeafa89a05486d4eb6f3f7626c1f813e4d.zip
emerge: add --rebuild and --norebuild-atoms opts
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
Diffstat (limited to 'pym/_emerge/depgraph.py')
-rw-r--r--pym/_emerge/depgraph.py190
1 files changed, 174 insertions, 16 deletions
diff --git a/pym/_emerge/depgraph.py b/pym/_emerge/depgraph.py
index 73b81e1d1..f55d84d49 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 = "--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, \