1 |
commit: 470871eeafa89a05486d4eb6f3f7626c1f813e4d |
2 |
Author: David James <davidjames <AT> google <DOT> com> |
3 |
AuthorDate: Sun May 1 17:21:45 2011 +0000 |
4 |
Commit: Zac Medico <zmedico <AT> gentoo <DOT> org> |
5 |
CommitDate: Sun May 1 17:21:45 2011 +0000 |
6 |
URL: http://git.overlays.gentoo.org/gitweb/?p=proj/portage.git;a=commit;h=470871ee |
7 |
|
8 |
emerge: add --rebuild and --norebuild-atoms opts |
9 |
|
10 |
Rebuild when build-time/run-time deps are upgraded. |
11 |
|
12 |
If pkgA has been updated, and pkgB depends on pkgA at both |
13 |
build-time and run-time, pkgB needs to be rebuilt. This |
14 |
feature ensures that all packages are consistent when |
15 |
dependencies that are used at both runtime and build time |
16 |
are changed. |
17 |
|
18 |
This feature only rebuilds packages one layer deep. That |
19 |
means that if you upgrade libcros, for example, packages |
20 |
that depend directly on libcros will be rebuilt and |
21 |
reinstalled, but indirect dependencies will not be rebuilt. |
22 |
|
23 |
BUG=chromium-os:14296 |
24 |
|
25 |
TEST=Test whether packages rebuilding a bunch of packages. |
26 |
|
27 |
Change-Id: Idbc0532b4b1de28fd9e5a0abe3b7dbe1a3abd2c8 |
28 |
|
29 |
Review URL: http://codereview.chromium.org/6905107 |
30 |
|
31 |
--- |
32 |
man/emerge.1 | 8 ++ |
33 |
pym/_emerge/DepPriority.py | 8 +- |
34 |
pym/_emerge/DepPriorityNormalRange.py | 2 +- |
35 |
pym/_emerge/DepPrioritySatisfiedRange.py | 1 + |
36 |
pym/_emerge/Dependency.py | 7 +- |
37 |
pym/_emerge/UnmergeDepPriority.py | 4 +- |
38 |
pym/_emerge/create_depgraph_params.py | 2 +- |
39 |
pym/_emerge/depgraph.py | 190 +++++++++++++++++++++++++--- |
40 |
pym/_emerge/help.py | 13 ++ |
41 |
pym/_emerge/main.py | 29 ++++- |
42 |
pym/_emerge/resolver/backtracking.py | 13 ++- |
43 |
pym/portage/dbapi/bintree.py | 8 +- |
44 |
pym/portage/tests/resolver/test_rebuild.py | 66 ++++++++++ |
45 |
13 files changed, 325 insertions(+), 26 deletions(-) |
46 |
|
47 |
diff --git a/man/emerge.1 b/man/emerge.1 |
48 |
index 56823a8..fc7ed61 100644 |
49 |
--- a/man/emerge.1 |
50 |
+++ b/man/emerge.1 |
51 |
@@ -489,6 +489,10 @@ terminal device is determined to be a TTY. This flag disables it regardless. |
52 |
A space separated list of package names or slot atoms. Emerge will ignore |
53 |
matching binary packages. |
54 |
.TP |
55 |
+.BR "\-\-norebuild\-atoms " ATOMS |
56 |
+A space separated list of package names or slot atoms. Emerge will not rebuild |
57 |
+matching packages due to \fB\-\-rebuild\fR. |
58 |
+.TP |
59 |
.BR "\-\-oneshot " (\fB\-1\fR) |
60 |
Emerge as normal, but do not add the packages to the world file |
61 |
for later updating. |
62 |
@@ -538,6 +542,10 @@ Disable the warning message that's shown prior to |
63 |
to be set in the \fBmake.conf\fR(5) |
64 |
\fBEMERGE_DEFAULT_OPTS\fR variable. |
65 |
.TP |
66 |
+.BR "\-\-rebuild [ y | n ]" |
67 |
+Rebuild packages when dependencies that are used at both build\-time and |
68 |
+run\-time are upgraded. |
69 |
+.TP |
70 |
.BR "\-\-rebuilt\-binaries [ y | n ]" |
71 |
Replace installed packages with binary packages that have |
72 |
been rebuilt. Rebuilds are detected by comparison of |
73 |
|
74 |
diff --git a/pym/_emerge/DepPriority.py b/pym/_emerge/DepPriority.py |
75 |
index f99b726..b08ffe5 100644 |
76 |
--- a/pym/_emerge/DepPriority.py |
77 |
+++ b/pym/_emerge/DepPriority.py |
78 |
@@ -4,7 +4,7 @@ |
79 |
from _emerge.AbstractDepPriority import AbstractDepPriority |
80 |
class DepPriority(AbstractDepPriority): |
81 |
|
82 |
- __slots__ = ("satisfied", "optional", "rebuild") |
83 |
+ __slots__ = ("satisfied", "optional", "rebuild", "ignored") |
84 |
|
85 |
def __int__(self): |
86 |
""" |
87 |
@@ -24,17 +24,19 @@ class DepPriority(AbstractDepPriority): |
88 |
|
89 |
""" |
90 |
|
91 |
+ if self.optional: |
92 |
+ return -3 |
93 |
if self.buildtime: |
94 |
return 0 |
95 |
if self.runtime: |
96 |
return -1 |
97 |
if self.runtime_post: |
98 |
return -2 |
99 |
- if self.optional: |
100 |
- return -3 |
101 |
return -4 |
102 |
|
103 |
def __str__(self): |
104 |
+ if self.ignored: |
105 |
+ return "ignored" |
106 |
if self.optional: |
107 |
return "optional" |
108 |
if self.buildtime: |
109 |
|
110 |
diff --git a/pym/_emerge/DepPriorityNormalRange.py b/pym/_emerge/DepPriorityNormalRange.py |
111 |
index 259a1df..808c950 100644 |
112 |
--- a/pym/_emerge/DepPriorityNormalRange.py |
113 |
+++ b/pym/_emerge/DepPriorityNormalRange.py |
114 |
@@ -33,7 +33,7 @@ class DepPriorityNormalRange(object): |
115 |
def _ignore_runtime(cls, priority): |
116 |
if priority.__class__ is not DepPriority: |
117 |
return False |
118 |
- return not priority.buildtime |
119 |
+ return bool(priority.optional or not priority.buildtime) |
120 |
|
121 |
ignore_medium = _ignore_runtime |
122 |
ignore_medium_soft = _ignore_runtime_post |
123 |
|
124 |
diff --git a/pym/_emerge/DepPrioritySatisfiedRange.py b/pym/_emerge/DepPrioritySatisfiedRange.py |
125 |
index aa32d8f..589afde 100644 |
126 |
--- a/pym/_emerge/DepPrioritySatisfiedRange.py |
127 |
+++ b/pym/_emerge/DepPrioritySatisfiedRange.py |
128 |
@@ -80,6 +80,7 @@ class DepPrioritySatisfiedRange(object): |
129 |
if priority.__class__ is not DepPriority: |
130 |
return False |
131 |
return bool(priority.satisfied or \ |
132 |
+ priority.optional or \ |
133 |
not priority.buildtime) |
134 |
|
135 |
ignore_medium = _ignore_runtime |
136 |
|
137 |
diff --git a/pym/_emerge/Dependency.py b/pym/_emerge/Dependency.py |
138 |
index 63b2a1b..d5d519d 100644 |
139 |
--- a/pym/_emerge/Dependency.py |
140 |
+++ b/pym/_emerge/Dependency.py |
141 |
@@ -5,11 +5,16 @@ from _emerge.DepPriority import DepPriority |
142 |
from _emerge.SlotObject import SlotObject |
143 |
class Dependency(SlotObject): |
144 |
__slots__ = ("atom", "blocker", "child", "depth", |
145 |
- "parent", "onlydeps", "priority", "root") |
146 |
+ "parent", "onlydeps", "priority", "root", |
147 |
+ "collapsed_parent", "collapsed_priority") |
148 |
def __init__(self, **kwargs): |
149 |
SlotObject.__init__(self, **kwargs) |
150 |
if self.priority is None: |
151 |
self.priority = DepPriority() |
152 |
if self.depth is None: |
153 |
self.depth = 0 |
154 |
+ if self.collapsed_parent is None: |
155 |
+ self.collapsed_parent = self.parent |
156 |
+ if self.collapsed_priority is None: |
157 |
+ self.collapsed_priority = self.priority |
158 |
|
159 |
|
160 |
diff --git a/pym/_emerge/UnmergeDepPriority.py b/pym/_emerge/UnmergeDepPriority.py |
161 |
index 0f67f3b..db4836e 100644 |
162 |
--- a/pym/_emerge/UnmergeDepPriority.py |
163 |
+++ b/pym/_emerge/UnmergeDepPriority.py |
164 |
@@ -3,7 +3,7 @@ |
165 |
|
166 |
from _emerge.AbstractDepPriority import AbstractDepPriority |
167 |
class UnmergeDepPriority(AbstractDepPriority): |
168 |
- __slots__ = ("optional", "satisfied",) |
169 |
+ __slots__ = ("ignored", "optional", "satisfied",) |
170 |
""" |
171 |
Combination of properties Priority Category |
172 |
|
173 |
@@ -32,6 +32,8 @@ class UnmergeDepPriority(AbstractDepPriority): |
174 |
return -2 |
175 |
|
176 |
def __str__(self): |
177 |
+ if self.ignored: |
178 |
+ return "ignored" |
179 |
myvalue = self.__int__() |
180 |
if myvalue > self.SOFT: |
181 |
return "hard" |
182 |
|
183 |
diff --git a/pym/_emerge/create_depgraph_params.py b/pym/_emerge/create_depgraph_params.py |
184 |
index d60259e..0986347 100644 |
185 |
--- a/pym/_emerge/create_depgraph_params.py |
186 |
+++ b/pym/_emerge/create_depgraph_params.py |
187 |
@@ -33,7 +33,7 @@ def create_depgraph_params(myopts, myaction): |
188 |
deep = myopts.get("--deep") |
189 |
if deep is not None and deep != 0: |
190 |
myparams["deep"] = deep |
191 |
- if "--complete-graph" in myopts: |
192 |
+ if "--complete-graph" in myopts or "--rebuild" in myopts: |
193 |
myparams["complete"] = True |
194 |
if "--emptytree" in myopts: |
195 |
myparams["empty"] = True |
196 |
|
197 |
diff --git a/pym/_emerge/depgraph.py b/pym/_emerge/depgraph.py |
198 |
index 73b81e1..f55d84d 100644 |
199 |
--- a/pym/_emerge/depgraph.py |
200 |
+++ b/pym/_emerge/depgraph.py |
201 |
@@ -9,6 +9,7 @@ import logging |
202 |
import re |
203 |
import sys |
204 |
import textwrap |
205 |
+from collections import deque |
206 |
from itertools import chain |
207 |
|
208 |
import portage |
209 |
@@ -127,6 +128,10 @@ class _frozen_depgraph_config(object): |
210 |
self.nousepkg_atoms = _wildcard_set(atoms) |
211 |
atoms = ' '.join(myopts.get("--useoldpkg-atoms", [])).split() |
212 |
self.useoldpkg_atoms = _wildcard_set(atoms) |
213 |
+ atoms = ' '.join(myopts.get("--norebuild-atoms", [])).split() |
214 |
+ self.norebuild_atoms = _wildcard_set(atoms) |
215 |
+ |
216 |
+ self.rebuild = "--rebuild" in myopts |
217 |
|
218 |
class _depgraph_sets(object): |
219 |
def __init__(self): |
220 |
@@ -139,6 +144,128 @@ class _depgraph_sets(object): |
221 |
self.atoms = InternalPackageSet(allow_repo=True) |
222 |
self.atom_arg_map = {} |
223 |
|
224 |
+class _rebuild_config(object): |
225 |
+ def __init__(self, frozen_config, backtrack_parameters): |
226 |
+ self._graph = digraph() |
227 |
+ self._frozen_config = frozen_config |
228 |
+ self.rebuild_list = backtrack_parameters.rebuild_list.copy() |
229 |
+ self.orig_rebuild_list = self.rebuild_list.copy() |
230 |
+ self.reinstall_list = backtrack_parameters.reinstall_list.copy() |
231 |
+ |
232 |
+ def add(self, dep_pkg, dep): |
233 |
+ parent = dep.collapsed_parent |
234 |
+ priority = dep.collapsed_priority |
235 |
+ norebuild_atoms = self._frozen_config.norebuild_atoms |
236 |
+ if (self._frozen_config.rebuild and isinstance(parent, Package) and |
237 |
+ parent.built and (priority.buildtime or priority.runtime) and |
238 |
+ isinstance(dep_pkg, Package) and |
239 |
+ not norebuild_atoms.findAtomForPackage(parent)): |
240 |
+ self._graph.add(dep_pkg, parent, priority) |
241 |
+ |
242 |
+ def _trigger_rebuild(self, parent, build_deps, runtime_deps): |
243 |
+ root_slot = (parent.root, parent.slot_atom) |
244 |
+ if root_slot in self.rebuild_list: |
245 |
+ return False |
246 |
+ trees = self._frozen_config.trees |
247 |
+ children = set(build_deps).intersection(runtime_deps) |
248 |
+ reinstall = False |
249 |
+ for slot_atom in children: |
250 |
+ kids = set([build_deps[slot_atom], runtime_deps[slot_atom]]) |
251 |
+ for dep_pkg in kids: |
252 |
+ dep_root_slot = (dep_pkg.root, slot_atom) |
253 |
+ if (not dep_pkg.built and |
254 |
+ dep_root_slot not in self.orig_rebuild_list): |
255 |
+ # There's no binary package for dep_pkg, so any binary |
256 |
+ # package for this parent would be invalid. Force rebuild. |
257 |
+ self.rebuild_list.add(root_slot) |
258 |
+ return True |
259 |
+ elif ("--usepkg" in self._frozen_config.myopts and |
260 |
+ (dep_root_slot in self.reinstall_list or |
261 |
+ dep_root_slot in self.rebuild_list or |
262 |
+ not dep_pkg.installed)): |
263 |
+ |
264 |
+ # A direct rebuild dependency is being installed. We |
265 |
+ # should update the parent as well to the latest binary, |
266 |
+ # if that binary is valid. |
267 |
+ # |
268 |
+ # To validate the binary, we check whether all of the |
269 |
+ # rebuild dependencies are present on the same binhost. |
270 |
+ # |
271 |
+ # 1) If parent is present on the binhost, but one of its |
272 |
+ # rebuild dependencies is not, then the parent should |
273 |
+ # be rebuilt from source. |
274 |
+ # 2) Otherwise, the parent binary is assumed to be valid, |
275 |
+ # because all of its rebuild dependencies are |
276 |
+ # consistent. |
277 |
+ bintree = trees[parent.root]["bintree"] |
278 |
+ uri = bintree.get_pkgindex_uri(parent.cpv) |
279 |
+ dep_uri = bintree.get_pkgindex_uri(dep_pkg.cpv) |
280 |
+ bindb = bintree.dbapi |
281 |
+ |
282 |
+ if uri and uri != dep_uri: |
283 |
+ # 1) Remote binary package is invalid because it was |
284 |
+ # built without dep_pkg. Force rebuild. |
285 |
+ self.rebuild_list.add(root_slot) |
286 |
+ return True |
287 |
+ elif (parent.installed and |
288 |
+ root_slot not in self.reinstall_list): |
289 |
+ inst_build_time = parent.metadata.get("BUILD_TIME") |
290 |
+ try: |
291 |
+ bin_build_time, = bindb.aux_get(parent.cpv, |
292 |
+ ["BUILD_TIME"]) |
293 |
+ except KeyError: |
294 |
+ continue |
295 |
+ if bin_build_time != inst_build_time: |
296 |
+ # 2) Remote binary package is valid, and local package |
297 |
+ # is not up to date. Force reinstall. |
298 |
+ reinstall = True |
299 |
+ if reinstall: |
300 |
+ self.reinstall_list.add(root_slot) |
301 |
+ return reinstall |
302 |
+ |
303 |
+ def trigger_rebuilds(self): |
304 |
+ """ |
305 |
+ Trigger rebuilds where necessary. If pkgA has been updated, and pkgB |
306 |
+ depends on pkgA at both build-time and run-time, pkgB needs to be |
307 |
+ rebuilt. |
308 |
+ """ |
309 |
+ need_restart = False |
310 |
+ graph = self._graph |
311 |
+ build_deps = {} |
312 |
+ runtime_deps = {} |
313 |
+ leaf_nodes = deque(graph.leaf_nodes()) |
314 |
+ |
315 |
+ # Trigger rebuilds bottom-up (starting with the leaves) so that parents |
316 |
+ # will always know which children are being rebuilt. |
317 |
+ while leaf_nodes: |
318 |
+ node = leaf_nodes.popleft() |
319 |
+ slot_atom = node.slot_atom |
320 |
+ |
321 |
+ # Remove our leaf node from the graph, keeping track of deps. |
322 |
+ parents = graph.nodes[node][1].items() |
323 |
+ graph.remove(node) |
324 |
+ for parent, priorities in parents: |
325 |
+ for priority in priorities: |
326 |
+ if priority.buildtime: |
327 |
+ build_deps.setdefault(parent, {})[slot_atom] = node |
328 |
+ if priority.runtime: |
329 |
+ runtime_deps.setdefault(parent, {})[slot_atom] = node |
330 |
+ if not graph.child_nodes(parent): |
331 |
+ leaf_nodes.append(parent) |
332 |
+ |
333 |
+ # Trigger rebuilds for our leaf node. Because all of our children |
334 |
+ # have been processed, build_deps and runtime_deps will be |
335 |
+ # completely filled in, and self.rebuild_list / self.reinstall_list |
336 |
+ # will tell us whether any of our children need to be rebuilt or |
337 |
+ # reinstalled. |
338 |
+ node_build_deps = build_deps.get(node, {}) |
339 |
+ node_runtime_deps = runtime_deps.get(node, {}) |
340 |
+ if self._trigger_rebuild(node, node_build_deps, node_runtime_deps): |
341 |
+ need_restart = True |
342 |
+ |
343 |
+ return need_restart |
344 |
+ |
345 |
+ |
346 |
class _dynamic_depgraph_config(object): |
347 |
|
348 |
def __init__(self, depgraph, myparams, allow_backtracking, backtrack_parameters): |
349 |
@@ -306,6 +433,7 @@ class depgraph(object): |
350 |
self._frozen_config = frozen_config |
351 |
self._dynamic_config = _dynamic_depgraph_config(self, myparams, |
352 |
allow_backtracking, backtrack_parameters) |
353 |
+ self._rebuild = _rebuild_config(frozen_config, backtrack_parameters) |
354 |
|
355 |
self._select_atoms = self._select_atoms_highest_available |
356 |
self._select_package = self._select_pkg_highest_available |
357 |
@@ -671,6 +799,8 @@ class depgraph(object): |
358 |
if dep.blocker: |
359 |
if not buildpkgonly and \ |
360 |
not nodeps and \ |
361 |
+ not dep.collapsed_priority.ignored and \ |
362 |
+ not dep.collapsed_priority.optional and \ |
363 |
dep.parent not in self._dynamic_config._slot_collision_nodes: |
364 |
if dep.parent.onlydeps: |
365 |
# It's safe to ignore blockers if the |
366 |
@@ -695,9 +825,9 @@ class depgraph(object): |
367 |
dep.root].get(dep_pkg.slot_atom) |
368 |
|
369 |
if not dep_pkg: |
370 |
- if dep.priority.optional: |
371 |
- # This could be an unnecessary build-time dep |
372 |
- # pulled in by --with-bdeps=y. |
373 |
+ if (dep.collapsed_priority.optional or |
374 |
+ dep.collapsed_priority.ignored): |
375 |
+ # This is an unnecessary build-time dep. |
376 |
return 1 |
377 |
if allow_unsatisfied: |
378 |
self._dynamic_config._unsatisfied_deps.append(dep) |
379 |
@@ -740,7 +870,10 @@ class depgraph(object): |
380 |
|
381 |
return 0 |
382 |
|
383 |
- if not self._add_pkg(dep_pkg, dep): |
384 |
+ self._rebuild.add(dep_pkg, dep) |
385 |
+ |
386 |
+ if (not dep.collapsed_priority.ignored and |
387 |
+ not self._add_pkg(dep_pkg, dep)): |
388 |
return 0 |
389 |
return 1 |
390 |
|
391 |
@@ -1110,6 +1243,7 @@ class depgraph(object): |
392 |
edepend["RDEPEND"] = "" |
393 |
edepend["PDEPEND"] = "" |
394 |
|
395 |
+ ignore_build_time_deps = False |
396 |
if pkg.built and not removal_action: |
397 |
if self._frozen_config.myopts.get("--with-bdeps", "n") == "y": |
398 |
# Pull in build time deps as requested, but marked them as |
399 |
@@ -1121,11 +1255,10 @@ class depgraph(object): |
400 |
# failing. |
401 |
pass |
402 |
else: |
403 |
- # built packages do not have build time dependencies. |
404 |
- edepend["DEPEND"] = "" |
405 |
+ ignore_build_time_deps = True |
406 |
|
407 |
if removal_action and self._frozen_config.myopts.get("--with-bdeps", "y") == "n": |
408 |
- edepend["DEPEND"] = "" |
409 |
+ ignore_build_time_deps = True |
410 |
|
411 |
if removal_action: |
412 |
depend_root = myroot |
413 |
@@ -1136,13 +1269,14 @@ class depgraph(object): |
414 |
if root_deps is True: |
415 |
depend_root = myroot |
416 |
elif root_deps == "rdeps": |
417 |
- edepend["DEPEND"] = "" |
418 |
+ ignore_build_time_deps = True |
419 |
|
420 |
deps = ( |
421 |
(depend_root, edepend["DEPEND"], |
422 |
- self._priority(buildtime=(not pkg.built), |
423 |
- optional=pkg.built), |
424 |
- pkg.built), |
425 |
+ self._priority(buildtime=True, |
426 |
+ optional=pkg.built, |
427 |
+ ignored=ignore_build_time_deps), |
428 |
+ pkg.built or ignore_build_time_deps), |
429 |
(myroot, edepend["RDEPEND"], |
430 |
self._priority(runtime=True), |
431 |
False), |
432 |
@@ -1266,6 +1400,7 @@ class depgraph(object): |
433 |
|
434 |
mypriority = dep_priority.copy() |
435 |
if not atom.blocker: |
436 |
+ root_slot = (pkg.root, pkg.slot_atom) |
437 |
inst_pkgs = [inst_pkg for inst_pkg in vardb.match_pkgs(atom) |
438 |
if not reinstall_atoms.findAtomForPackage(inst_pkg, |
439 |
modified_use=self._pkg_use_enabled(inst_pkg))] |
440 |
@@ -1375,7 +1510,8 @@ class depgraph(object): |
441 |
# same depth as the virtual itself. |
442 |
dep = Dependency(atom=atom, |
443 |
blocker=atom.blocker, child=child, depth=virt_dep.depth, |
444 |
- parent=virt_pkg, priority=mypriority, root=dep_root) |
445 |
+ parent=virt_pkg, priority=mypriority, root=dep_root, |
446 |
+ collapsed_parent=pkg, collapsed_priority=dep_priority) |
447 |
|
448 |
ignored = False |
449 |
if not atom.blocker and \ |
450 |
@@ -1931,9 +2067,12 @@ class depgraph(object): |
451 |
pkgsettings = self._frozen_config.pkgsettings[myroot] |
452 |
pprovideddict = pkgsettings.pprovideddict |
453 |
virtuals = pkgsettings.getvirtuals() |
454 |
- for arg in self._expand_set_args( |
455 |
- self._dynamic_config._initial_arg_list, |
456 |
- add_to_digraph=True): |
457 |
+ args = self._dynamic_config._initial_arg_list[:] |
458 |
+ for root, atom in chain(self._rebuild.rebuild_list, |
459 |
+ self._rebuild.reinstall_list): |
460 |
+ args.append(AtomArg(arg=atom, atom=atom, |
461 |
+ root_config=self._frozen_config.roots[root])) |
462 |
+ for arg in self._expand_set_args(args, add_to_digraph=True): |
463 |
for atom in arg.pset.getAtoms(): |
464 |
self._spinner_update() |
465 |
dep = Dependency(atom=atom, onlydeps=onlydeps, |
466 |
@@ -2049,6 +2188,14 @@ class depgraph(object): |
467 |
self._dynamic_config._success_without_autounmask = True |
468 |
return False, myfavorites |
469 |
|
470 |
+ if self._rebuild.trigger_rebuilds(): |
471 |
+ backtrack_infos = self._dynamic_config._backtrack_infos |
472 |
+ config = backtrack_infos.setdefault("config", {}) |
473 |
+ config["rebuild_list"] = self._rebuild.rebuild_list |
474 |
+ config["reinstall_list"] = self._rebuild.reinstall_list |
475 |
+ self._dynamic_config._need_restart = True |
476 |
+ return False, myfavorites |
477 |
+ |
478 |
# We're true here unless we are missing binaries. |
479 |
return (True, myfavorites) |
480 |
|
481 |
@@ -2538,7 +2685,12 @@ class depgraph(object): |
482 |
pkg.iuse.is_valid_flag): |
483 |
required_use_unsatisfied.append(pkg) |
484 |
continue |
485 |
- if pkg.built and not mreasons: |
486 |
+ root_slot = (pkg.root, pkg.slot_atom) |
487 |
+ if pkg.built and root_slot in self._rebuild.rebuild_list: |
488 |
+ mreasons = ["need to rebuild from source"] |
489 |
+ elif pkg.installed and root_slot in self._rebuild.reinstall_list: |
490 |
+ mreasons = ["need to rebuild from source"] |
491 |
+ elif pkg.built and not mreasons: |
492 |
mreasons = ["use flag configuration mismatch"] |
493 |
masked_packages.append( |
494 |
(root_config, pkgsettings, cpv, repo, metadata, mreasons)) |
495 |
@@ -3216,6 +3368,12 @@ class depgraph(object): |
496 |
if pkg in self._dynamic_config._runtime_pkg_mask: |
497 |
# The package has been masked by the backtracking logic |
498 |
continue |
499 |
+ root_slot = (pkg.root, pkg.slot_atom) |
500 |
+ if pkg.built and root_slot in self._rebuild.rebuild_list: |
501 |
+ continue |
502 |
+ if (pkg.installed and |
503 |
+ root_slot in self._rebuild.reinstall_list): |
504 |
+ continue |
505 |
|
506 |
if not pkg.installed and \ |
507 |
self._frozen_config.excluded_pkgs.findAtomForPackage(pkg, \ |
508 |
|
509 |
diff --git a/pym/_emerge/help.py b/pym/_emerge/help.py |
510 |
index fb1e129..bf2437d 100644 |
511 |
--- a/pym/_emerge/help.py |
512 |
+++ b/pym/_emerge/help.py |
513 |
@@ -565,6 +565,12 @@ def help(myopts, havecolor=1): |
514 |
for line in wrap(desc, desc_width): |
515 |
print(desc_indent + line) |
516 |
print() |
517 |
+ print(" " + green("--norebuild-atoms") + " " + turquoise("ATOMS")) |
518 |
+ desc = "A space separated list of package names or slot atoms." + \ |
519 |
+ " Emerge will not rebuild matching packages due to --rebuild." |
520 |
+ for line in wrap(desc, desc_width): |
521 |
+ print(desc_indent + line) |
522 |
+ print() |
523 |
print(" "+green("--oneshot")+" ("+green("-1")+" short option)") |
524 |
print(" Emerge as normal, but don't add packages to the world profile.") |
525 |
print(" This package will only be updated if it is depended upon by") |
526 |
@@ -616,6 +622,13 @@ def help(myopts, havecolor=1): |
527 |
for line in wrap(desc, desc_width): |
528 |
print(desc_indent + line) |
529 |
print() |
530 |
+ print(" " + green("--rebuild") + " [ %s | %s ]" % \ |
531 |
+ (turquoise("y"), turquoise("n"))) |
532 |
+ desc = "Rebuild packages when dependencies that are used " + \ |
533 |
+ "at both build-time and run-time are upgraded." |
534 |
+ for line in wrap(desc, desc_width): |
535 |
+ print(desc_indent + line) |
536 |
+ print() |
537 |
print(" " + green("--rebuilt-binaries") + " [ %s | %s ]" % \ |
538 |
(turquoise("y"), turquoise("n"))) |
539 |
desc = "Replace installed packages with binary packages that have " + \ |
540 |
|
541 |
diff --git a/pym/_emerge/main.py b/pym/_emerge/main.py |
542 |
index e0cd0c0..434fd5a 100644 |
543 |
--- a/pym/_emerge/main.py |
544 |
+++ b/pym/_emerge/main.py |
545 |
@@ -440,6 +440,7 @@ def insert_optional_args(args): |
546 |
'--package-moves' : y_or_n, |
547 |
'--quiet' : y_or_n, |
548 |
'--quiet-build' : y_or_n, |
549 |
+ '--rebuild' : y_or_n, |
550 |
'--rebuilt-binaries' : y_or_n, |
551 |
'--root-deps' : ('rdeps',), |
552 |
'--select' : y_or_n, |
553 |
@@ -741,6 +742,14 @@ def parse_opts(tmpcmdline, silent=False): |
554 |
"action" : "append", |
555 |
}, |
556 |
|
557 |
+ "--norebuild-atoms": { |
558 |
+ "help" :"A space separated list of package names or slot atoms. " + \ |
559 |
+ "Emerge will not rebuild these packages due to the " + \ |
560 |
+ "--rebuild flag. ", |
561 |
+ |
562 |
+ "action" : "append", |
563 |
+ }, |
564 |
+ |
565 |
"--package-moves": { |
566 |
"help" : "perform package moves when necessary", |
567 |
"type" : "choice", |
568 |
@@ -760,6 +769,13 @@ def parse_opts(tmpcmdline, silent=False): |
569 |
"choices" : true_y_or_n |
570 |
}, |
571 |
|
572 |
+ "--rebuild": { |
573 |
+ "help" : "Rebuild packages when dependencies that are " + \ |
574 |
+ "used at both build-time and run-time are upgraded.", |
575 |
+ "type" : "choice", |
576 |
+ "choices" : true_y_or_n |
577 |
+ }, |
578 |
+ |
579 |
"--rebuilt-binaries": { |
580 |
"help" : "replace installed packages with binary " + \ |
581 |
"packages that have been rebuilt", |
582 |
@@ -889,7 +905,7 @@ def parse_opts(tmpcmdline, silent=False): |
583 |
else: |
584 |
myoptions.binpkg_respect_use = None |
585 |
|
586 |
- if myoptions.complete_graph in true_y: |
587 |
+ if myoptions.complete_graph in true_y or myoptions.rebuild in true_y: |
588 |
myoptions.complete_graph = True |
589 |
else: |
590 |
myoptions.complete_graph = None |
591 |
@@ -910,6 +926,12 @@ def parse_opts(tmpcmdline, silent=False): |
592 |
parser.error("Invalid Atom(s) in --reinstall-atoms parameter: '%s' (only package names and slot atoms (with wildcards) allowed)\n" % \ |
593 |
(",".join(bad_atoms),)) |
594 |
|
595 |
+ if myoptions.norebuild_atoms: |
596 |
+ bad_atoms = _find_bad_atoms(myoptions.norebuild_atoms) |
597 |
+ if bad_atoms and not silent: |
598 |
+ parser.error("Invalid Atom(s) in --norebuild-atoms parameter: '%s' (only package names and slot atoms (with wildcards) allowed)\n" % \ |
599 |
+ (",".join(bad_atoms),)) |
600 |
+ |
601 |
if myoptions.nousepkg_atoms: |
602 |
bad_atoms = _find_bad_atoms(myoptions.nousepkg_atoms) |
603 |
if bad_atoms and not silent: |
604 |
@@ -953,6 +975,11 @@ def parse_opts(tmpcmdline, silent=False): |
605 |
else: |
606 |
myoptions.quiet_build = None |
607 |
|
608 |
+ if myoptions.rebuild in true_y: |
609 |
+ myoptions.rebuild = True |
610 |
+ else: |
611 |
+ myoptions.rebuild = None |
612 |
+ |
613 |
if myoptions.rebuilt_binaries in true_y: |
614 |
myoptions.rebuilt_binaries = True |
615 |
|
616 |
|
617 |
diff --git a/pym/_emerge/resolver/backtracking.py b/pym/_emerge/resolver/backtracking.py |
618 |
index 1ffada9..f00e6ca 100644 |
619 |
--- a/pym/_emerge/resolver/backtracking.py |
620 |
+++ b/pym/_emerge/resolver/backtracking.py |
621 |
@@ -7,6 +7,7 @@ class BacktrackParameter(object): |
622 |
|
623 |
__slots__ = ( |
624 |
"needed_unstable_keywords", "runtime_pkg_mask", "needed_use_config_changes", "needed_license_changes", |
625 |
+ "rebuild_list", "reinstall_list" |
626 |
) |
627 |
|
628 |
def __init__(self): |
629 |
@@ -14,6 +15,8 @@ class BacktrackParameter(object): |
630 |
self.runtime_pkg_mask = {} |
631 |
self.needed_use_config_changes = {} |
632 |
self.needed_license_changes = {} |
633 |
+ self.rebuild_list = set() |
634 |
+ self.reinstall_list = set() |
635 |
|
636 |
def __deepcopy__(self, memo=None): |
637 |
if memo is None: |
638 |
@@ -27,6 +30,8 @@ class BacktrackParameter(object): |
639 |
result.runtime_pkg_mask = copy.copy(self.runtime_pkg_mask) |
640 |
result.needed_use_config_changes = copy.copy(self.needed_use_config_changes) |
641 |
result.needed_license_changes = copy.copy(self.needed_license_changes) |
642 |
+ result.rebuild_list = copy.copy(self.rebuild_list) |
643 |
+ result.reinstall_list = copy.copy(self.reinstall_list) |
644 |
|
645 |
return result |
646 |
|
647 |
@@ -34,7 +39,9 @@ class BacktrackParameter(object): |
648 |
return self.needed_unstable_keywords == other.needed_unstable_keywords and \ |
649 |
self.runtime_pkg_mask == other.runtime_pkg_mask and \ |
650 |
self.needed_use_config_changes == other.needed_use_config_changes and \ |
651 |
- self.needed_license_changes == other.needed_license_changes |
652 |
+ self.needed_license_changes == other.needed_license_changes and \ |
653 |
+ self.rebuild_list == other.rebuild_list and \ |
654 |
+ self.reinstall_list == other.reinstall_list |
655 |
|
656 |
|
657 |
class _BacktrackNode: |
658 |
@@ -137,6 +144,10 @@ class Backtracker(object): |
659 |
elif change == "needed_use_config_changes": |
660 |
for pkg, (new_use, new_changes) in data: |
661 |
para.needed_use_config_changes[pkg] = (new_use, new_changes) |
662 |
+ elif change == "rebuild_list": |
663 |
+ para.rebuild_list.update(data) |
664 |
+ elif change == "reinstall_list": |
665 |
+ para.reinstall_list.update(data) |
666 |
|
667 |
self._add(new_node, explore=explore) |
668 |
self._current_node = new_node |
669 |
|
670 |
diff --git a/pym/portage/dbapi/bintree.py b/pym/portage/dbapi/bintree.py |
671 |
index 16b79db..33cd658 100644 |
672 |
--- a/pym/portage/dbapi/bintree.py |
673 |
+++ b/pym/portage/dbapi/bintree.py |
674 |
@@ -228,6 +228,7 @@ class binarytree(object): |
675 |
self.invalids = [] |
676 |
self.settings = settings |
677 |
self._pkg_paths = {} |
678 |
+ self._pkgindex_uri = {} |
679 |
self._populating = False |
680 |
self._all_directory = os.path.isdir( |
681 |
os.path.join(self.pkgdir, "All")) |
682 |
@@ -874,8 +875,9 @@ class binarytree(object): |
683 |
# Organize remote package list as a cpv -> metadata map. |
684 |
remotepkgs = _pkgindex_cpv_map_latest_build(pkgindex) |
685 |
remote_base_uri = pkgindex.header.get("URI", base_url) |
686 |
- for remote_metadata in remotepkgs.values(): |
687 |
+ for cpv, remote_metadata in remotepkgs.items(): |
688 |
remote_metadata["BASE_URI"] = remote_base_uri |
689 |
+ self._pkgindex_uri[cpv] = url |
690 |
self._remotepkgs.update(remotepkgs) |
691 |
self._remote_has_index = True |
692 |
for cpv in remotepkgs: |
693 |
@@ -1225,6 +1227,10 @@ class binarytree(object): |
694 |
# package is downloaded, state is updated by self.inject(). |
695 |
return True |
696 |
|
697 |
+ def get_pkgindex_uri(self, pkgname): |
698 |
+ """Returns the URI to the Packages file for a given package.""" |
699 |
+ return self._pkgindex_uri.get(pkgname) |
700 |
+ |
701 |
def gettbz2(self, pkgname): |
702 |
"""Fetches the package from a remote site, if necessary. Attempts to |
703 |
resume if the file appears to be partially downloaded.""" |
704 |
|
705 |
diff --git a/pym/portage/tests/resolver/test_rebuild.py b/pym/portage/tests/resolver/test_rebuild.py |
706 |
new file mode 100644 |
707 |
index 0000000..2185bf7 |
708 |
--- /dev/null |
709 |
+++ b/pym/portage/tests/resolver/test_rebuild.py |
710 |
@@ -0,0 +1,66 @@ |
711 |
+from portage.tests import TestCase |
712 |
+from portage.tests.resolver.ResolverPlayground import (ResolverPlayground, |
713 |
+ ResolverPlaygroundTestCase) |
714 |
+ |
715 |
+class RebuildTestCase(TestCase): |
716 |
+ |
717 |
+ def testRebuild(self): |
718 |
+ """ |
719 |
+ Rebuild packages when dependencies that are used at both build-time and |
720 |
+ run-time are upgraded. |
721 |
+ """ |
722 |
+ |
723 |
+ ebuilds = { |
724 |
+ "sys-libs/x-1": { }, |
725 |
+ "sys-libs/x-2": { }, |
726 |
+ "sys-apps/a-1": { "DEPEND" : "sys-libs/x", "RDEPEND" : "sys-libs/x"}, |
727 |
+ "sys-apps/a-2": { "DEPEND" : "sys-libs/x", "RDEPEND" : "sys-libs/x"}, |
728 |
+ "sys-apps/b-1": { "DEPEND" : "sys-libs/x", "RDEPEND" : "sys-libs/x"}, |
729 |
+ "sys-apps/b-2": { "DEPEND" : "sys-libs/x", "RDEPEND" : "sys-libs/x"}, |
730 |
+ "sys-apps/c-1": { "DEPEND" : "sys-libs/x", "RDEPEND" : ""}, |
731 |
+ "sys-apps/c-2": { "DEPEND" : "sys-libs/x", "RDEPEND" : ""}, |
732 |
+ "sys-apps/d-1": { "RDEPEND" : "sys-libs/x"}, |
733 |
+ "sys-apps/d-2": { "RDEPEND" : "sys-libs/x"}, |
734 |
+ "sys-apps/e-2": { "DEPEND" : "sys-libs/x", "RDEPEND" : "sys-libs/x"}, |
735 |
+ "sys-apps/f-2": { "DEPEND" : "sys-apps/a", "RDEPEND" : "sys-apps/a"}, |
736 |
+ } |
737 |
+ |
738 |
+ installed = { |
739 |
+ "sys-libs/x-1": { }, |
740 |
+ "sys-apps/a-1": { "DEPEND" : "sys-libs/x", "RDEPEND" : "sys-libs/x"}, |
741 |
+ "sys-apps/b-1": { "DEPEND" : "sys-libs/x", "RDEPEND" : "sys-libs/x"}, |
742 |
+ "sys-apps/c-1": { "DEPEND" : "sys-libs/x", "RDEPEND" : ""}, |
743 |
+ "sys-apps/d-1": { "RDEPEND" : "sys-libs/x"}, |
744 |
+ "sys-apps/e-1": { "DEPEND" : "sys-libs/x", "RDEPEND" : "sys-libs/x"}, |
745 |
+ "sys-apps/f-1": { "DEPEND" : "sys-apps/a", "RDEPEND" : "sys-apps/a"}, |
746 |
+ } |
747 |
+ |
748 |
+ world = ["sys-apps/a", "sys-apps/b", "sys-apps/c", "sys-apps/d", |
749 |
+ "sys-apps/e", "sys-apps/f"] |
750 |
+ |
751 |
+ test_cases = ( |
752 |
+ ResolverPlaygroundTestCase( |
753 |
+ ["sys-libs/x"], |
754 |
+ options = {"--rebuild" : True, |
755 |
+ "--norebuild-atoms" : ["sys-apps/b"]}, |
756 |
+ mergelist = ['sys-libs/x-2', 'sys-apps/a-2', 'sys-apps/e-2'], |
757 |
+ ignore_mergelist_order = True, |
758 |
+ success = True), |
759 |
+ |
760 |
+ ResolverPlaygroundTestCase( |
761 |
+ ["sys-libs/x"], |
762 |
+ options = {"--rebuild" : True}, |
763 |
+ mergelist = ['sys-libs/x-2', 'sys-apps/a-2', 'sys-apps/b-2', 'sys-apps/e-2'], |
764 |
+ ignore_mergelist_order = True, |
765 |
+ success = True), |
766 |
+ ) |
767 |
+ |
768 |
+ playground = ResolverPlayground(ebuilds=ebuilds, |
769 |
+ installed=installed, world=world) |
770 |
+ |
771 |
+ try: |
772 |
+ for test_case in test_cases: |
773 |
+ playground.run_TestCase(test_case) |
774 |
+ self.assertEqual(test_case.test_success, True, test_case.fail_msg) |
775 |
+ finally: |
776 |
+ playground.cleanup() |