Gentoo Archives: gentoo-portage-dev

From: Zac Medico <zmedico@g.o>
To: gentoo-portage-dev@l.g.o
Cc: Zac Medico <zmedico@g.o>
Subject: [gentoo-portage-dev] [PATCH] dep_zapdeps: sort by new_slot_count for DNF only (bug 645002)
Date: Fri, 02 Feb 2018 12:04:21
Message-Id: 20180202120329.7741-1-zmedico@gentoo.org
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