From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from lists.gentoo.org (pigeon.gentoo.org [208.92.234.80]) by finch.gentoo.org (Postfix) with ESMTP id 9112613838B for ; Thu, 11 Sep 2014 21:37:16 +0000 (UTC) Received: from pigeon.gentoo.org (localhost [127.0.0.1]) by pigeon.gentoo.org (Postfix) with SMTP id 97AF3E08C1; Thu, 11 Sep 2014 21:37:15 +0000 (UTC) Received: from smtp.gentoo.org (smtp.gentoo.org [140.211.166.183]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by pigeon.gentoo.org (Postfix) with ESMTPS id 26556E08C1 for ; Thu, 11 Sep 2014 21:37:15 +0000 (UTC) Received: from oystercatcher.gentoo.org (oystercatcher.gentoo.org [148.251.78.52]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by smtp.gentoo.org (Postfix) with ESMTPS id E005A340092 for ; Thu, 11 Sep 2014 21:37:13 +0000 (UTC) Received: from localhost.localdomain (localhost [127.0.0.1]) by oystercatcher.gentoo.org (Postfix) with ESMTP id 9BE1F53E1 for ; Thu, 11 Sep 2014 21:37:12 +0000 (UTC) From: "Zac Medico" To: gentoo-commits@lists.gentoo.org Content-Transfer-Encoding: 8bit Content-type: text/plain; charset=UTF-8 Reply-To: gentoo-dev@lists.gentoo.org, "Zac Medico" Message-ID: <1410471368.336ab90212c80ce9548362bf4fbdafd388c3515c.zmedico@gentoo> Subject: [gentoo-commits] proj/portage:master commit in: pym/portage/tests/resolver/, pym/_emerge/ X-VCS-Repository: proj/portage X-VCS-Files: pym/_emerge/depgraph.py pym/portage/tests/resolver/ResolverPlayground.py pym/portage/tests/resolver/test_slot_conflict_unsatisfied_deep_deps.py X-VCS-Directories: pym/portage/tests/resolver/ pym/_emerge/ X-VCS-Committer: zmedico X-VCS-Committer-Name: Zac Medico X-VCS-Revision: 336ab90212c80ce9548362bf4fbdafd388c3515c X-VCS-Branch: master Date: Thu, 11 Sep 2014 21:37:12 +0000 (UTC) Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-Id: Gentoo Linux mail X-BeenThere: gentoo-commits@lists.gentoo.org X-Archives-Salt: 365a77f5-d0c0-4cb7-8ff7-10f1b0a4b417 X-Archives-Hash: eb36acb37d88702ed7438c26f1d829a4 commit: 336ab90212c80ce9548362bf4fbdafd388c3515c Author: Zac Medico gentoo org> AuthorDate: Sun Sep 7 05:19:46 2014 +0000 Commit: Zac Medico gentoo org> CommitDate: Thu Sep 11 21:36:08 2014 +0000 URL: http://sources.gentoo.org/gitweb/?p=proj/portage.git;a=commit;h=336ab902 depgraph._add_dep: fix bug #520950 This handles a case which occurs when _solve_non_slot_operator_slot_conflicts calls _create_graph. In this case, ignore unsatisfied deps for installed packages only if their depth is beyond the depth requested by the user and the dep was initially unsatisfied (not broken by a slot conflict in the current graph). Since depth is meaningless for packages that are not reachable as deep dependencies of arguments, the _UNREACHABLE_DEPTH constant is used as the depth value for any packages added via _complete_graph. Also, any sets added via _complete_graph have their reset_depth attribute set to False. The sys.stderr -> writemsg changes are necessary to ensure that the test cases do not output unwanted error messages. X-Gentoo-Bug: 520950 X-Gentoo-Bug-URL: https://bugs.gentoo.org/show_bug.cgi?id=520950 --- pym/_emerge/depgraph.py | 128 ++++++++++++++++----- pym/portage/tests/resolver/ResolverPlayground.py | 11 +- .../test_slot_conflict_unsatisfied_deep_deps.py | 115 ++++++++++++++++++ 3 files changed, 226 insertions(+), 28 deletions(-) diff --git a/pym/_emerge/depgraph.py b/pym/_emerge/depgraph.py index d6cd24d..cc87d9f 100644 --- a/pym/_emerge/depgraph.py +++ b/pym/_emerge/depgraph.py @@ -107,7 +107,7 @@ def _wildcard_set(atoms): class _frozen_depgraph_config(object): - def __init__(self, settings, trees, myopts, spinner): + def __init__(self, settings, trees, myopts, params, spinner): self.settings = settings self.target_root = settings["EROOT"] self.myopts = myopts @@ -115,6 +115,7 @@ class _frozen_depgraph_config(object): if settings.get("PORTAGE_DEBUG", "") == "1": self.edebug = 1 self.spinner = spinner + self.requested_depth = params.get("deep", 0) self._running_root = trees[trees._running_eroot]["root_config"] self.pkgsettings = {} self.trees = {} @@ -502,13 +503,18 @@ class _dynamic_depgraph_config(object): class depgraph(object): + # Represents the depth of a node that is unreachable from explicit + # user arguments (or their deep dependencies). Such nodes are pulled + # in by the _complete_graph method. + _UNREACHABLE_DEPTH = object() + pkg_tree_map = RootConfig.pkg_tree_map def __init__(self, settings, trees, myopts, myparams, spinner, frozen_config=None, backtrack_parameters=BacktrackParameter(), allow_backtracking=False): if frozen_config is None: frozen_config = _frozen_depgraph_config(settings, trees, - myopts, spinner) + myopts, myparams, spinner) self._frozen_config = frozen_config self._dynamic_config = _dynamic_depgraph_config(self, myparams, allow_backtracking, backtrack_parameters) @@ -2095,6 +2101,13 @@ class depgraph(object): arg = arg_stack.pop() if arg in traversed_set_args: continue + + # If a node with the same hash already exists in + # the digraph, preserve the existing instance which + # may have a different reset_depth attribute + # (distiguishes user arguments from sets added for + # another reason such as complete mode). + arg = self._dynamic_config.digraph.get(arg, arg) traversed_set_args.add(arg) if add_to_digraph: @@ -2114,8 +2127,16 @@ class depgraph(object): if nested_set is None: nested_set = root_config.sets.get(s) if nested_set is not None: + # Propagate the reset_depth attribute from + # parent set to nested set. nested_arg = SetArg(arg=token, pset=nested_set, + reset_depth=arg.reset_depth, root_config=root_config) + + # Preserve instances already in the graph (same + # reason as for the "arg" variable above). + nested_arg = self._dynamic_config.digraph.get( + nested_arg, nested_arg) arg_stack.append(nested_arg) if add_to_digraph: self._dynamic_config.digraph.add(nested_arg, arg, @@ -2164,9 +2185,42 @@ class depgraph(object): dep.collapsed_priority.ignored): # This is an unnecessary build-time dep. return 1 + + # NOTE: For removal actions, allow_unsatisfied is always + # True since all existing removal actions traverse all + # installed deps deeply via the _complete_graph method, + # which calls _create_graph with allow_unsatisfied = True. if allow_unsatisfied: self._dynamic_config._unsatisfied_deps.append(dep) return 1 + + # The following case occurs when + # _solve_non_slot_operator_slot_conflicts calls + # _create_graph. In this case, ignore unsatisfied deps for + # installed packages only if their depth is beyond the depth + # requested by the user and the dep was initially + # unsatisfied (not broken by a slot conflict in the current + # graph). See bug #520950. + # NOTE: The value of dep.parent.depth is guaranteed to be + # either an integer or _UNREACHABLE_DEPTH, where + # _UNREACHABLE_DEPTH indicates that the parent has been + # pulled in by the _complete_graph method (rather than by + # explicit arguments or their deep dependencies). These + # cases must be distinguished because depth is meaningless + # for packages that are not reachable as deep dependencies + # of arguments. + if (self._dynamic_config._complete_mode and + isinstance(dep.parent, Package) and + dep.parent.installed and + (dep.parent.depth is self._UNREACHABLE_DEPTH or + (self._frozen_config.requested_depth is not True and + dep.parent.depth >= self._frozen_config.requested_depth))): + inst_pkg, in_graph = \ + self._select_pkg_from_installed(dep.root, dep.atom) + if inst_pkg is None: + self._dynamic_config._initially_unsatisfied_deps.append(dep) + return 1 + self._dynamic_config._unsatisfied_deps_for_display.append( ((dep.root, dep.atom), {"myparent":dep.parent})) @@ -2411,14 +2465,23 @@ class depgraph(object): # Installing package A, we need to make sure package A's deps are met. # emerge --deep ; we need to recursively check dependencies of pkgspec # If we are in --nodeps (no recursion) mode, we obviously only check 1 level of dependencies. - if arg_atoms and depth > 0: + if arg_atoms and depth != 0: for parent, atom in arg_atoms: if parent.reset_depth: depth = 0 break - if previously_added and pkg.depth is not None: - depth = min(pkg.depth, depth) + if previously_added and depth != 0 and \ + isinstance(pkg.depth, int): + # Use pkg.depth if it is less than depth. + if isinstance(depth, int): + depth = min(pkg.depth, depth) + else: + # depth is _UNREACHABLE_DEPTH and pkg.depth is + # an int, so use the int because it's considered + # to be less than _UNREACHABLE_DEPTH. + depth = pkg.depth + pkg.depth = depth deep = self._dynamic_config.myparams.get("deep", 0) update = "--update" in self._frozen_config.myopts @@ -2716,7 +2779,11 @@ class depgraph(object): def _wrapped_add_pkg_dep_string(self, pkg, dep_root, dep_priority, dep_string, allow_unsatisfied): - depth = pkg.depth + 1 + if isinstance(pkg.depth, int): + depth = pkg.depth + 1 + else: + depth = pkg.depth + deep = self._dynamic_config.myparams.get("deep", 0) recurse_satisfied = deep is True or depth <= deep debug = "--debug" in self._frozen_config.myopts @@ -3544,9 +3611,9 @@ class depgraph(object): if not self._add_pkg(arg.package, dep) or \ not self._create_graph(): if not self.need_restart(): - sys.stderr.write(("\n\n!!! Problem " + \ + writemsg(("\n\n!!! Problem " + \ "resolving dependencies for %s\n") % \ - arg.arg) + arg.arg, noiselevel=-1) return 0, myfavorites continue if debug: @@ -3947,10 +4014,14 @@ class depgraph(object): # Recursively traversed virtual dependencies, and their # direct dependencies, are considered to have the same # depth as direct dependencies. - if parent.depth is None: - virt_depth = None - else: + if isinstance(parent.depth, int): virt_depth = parent.depth + 1 + else: + # The depth may be None when called via + # _select_atoms_probe, or it may be + # _UNREACHABLE_DEPTH for complete mode. + virt_depth = parent.depth + chosen_atom_ids = frozenset(id(atom) for atom in mycheck[1]) selected_atoms = OrderedDict() node_stack = [(parent, None, None)] @@ -5833,14 +5904,14 @@ class depgraph(object): pset = root_config.sets[s] atom = SETPREFIX + s args.append(SetArg(arg=atom, pset=pset, - root_config=root_config)) + reset_depth=False, root_config=root_config)) self._set_args(args) for arg in self._expand_set_args(args, add_to_digraph=True): for atom in arg.pset.getAtoms(): self._dynamic_config._dep_stack.append( Dependency(atom=atom, root=arg.root_config.root, - parent=arg)) + parent=arg, depth=self._UNREACHABLE_DEPTH)) if True: if self._dynamic_config._ignored_deps: @@ -7786,18 +7857,23 @@ class depgraph(object): break if world_problems: - sys.stderr.write("\n!!! Problems have been " + \ - "detected with your world file\n") - sys.stderr.write("!!! Please run " + \ - green("emaint --check world")+"\n\n") + writemsg("\n!!! Problems have been " + \ + "detected with your world file\n", + noiselevel=-1) + writemsg("!!! Please run " + \ + green("emaint --check world")+"\n\n", + noiselevel=-1) if self._dynamic_config._missing_args: - sys.stderr.write("\n" + colorize("BAD", "!!!") + \ - " Ebuilds for the following packages are either all\n") - sys.stderr.write(colorize("BAD", "!!!") + \ - " masked or don't exist:\n") - sys.stderr.write(" ".join(str(atom) for arg, atom in \ - self._dynamic_config._missing_args) + "\n") + writemsg("\n" + colorize("BAD", "!!!") + \ + " Ebuilds for the following packages are either all\n", + noiselevel=-1) + writemsg(colorize("BAD", "!!!") + \ + " masked or don't exist:\n", + noiselevel=-1) + writemsg(" ".join(str(atom) for arg, atom in \ + self._dynamic_config._missing_args) + "\n", + noiselevel=-1) if self._dynamic_config._pprovided_args: arg_refs = {} @@ -7837,7 +7913,7 @@ class depgraph(object): msg.append(" C) Remove offending entries from package.provided.\n\n") msg.append("The best course of action depends on the reason that an offending\n") msg.append("package.provided entry exists.\n\n") - sys.stderr.write("".join(msg)) + writemsg("".join(msg), noiselevel=-1) masked_packages = [] for pkg in self._dynamic_config._masked_license_updates: @@ -8487,7 +8563,7 @@ def _backtrack_depgraph(settings, trees, myopts, myparams, myaction, myfiles, sp backtracked = 0 frozen_config = _frozen_depgraph_config(settings, trees, - myopts, spinner) + myopts, myparams, spinner) while backtracker: @@ -8569,7 +8645,7 @@ def _resume_depgraph(settings, trees, mtimedb, myopts, myparams, spinner): mergelist = mtimedb["resume"]["mergelist"] dropped_tasks = {} frozen_config = _frozen_depgraph_config(settings, trees, - myopts, spinner) + myopts, myparams, spinner) while True: mydepgraph = depgraph(settings, trees, myopts, myparams, spinner, frozen_config=frozen_config) diff --git a/pym/portage/tests/resolver/ResolverPlayground.py b/pym/portage/tests/resolver/ResolverPlayground.py index 9ee1d5e..3476aba 100644 --- a/pym/portage/tests/resolver/ResolverPlayground.py +++ b/pym/portage/tests/resolver/ResolverPlayground.py @@ -662,7 +662,8 @@ class ResolverPlaygroundTestCase(object): str((node1, node2))) + \ ", got: " + str(got)) - elif key in ("unstable_keywords", "needed_p_mask_changes") and expected is not None: + elif key in ("unstable_keywords", "needed_p_mask_changes", + "unsatisfied_deps") and expected is not None: expected = set(expected) if got != expected: @@ -678,9 +679,10 @@ class ResolverPlaygroundResult(object): checks = ( "success", "mergelist", "use_changes", "license_changes", "unstable_keywords", "slot_collision_solutions", - "circular_dependency_solutions", "needed_p_mask_changes", + "circular_dependency_solutions", "needed_p_mask_changes", "unsatisfied_deps", ) optional_checks = ( + "unsatisfied_deps" ) def __init__(self, atoms, success, mydepgraph, favorites): @@ -695,6 +697,7 @@ class ResolverPlaygroundResult(object): self.needed_p_mask_changes = None self.slot_collision_solutions = None self.circular_dependency_solutions = None + self.unsatisfied_deps = frozenset() if self.depgraph._dynamic_config._serialized_tasks_cache is not None: self.mergelist = [] @@ -754,6 +757,10 @@ class ResolverPlaygroundResult(object): sol = handler.solutions self.circular_dependency_solutions = dict(zip([x.cpv for x in sol.keys()], sol.values())) + if self.depgraph._dynamic_config._unsatisfied_deps_for_display: + self.unsatisfied_deps = set(dep_info[0][1] + for dep_info in self.depgraph._dynamic_config._unsatisfied_deps_for_display) + class ResolverPlaygroundDepcleanResult(object): checks = ( 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 new file mode 100644 index 0000000..13f7e67 --- /dev/null +++ b/pym/portage/tests/resolver/test_slot_conflict_unsatisfied_deep_deps.py @@ -0,0 +1,115 @@ +# Copyright 2014 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +from portage.tests import TestCase +from portage.tests.resolver.ResolverPlayground import ResolverPlayground, ResolverPlaygroundTestCase + +class SlotConflictUnsatisfiedDeepDepsTestCase(TestCase): + + def testSlotConflictUnsatisfiedDeepDeps(self): + + ebuilds = { + "dev-libs/A-1": { }, + "dev-libs/A-2": { "KEYWORDS": "~x86" }, + "dev-libs/B-1": { "DEPEND": "dev-libs/A" }, + "dev-libs/C-1": { "DEPEND": ">=dev-libs/A-2" }, + "dev-libs/D-1": { "DEPEND": "dev-libs/A" }, + } + + installed = { + "dev-libs/broken-1": { + "RDEPEND": "dev-libs/A dev-libs/initially-unsatisfied" + }, + } + + world = ( + "dev-libs/A", + "dev-libs/B", + "dev-libs/C", + "dev-libs/D", + "dev-libs/broken" + ) + + test_cases = ( + # Test bug #520950, where unsatisfied deps of installed + # packages are supposed to be ignored when they are beyond + # the depth requested by the user. + ResolverPlaygroundTestCase( + ["dev-libs/B", "dev-libs/C", "dev-libs/D"], + all_permutations=True, + options={ + "--autounmask": "y", + "--complete-graph": True + }, + mergelist=["dev-libs/A-2", "dev-libs/B-1", "dev-libs/C-1", "dev-libs/D-1"], + ignore_mergelist_order=True, + unstable_keywords=["dev-libs/A-2"], + unsatisfied_deps=[], + success=False), + + ResolverPlaygroundTestCase( + ["@world"], + options={ + "--autounmask": "y", + "--complete-graph": True + }, + mergelist=["dev-libs/A-2", "dev-libs/B-1", "dev-libs/C-1", "dev-libs/D-1"], + ignore_mergelist_order=True, + unstable_keywords=["dev-libs/A-2"], + unsatisfied_deps=["dev-libs/broken"], + success=False), + + # Test --selective with --deep = 0 + ResolverPlaygroundTestCase( + ["@world"], + options={ + "--autounmask": "y", + "--complete-graph": True, + "--selective": True, + "--deep": 0 + }, + mergelist=["dev-libs/A-2", "dev-libs/B-1", "dev-libs/C-1", "dev-libs/D-1"], + ignore_mergelist_order=True, + unstable_keywords=["dev-libs/A-2"], + unsatisfied_deps=[], + success=False), + + # Test --deep = 1 + ResolverPlaygroundTestCase( + ["@world"], + options={ + "--autounmask": "y", + "--complete-graph": True, + "--selective": True, + "--deep": 1 + }, + mergelist=["dev-libs/A-2", "dev-libs/B-1", "dev-libs/C-1", "dev-libs/D-1"], + ignore_mergelist_order=True, + unstable_keywords=["dev-libs/A-2"], + unsatisfied_deps=["dev-libs/initially-unsatisfied"], + success=False), + + # Test --deep = True + ResolverPlaygroundTestCase( + ["@world"], + options={ + "--autounmask": "y", + "--complete-graph": True, + "--selective": True, + "--deep": True + }, + mergelist=["dev-libs/A-2", "dev-libs/B-1", "dev-libs/C-1", "dev-libs/D-1"], + ignore_mergelist_order=True, + unstable_keywords=["dev-libs/A-2"], + unsatisfied_deps=["dev-libs/initially-unsatisfied"], + success=False), + ) + + playground = ResolverPlayground(ebuilds=ebuilds, installed=installed, + world=world, debug=False) + try: + for test_case in test_cases: + playground.run_TestCase(test_case) + self.assertEqual(test_case.test_success, True, test_case.fail_msg) + finally: + playground.cleanup()