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