1 |
Sorting of choices by new_slot_count causes the order of |
2 |
choices specified in the ebuild to be discarded, and |
3 |
new_slot_count may have variance related to the order that |
4 |
packages are added to the graph. This variance can |
5 |
contribute to outcomes that appear to be random, like when |
6 |
catalyst stage1 builds sometimes pull in paludis to |
7 |
satisfy perl-cleaner dependencies. |
8 |
|
9 |
Meanwhile, the order specified in the ebuild has no |
10 |
variance, and the preferences that it specifies can serve |
11 |
as a crucial sources of guidance. Therefore, take advantage |
12 |
of the order specified in the ebuild whenever possible, and |
13 |
use new_slot_count only when it is needed to select optimal |
14 |
choices from DNF (as in bug 632026). |
15 |
|
16 |
This fixes random outcomes in the unit test for bug 645002. |
17 |
Previously, the unit test pulled in paludis randomly unless |
18 |
portage-utils was added to the arguments. With this patch, |
19 |
the portage-utils argument is no longer needed for the unit |
20 |
test to succeed consistently. The perl-cleaner dependencies |
21 |
do not have any overlapping || deps, so the DNF conversion |
22 |
and new_slot_count sorting do not apply, and the first |
23 |
choice is preferred regardless of the number of slots that |
24 |
it pulls in: |
25 |
|
26 |
|| ( |
27 |
( sys-apps/portage app-portage/portage-utils ) |
28 |
sys-apps/pkgcore |
29 |
sys-apps/paludis |
30 |
) |
31 |
|
32 |
The _overlap_dnf function now returns the argument object |
33 |
when there is no overlap, so OverlapDNFTestCase has to be |
34 |
adjusted to account for this. |
35 |
|
36 |
Bug: https://bugs.gentoo.org/645002 |
37 |
Fixes: 9fdaf9bdbdf5 ("dep_check: use DNF to optimize overlapping virtual || deps (bug 632026)") |
38 |
--- |
39 |
pym/portage/dep/dep_check.py | 49 ++++++++++++++++++---- |
40 |
pym/portage/tests/dep/test_overlap_dnf.py | 2 +- |
41 |
.../resolver/test_virtual_minimize_children.py | 12 +++--- |
42 |
3 files changed, 47 insertions(+), 16 deletions(-) |
43 |
|
44 |
diff --git a/pym/portage/dep/dep_check.py b/pym/portage/dep/dep_check.py |
45 |
index 7e5a3186e..2896e2389 100644 |
46 |
--- a/pym/portage/dep/dep_check.py |
47 |
+++ b/pym/portage/dep/dep_check.py |
48 |
@@ -298,7 +298,8 @@ class _dep_choice(SlotObject): |
49 |
__slots__ = ('atoms', 'slot_map', 'cp_map', 'all_available', |
50 |
'all_installed_slots', 'new_slot_count') |
51 |
|
52 |
-def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None): |
53 |
+def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None, |
54 |
+ minimize_slots=False): |
55 |
""" |
56 |
Takes an unreduced and reduced deplist and removes satisfied dependencies. |
57 |
Returned deplist contains steps that must be taken to satisfy dependencies. |
58 |
@@ -314,7 +315,8 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None): |
59 |
for x, satisfied in zip(unreduced, reduced): |
60 |
if isinstance(x, list): |
61 |
unresolved += dep_zapdeps(x, satisfied, myroot, |
62 |
- use_binaries=use_binaries, trees=trees) |
63 |
+ use_binaries=use_binaries, trees=trees, |
64 |
+ minimize_slots=minimize_slots) |
65 |
elif not satisfied: |
66 |
unresolved.append(x) |
67 |
return unresolved |
68 |
@@ -386,7 +388,8 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None): |
69 |
for x, satisfied in zip(deps, satisfieds): |
70 |
if isinstance(x, list): |
71 |
atoms = dep_zapdeps(x, satisfied, myroot, |
72 |
- use_binaries=use_binaries, trees=trees) |
73 |
+ use_binaries=use_binaries, trees=trees, |
74 |
+ minimize_slots=minimize_slots) |
75 |
else: |
76 |
atoms = [x] |
77 |
if vardb is None: |
78 |
@@ -663,9 +666,28 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None): |
79 |
for choices in choice_bins: |
80 |
if len(choices) < 2: |
81 |
continue |
82 |
- # Prefer choices with all_installed_slots for bug #480736, and |
83 |
- # choices with a smaller number of new slots for bug #632026. |
84 |
- choices.sort(key=lambda x: (not x.all_installed_slots, x.new_slot_count)) |
85 |
+ |
86 |
+ sort_keys = [] |
87 |
+ # Prefer choices with all_installed_slots for bug #480736. |
88 |
+ sort_keys.append(lambda x: not x.all_installed_slots) |
89 |
+ |
90 |
+ if minimize_slots: |
91 |
+ # Prefer choices having fewer new slots. When used with DNF form, |
92 |
+ # this can eliminate unecessary packages that depclean would |
93 |
+ # ultimately eliminate (see bug 632026). Only use this behavior |
94 |
+ # when deemed necessary by the caller, since this will discard the |
95 |
+ # order specified in the ebuild, and the preferences specified |
96 |
+ # there can serve as a crucial sources of guidance (see bug 645002). |
97 |
+ |
98 |
+ # NOTE: Under some conditions, new_slot_count value may have some |
99 |
+ # variance from one calculation to the next because it depends on |
100 |
+ # the order that packages are added to the graph. This variance can |
101 |
+ # contribute to outcomes that appear to be random. Meanwhile, |
102 |
+ # the order specified in the ebuild is without variance, so it |
103 |
+ # does not have this problem. |
104 |
+ sort_keys.append(lambda x: x.new_slot_count) |
105 |
+ |
106 |
+ choices.sort(key=lambda x: tuple(f(x) for f in sort_keys)) |
107 |
for choice_1 in choices[1:]: |
108 |
cps = set(choice_1.cp_map) |
109 |
for choice_2 in choices: |
110 |
@@ -792,8 +814,11 @@ def dep_check(depstring, mydbapi, mysettings, use="yes", mode=None, myuse=None, |
111 |
except ParseError as e: |
112 |
return [0, "%s" % (e,)] |
113 |
|
114 |
+ dnf = False |
115 |
if mysettings.local_config: # if not repoman |
116 |
+ orig_split = mysplit |
117 |
mysplit = _overlap_dnf(mysplit) |
118 |
+ dnf = mysplit is not orig_split |
119 |
|
120 |
mysplit2 = dep_wordreduce(mysplit, |
121 |
mysettings, mydbapi, mode, use_cache=use_cache) |
122 |
@@ -805,7 +830,7 @@ def dep_check(depstring, mydbapi, mysettings, use="yes", mode=None, myuse=None, |
123 |
writemsg("mysplit2: %s\n" % (mysplit2), 1) |
124 |
|
125 |
selected_atoms = dep_zapdeps(mysplit, mysplit2, myroot, |
126 |
- use_binaries=use_binaries, trees=trees) |
127 |
+ use_binaries=use_binaries, trees=trees, minimize_slots=dnf) |
128 |
|
129 |
return [1, selected_atoms] |
130 |
|
131 |
@@ -817,6 +842,12 @@ def _overlap_dnf(dep_struct): |
132 |
"|| ( foo bar ) || ( bar baz )" as in bug #632026. Non-overlapping |
133 |
groups are excluded from the conversion, since DNF leads to exponential |
134 |
explosion of the formula. |
135 |
+ |
136 |
+ When dep_struct does not contain any overlapping groups, no DNF |
137 |
+ conversion will be performed, and dep_struct will be returned as-is. |
138 |
+ Callers can detect this case by checking if the returned object has |
139 |
+ the same identity as dep_struct. If the identity is different, then |
140 |
+ DNF conversion was performed. |
141 |
""" |
142 |
if not _contains_disjunction(dep_struct): |
143 |
return dep_struct |
144 |
@@ -847,6 +878,7 @@ def _overlap_dnf(dep_struct): |
145 |
|
146 |
# group together disjunctions having atom.cp overlap |
147 |
traversed = set() |
148 |
+ overlap = False |
149 |
for cp in overlap_graph: |
150 |
if cp in traversed: |
151 |
continue |
152 |
@@ -863,6 +895,7 @@ def _overlap_dnf(dep_struct): |
153 |
stack.append(other_cp) |
154 |
|
155 |
if len(disjunctions) > 1: |
156 |
+ overlap = True |
157 |
# convert overlapping disjunctions to DNF |
158 |
result.extend(_dnf_convert( |
159 |
sorted(disjunctions.values(), key=order_key))) |
160 |
@@ -870,7 +903,7 @@ def _overlap_dnf(dep_struct): |
161 |
# pass through non-overlapping disjunctions |
162 |
result.append(disjunctions.popitem()[1]) |
163 |
|
164 |
- return result |
165 |
+ return result if overlap else dep_struct |
166 |
|
167 |
|
168 |
def _iter_flatten(dep_struct): |
169 |
diff --git a/pym/portage/tests/dep/test_overlap_dnf.py b/pym/portage/tests/dep/test_overlap_dnf.py |
170 |
index 79b3e8e46..ee48e5556 100644 |
171 |
--- a/pym/portage/tests/dep/test_overlap_dnf.py |
172 |
+++ b/pym/portage/tests/dep/test_overlap_dnf.py |
173 |
@@ -12,7 +12,7 @@ class OverlapDNFTestCase(TestCase): |
174 |
test_cases = ( |
175 |
( |
176 |
'|| ( cat/A cat/B ) cat/E || ( cat/C cat/D )', |
177 |
- ['cat/E', ['||', 'cat/A', 'cat/B'], ['||', 'cat/C', 'cat/D']], |
178 |
+ [['||', 'cat/A', 'cat/B'], 'cat/E', ['||', 'cat/C', 'cat/D']], |
179 |
), |
180 |
( |
181 |
'|| ( cat/A cat/B ) cat/D || ( cat/B cat/C )', |
182 |
diff --git a/pym/portage/tests/resolver/test_virtual_minimize_children.py b/pym/portage/tests/resolver/test_virtual_minimize_children.py |
183 |
index 287445e58..b566cb592 100644 |
184 |
--- a/pym/portage/tests/resolver/test_virtual_minimize_children.py |
185 |
+++ b/pym/portage/tests/resolver/test_virtual_minimize_children.py |
186 |
@@ -168,17 +168,15 @@ class VirtualMinimizeChildrenTestCase(TestCase): |
187 |
} |
188 |
|
189 |
test_cases = ( |
190 |
- # Test bug 645002, where we want to prefer choices |
191 |
- # based on the number of new slots rather than the total |
192 |
- # number of slots. This is necessary so that perl-cleaner's |
193 |
- # deps are satisfied by the ( portage portage-utils ) |
194 |
- # choice which has a larger total number of slots than the |
195 |
- # paludis choice. |
196 |
+ # Test bug 645002, where paludis was selected to satisfy a |
197 |
+ # perl-cleaner dependency because that choice contained fewer |
198 |
+ # packages than the ( portage portage-utils ) choice which |
199 |
+ # should have been preferred according to the order of |
200 |
+ # choices specified in the ebuild. |
201 |
ResolverPlaygroundTestCase( |
202 |
[ |
203 |
'app-admin/perl-cleaner', |
204 |
'virtual/package-manager', |
205 |
- 'app-portage/portage-utils', |
206 |
], |
207 |
all_permutations=True, |
208 |
success=True, |
209 |
-- |
210 |
2.13.6 |