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] emerge: add --changed-deps/--binpkg-changed-deps (282927)
Date: Tue, 23 Dec 2014 20:46:12
Message-Id: 1419367536-30393-1-git-send-email-zmedico@gentoo.org
1 The @changed-deps set is useful, but it has limitations similar to the
2 @installed set (see bug #387059), which can make it unsuitable for use
3 when updating the whole system. Therefore, implement two new options
4 that are analogous to --newuse and --binpkg-respect-use, called
5 --changed-deps and --binpkg-changed-deps.
6
7 The rationale for having a separate --binpkg-* option is the same in
8 both cases: depending on the situation, people may want different
9 behavior for binary packages. For example, just like
10 ---binpkg-respect-use is automatically enabled if the user has not
11 specified --usepkgonly, so is --binpkg-changed-deps (though the user
12 can explicitly override the automatic behavior). In both cases,
13 inconsistencies in dependencies are automatically avoided, increasing
14 the probability of a successful dependency calculation.
15
16 X-Gentoo-Bug: 282927
17 X-Gentoo-Bug-URL: https://bugs.gentoo.org/show_bug.cgi?id=282927
18 ---
19 man/emerge.1 | 22 +++-
20 pym/_emerge/create_depgraph_params.py | 16 +++
21 pym/_emerge/depgraph.py | 138 ++++++++++++++++++++++--
22 pym/_emerge/main.py | 26 +++++
23 pym/portage/dep/_slot_operator.py | 13 +++
24 pym/portage/tests/resolver/test_changed_deps.py | 120 +++++++++++++++++++++
25 6 files changed, 323 insertions(+), 12 deletions(-)
26 create mode 100644 pym/portage/tests/resolver/test_changed_deps.py
27
28 diff --git a/man/emerge.1 b/man/emerge.1
29 index faa1f33..7eb30a5 100644
30 --- a/man/emerge.1
31 +++ b/man/emerge.1
32 @@ -386,9 +386,20 @@ Specifies an integer number of times to backtrack if
33 dependency calculation fails due to a conflict or an
34 unsatisfied dependency (default: \'10\').
35 .TP
36 +.BR "\-\-binpkg\-changed\-deps [ y | n ]"
37 +Tells emerge to ignore binary packages for which the corresponding
38 +ebuild dependencies have changed since the packages were built.
39 +In order to help avoid issues with resolving inconsistent dependencies,
40 +this option is automatically enabled unless the \fB\-\-usepkgonly\fR
41 +option is enabled. Behavior with respect to changed build\-time
42 +dependencies is controlled by the \fB\-\-with\-bdeps\fR option.
43 +.TP
44 .BR "\-\-binpkg\-respect\-use [ y | n ]"
45 -Tells emerge to ignore binary packages if their use flags
46 -don't match the current configuration. (default: \'n\')
47 +Tells emerge to ignore binary packages if their USE flags
48 +don't match the current configuration. In order to help avoid issues
49 +with resolving inconsistent USE flag settings, this option is
50 +automatically enabled unless the \fB\-\-usepkgonly\fR option
51 +is enabled.
52 .TP
53 .BR "\-\-buildpkg [ y | n ] (\-b short option)"
54 Tells emerge to build binary packages for all ebuilds processed in
55 @@ -410,6 +421,13 @@ Creates binary packages for all ebuilds processed without actually
56 merging the packages. This comes with the caveat that all build-time
57 dependencies must already be emerged on the system.
58 .TP
59 +.BR "\-\-changed\-deps [ y | n ]"
60 +Tells emerge to replace installed packages for which the corresponding
61 +ebuild dependencies have changed since the packages were built. This
62 +option also implies the \fB\-\-selective\fR option. Behavior with
63 +respect to changed build\-time dependencies is controlled by the
64 +\fB\-\-with\-bdeps\fR option.
65 +.TP
66 .BR "\-\-changed\-use " (\fB\-U\fR)
67 Tells emerge to include installed packages where USE flags have
68 changed since installation. This option also implies the
69 diff --git a/pym/_emerge/create_depgraph_params.py b/pym/_emerge/create_depgraph_params.py
70 index 6f74de7..11e20f4 100644
71 --- a/pym/_emerge/create_depgraph_params.py
72 +++ b/pym/_emerge/create_depgraph_params.py
73 @@ -22,6 +22,8 @@ def create_depgraph_params(myopts, myaction):
74 # ignore_built_slot_operator_deps: ignore the slot/sub-slot := operator parts
75 # of dependencies that have been recorded when packages where built
76 # with_test_deps: pull in test deps for packages matched by arguments
77 + # changed_deps: rebuild installed packages with outdated deps
78 + # binpkg_changed_deps: reject binary packages with outdated deps
79 myparams = {"recurse" : True}
80
81 bdeps = myopts.get("--with-bdeps")
82 @@ -51,6 +53,7 @@ def create_depgraph_params(myopts, myaction):
83 "--newuse" in myopts or \
84 "--reinstall" in myopts or \
85 "--noreplace" in myopts or \
86 + myopts.get("--changed-deps", "n") != "n" or \
87 myopts.get("--selective", "n") != "n":
88 myparams["selective"] = True
89
90 @@ -99,6 +102,19 @@ def create_depgraph_params(myopts, myaction):
91 # have been specified.
92 myparams['binpkg_respect_use'] = 'auto'
93
94 + binpkg_changed_deps = myopts.get('--binpkg-changed-deps')
95 + if binpkg_changed_deps is not None:
96 + myparams['binpkg_changed_deps'] = binpkg_changed_deps
97 + elif '--usepkgonly' not in myopts:
98 + # In order to avoid dependency resolution issues due to changed
99 + # dependencies, enable this automatically, as long as it doesn't
100 + # strongly conflict with other options that have been specified.
101 + myparams['binpkg_changed_deps'] = 'auto'
102 +
103 + changed_deps = myopts.get('--changed-deps')
104 + if changed_deps is not None:
105 + myparams['changed_deps'] = changed_deps
106 +
107 if myopts.get("--selective") == "n":
108 # --selective=n can be used to remove selective
109 # behavior that may have been implied by some
110 diff --git a/pym/_emerge/depgraph.py b/pym/_emerge/depgraph.py
111 index 28abea4..fae117d 100644
112 --- a/pym/_emerge/depgraph.py
113 +++ b/pym/_emerge/depgraph.py
114 @@ -24,7 +24,8 @@ from portage.dbapi._similar_name_search import similar_name_search
115 from portage.dep import Atom, best_match_to_list, extract_affecting_use, \
116 check_required_use, human_readable_required_use, match_from_list, \
117 _repo_separator
118 -from portage.dep._slot_operator import ignore_built_slot_operator_deps
119 +from portage.dep._slot_operator import (ignore_built_slot_operator_deps,
120 + strip_slots)
121 from portage.eapi import eapi_has_strong_blocks, eapi_has_required_use, \
122 _get_eapi_attrs
123 from portage.exception import (InvalidAtom, InvalidData, InvalidDependString,
124 @@ -794,14 +795,12 @@ class depgraph(object):
125 match the user's config.
126 """
127 if not self._dynamic_config.ignored_binaries \
128 - or '--quiet' in self._frozen_config.myopts \
129 - or self._dynamic_config.myparams.get(
130 - "binpkg_respect_use") in ("y", "n"):
131 + or '--quiet' in self._frozen_config.myopts:
132 return
133
134 - for pkg in list(self._dynamic_config.ignored_binaries):
135 + ignored_binaries = {}
136
137 - selected_pkg = list()
138 + for pkg in list(self._dynamic_config.ignored_binaries):
139
140 for selected_pkg in self._dynamic_config._package_tracker.match(
141 pkg.root, pkg.slot_atom):
142 @@ -819,15 +818,38 @@ class depgraph(object):
143 self._dynamic_config.ignored_binaries.pop(pkg)
144 break
145
146 - if not self._dynamic_config.ignored_binaries:
147 + else:
148 + for reason, info in self._dynamic_config.\
149 + ignored_binaries[pkg].items():
150 + ignored_binaries.setdefault(reason, {})[pkg] = info
151 +
152 + if self._dynamic_config.myparams.get(
153 + "binpkg_respect_use") in ("y", "n"):
154 + ignored_binaries.pop("respect_use", None)
155 +
156 + if self._dynamic_config.myparams.get(
157 + "binpkg_changed_deps") in ("y", "n"):
158 + ignored_binaries.pop("changed_deps", None)
159 +
160 + if not ignored_binaries:
161 return
162
163 self._show_merge_list()
164
165 + if ignored_binaries.get("respect_use"):
166 + self._show_ignored_binaries_respect_use(
167 + ignored_binaries["respect_use"])
168 +
169 + if ignored_binaries.get("changed_deps"):
170 + self._show_ignored_binaries_changed_deps(
171 + ignored_binaries["changed_deps"])
172 +
173 + def _show_ignored_binaries_respect_use(self, respect_use):
174 +
175 writemsg("\n!!! The following binary packages have been ignored " + \
176 "due to non matching USE:\n\n", noiselevel=-1)
177
178 - for pkg, flags in self._dynamic_config.ignored_binaries.items():
179 + for pkg, flags in respect_use.items():
180 flag_display = []
181 for flag in sorted(flags):
182 if flag not in pkg.use.enabled:
183 @@ -852,6 +874,30 @@ class depgraph(object):
184 line = colorize("INFORM", line)
185 writemsg(line + "\n", noiselevel=-1)
186
187 + def _show_ignored_binaries_changed_deps(self, changed_deps):
188 +
189 + writemsg("\n!!! The following binary packages have been "
190 + "ignored due to changed dependencies:\n\n",
191 + noiselevel=-1)
192 +
193 + for pkg in changed_deps:
194 + msg = " %s%s%s" % (pkg.cpv, _repo_separator, pkg.repo)
195 + if pkg.root_config.settings["ROOT"] != "/":
196 + msg += " for %s" % pkg.root
197 + writemsg("%s\n" % msg, noiselevel=-1)
198 +
199 + msg = [
200 + "",
201 + "NOTE: The --binpkg-changed-deps=n option will prevent emerge",
202 + " from ignoring these binary packages if possible.",
203 + " Using --binpkg-changed-deps=y will silence this warning."
204 + ]
205 +
206 + for line in msg:
207 + if line:
208 + line = colorize("INFORM", line)
209 + writemsg(line + "\n", noiselevel=-1)
210 +
211 def _get_missed_updates(self):
212
213 # In order to minimize noise, show only the highest
214 @@ -2173,6 +2219,52 @@ class depgraph(object):
215 return flags
216 return None
217
218 + def _changed_deps(self, pkg):
219 +
220 + ebuild = None
221 + try:
222 + ebuild = self._pkg(pkg.cpv, "ebuild",
223 + pkg.root_config, myrepo=pkg.repo)
224 + except PackageNotFound:
225 + # Use first available instance of the same version.
226 + for ebuild in self._iter_match_pkgs(
227 + pkg.root_config, "ebuild", Atom("=" + pkg.cpv)):
228 + break
229 +
230 + if ebuild is None:
231 + changed = False
232 + else:
233 + if self._dynamic_config.myparams.get("bdeps", "n") == "y":
234 + depvars = Package._dep_keys
235 + else:
236 + depvars = Package._runtime_keys
237 +
238 + # Use _raw_metadata, in order to avoid interaction
239 + # with --dynamic-deps.
240 + try:
241 + built_deps = []
242 + for k in depvars:
243 + dep_struct = portage.dep.use_reduce(
244 + pkg._raw_metadata[k], uselist=pkg.use.enabled,
245 + eapi=pkg.eapi, token_class=Atom)
246 + strip_slots(dep_struct)
247 + built_deps.append(dep_struct)
248 + except InvalidDependString:
249 + changed = True
250 + else:
251 + unbuilt_deps = []
252 + for k in depvars:
253 + dep_struct = portage.dep.use_reduce(
254 + ebuild._raw_metadata[k],
255 + uselist=pkg.use.enabled,
256 + eapi=ebuild.eapi, token_class=Atom)
257 + strip_slots(dep_struct)
258 + unbuilt_deps.append(dep_struct)
259 +
260 + changed = built_deps != unbuilt_deps
261 +
262 + return changed
263 +
264 def _create_graph(self, allow_unsatisfied=False):
265 dep_stack = self._dynamic_config._dep_stack
266 dep_disjunctive_stack = self._dynamic_config._dep_disjunctive_stack
267 @@ -4595,8 +4687,14 @@ class depgraph(object):
268 mreasons = ["need to rebuild from source"]
269 elif pkg.installed and root_slot in self._rebuild.reinstall_list:
270 mreasons = ["need to rebuild from source"]
271 - elif pkg.built and not mreasons:
272 + elif (pkg.built and not mreasons and
273 + self._dynamic_config.ignored_binaries.get(
274 + pkg, {}).get("respect_use")):
275 mreasons = ["use flag configuration mismatch"]
276 + elif (pkg.built and not mreasons and
277 + self._dynamic_config.ignored_binaries.get(
278 + pkg, {}).get("changed_deps")):
279 + mreasons = ["changed deps"]
280 masked_packages.append(
281 (root_config, pkgsettings, cpv, repo, metadata, mreasons))
282
283 @@ -5693,6 +5791,12 @@ class depgraph(object):
284 # reject the built package if necessary.
285 reinstall_use = ("--newuse" in self._frozen_config.myopts or \
286 "--reinstall" in self._frozen_config.myopts)
287 + changed_deps = (
288 + self._dynamic_config.myparams.get(
289 + "changed_deps", "n") != "n")
290 + binpkg_changed_deps = (
291 + self._dynamic_config.myparams.get(
292 + "binpkg_changed_deps", "n") != "n")
293 respect_use = self._dynamic_config.myparams.get("binpkg_respect_use") in ("y", "auto")
294 if built and not useoldpkg and \
295 (not installed or matched_packages) and \
296 @@ -5719,8 +5823,22 @@ class depgraph(object):
297 forced_flags, old_use, iuses, now_use, cur_iuse)
298 if reinstall_for_flags:
299 if not pkg.installed:
300 - self._dynamic_config.ignored_binaries.setdefault(pkg, set()).update(reinstall_for_flags)
301 + self._dynamic_config.\
302 + ignored_binaries.setdefault(
303 + pkg, {}).setdefault(
304 + "respect_use", set()).update(
305 + reinstall_for_flags)
306 break
307 +
308 + if (((installed and changed_deps) or
309 + (not installed and binpkg_changed_deps)) and
310 + self._changed_deps(pkg)):
311 + if not installed:
312 + self._dynamic_config.\
313 + ignored_binaries.setdefault(
314 + pkg, {})["changed_deps"] = True
315 + break
316 +
317 # Compare current config to installed package
318 # and do not reinstall if possible.
319 if not installed and not useoldpkg and cpv in vardb.match(atom):
320 diff --git a/pym/_emerge/main.py b/pym/_emerge/main.py
321 index 7c707f9..ecbbdb0 100644
322 --- a/pym/_emerge/main.py
323 +++ b/pym/_emerge/main.py
324 @@ -129,7 +129,9 @@ def insert_optional_args(args):
325 '--autounmask-keep-masks': y_or_n,
326 '--autounmask-unrestricted-atoms' : y_or_n,
327 '--autounmask-write' : y_or_n,
328 + '--binpkg-changed-deps' : y_or_n,
329 '--buildpkg' : y_or_n,
330 + '--changed-deps' : y_or_n,
331 '--complete-graph' : y_or_n,
332 '--deep' : valid_integers,
333 '--depclean-lib-check' : y_or_n,
334 @@ -353,6 +355,12 @@ def parse_opts(tmpcmdline, silent=False):
335 "action" : "store"
336 },
337
338 + "--binpkg-changed-deps": {
339 + "help" : ("reject binary packages with outdated "
340 + "dependencies"),
341 + "choices" : true_y_or_n
342 + },
343 +
344 "--buildpkg": {
345 "shortopt" : "-b",
346 "help" : "build binary packages",
347 @@ -367,6 +375,12 @@ def parse_opts(tmpcmdline, silent=False):
348 "action" : "append"
349 },
350
351 + "--changed-deps": {
352 + "help" : ("replace installed packages with "
353 + "outdated dependencies"),
354 + "choices" : true_y_or_n
355 + },
356 +
357 "--config-root": {
358 "help":"specify the location for portage configuration files",
359 "action":"store"
360 @@ -722,6 +736,12 @@ def parse_opts(tmpcmdline, silent=False):
361 if myoptions.autounmask_write in true_y:
362 myoptions.autounmask_write = True
363
364 + if myoptions.binpkg_changed_deps is not None:
365 + if myoptions.binpkg_changed_deps in true_y:
366 + myoptions.binpkg_changed_deps = 'y'
367 + else:
368 + myoptions.binpkg_changed_deps = 'n'
369 +
370 if myoptions.buildpkg in true_y:
371 myoptions.buildpkg = True
372
373 @@ -731,6 +751,12 @@ def parse_opts(tmpcmdline, silent=False):
374 parser.error("Invalid Atom(s) in --buildpkg-exclude parameter: '%s'\n" % \
375 (",".join(bad_atoms),))
376
377 + if myoptions.changed_deps is not None:
378 + if myoptions.changed_deps in true_y:
379 + myoptions.changed_deps = 'y'
380 + else:
381 + myoptions.changed_deps = 'n'
382 +
383 if myoptions.changed_use is not False:
384 myoptions.reinstall = "changed-use"
385 myoptions.changed_use = False
386 diff --git a/pym/portage/dep/_slot_operator.py b/pym/portage/dep/_slot_operator.py
387 index 8b67fc5..8ce570d 100644
388 --- a/pym/portage/dep/_slot_operator.py
389 +++ b/pym/portage/dep/_slot_operator.py
390 @@ -8,6 +8,19 @@ from portage.eapi import _get_eapi_attrs
391 from portage.exception import InvalidData
392 from _emerge.Package import Package
393
394 +def strip_slots(dep_struct):
395 + """
396 + Search dep_struct for any slot := operators and remove the
397 + slot/sub-slot part, while preserving the operator. The result
398 + is suitable for --changed-deps comparisons.
399 + """
400 + for i, x in enumerate(dep_struct):
401 + if isinstance(x, list):
402 + strip_slots(x)
403 + elif (isinstance(x, Atom) and
404 + x.slot_operator == "=" and x.slot is not None):
405 + dep_struct[i] = x.with_slot("=")
406 +
407 def find_built_slot_operator_atoms(pkg):
408 atoms = {}
409 for k in Package._dep_keys:
410 diff --git a/pym/portage/tests/resolver/test_changed_deps.py b/pym/portage/tests/resolver/test_changed_deps.py
411 new file mode 100644
412 index 0000000..2421c53
413 --- /dev/null
414 +++ b/pym/portage/tests/resolver/test_changed_deps.py
415 @@ -0,0 +1,120 @@
416 +# Copyright 2014 Gentoo Foundation
417 +# Distributed under the terms of the GNU General Public License v2
418 +
419 +from portage.tests import TestCase
420 +from portage.tests.resolver.ResolverPlayground import (
421 + ResolverPlayground, ResolverPlaygroundTestCase)
422 +
423 +class ChangedDepsTestCase(TestCase):
424 +
425 + def testChangedDeps(self):
426 +
427 + ebuilds = {
428 + "app-misc/A-0": {
429 + "DEPEND": "app-misc/B",
430 + "RDEPEND": "app-misc/B",
431 + },
432 + "app-misc/B-0": {
433 + }
434 + }
435 +
436 + binpkgs = {
437 + "app-misc/A-0": {},
438 + }
439 +
440 + installed = {
441 + "app-misc/A-0": {},
442 + }
443 +
444 + world= (
445 + "app-misc/A",
446 + )
447 +
448 + test_cases = (
449 +
450 + # --dynamic-deps=n causes the original deps to be respected
451 + ResolverPlaygroundTestCase(
452 + ["@world"],
453 + success = True,
454 + options = {
455 + "--update": True,
456 + "--deep": True,
457 + "--dynamic-deps": "n",
458 + "--usepkg": True,
459 + },
460 + mergelist = []
461 + ),
462 +
463 + # --dynamic-deps causes app-misc/B to get pulled in
464 + ResolverPlaygroundTestCase(
465 + ["@world"],
466 + success = True,
467 + options = {
468 + "--update": True,
469 + "--deep": True,
470 + "--usepkg": True,
471 + },
472 + mergelist = ["app-misc/B-0"]
473 + ),
474 +
475 + # --changed-deps causes app-misc/A to be rebuilt
476 + ResolverPlaygroundTestCase(
477 + ["@world"],
478 + success = True,
479 + options = {
480 + "--update": True,
481 + "--deep": True,
482 + "--changed-deps": "y",
483 + "--usepkg": True,
484 + },
485 + mergelist = ["app-misc/B-0", "app-misc/A-0"]
486 + ),
487 +
488 + # --usepkgonly prevents automatic --binpkg-changed-deps
489 + ResolverPlaygroundTestCase(
490 + ["app-misc/A"],
491 + success = True,
492 + options = {
493 + "--changed-deps": "y",
494 + "--usepkgonly": True,
495 + },
496 + mergelist = ["[binary]app-misc/A-0"]
497 + ),
498 +
499 + # Test automatic --binpkg-changed-deps, which cases the
500 + # binpkg with stale deps to be ignored (with warning
501 + # message)
502 + ResolverPlaygroundTestCase(
503 + ["app-misc/A"],
504 + success = True,
505 + options = {
506 + "--usepkg": True,
507 + },
508 + mergelist = ["app-misc/B-0", "app-misc/A-0"]
509 + ),
510 + )
511 + test_cases = (
512 +
513 + # Forcibly disable --binpkg-changed-deps, which causes
514 + # --changed-deps to be overridden by --binpkg-changed-deps
515 + ResolverPlaygroundTestCase(
516 + ["app-misc/A"],
517 + success = True,
518 + options = {
519 + "--binpkg-changed-deps": "n",
520 + "--changed-deps": "y",
521 + "--usepkg": True,
522 + },
523 + mergelist = ["[binary]app-misc/A-0"]
524 + ),
525 + )
526 +
527 + playground = ResolverPlayground(debug=False, ebuilds=ebuilds,
528 + binpkgs=binpkgs, installed=installed, world=world)
529 + try:
530 + for test_case in test_cases:
531 + playground.run_TestCase(test_case)
532 + self.assertEqual(test_case.test_success,
533 + True, test_case.fail_msg)
534 + finally:
535 + playground.cleanup()
536 --
537 2.0.5

Replies