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 1/2] emerge: terminate backtracking early for autounmask changes (bug 615680)
Date: Wed, 10 May 2017 03:53:26
Message-Id: 20170510035235.25683-1-zmedico@gentoo.org
1 Since autounmask changes are a strong indicator that backtracking
2 will ultimately fail to produce a solution, terminate early for
3 autounmask changes, and add a --autounmask-backtrack=<y|n> option
4 to modify this behavior. The --autounmask-continue option implies
5 --autounmask-backtrack=y behavior, for backward compatibility.
6
7 When backtracking terminates early, the following warning message
8 is displayed after the autounmask change(s):
9
10 * In order to avoid wasting time, backtracking has terminated early
11 * due to the above autounmask change(s). The --autounmask-backtrack=y
12 * option can be used to force further backtracking, but there is no
13 * guarantee that it will produce a solution.
14
15 With this change, five of the existing cases fail unless
16 --autounmask-backtrack=y is added to the options. For each of
17 these cases, comments below the test case document how it behaves
18 with and without --autounmask-backtrack=y enabled.
19
20 X-Gentoo-bug: 615680
21 X-Gentoo-bug-url: https://bugs.gentoo.org/show_bug.cgi?id=615680
22 ---
23 man/emerge.1 | 10 ++-
24 pym/_emerge/depgraph.py | 80 ++++++++++++++++++----
25 pym/_emerge/main.py | 6 ++
26 pym/portage/tests/resolver/test_autounmask.py | 57 ++++++++++++++-
27 .../tests/resolver/test_autounmask_use_breakage.py | 40 +++++++++++
28 .../test_slot_conflict_unsatisfied_deep_deps.py | 61 +++++++++++++++++
29 6 files changed, 237 insertions(+), 17 deletions(-)
30
31 diff --git a/man/emerge.1 b/man/emerge.1
32 index f1a9d4f..94edc90 100644
33 --- a/man/emerge.1
34 +++ b/man/emerge.1
35 @@ -363,12 +363,20 @@ the specified configuration file(s), or enable the
36 \fBEMERGE_DEFAULT_OPTS\fR variable may be used to
37 disable this option by default in \fBmake.conf\fR(5).
38 .TP
39 +.BR "\-\-autounmask\-backtrack < y | n >"
40 +Allow backtracking after autounmask has detected that
41 +configuration changes are necessary. This option is not
42 +recommended, since it can cause a large amount of time to
43 +be wasted by backtracking calculations, even though there
44 +is no guarantee that it will produce a solution. This
45 +option is disabled by default.
46 +.TP
47 .BR "\-\-autounmask\-continue [ y | n ]"
48 Automatically apply autounmask changes to configuration
49 files, and continue to execute the specified command. If
50 the dependency calculation is not entirely successful, then
51 emerge will simply abort without modifying any configuration
52 -files.
53 +files. This option implies \fB\-\-autounmask\-backtrack=y\fR.
54 \fBWARNING:\fR
55 This option is intended to be used only with great caution,
56 since it is possible for it to make nonsensical configuration
57 diff --git a/pym/_emerge/depgraph.py b/pym/_emerge/depgraph.py
58 index e1119af..53910dd 100644
59 --- a/pym/_emerge/depgraph.py
60 +++ b/pym/_emerge/depgraph.py
61 @@ -444,6 +444,7 @@ class _dynamic_depgraph_config(object):
62 self._autounmask = depgraph._frozen_config.myopts.get('--autounmask') != 'n'
63 self._displayed_autounmask = False
64 self._success_without_autounmask = False
65 + self._autounmask_backtrack_disabled = False
66 self._required_use_unsatisfied = False
67 self._traverse_ignored_deps = False
68 self._complete_mode = False
69 @@ -1129,7 +1130,8 @@ class depgraph(object):
70
71 self._show_merge_list()
72
73 - self._dynamic_config._slot_conflict_handler = slot_conflict_handler(self)
74 + if self._dynamic_config._slot_conflict_handler is None:
75 + self._dynamic_config._slot_conflict_handler = slot_conflict_handler(self)
76 handler = self._dynamic_config._slot_conflict_handler
77
78 conflict = handler.get_conflict()
79 @@ -4243,17 +4245,7 @@ class depgraph(object):
80 # set below is reserved for cases where there are *zero* other
81 # problems. For reference, see backtrack_depgraph, where it skips the
82 # get_best_run() call when success_without_autounmask is True.
83 -
84 - digraph_nodes = self._dynamic_config.digraph.nodes
85 -
86 - if any(x in digraph_nodes for x in
87 - self._dynamic_config._needed_unstable_keywords) or \
88 - any(x in digraph_nodes for x in
89 - self._dynamic_config._needed_p_mask_changes) or \
90 - any(x in digraph_nodes for x in
91 - self._dynamic_config._needed_use_config_changes) or \
92 - any(x in digraph_nodes for x in
93 - self._dynamic_config._needed_license_changes) :
94 + if self._have_autounmask_changes():
95 #We failed if the user needs to change the configuration
96 self._dynamic_config._success_without_autounmask = True
97 if (self._frozen_config.myopts.get("--autounmask-continue") is True and
98 @@ -8564,6 +8556,17 @@ class depgraph(object):
99 "experimental or unstable packages.\n",
100 noiselevel=-1)
101
102 + if self._dynamic_config._autounmask_backtrack_disabled:
103 + msg = [
104 + "In order to avoid wasting time, backtracking has terminated early",
105 + "due to the above autounmask change(s). The --autounmask-backtrack=y",
106 + "option can be used to force further backtracking, but there is no",
107 + "guarantee that it will produce a solution.",
108 + ]
109 + writemsg("\n", noiselevel=-1)
110 + for line in msg:
111 + writemsg(" %s %s\n" % (colorize("WARN", "*"), line),
112 + noiselevel=-1)
113
114 def display_problems(self):
115 """
116 @@ -9072,8 +9075,57 @@ class depgraph(object):
117 not self._dynamic_config._skip_restart
118
119 def need_config_change(self):
120 - return self._dynamic_config._success_without_autounmask or \
121 - self._dynamic_config._required_use_unsatisfied
122 + """
123 + Returns true if backtracking should terminate due to a needed
124 + configuration change.
125 + """
126 + if (self._dynamic_config._success_without_autounmask or
127 + self._dynamic_config._required_use_unsatisfied):
128 + return True
129 +
130 + if (self._dynamic_config._slot_conflict_handler is None and
131 + not self._accept_blocker_conflicts() and
132 + any(self._dynamic_config._package_tracker.slot_conflicts())):
133 + self._dynamic_config._slot_conflict_handler = slot_conflict_handler(self)
134 + if self._dynamic_config._slot_conflict_handler.changes:
135 + # Terminate backtracking early if the slot conflict
136 + # handler finds some changes to suggest. The case involving
137 + # sci-libs/L and sci-libs/M in SlotCollisionTestCase will
138 + # otherwise fail with --autounmask-backtrack=n, since
139 + # backtracking will eventually lead to some autounmask
140 + # changes. Changes suggested by the slot conflict handler
141 + # are more likely to be useful.
142 + return True
143 +
144 + if (self._dynamic_config._allow_backtracking and
145 + self._frozen_config.myopts.get("--autounmask-backtrack") != 'y' and
146 + self._have_autounmask_changes()):
147 +
148 + if (self._frozen_config.myopts.get("--autounmask-continue") is True and
149 + self._frozen_config.myopts.get("--autounmask-backtrack") != 'n'):
150 + # --autounmask-continue implies --autounmask-backtrack=y behavior,
151 + # for backward compatibility.
152 + return False
153 +
154 + # This disables backtracking when there are autounmask
155 + # config changes. The display_problems method will notify
156 + # the user that --autounmask-backtrack=y can be used to
157 + # force backtracking in this case.
158 + self._dynamic_config._autounmask_backtrack_disabled = True
159 + return True
160 +
161 + return False
162 +
163 + def _have_autounmask_changes(self):
164 + digraph_nodes = self._dynamic_config.digraph.nodes
165 + return (any(x in digraph_nodes for x in
166 + self._dynamic_config._needed_unstable_keywords) or
167 + any(x in digraph_nodes for x in
168 + self._dynamic_config._needed_p_mask_changes) or
169 + any(x in digraph_nodes for x in
170 + self._dynamic_config._needed_use_config_changes) or
171 + any(x in digraph_nodes for x in
172 + self._dynamic_config._needed_license_changes))
173
174 def need_config_reload(self):
175 return self._dynamic_config._need_config_reload
176 diff --git a/pym/_emerge/main.py b/pym/_emerge/main.py
177 index 76e963a..8084967 100644
178 --- a/pym/_emerge/main.py
179 +++ b/pym/_emerge/main.py
180 @@ -326,6 +326,12 @@ def parse_opts(tmpcmdline, silent=False):
181 "choices" : true_y_or_n
182 },
183
184 + "--autounmask-backtrack": {
185 + "help": ("continue backtracking when there are autounmask "
186 + "configuration changes"),
187 + "choices":("y", "n")
188 + },
189 +
190 "--autounmask-continue": {
191 "help" : "write autounmask changes and continue",
192 "choices" : true_y_or_n
193 diff --git a/pym/portage/tests/resolver/test_autounmask.py b/pym/portage/tests/resolver/test_autounmask.py
194 index 75fb368..e2a7de0 100644
195 --- a/pym/portage/tests/resolver/test_autounmask.py
196 +++ b/pym/portage/tests/resolver/test_autounmask.py
197 @@ -81,20 +81,73 @@ class AutounmaskTestCase(TestCase):
198 #Make sure we restart if needed.
199 ResolverPlaygroundTestCase(
200 ["dev-libs/A:1", "dev-libs/B"],
201 - options={"--autounmask": True},
202 + options={"--autounmask": True, "--autounmask-backtrack": "y"},
203 all_permutations=True,
204 success=False,
205 mergelist=["dev-libs/C-1", "dev-libs/B-1", "dev-libs/A-1"],
206 use_changes={ "dev-libs/B-1": {"foo": True} }),
207 +
208 + # With --autounmask-backtrack=y:
209 + #[ebuild N ] dev-libs/C-1
210 + #[ebuild N ] dev-libs/B-1 USE="foo -bar"
211 + #[ebuild N ] dev-libs/A-1
212 + #
213 + #The following USE changes are necessary to proceed:
214 + # (see "package.use" in the portage(5) man page for more details)
215 + ## required by dev-libs/A-1::test_repo
216 + ## required by dev-libs/A:1 (argument)
217 + #>=dev-libs/B-1 foo
218 +
219 + # Without --autounmask-backtrack=y:
220 + #[ebuild N ] dev-libs/B-1 USE="foo -bar"
221 + #[ebuild N ] dev-libs/A-1
222 + #
223 + #The following USE changes are necessary to proceed:
224 + # (see "package.use" in the portage(5) man page for more details)
225 + ## required by dev-libs/A-1::test_repo
226 + ## required by dev-libs/A:1 (argument)
227 + #>=dev-libs/B-1 foo
228 +
229 ResolverPlaygroundTestCase(
230 ["dev-libs/A:1", "dev-libs/A:2", "dev-libs/B"],
231 - options={"--autounmask": True},
232 + options={"--autounmask": True, "--autounmask-backtrack": "y"},
233 all_permutations=True,
234 success=False,
235 mergelist=["dev-libs/D-1", "dev-libs/C-1", "dev-libs/B-1", "dev-libs/A-1", "dev-libs/A-2"],
236 ignore_mergelist_order=True,
237 use_changes={ "dev-libs/B-1": {"foo": True, "bar": True} }),
238
239 + # With --autounmask-backtrack=y:
240 + #[ebuild N ] dev-libs/C-1
241 + #[ebuild N ] dev-libs/D-1
242 + #[ebuild N ] dev-libs/B-1 USE="bar foo"
243 + #[ebuild N ] dev-libs/A-2
244 + #[ebuild N ] dev-libs/A-1
245 + #
246 + #The following USE changes are necessary to proceed:
247 + # (see "package.use" in the portage(5) man page for more details)
248 + ## required by dev-libs/A-2::test_repo
249 + ## required by dev-libs/A:2 (argument)
250 + #>=dev-libs/B-1 bar foo
251 +
252 + # Without --autounmask-backtrack=y:
253 + #[ebuild N ] dev-libs/B-1 USE="bar foo"
254 + #[ebuild N ] dev-libs/A-1
255 + #[ebuild N ] dev-libs/A-2
256 + #
257 + #The following USE changes are necessary to proceed:
258 + # (see "package.use" in the portage(5) man page for more details)
259 + ## required by dev-libs/A-1::test_repo
260 + ## required by dev-libs/A:1 (argument)
261 + #>=dev-libs/B-1 foo bar
262 +
263 + # NOTE: The --autounmask-backtrack=n behavior is acceptable, but
264 + # it would be nicer if it added the dev-libs/C-1 and dev-libs/D-1
265 + # deps to the depgraph without backtracking. It could add two
266 + # instances of dev-libs/B-1 to the graph with different USE flags,
267 + # and then use _solve_non_slot_operator_slot_conflicts to eliminate
268 + # the redundant instance.
269 +
270 #Test keywording.
271 #The simple case.
272
273 diff --git a/pym/portage/tests/resolver/test_autounmask_use_breakage.py b/pym/portage/tests/resolver/test_autounmask_use_breakage.py
274 index 3654aa6..1739416 100644
275 --- a/pym/portage/tests/resolver/test_autounmask_use_breakage.py
276 +++ b/pym/portage/tests/resolver/test_autounmask_use_breakage.py
277 @@ -46,12 +46,52 @@ class AutounmaskUseBreakageTestCase(TestCase):
278 # due to autounmask USE breakage.
279 ResolverPlaygroundTestCase(
280 ["app-misc/C", "app-misc/B", "app-misc/A"],
281 + options={"--autounmask-backtrack": "y"},
282 all_permutations = True,
283 success = False,
284 ambiguous_slot_collision_solutions = True,
285 slot_collision_solutions = [None, []]
286 ),
287
288 + # With --autounmask-backtrack=y:
289 + #emerge: there are no ebuilds built with USE flags to satisfy "app-misc/D[foo]".
290 + #!!! One of the following packages is required to complete your request:
291 + #- app-misc/D-0::test_repo (Change USE: +foo)
292 + #(dependency required by "app-misc/B-0::test_repo" [ebuild])
293 + #(dependency required by "app-misc/B" [argument])
294 +
295 + # Without --autounmask-backtrack=y:
296 + #[ebuild N ] app-misc/D-0 USE="foo"
297 + #[ebuild N ] app-misc/D-1 USE="-bar"
298 + #[ebuild N ] app-misc/C-0
299 + #[ebuild N ] app-misc/B-0
300 + #[ebuild N ] app-misc/A-0
301 + #
302 + #!!! Multiple package instances within a single package slot have been pulled
303 + #!!! into the dependency graph, resulting in a slot conflict:
304 + #
305 + #app-misc/D:0
306 + #
307 + # (app-misc/D-0:0/0::test_repo, ebuild scheduled for merge) pulled in by
308 + # app-misc/D[-foo] required by (app-misc/A-0:0/0::test_repo, ebuild scheduled for merge)
309 + # ^^^^
310 + # app-misc/D[foo] required by (app-misc/B-0:0/0::test_repo, ebuild scheduled for merge)
311 + # ^^^
312 + #
313 + # (app-misc/D-1:0/0::test_repo, ebuild scheduled for merge) pulled in by
314 + # >=app-misc/D-1 required by (app-misc/C-0:0/0::test_repo, ebuild scheduled for merge)
315 + # ^^ ^
316 + #
317 + #The following USE changes are necessary to proceed:
318 + # (see "package.use" in the portage(5) man page for more details)
319 + ## required by app-misc/B-0::test_repo
320 + ## required by app-misc/B (argument)
321 + #=app-misc/D-0 foo
322 +
323 + # NOTE: The --autounmask-backtrack=n output is preferable here,
324 + # because it highlights the unsolvable dependency conflict.
325 + # It would be better if it eliminated the autounmask suggestion,
326 + # since that suggestion won't solve the conflict.
327 )
328
329 playground = ResolverPlayground(ebuilds=ebuilds, debug=False)
330 diff --git a/pym/portage/tests/resolver/test_slot_conflict_unsatisfied_deep_deps.py b/pym/portage/tests/resolver/test_slot_conflict_unsatisfied_deep_deps.py
331 index 13f7e67..846ba0e 100644
332 --- a/pym/portage/tests/resolver/test_slot_conflict_unsatisfied_deep_deps.py
333 +++ b/pym/portage/tests/resolver/test_slot_conflict_unsatisfied_deep_deps.py
334 @@ -79,6 +79,7 @@ class SlotConflictUnsatisfiedDeepDepsTestCase(TestCase):
335 ["@world"],
336 options={
337 "--autounmask": "y",
338 + "--autounmask-backtrack": "y",
339 "--complete-graph": True,
340 "--selective": True,
341 "--deep": 1
342 @@ -89,11 +90,63 @@ class SlotConflictUnsatisfiedDeepDepsTestCase(TestCase):
343 unsatisfied_deps=["dev-libs/initially-unsatisfied"],
344 success=False),
345
346 + # With --autounmask-backtrack=y:
347 + #[ebuild N ~] dev-libs/A-2
348 + #[ebuild N ] dev-libs/C-1
349 + #[ebuild N ] dev-libs/D-1
350 + #[ebuild N ] dev-libs/B-1
351 + #
352 + #The following keyword changes are necessary to proceed:
353 + # (see "package.accept_keywords" in the portage(5) man page for more details)
354 + ## required by dev-libs/C-1::test_repo
355 + ## required by @selected
356 + ## required by @world (argument)
357 + #=dev-libs/A-2 ~x86
358 + #
359 + #!!! Problems have been detected with your world file
360 + #!!! Please run emaint --check world
361 + #
362 + #
363 + #!!! Ebuilds for the following packages are either all
364 + #!!! masked or don't exist:
365 + #dev-libs/broken
366 + #
367 + #emerge: there are no ebuilds to satisfy "dev-libs/initially-unsatisfied".
368 + #(dependency required by "dev-libs/broken-1::test_repo" [installed])
369 + #(dependency required by "@selected" [set])
370 + #(dependency required by "@world" [argument])
371 +
372 + # Without --autounmask-backtrack=y:
373 + #!!! Multiple package instances within a single package slot have been pulled
374 + #!!! into the dependency graph, resulting in a slot conflict:
375 + #
376 + #dev-libs/A:0
377 + #
378 + # (dev-libs/A-1:0/0::test_repo, ebuild scheduled for merge) pulled in by
379 + # (no parents that aren't satisfied by other packages in this slot)
380 + #
381 + # (dev-libs/A-2:0/0::test_repo, ebuild scheduled for merge) pulled in by
382 + # >=dev-libs/A-2 required by (dev-libs/C-1:0/0::test_repo, ebuild scheduled for merge)
383 + # ^^ ^
384 + #
385 + #The following keyword changes are necessary to proceed:
386 + # (see "package.accept_keywords" in the portage(5) man page for more details)
387 + ## required by dev-libs/C-1::test_repo
388 + ## required by @selected
389 + ## required by @world (argument)
390 + #=dev-libs/A-2 ~x86
391 + #
392 + #emerge: there are no ebuilds to satisfy "dev-libs/initially-unsatisfied".
393 + #(dependency required by "dev-libs/broken-1::test_repo" [installed])
394 + #(dependency required by "@selected" [set])
395 + #(dependency required by "@world" [argument])
396 +
397 # Test --deep = True
398 ResolverPlaygroundTestCase(
399 ["@world"],
400 options={
401 "--autounmask": "y",
402 + "--autounmask-backtrack": "y",
403 "--complete-graph": True,
404 "--selective": True,
405 "--deep": True
406 @@ -103,6 +156,14 @@ class SlotConflictUnsatisfiedDeepDepsTestCase(TestCase):
407 unstable_keywords=["dev-libs/A-2"],
408 unsatisfied_deps=["dev-libs/initially-unsatisfied"],
409 success=False),
410 +
411 + # The effects of --autounmask-backtrack are the same as the previous test case.
412 + # Both test cases can randomly succeed with --autounmask-backtrack=n, when
413 + # "backtracking due to unsatisfied dep" randomly occurs before the autounmask
414 + # unstable keyword change. It would be possible to eliminate backtracking here
415 + # by recognizing that there are no alternatives to satisfy the dev-libs/broken
416 + # atom in the world file. Then the test cases will consistently succeed with
417 + # --autounmask-backtrack=n.
418 )
419
420 playground = ResolverPlayground(ebuilds=ebuilds, installed=installed,
421 --
422 2.10.2

Replies