1 |
This function allows emerge to solve more slot conflicts without |
2 |
backtracking. |
3 |
|
4 |
It does this by creating more conflicts for packages with built slot |
5 |
operator dependencies. This gives more freedom to |
6 |
depgraph._solve_non_slot_operator_slot_conflicts, which is then able |
7 |
to solve conflicts it wouldn't have otherwise. |
8 |
--- |
9 |
pym/_emerge/depgraph.py | 483 +++++++++++++++++++++++++++++++++++------ |
10 |
pym/_emerge/resolver/output.py | 5 +- |
11 |
2 files changed, 415 insertions(+), 73 deletions(-) |
12 |
|
13 |
diff --git a/pym/_emerge/depgraph.py b/pym/_emerge/depgraph.py |
14 |
index 835bd6b..970a9c7 100644 |
15 |
--- a/pym/_emerge/depgraph.py |
16 |
+++ b/pym/_emerge/depgraph.py |
17 |
@@ -427,6 +427,12 @@ class _dynamic_depgraph_config(object): |
18 |
# Track missed updates caused by solved conflicts. |
19 |
self._conflict_missed_update = collections.defaultdict(dict) |
20 |
|
21 |
+ # Rebuilds caused by slot conflicts. |
22 |
+ self._slot_conflict_rebuilds = {} |
23 |
+ # Rebuilds caused by missed slot operator updates or |
24 |
+ # slot conflicts. |
25 |
+ self._forced_rebuilds = None |
26 |
+ |
27 |
for myroot in depgraph._frozen_config.trees: |
28 |
self.sets[myroot] = _depgraph_sets() |
29 |
vardb = depgraph._frozen_config.trees[myroot]["vartree"].dbapi |
30 |
@@ -614,6 +620,9 @@ class depgraph(object): |
31 |
Fill self._forced_rebuilds with packages that cause rebuilds. |
32 |
""" |
33 |
|
34 |
+ if self._dynamic_config._forced_rebuilds is not None: |
35 |
+ return |
36 |
+ |
37 |
debug = "--debug" in self._frozen_config.myopts |
38 |
|
39 |
# Get all atoms that might have caused a forced rebuild. |
40 |
@@ -687,19 +696,23 @@ class depgraph(object): |
41 |
writemsg_level("\n\n", |
42 |
level=logging.DEBUG, noiselevel=-1) |
43 |
|
44 |
- self._forced_rebuilds = forced_rebuilds |
45 |
+ for child, parents in self._dynamic_config._slot_conflict_rebuilds.items(): |
46 |
+ forced_rebuilds.setdefault(child.root, {}).setdefault(child, set()).update(parents) |
47 |
+ |
48 |
+ self._dynamic_config._forced_rebuilds = forced_rebuilds |
49 |
|
50 |
def _show_abi_rebuild_info(self): |
51 |
|
52 |
- if not self._forced_rebuilds: |
53 |
+ forced_rebuilds = self._dynamic_config._forced_rebuilds |
54 |
+ if not forced_rebuilds: |
55 |
return |
56 |
|
57 |
writemsg_stdout("\nThe following packages are causing rebuilds:\n\n", noiselevel=-1) |
58 |
|
59 |
- for root in self._forced_rebuilds: |
60 |
- for child in self._forced_rebuilds[root]: |
61 |
+ for root in forced_rebuilds: |
62 |
+ for child in forced_rebuilds[root]: |
63 |
writemsg_stdout(" %s causes rebuilds for:\n" % (child,), noiselevel=-1) |
64 |
- for parent in self._forced_rebuilds[root][child]: |
65 |
+ for parent in forced_rebuilds[root][child]: |
66 |
writemsg_stdout(" %s\n" % (parent,), noiselevel=-1) |
67 |
|
68 |
def _show_ignored_binaries(self): |
69 |
@@ -968,16 +981,16 @@ class depgraph(object): |
70 |
writemsg(line + '\n', noiselevel=-1) |
71 |
writemsg('\n', noiselevel=-1) |
72 |
|
73 |
- def _solve_non_slot_operator_slot_conflicts(self): |
74 |
+ |
75 |
+ def _extend_slot_operator_conflicts(self): |
76 |
""" |
77 |
- This function solves slot conflicts which can |
78 |
- be solved by simply choosing one of the conflicting |
79 |
- and removing all the other ones. |
80 |
- It is able to solve somewhat more complex cases where |
81 |
- conflicts can only be solved simultaniously. |
82 |
+ This function searches for packages that cause |
83 |
+ slot conflicts by dependening on conflict packages |
84 |
+ with built slot operator deps. If such a package |
85 |
+ is found an alternative package is pulled in with |
86 |
+ the hope that the alternative package would not |
87 |
+ cuase the slot conflict. |
88 |
""" |
89 |
- debug = "--debug" in self._frozen_config.myopts |
90 |
- |
91 |
# List all conflicts. Ignore those that involve slot operator rebuilds |
92 |
# as the logic there needs special slot conflict behavior which isn't |
93 |
# provided by this function. |
94 |
@@ -990,9 +1003,133 @@ class depgraph(object): |
95 |
if not conflicts: |
96 |
return |
97 |
|
98 |
- # Get a set of all conflicting packages. |
99 |
+ # Compute a mapping from parent packages to hard |
100 |
+ # required conflict packages. |
101 |
+ conflict_parents = collections.defaultdict(list) |
102 |
+ for conflict in conflicts: |
103 |
+ all_parent_atoms = set() |
104 |
+ for pkg in conflict: |
105 |
+ all_parent_atoms.update( |
106 |
+ self._dynamic_config._parent_atoms.get(pkg, [])) |
107 |
+ |
108 |
+ for parent, atom in all_parent_atoms: |
109 |
+ atom_set = InternalPackageSet( |
110 |
+ initial_atoms=(atom,), allow_repo=True) |
111 |
+ |
112 |
+ for pkg in conflict: |
113 |
+ if not atom_set.findAtomForPackage(pkg, \ |
114 |
+ modified_use=self._pkg_use_enabled(pkg)): |
115 |
+ conflict_parents[parent].append((pkg, atom)) |
116 |
+ |
117 |
+ def _iter_alternatives(pkg): |
118 |
+ """ |
119 |
+ A wrapper around _iter_similar_available that |
120 |
+ deals with autounmask. |
121 |
+ """ |
122 |
+ tried = set() |
123 |
+ for other in self._iter_similar_available(pkg, |
124 |
+ Atom(pkg.cp), autounmask_level=None, installed=False): |
125 |
+ tried.add(other) |
126 |
+ yield other, None |
127 |
+ |
128 |
+ if self._dynamic_config._autounmask is not True: |
129 |
+ return |
130 |
+ |
131 |
+ for autounmask_level in self._autounmask_levels(): |
132 |
+ for other in self._iter_similar_available(pkg, |
133 |
+ Atom(pkg.cp), autounmask_level=autounmask_level, installed=False): |
134 |
+ if other not in tried: |
135 |
+ tried.add(other) |
136 |
+ yield other, autounmask_level |
137 |
+ |
138 |
+ |
139 |
+ # Compute a list of possible alternatives |
140 |
+ # for each conflict parent. |
141 |
+ parent_matches = {} |
142 |
+ for parent in conflict_parents: |
143 |
+ slot_op_children = [] |
144 |
+ for child, atom in conflict_parents[parent]: |
145 |
+ if atom.slot_operator == "=" and parent.built: |
146 |
+ slot_op_children.append(child) |
147 |
+ |
148 |
+ if not slot_op_children: |
149 |
+ # This parent doesn't depend with a built slot |
150 |
+ # operator on a conflict package. |
151 |
+ continue |
152 |
+ |
153 |
+ matches = [] |
154 |
+ highest_ebuilds = {} |
155 |
+ for other, autounmask_level in _iter_alternatives(parent): |
156 |
+ if parent.slot_atom != other.slot_atom and parent.cpv != other.cpv: |
157 |
+ # 'other' is not a replacement for 'parent'. |
158 |
+ continue |
159 |
+ |
160 |
+ highest_ebuild = highest_ebuilds.get(autounmask_level) |
161 |
+ if not other.built and \ |
162 |
+ (highest_ebuild is None or highest_ebuild < other): |
163 |
+ # Remember the highest ebuild for |
164 |
+ # downgrade testing later. |
165 |
+ highest_ebuilds[autounmask_level] = other |
166 |
+ |
167 |
+ # Go through 'parents' parents and check if 'other' |
168 |
+ # satisfies their dependencies. Ignore built slot |
169 |
+ # operator deps. |
170 |
+ is_match = True |
171 |
+ for pparent, patom in self._dynamic_config._parent_atoms.get(parent, []): |
172 |
+ if patom.slot_operator == "=" and pparent.built and parent.built: |
173 |
+ continue |
174 |
+ |
175 |
+ atom_set = InternalPackageSet( |
176 |
+ initial_atoms=(patom,), allow_repo=True) |
177 |
+ |
178 |
+ if not atom_set.findAtomForPackage(other, \ |
179 |
+ modified_use=self._pkg_use_enabled(other)): |
180 |
+ is_match = False |
181 |
+ break |
182 |
+ |
183 |
+ if is_match: |
184 |
+ matches.append((other, autounmask_level)) |
185 |
+ |
186 |
+ # Filter downgrades. |
187 |
+ no_downgrade_matches = [] |
188 |
+ for match, autounmask_level in matches: |
189 |
+ highest_ebuild = highest_ebuilds.get(autounmask_level) |
190 |
+ if highest_ebuild and match >= highest_ebuild: |
191 |
+ no_downgrade_matches.append(match) |
192 |
+ |
193 |
+ parent_matches[parent] = no_downgrade_matches |
194 |
+ |
195 |
+ # Pull in alternatives. |
196 |
+ for parent in parent_matches: |
197 |
+ for match in parent_matches[parent]: |
198 |
+ other.depth = parent.depth |
199 |
+ dep = Dependency(atom=Atom('=' + match.cpv), child=match, |
200 |
+ parent=None, root=match.root) |
201 |
+ |
202 |
+ if not self._add_pkg(match, dep, reuse_existing=False) or \ |
203 |
+ not self._create_graph(): |
204 |
+ self._remove_pkg(match) |
205 |
+ continue |
206 |
+ |
207 |
+ # Record forces rebuilds. |
208 |
+ for child, atom in conflict_parents[parent]: |
209 |
+ self._dynamic_config._slot_conflict_rebuilds.setdefault( |
210 |
+ child, set()).add(match) |
211 |
+ break |
212 |
+ |
213 |
+ |
214 |
+ def _get_conflicts_data(self, conflicts): |
215 |
+ """ |
216 |
+ This function creates the conflict graph and some |
217 |
+ helper data structures for _solve_non_slot_operator_slot_conflicts. |
218 |
+ """ |
219 |
+ selective = "selective" in self._dynamic_config.myparams |
220 |
+ |
221 |
+ pkg_to_conflict = {} |
222 |
conflict_pkgs = set() |
223 |
for conflict in conflicts: |
224 |
+ for pkg in conflict: |
225 |
+ pkg_to_conflict[pkg] = conflict |
226 |
conflict_pkgs.update(conflict) |
227 |
|
228 |
# Get the list of other packages which are only |
229 |
@@ -1050,7 +1187,7 @@ class depgraph(object): |
230 |
self._dynamic_config._parent_atoms.get(pkg, [])) |
231 |
|
232 |
for parent, atom in all_parent_atoms: |
233 |
- is_arg_parent = isinstance(parent, AtomArg) |
234 |
+ is_arg_parent = isinstance(parent, AtomArg) and not selective |
235 |
|
236 |
if parent not in conflict_pkgs and \ |
237 |
parent not in indirect_conflict_pkgs: |
238 |
@@ -1072,7 +1209,11 @@ class depgraph(object): |
239 |
conflict_graph.add(matched[0], parent) |
240 |
else: |
241 |
# More than one packages matched, but not all. |
242 |
- conflict_graph.add(or_tuple(matched), parent) |
243 |
+ match_tuple = or_tuple(matched) |
244 |
+ conflict_graph.add(match_tuple, parent) |
245 |
+ for match in matched: |
246 |
+ conflict_graph.add(match, match_tuple) |
247 |
+ |
248 |
|
249 |
for pkg in indirect_conflict_pkgs: |
250 |
for parent, atom in self._dynamic_config._parent_atoms.get(pkg, []): |
251 |
@@ -1081,6 +1222,42 @@ class depgraph(object): |
252 |
parent = non_conflict_node |
253 |
conflict_graph.add(pkg, parent) |
254 |
|
255 |
+ for conflict in conflicts: |
256 |
+ if all(not conflict_graph.parent_nodes(node) for node in conflict): |
257 |
+ # No conflict parents, all parents accept all conflict packages. |
258 |
+ # This happens when _extend_slot_operator_conflict pulls in |
259 |
+ # alternative parents for other conflict paclages. |
260 |
+ conflict_graph.add(or_tuple(conflict), non_conflict_node) |
261 |
+ |
262 |
+ return conflict_graph, pkg_to_conflict, conflict_pkgs, non_conflict_node |
263 |
+ |
264 |
+ |
265 |
+ def _solve_non_slot_operator_slot_conflicts(self): |
266 |
+ """ |
267 |
+ This function solves slot conflicts which can |
268 |
+ be solved by simply choosing one of the conflicting |
269 |
+ packages and removing all the other ones. |
270 |
+ It is able to solve somewhat more complex cases where |
271 |
+ conflicts can only be solved simultaniously. |
272 |
+ """ |
273 |
+ debug = "--debug" in self._frozen_config.myopts |
274 |
+ selective = "selective" in self._dynamic_config.myparams |
275 |
+ |
276 |
+ # List all conflicts. Ignore those that involve slot operator rebuilds |
277 |
+ # as the logic there needs special slot conflict behavior which isn't |
278 |
+ # provided by this function. |
279 |
+ conflicts = [] |
280 |
+ for conflict in self._dynamic_config._package_tracker.slot_conflicts(): |
281 |
+ slot_key = conflict.root, conflict.atom |
282 |
+ if slot_key not in self._dynamic_config._slot_operator_replace_installed: |
283 |
+ conflicts.append(conflict) |
284 |
+ |
285 |
+ if not conflicts: |
286 |
+ return |
287 |
+ |
288 |
+ conflict_graph, pkg_to_conflict, conflict_pkgs, non_conflict_node = \ |
289 |
+ self._get_conflicts_data(conflicts) |
290 |
+ |
291 |
if debug: |
292 |
writemsg_level( |
293 |
"\n!!! Slot conflict graph:\n", |
294 |
@@ -1091,14 +1268,20 @@ class depgraph(object): |
295 |
# 'forced' set. |
296 |
forced = set([non_conflict_node]) |
297 |
unexplored = set([non_conflict_node]) |
298 |
+ # Keep track of packages for which another conflicting |
299 |
+ # package has already been choosen. Discourage those |
300 |
+ # in case the choice between several packages has to be |
301 |
+ # made. |
302 |
+ discouraged = set() |
303 |
# or_tuples get special handling. We first explore |
304 |
# all packages in the hope of having forced one of |
305 |
# the packages in the tuple. This way we don't have |
306 |
# to choose one. |
307 |
unexplored_tuples = set() |
308 |
|
309 |
- while unexplored: |
310 |
+ while unexplored or unexplored_tuples: |
311 |
# Handle all unexplored packages. |
312 |
+ new_discouraged = set() |
313 |
while unexplored: |
314 |
node = unexplored.pop() |
315 |
for child in conflict_graph.child_nodes(node): |
316 |
@@ -1107,30 +1290,129 @@ class depgraph(object): |
317 |
forced.add(child) |
318 |
if isinstance(child, Package): |
319 |
unexplored.add(child) |
320 |
+ for other in pkg_to_conflict.get(child, []): |
321 |
+ if other in forced or other in discouraged: |
322 |
+ continue |
323 |
+ new_discouraged.add(other) |
324 |
else: |
325 |
unexplored_tuples.add(child) |
326 |
+ for other in pkg_to_conflict[child[0]]: |
327 |
+ if other in child: |
328 |
+ continue |
329 |
+ if other in forced or other in discouraged: |
330 |
+ continue |
331 |
+ new_discouraged.add(other) |
332 |
+ |
333 |
+ # Now mark packages which aren't forced yet |
334 |
+ # but would cause a conflict if they were as |
335 |
+ # discouraged. |
336 |
+ while new_discouraged: |
337 |
+ pkg = new_discouraged.pop() |
338 |
+ discouraged.add(pkg) |
339 |
+ for parent in conflict_graph.parent_nodes(pkg): |
340 |
+ if parent in forced or parent in discouraged: |
341 |
+ continue |
342 |
+ if isinstance(parent, Package): |
343 |
+ new_discouraged.add(parent) |
344 |
+ else: |
345 |
+ if all(other in discouraged and other not in forced \ |
346 |
+ for other in parent): |
347 |
+ new_discouraged.add(parent) |
348 |
|
349 |
# Now handle unexplored or_tuples. Move on with packages |
350 |
# once we had to choose one. |
351 |
- while unexplored_tuples: |
352 |
- nodes = unexplored_tuples.pop() |
353 |
+ remaining_unexplored_tuples = set() |
354 |
+ for nodes in unexplored_tuples: |
355 |
if any(node in forced for node in nodes): |
356 |
- # At least one of the packages in the |
357 |
- # tuple is already forced, which means the |
358 |
- # dependency represented by this tuple |
359 |
- # is satisfied. |
360 |
+ # Already satisfied. |
361 |
continue |
362 |
|
363 |
- # We now have to choose one of packages in the tuple. |
364 |
- # In theory one could solve more conflicts if we'd be |
365 |
- # able to try different choices here, but that has lots |
366 |
- # of other problems. For now choose the package that was |
367 |
- # pulled first, as this should be the most desirable choice |
368 |
- # (otherwise it wouldn't have been the first one). |
369 |
- forced.add(nodes[0]) |
370 |
- unexplored.add(nodes[0]) |
371 |
+ is_superset = False |
372 |
+ for other_nodes in unexplored_tuples: |
373 |
+ if other_nodes is nodes: |
374 |
+ continue |
375 |
+ if set(other_nodes).issubset(nodes): |
376 |
+ is_superset = True |
377 |
+ break |
378 |
+ if is_superset: |
379 |
+ continue |
380 |
+ |
381 |
+ non_dicouraged = list(node for node in nodes \ |
382 |
+ if node not in discouraged) |
383 |
+ |
384 |
+ if len(non_dicouraged) == 1: |
385 |
+ forced.add(non_dicouraged[0]) |
386 |
+ unexplored.add(non_dicouraged[0]) |
387 |
+ continue |
388 |
+ |
389 |
+ remaining_unexplored_tuples.add(nodes) |
390 |
+ |
391 |
+ unexplored_tuples = remaining_unexplored_tuples |
392 |
+ if unexplored: |
393 |
+ continue |
394 |
+ |
395 |
+ # For each package compute if it is discouraged |
396 |
+ # and if all its dependencies are already forced. |
397 |
+ status = collections.defaultdict(set) |
398 |
+ for nodes in unexplored_tuples: |
399 |
+ for node in nodes: |
400 |
+ is_discouraged = node in discouraged |
401 |
+ |
402 |
+ is_satisfied = True |
403 |
+ for child in conflict_graph.child_nodes(node): |
404 |
+ if isinstance(child, Package): |
405 |
+ if child not in forced: |
406 |
+ is_satisfied = False |
407 |
+ break |
408 |
+ else: |
409 |
+ if any(child_node not in forced for child_node in child): |
410 |
+ is_satisfied = False |
411 |
+ break |
412 |
+ status[(not is_discouraged, is_satisfied)].add(node) |
413 |
+ |
414 |
+ # Go through the state combinations to find |
415 |
+ # an or_tuple with the least chance of |
416 |
+ # causing a conflict. At this point we resort to |
417 |
+ # educted guessing and force one package. |
418 |
+ for state in (True, True), (True, False), (False, True), (False, False): |
419 |
+ selected = {} |
420 |
+ for nodes in unexplored_tuples: |
421 |
+ candidates = [] |
422 |
+ for node in nodes: |
423 |
+ if node in status[state]: |
424 |
+ candidates.append(node) |
425 |
+ if candidates: |
426 |
+ selected[nodes] = candidates |
427 |
+ |
428 |
+ # Search for the or_tuple with the least |
429 |
+ # number of candidates. |
430 |
+ choosen = None |
431 |
+ for nodes in selected: |
432 |
+ candidates = selected[nodes] |
433 |
+ if choosen is None or len(choosen) > len(candidates): |
434 |
+ choosen = candidates |
435 |
+ |
436 |
+ if not choosen: |
437 |
+ continue |
438 |
+ |
439 |
+ if selective: |
440 |
+ # Prefer installed packages. |
441 |
+ selected = None |
442 |
+ for node in choosen: |
443 |
+ if node.installed: |
444 |
+ selected = node |
445 |
+ break |
446 |
+ if selected is None: |
447 |
+ selected = choosen[0] |
448 |
+ else: |
449 |
+ # Prefer the first package. |
450 |
+ selected = choosen[0] |
451 |
+ |
452 |
+ forced.add(selected) |
453 |
+ unexplored.add(selected) |
454 |
break |
455 |
|
456 |
+ |
457 |
# Remove 'non_conflict_node' and or_tuples from 'forced'. |
458 |
forced = set(pkg for pkg in forced if isinstance(pkg, Package)) |
459 |
non_forced = set(pkg for pkg in conflict_pkgs if pkg not in forced) |
460 |
@@ -1206,9 +1488,10 @@ class depgraph(object): |
461 |
If there are any slot conflicts and backtracking is enabled, |
462 |
_complete_graph should complete the graph before this method |
463 |
is called, so that all relevant reverse dependencies are |
464 |
- available for use in backtracking decisions. |
465 |
+ available for use during conflict resolution. |
466 |
""" |
467 |
- |
468 |
+ self._solve_non_slot_operator_slot_conflicts() |
469 |
+ self._extend_slot_operator_conflicts() |
470 |
self._solve_non_slot_operator_slot_conflicts() |
471 |
|
472 |
for conflict in self._dynamic_config._package_tracker.slot_conflicts(): |
473 |
@@ -1842,7 +2125,7 @@ class depgraph(object): |
474 |
return frozenset(x.unevaluated_atom for |
475 |
x in selected_atoms) |
476 |
|
477 |
- def _iter_similar_available(self, graph_pkg, atom, autounmask_level=None): |
478 |
+ def _iter_similar_available(self, graph_pkg, atom, autounmask_level=None, installed=False): |
479 |
""" |
480 |
Given a package that's in the graph, do a rough check to |
481 |
see if a similar package is available to install. The given |
482 |
@@ -1859,7 +2142,7 @@ class depgraph(object): |
483 |
if pkg.cp != graph_pkg.cp: |
484 |
# discard old-style virtual match |
485 |
continue |
486 |
- if pkg.installed: |
487 |
+ if pkg.installed and not installed: |
488 |
continue |
489 |
if pkg in self._dynamic_config._runtime_pkg_mask: |
490 |
continue |
491 |
@@ -2176,7 +2459,7 @@ class depgraph(object): |
492 |
|
493 |
return (existing_node, matches) |
494 |
|
495 |
- def _add_pkg(self, pkg, dep): |
496 |
+ def _add_pkg(self, pkg, dep, reuse_existing=True): |
497 |
""" |
498 |
Adds a package to the depgraph, queues dependencies, and handles |
499 |
slot conflicts. |
500 |
@@ -2268,7 +2551,7 @@ class depgraph(object): |
501 |
existing_node, existing_node_matches = \ |
502 |
self._check_slot_conflict(pkg, dep.atom) |
503 |
if existing_node: |
504 |
- if existing_node_matches: |
505 |
+ if existing_node_matches and reuse_existing: |
506 |
# The existing node can be reused. |
507 |
if pkg != existing_node: |
508 |
pkg = existing_node |
509 |
@@ -2415,14 +2698,19 @@ class depgraph(object): |
510 |
pass |
511 |
|
512 |
# Remove slot operator dependencies. |
513 |
- slot_key = (pkg.root, pkg.slot_atom) |
514 |
- if slot_key in self._dynamic_config._slot_operator_deps: |
515 |
+ for slot_key in list(self._dynamic_config._slot_operator_deps.keys()): |
516 |
self._dynamic_config._slot_operator_deps[slot_key] = \ |
517 |
[dep for dep in self._dynamic_config._slot_operator_deps[slot_key] \ |
518 |
- if dep.child is not pkg] |
519 |
+ if dep.child is not pkg and dep.parent is not pkg] |
520 |
if not self._dynamic_config._slot_operator_deps[slot_key]: |
521 |
del self._dynamic_config._slot_operator_deps[slot_key] |
522 |
|
523 |
+ # Rebuild tracking data structures. |
524 |
+ self._dynamic_config._forced_rebuilds = None |
525 |
+ self._dynamic_config._slot_conflict_rebuilds.pop(pkg, None) |
526 |
+ for child in self._dynamic_config._slot_conflict_rebuilds: |
527 |
+ self._dynamic_config._slot_conflict_rebuilds[child].discard(pkg) |
528 |
+ |
529 |
# Remove blockers. |
530 |
self._dynamic_config._blocker_parents.discard(pkg) |
531 |
self._dynamic_config._irrelevant_blockers.discard(pkg) |
532 |
@@ -2430,6 +2718,13 @@ class depgraph(object): |
533 |
self._dynamic_config._blocked_pkgs.discard(pkg) |
534 |
self._dynamic_config._blocked_world_pkgs.pop(pkg, None) |
535 |
|
536 |
+ # Remove package's unsatisfied dependencies. |
537 |
+ _unsatisfied_deps_for_display = [] |
538 |
+ for (root, atom), info in self._dynamic_config._unsatisfied_deps_for_display: |
539 |
+ if info["myparent"] is not pkg: |
540 |
+ _unsatisfied_deps_for_display.append(((root, atom), info)) |
541 |
+ self._dynamic_config._unsatisfied_deps_for_display = _unsatisfied_deps_for_display |
542 |
+ |
543 |
for child in children: |
544 |
if not self._dynamic_config.digraph.parent_nodes(child): |
545 |
self._remove_pkg(child) |
546 |
@@ -5630,6 +5925,63 @@ class depgraph(object): |
547 |
|
548 |
return pkg, in_graph |
549 |
|
550 |
+ def _enable_complete_mode(self): |
551 |
+ """ |
552 |
+ Put the depgraph into a mode that causes it to only |
553 |
+ select packages that have already been added to the |
554 |
+ graph or those that are installed and have not been |
555 |
+ scheduled for replacement. Also, toggle the "deep" |
556 |
+ parameter so that all dependencies are traversed and |
557 |
+ accounted for. |
558 |
+ """ |
559 |
+ |
560 |
+ previous_state = {} |
561 |
+ previous_state["complete_mode"] = self._dynamic_config._complete_mode |
562 |
+ previous_state["_select_atoms"] = self._select_atoms |
563 |
+ previous_state["_select_package"] = self._select_package |
564 |
+ previous_state["_traverse_ignored_deps"] = \ |
565 |
+ self._dynamic_config._traverse_ignored_deps |
566 |
+ previous_state["deep"] = self._dynamic_config.myparams.get("deep") |
567 |
+ |
568 |
+ self._dynamic_config._complete_mode = True |
569 |
+ self._select_atoms = self._select_atoms_from_graph |
570 |
+ if "remove" in self._dynamic_config.myparams: |
571 |
+ self._select_package = self._select_pkg_from_installed |
572 |
+ else: |
573 |
+ self._select_package = self._select_pkg_from_graph |
574 |
+ self._dynamic_config._traverse_ignored_deps = True |
575 |
+ already_deep = self._dynamic_config.myparams.get("deep") is True |
576 |
+ if not already_deep: |
577 |
+ self._dynamic_config.myparams["deep"] = True |
578 |
+ |
579 |
+ # Invalidate the package selection cache, since |
580 |
+ # _select_package has just changed implementations. |
581 |
+ for trees in self._dynamic_config._filtered_trees.values(): |
582 |
+ trees["porttree"].dbapi._clear_cache() |
583 |
+ |
584 |
+ return previous_state |
585 |
+ |
586 |
+ def _disable_complete_mode(self, previous_state): |
587 |
+ """ |
588 |
+ Reverts the changes made by _enable_complete_mode. |
589 |
+ """ |
590 |
+ self._dynamic_config._complete_mode = previous_state["complete_mode"] |
591 |
+ self._select_atoms = previous_state["_select_atoms"] |
592 |
+ self._select_package = previous_state["_select_package"] |
593 |
+ self._dynamic_config._traverse_ignored_deps = \ |
594 |
+ previous_state["_traverse_ignored_deps"] |
595 |
+ |
596 |
+ if previous_state["deep"] is None: |
597 |
+ del self._dynamic_config.myparams["deep"] |
598 |
+ else: |
599 |
+ self._dynamic_config.myparams["deep"] = previous_state["deep"] |
600 |
+ |
601 |
+ # Invalidate the package selection cache, since |
602 |
+ # _select_package has just changed implementations. |
603 |
+ for trees in self._dynamic_config._filtered_trees.values(): |
604 |
+ trees["porttree"].dbapi._clear_cache() |
605 |
+ |
606 |
+ |
607 |
def _complete_graph(self, required_sets=None): |
608 |
""" |
609 |
Add any deep dependencies of required sets (args, system, world) that |
610 |
@@ -5713,27 +6065,8 @@ class depgraph(object): |
611 |
|
612 |
self._load_vdb() |
613 |
|
614 |
- # Put the depgraph into a mode that causes it to only |
615 |
- # select packages that have already been added to the |
616 |
- # graph or those that are installed and have not been |
617 |
- # scheduled for replacement. Also, toggle the "deep" |
618 |
- # parameter so that all dependencies are traversed and |
619 |
- # accounted for. |
620 |
- self._dynamic_config._complete_mode = True |
621 |
- self._select_atoms = self._select_atoms_from_graph |
622 |
- if "remove" in self._dynamic_config.myparams: |
623 |
- self._select_package = self._select_pkg_from_installed |
624 |
- else: |
625 |
- self._select_package = self._select_pkg_from_graph |
626 |
- self._dynamic_config._traverse_ignored_deps = True |
627 |
- already_deep = self._dynamic_config.myparams.get("deep") is True |
628 |
- if not already_deep: |
629 |
- self._dynamic_config.myparams["deep"] = True |
630 |
- |
631 |
- # Invalidate the package selection cache, since |
632 |
- # _select_package has just changed implementations. |
633 |
- for trees in self._dynamic_config._filtered_trees.values(): |
634 |
- trees["porttree"].dbapi._clear_cache() |
635 |
+ previous_state = self._enable_complete_mode() |
636 |
+ already_deep = previous_state["deep"] is True |
637 |
|
638 |
args = self._dynamic_config._initial_arg_list[:] |
639 |
for root in self._frozen_config.roots: |
640 |
@@ -5778,12 +6111,12 @@ class depgraph(object): |
641 |
Dependency(atom=atom, root=arg.root_config.root, |
642 |
parent=arg)) |
643 |
|
644 |
- if True: |
645 |
- if self._dynamic_config._ignored_deps: |
646 |
- self._dynamic_config._dep_stack.extend(self._dynamic_config._ignored_deps) |
647 |
- self._dynamic_config._ignored_deps = [] |
648 |
- if not self._create_graph(allow_unsatisfied=True): |
649 |
- return 0 |
650 |
+ if self._dynamic_config._ignored_deps: |
651 |
+ self._dynamic_config._dep_stack.extend(self._dynamic_config._ignored_deps) |
652 |
+ self._dynamic_config._ignored_deps = [] |
653 |
+ |
654 |
+ ret = 1 |
655 |
+ if self._create_graph(allow_unsatisfied=True): |
656 |
# Check the unsatisfied deps to see if any initially satisfied deps |
657 |
# will become unsatisfied due to an upgrade. Initially unsatisfied |
658 |
# deps are irrelevant since we only want to avoid breaking deps |
659 |
@@ -5802,10 +6135,17 @@ class depgraph(object): |
660 |
# (possibly solvable via backtracking). |
661 |
pkg = matches[-1] # highest match |
662 |
if not self._add_pkg(pkg, dep): |
663 |
- return 0 |
664 |
+ ret = 0 |
665 |
+ break |
666 |
if not self._create_graph(allow_unsatisfied=True): |
667 |
- return 0 |
668 |
- return 1 |
669 |
+ ret = 0 |
670 |
+ break |
671 |
+ else: |
672 |
+ ret = 0 |
673 |
+ |
674 |
+ self._disable_complete_mode(previous_state) |
675 |
+ |
676 |
+ return ret |
677 |
|
678 |
def _pkg(self, cpv, type_name, root_config, installed=False, |
679 |
onlydeps=False, myrepo = None): |
680 |
@@ -7285,8 +7625,9 @@ class depgraph(object): |
681 |
if "--tree" in self._frozen_config.myopts: |
682 |
mylist = tuple(reversed(mylist)) |
683 |
|
684 |
- display = Display() |
685 |
+ self._compute_abi_rebuild_info() |
686 |
|
687 |
+ display = Display() |
688 |
return display(self, mylist, favorites, verbosity) |
689 |
|
690 |
def _display_autounmask(self): |
691 |
diff --git a/pym/_emerge/resolver/output.py b/pym/_emerge/resolver/output.py |
692 |
index 5f550be..72a1ec2 100644 |
693 |
--- a/pym/_emerge/resolver/output.py |
694 |
+++ b/pym/_emerge/resolver/output.py |
695 |
@@ -836,8 +836,9 @@ class Display(object): |
696 |
self._get_installed_best(pkg, pkg_info) |
697 |
if ordered and pkg_info.merge and \ |
698 |
not pkg_info.attr_display.new: |
699 |
- for arg, atom in depgraph._iter_atoms_for_pkg(pkg): |
700 |
- if arg.force_reinstall: |
701 |
+ forced_rebuilds = depgraph._dynamic_config._forced_rebuilds.get(pkg.root, {}) |
702 |
+ for child in forced_rebuilds: |
703 |
+ if pkg in forced_rebuilds[child]: |
704 |
pkg_info.attr_display.force_reinstall = True |
705 |
break |
706 |
|
707 |
-- |
708 |
1.8.3.2 |