Gentoo Archives: gentoo-commits

From: Zac Medico <zmedico@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/portage:master commit in: pym/_emerge/resolver/, man/, pym/portage/dbapi/, pym/portage/tests/resolver/, ...
Date: Sun, 01 May 2011 17:22:48
Message-Id: 470871eeafa89a05486d4eb6f3f7626c1f813e4d.zmedico@gentoo
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()