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] autounmask: prefer || choices with existing packages (bug 327177)
Date: Mon, 29 Apr 2019 07:36:23
Message-Id: 20190429073311.10037-1-zmedico@gentoo.org
1 When autounmask is enabled, make depgraph retry _select_atoms calls when
2 necessary to prefer || choices with existing packages. For example,
3 if package C is masked and package B does not exist, then autounmask
4 should choose C when given the choice || ( B C ), as shown in the
5 included unit tests. The unit tests also show that autounmask still
6 prefers choices containing packages that are not masked (if available).
7
8 Bug: https://bugs.gentoo.org/327177
9 Signed-off-by: Zac Medico <zmedico@g.o>
10 ---
11 lib/_emerge/depgraph.py | 152 ++++++++++++------
12 .../resolver/test_autounmask_or_choices.py | 71 ++++++++
13 2 files changed, 176 insertions(+), 47 deletions(-)
14 create mode 100644 lib/portage/tests/resolver/test_autounmask_or_choices.py
15
16 diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
17 index 32b2d7da3..7a54aeb34 100644
18 --- a/lib/_emerge/depgraph.py
19 +++ b/lib/_emerge/depgraph.py
20 @@ -365,6 +365,14 @@ class _use_changes(tuple):
21 return obj
22
23
24 +_select_atoms_deps = collections.namedtuple('_select_atoms_deps', (
25 + 'normal_deps',
26 + 'virt_deps',
27 + 'ignored_deps',
28 + 'unsatisfied_deps',
29 +))
30 +
31 +
32 class _dynamic_depgraph_config(object):
33
34 """
35 @@ -3479,8 +3487,13 @@ class depgraph(object):
36 dep.child.root, dep.child.slot_atom, installed=False)) and \
37 not slot_operator_rebuild
38
39 - def _wrapped_add_pkg_dep_string(self, pkg, dep_root, dep_priority,
40 - dep_string, allow_unsatisfied):
41 + def _select_atoms_deps(self, pkg, dep_root, dep_priority, selected_atoms):
42 + """
43 + Create Dependency instances from a _select_atoms result, and create a
44 + list of unsatisfied dependencies which is useful for deciding when
45 + to retry a _select_atoms call with autounmask enabled.
46 + """
47 +
48 if isinstance(pkg.depth, int):
49 depth = pkg.depth + 1
50 else:
51 @@ -3489,38 +3502,13 @@ class depgraph(object):
52 deep = self._dynamic_config.myparams.get("deep", 0)
53 recurse_satisfied = deep is True or depth <= deep
54 debug = "--debug" in self._frozen_config.myopts
55 - strict = pkg.type_name != "installed"
56 -
57 - if debug:
58 - writemsg_level("\nParent: %s\n" % (pkg,),
59 - noiselevel=-1, level=logging.DEBUG)
60 - dep_repr = portage.dep.paren_enclose(dep_string,
61 - unevaluated_atom=True, opconvert=True)
62 - writemsg_level("Depstring: %s\n" % (dep_repr,),
63 - noiselevel=-1, level=logging.DEBUG)
64 - writemsg_level("Priority: %s\n" % (dep_priority,),
65 - noiselevel=-1, level=logging.DEBUG)
66 -
67 - try:
68 - selected_atoms = self._select_atoms(dep_root,
69 - dep_string, myuse=self._pkg_use_enabled(pkg), parent=pkg,
70 - strict=strict, priority=dep_priority)
71 - except portage.exception.InvalidDependString:
72 - if pkg.installed:
73 - self._dynamic_config._masked_installed.add(pkg)
74 - return 1
75 -
76 - # should have been masked before it was selected
77 - raise
78 -
79 - if debug:
80 - writemsg_level("Candidates: %s\n" % \
81 - ([str(x) for x in selected_atoms[pkg]],),
82 - noiselevel=-1, level=logging.DEBUG)
83 -
84 root_config = self._frozen_config.roots[dep_root]
85 vardb = root_config.trees["vartree"].dbapi
86 traversed_virt_pkgs = set()
87 + normal_deps = []
88 + virt_deps = []
89 + ignored_deps = []
90 + unsatisfied_deps = []
91
92 reinstall_atoms = self._frozen_config.reinstall_atoms
93 for atom, child in self._minimize_children(
94 @@ -3586,7 +3574,7 @@ class depgraph(object):
95 # mode may select a different child later.
96 ignored = True
97 dep.child = None
98 - self._dynamic_config._ignored_deps.append(dep)
99 + ignored_deps.append(dep)
100
101 if not ignored:
102 if dep_priority.ignored and \
103 @@ -3594,11 +3582,11 @@ class depgraph(object):
104 if is_virt and dep.child is not None:
105 traversed_virt_pkgs.add(dep.child)
106 dep.child = None
107 - self._dynamic_config._ignored_deps.append(dep)
108 + ignored_deps.append(dep)
109 else:
110 - if not self._add_dep(dep,
111 - allow_unsatisfied=allow_unsatisfied):
112 - return 0
113 + if dep.child is None and not dep.blocker:
114 + unsatisfied_deps.append(dep)
115 + normal_deps.append(dep)
116 if is_virt and dep.child is not None:
117 traversed_virt_pkgs.add(dep.child)
118
119 @@ -3637,8 +3625,7 @@ class depgraph(object):
120 # none visible, so use highest
121 virt_dep.priority.satisfied = inst_pkgs[0]
122
123 - if not self._add_pkg(virt_pkg, virt_dep):
124 - return 0
125 + virt_deps.append(virt_dep)
126
127 for atom, child in self._minimize_children(
128 pkg, self._priority(runtime=True), root_config, atoms):
129 @@ -3686,7 +3673,7 @@ class depgraph(object):
130 if myarg is None:
131 ignored = True
132 dep.child = None
133 - self._dynamic_config._ignored_deps.append(dep)
134 + ignored_deps.append(dep)
135
136 if not ignored:
137 if dep_priority.ignored and \
138 @@ -3694,14 +3681,82 @@ class depgraph(object):
139 if is_virt and dep.child is not None:
140 traversed_virt_pkgs.add(dep.child)
141 dep.child = None
142 - self._dynamic_config._ignored_deps.append(dep)
143 + ignored_deps.append(dep)
144 else:
145 - if not self._add_dep(dep,
146 - allow_unsatisfied=allow_unsatisfied):
147 - return 0
148 + if dep.child is None and not dep.blocker:
149 + unsatisfied_deps.append(dep)
150 + normal_deps.append(dep)
151 if is_virt and dep.child is not None:
152 traversed_virt_pkgs.add(dep.child)
153
154 + return _select_atoms_deps(normal_deps, virt_deps, ignored_deps, unsatisfied_deps)
155 +
156 + def _wrapped_add_pkg_dep_string(self, pkg, dep_root, dep_priority,
157 + dep_string, allow_unsatisfied):
158 +
159 + debug = "--debug" in self._frozen_config.myopts
160 + strict = pkg.type_name != "installed"
161 +
162 + if debug:
163 + writemsg_level("\nParent: %s\n" % (pkg,),
164 + noiselevel=-1, level=logging.DEBUG)
165 + dep_repr = portage.dep.paren_enclose(dep_string,
166 + unevaluated_atom=True, opconvert=True)
167 + writemsg_level("Depstring: %s\n" % (dep_repr,),
168 + noiselevel=-1, level=logging.DEBUG)
169 + writemsg_level("Priority: %s\n" % (dep_priority,),
170 + noiselevel=-1, level=logging.DEBUG)
171 +
172 + autounmask_states = [False]
173 + if self._dynamic_config._autounmask:
174 + autounmask_states.append(True)
175 +
176 + choices = []
177 + for autounmask in autounmask_states:
178 + if autounmask:
179 + # Clear the package selection cache so that autounmask
180 + # can make new selections.
181 + self._dynamic_config._filtered_trees[dep_root]["porttree"].dbapi._clear_cache()
182 + try:
183 + selected_atoms = self._select_atoms(dep_root,
184 + dep_string, myuse=self._pkg_use_enabled(pkg), parent=pkg,
185 + strict=strict, priority=dep_priority, autounmask=autounmask)
186 + except portage.exception.InvalidDependString:
187 + if pkg.installed:
188 + self._dynamic_config._masked_installed.add(pkg)
189 + return 1
190 +
191 + # should have been masked before it was selected
192 + raise
193 +
194 + if debug:
195 + writemsg_level("Candidates (autounmask=%s): %s\n" % \
196 + (autounmask, [str(x) for x in selected_atoms[pkg]],),
197 + noiselevel=-1, level=logging.DEBUG)
198 +
199 + choice = self._select_atoms_deps(pkg, dep_root, dep_priority, selected_atoms)
200 + choices.append(choice)
201 + if not choice.unsatisfied_deps:
202 + break
203 + else:
204 + # If all choices have unsatisfied deps, fall back to default
205 + # autounmask=False behavior.
206 + choice = choices[0]
207 +
208 + if autounmask:
209 + # An autounmask choice has been rejected, so clear its
210 + # package selections from the cache.
211 + self._dynamic_config._filtered_trees[dep_root]["porttree"].dbapi._clear_cache()
212 +
213 + for dep in choice.normal_deps:
214 + if not self._add_dep(dep,
215 + allow_unsatisfied=allow_unsatisfied):
216 + return 0
217 + for virt_dep in choice.virt_deps:
218 + if not self._add_pkg(virt_dep.child, virt_dep):
219 + return 0
220 + self._dynamic_config._ignored_deps.extend(choice.ignored_deps)
221 +
222 if debug:
223 writemsg_level("\nExiting... %s\n" % (pkg,),
224 noiselevel=-1, level=logging.DEBUG)
225 @@ -4699,7 +4754,8 @@ class depgraph(object):
226 return self._select_atoms_highest_available(*pargs, **kwargs)
227
228 def _select_atoms_highest_available(self, root, depstring,
229 - myuse=None, parent=None, strict=True, trees=None, priority=None):
230 + myuse=None, parent=None, strict=True, trees=None, priority=None,
231 + autounmask=False):
232 """This will raise InvalidDependString if necessary. If trees is
233 None then self._dynamic_config._filtered_trees is used."""
234
235 @@ -4727,8 +4783,9 @@ class depgraph(object):
236 if True:
237 # Temporarily disable autounmask so that || preferences
238 # account for masking and USE settings.
239 - _autounmask_backup = self._dynamic_config._autounmask
240 - self._dynamic_config._autounmask = False
241 + if not autounmask:
242 + _autounmask_backup = self._dynamic_config._autounmask
243 + self._dynamic_config._autounmask = False
244 # backup state for restoration, in case of recursive
245 # calls to this method
246 backup_parent = self._select_atoms_parent
247 @@ -4756,7 +4813,8 @@ class depgraph(object):
248 myroot=root, trees=trees)
249 finally:
250 # restore state
251 - self._dynamic_config._autounmask = _autounmask_backup
252 + if not autounmask:
253 + self._dynamic_config._autounmask = _autounmask_backup
254 self._select_atoms_parent = backup_parent
255 mytrees.pop("pkg_use_enabled", None)
256 mytrees.pop("parent", None)
257 diff --git a/lib/portage/tests/resolver/test_autounmask_or_choices.py b/lib/portage/tests/resolver/test_autounmask_or_choices.py
258 new file mode 100644
259 index 000000000..b5f2044e3
260 --- /dev/null
261 +++ b/lib/portage/tests/resolver/test_autounmask_or_choices.py
262 @@ -0,0 +1,71 @@
263 +# Copyright 2019 Gentoo Authors
264 +# Distributed under the terms of the GNU General Public License v2
265 +
266 +from portage.tests import TestCase
267 +from portage.tests.resolver.ResolverPlayground import (
268 + ResolverPlayground,
269 + ResolverPlaygroundTestCase,
270 +)
271 +
272 +class AutounmaskOrChoicesTestCase(TestCase):
273 +
274 + def testAutounmaskOrChoices(self):
275 + ebuilds = {
276 + 'dev-libs/A-1': {
277 + 'EAPI': '7',
278 + 'RDEPEND': '|| ( dev-libs/B dev-libs/C )',
279 + },
280 + 'dev-libs/C-1': {
281 + 'EAPI': '7',
282 + 'KEYWORDS': '~x86',
283 + },
284 + 'dev-libs/D-1': {
285 + 'EAPI': '7',
286 + 'RDEPEND': '|| ( dev-libs/E dev-libs/F )',
287 + },
288 + 'dev-libs/E-1': {
289 + 'EAPI': '7',
290 + 'KEYWORDS': '~x86',
291 + },
292 + 'dev-libs/F-1': {
293 + 'EAPI': '7',
294 + 'KEYWORDS': 'x86',
295 + },
296 + }
297 +
298 + test_cases = (
299 + # Test bug 327177, where we want to prefer choices with masked
300 + # packages over those with nonexisting packages.
301 + ResolverPlaygroundTestCase(
302 + ['dev-libs/A'],
303 + options={"--autounmask": True},
304 + success=False,
305 + mergelist=[
306 + 'dev-libs/C-1',
307 + 'dev-libs/A-1',
308 + ],
309 + unstable_keywords = ('dev-libs/C-1',),
310 + ),
311 + # Test that autounmask prefers choices with packages that
312 + # are not masked.
313 + ResolverPlaygroundTestCase(
314 + ['dev-libs/D'],
315 + options={"--autounmask": True},
316 + success=True,
317 + mergelist=[
318 + 'dev-libs/F-1',
319 + 'dev-libs/D-1',
320 + ],
321 + ),
322 + )
323 +
324 + playground = ResolverPlayground(ebuilds=ebuilds, debug=False)
325 +
326 + try:
327 + for test_case in test_cases:
328 + playground.run_TestCase(test_case)
329 + self.assertEqual(test_case.test_success, True,
330 + test_case.fail_msg)
331 + finally:
332 + playground.debug = False
333 + playground.cleanup()
334 --
335 2.21.0

Replies