public inbox for gentoo-commits@lists.gentoo.org
 help / color / mirror / Atom feed
* [gentoo-commits] proj/portage:master commit in: lib/_emerge/, lib/portage/tests/resolver/
@ 2019-09-12  1:51 Zac Medico
  0 siblings, 0 replies; 13+ messages in thread
From: Zac Medico @ 2019-09-12  1:51 UTC (permalink / raw
  To: gentoo-commits

commit:     1e61c439143b12d079e1fc344bbc0c192a84cbe0
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Wed Sep 11 02:54:51 2019 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Thu Sep 12 01:31:21 2019 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=1e61c439

_add_dep: less aggressive backtracking (bug 693836)

In order to suppress the sort of aggressive backtracking that can
trigger undesirable downgrades as in bug 693836, do not backtrack
for an unsatisfied dependency if there's an available package in
the runtime package mask which was involved in a slot conflict and
satisfied all involved parent atoms. Instead, discard the current
depgraph in favor of other backtracking configurations that may
exist. This case would not have been encountered prior to the fix
for bug 692746 which enabled backtracking for the type of slot
conflict that is detected here.

Fixes: 994ac00aa764 ("_slot_confict_backtrack: consider masking a package matched by all parent atoms (bug 692746)")
Bug: https://bugs.gentoo.org/693836
Signed-off-by: Zac Medico <zmedico <AT> gentoo.org>

 lib/_emerge/depgraph.py                            | 13 ++++
 .../test_aggressive_backtrack_downgrade.py         | 91 ++++++++++++++++++++++
 2 files changed, 104 insertions(+)

diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
index 6be1b3ec7..51614fc14 100644
--- a/lib/_emerge/depgraph.py
+++ b/lib/_emerge/depgraph.py
@@ -2888,6 +2888,19 @@ class depgraph(object):
 							dep.atom.without_use if dep.atom.package
 							else dep.atom, onlydeps=dep.onlydeps)
 					if dep_pkg is None:
+
+						# In order to suppress the sort of aggressive
+						# backtracking that can trigger undesirable downgrades
+						# as in bug 693836, do not backtrack if there's an
+						# available package which was involved in a slot
+						# conflict and satisfied all involved parent atoms.
+						for dep_pkg, reasons in self._dynamic_config._runtime_pkg_mask.items():
+							if (dep.atom.match(dep_pkg) and
+								len(reasons) == 1 and
+								not reasons.get("slot conflict", True)):
+								self._dynamic_config._skip_restart = True
+								return 0
+
 						self._dynamic_config._backtrack_infos["missing dependency"] = dep
 						self._dynamic_config._need_restart = True
 						if debug:

diff --git a/lib/portage/tests/resolver/test_aggressive_backtrack_downgrade.py b/lib/portage/tests/resolver/test_aggressive_backtrack_downgrade.py
new file mode 100644
index 000000000..fbe85dc89
--- /dev/null
+++ b/lib/portage/tests/resolver/test_aggressive_backtrack_downgrade.py
@@ -0,0 +1,91 @@
+# Copyright 2019 Gentoo Authors
+# 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 AgressiveBacktrackDowngradeTestCase(TestCase):
+
+	def testAgressiveBacktrackDowngrade(self):
+
+		ebuilds = {
+			'www-client/firefox-69.0' : {
+				'EAPI': '7',
+				'RDEPEND': '=media-libs/libvpx-1.7*:0=[postproc] media-video/ffmpeg'
+			},
+
+			'www-client/firefox-60.9.0' : {
+				'EAPI': '7',
+				'RDEPEND': ''
+			},
+
+			'media-libs/libvpx-1.8.0' : {
+				'EAPI': '7',
+				'SLOT' : '0/6',
+				'IUSE': 'postproc',
+			},
+
+			'media-libs/libvpx-1.7.0' : {
+				'EAPI': '7',
+				'SLOT' : '0/5',
+				'IUSE': '+postproc',
+			},
+
+			'media-libs/libvpx-1.6.0' : {
+				'EAPI': '7',
+				'SLOT' : '0/4',
+				'IUSE': 'postproc',
+			},
+
+			'media-video/ffmpeg-4.2' : {
+				'EAPI': '7',
+				'RDEPEND': 'media-libs/libvpx:=',
+			},
+		}
+
+		installed = {
+			'www-client/firefox-69.0' : {
+				'EAPI': '7',
+				'RDEPEND': '=media-libs/libvpx-1.7*:0/5=[postproc] media-video/ffmpeg'
+			},
+
+			'media-libs/libvpx-1.7.0' : {
+				'EAPI': '7',
+				'SLOT' : '0/5',
+				'IUSE': '+postproc',
+				'USE': 'postproc',
+			},
+
+			'media-video/ffmpeg-4.2' : {
+				'EAPI': '7',
+				'RDEPEND': 'media-libs/libvpx:0/5=',
+			},
+		}
+
+		world = ['media-video/ffmpeg', 'www-client/firefox']
+
+		test_cases = (
+			# Test bug 693836, where an attempt to upgrade libvpx lead
+			# to aggressive backtracking which ultimately triggered an
+			# undesirable firefox downgrade like this:
+			# [ebuild     U  ] media-libs/libvpx-1.8.0 [1.7.0]
+			# [ebuild     UD ] www-client/firefox-60.9.0 [69.0]
+			# [ebuild  rR    ] media-video/ffmpeg-4.2
+			ResolverPlaygroundTestCase(
+				['@world'],
+				options = {'--update': True, '--deep': True},
+				success = True,
+				mergelist = [],
+			),
+		)
+
+		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.debug = False
+			playground.cleanup()


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [gentoo-commits] proj/portage:master commit in: lib/_emerge/, lib/portage/tests/resolver/
@ 2020-02-15  0:05 Zac Medico
  0 siblings, 0 replies; 13+ messages in thread
From: Zac Medico @ 2020-02-15  0:05 UTC (permalink / raw
  To: gentoo-commits

commit:     079f8c4a36ccc2ef5e25e7a57cd0707640f82592
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Fri Feb 14 19:21:28 2020 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Fri Feb 14 23:10:37 2020 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=079f8c4a

depclean: ensure consistency with update actions (bug 649622)

Make depclean traverse dependencies in the same order as update
actions, in order to ensure consistency in decisions which are
dependent on the order of dependency evaluation due to inconsistent
use of || preferences in different packages.

In unit tests, update test_virtual_w3m_realistic to assert that
the order of graph traversal is deterministic and consistent
between update and removal (depclean) actions.

Bug: https://bugs.gentoo.org/649622
Signed-off-by: Zac Medico <zmedico <AT> gentoo.org>

 lib/_emerge/actions.py                           | 33 +++++++---
 lib/_emerge/depgraph.py                          | 21 +++++--
 lib/portage/tests/resolver/ResolverPlayground.py | 77 +++++++++++++++---------
 lib/portage/tests/resolver/test_or_choices.py    | 12 +++-
 4 files changed, 96 insertions(+), 47 deletions(-)

diff --git a/lib/_emerge/actions.py b/lib/_emerge/actions.py
index 31252af16..4bf9ce425 100644
--- a/lib/_emerge/actions.py
+++ b/lib/_emerge/actions.py
@@ -1,8 +1,9 @@
-# Copyright 1999-2019 Gentoo Authors
+# Copyright 1999-2020 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 from __future__ import division, print_function, unicode_literals
 
+import collections
 import errno
 import logging
 import operator
@@ -741,7 +742,19 @@ def action_depclean(settings, trees, ldpath_mtimes,
 
 	return rval
 
+
 def calc_depclean(settings, trees, ldpath_mtimes,
+	myopts, action, args_set, spinner):
+	result = _calc_depclean(settings, trees, ldpath_mtimes,
+		myopts, action, args_set, spinner)
+	return result.returncode, result.cleanlist, result.ordered, result.req_pkg_count
+
+
+_depclean_result = collections.namedtuple('_depclean_result',
+	('returncode', 'cleanlist', 'ordered', 'req_pkg_count', 'depgraph'))
+
+
+def _calc_depclean(settings, trees, ldpath_mtimes,
 	myopts, action, args_set, spinner):
 	allow_missing_deps = bool(args_set)
 
@@ -805,7 +818,7 @@ def calc_depclean(settings, trees, ldpath_mtimes,
 		writemsg_level(_("!!! Aborting due to set configuration "
 			"errors displayed above.\n"),
 			level=logging.ERROR, noiselevel=-1)
-		return 1, [], False, 0
+		return _depclean_result(1, [], False, 0, None)
 
 	if action == "depclean":
 		emergelog(xterm_titles, " >>> depclean")
@@ -920,7 +933,7 @@ def calc_depclean(settings, trees, ldpath_mtimes,
 	resolver.display_problems()
 
 	if not success:
-		return 1, [], False, 0
+		return _depclean_result(1, [], False, 0, resolver)
 
 	def unresolved_deps():
 
@@ -1020,7 +1033,7 @@ def calc_depclean(settings, trees, ldpath_mtimes,
 		return False
 
 	if unresolved_deps():
-		return 1, [], False, 0
+		return _depclean_result(1, [], False, 0, resolver)
 
 	graph = resolver._dynamic_config.digraph.copy()
 	required_pkgs_total = 0
@@ -1321,7 +1334,7 @@ def calc_depclean(settings, trees, ldpath_mtimes,
 							runtime_slot_op=True),
 						root=pkg.root)):
 						resolver.display_problems()
-						return 1, [], False, 0
+						return _depclean_result(1, [], False, 0, resolver)
 
 			writemsg_level("\nCalculating dependencies  ")
 			success = resolver._complete_graph(
@@ -1329,9 +1342,9 @@ def calc_depclean(settings, trees, ldpath_mtimes,
 			writemsg_level("\b\b... done!\n")
 			resolver.display_problems()
 			if not success:
-				return 1, [], False, 0
+				return _depclean_result(1, [], False, 0, resolver)
 			if unresolved_deps():
-				return 1, [], False, 0
+				return _depclean_result(1, [], False, 0, resolver)
 
 			graph = resolver._dynamic_config.digraph.copy()
 			required_pkgs_total = 0
@@ -1340,7 +1353,7 @@ def calc_depclean(settings, trees, ldpath_mtimes,
 					required_pkgs_total += 1
 			cleanlist = create_cleanlist()
 			if not cleanlist:
-				return 0, [], False, required_pkgs_total
+				return _depclean_result(0, [], False, required_pkgs_total, resolver)
 			clean_set = set(cleanlist)
 
 	if clean_set:
@@ -1458,8 +1471,8 @@ def calc_depclean(settings, trees, ldpath_mtimes,
 					graph.remove(node)
 					cleanlist.append(node.cpv)
 
-		return 0, cleanlist, ordered, required_pkgs_total
-	return 0, [], False, required_pkgs_total
+		return _depclean_result(0, cleanlist, ordered, required_pkgs_total, resolver)
+	return _depclean_result(0, [], False, required_pkgs_total, resolver)
 
 def action_deselect(settings, trees, opts, atoms):
 	enter_invalid = '--ask-enter-invalid' in opts

diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
index 8e0d79e29..27696ad40 100644
--- a/lib/_emerge/depgraph.py
+++ b/lib/_emerge/depgraph.py
@@ -6955,9 +6955,18 @@ class depgraph(object):
 				# Removal actions may override sets with temporary
 				# replacements that have had atoms removed in order
 				# to implement --deselect behavior.
-				required_set_names = set(required_sets[root])
 				depgraph_sets.sets.clear()
 				depgraph_sets.sets.update(required_sets[root])
+				if 'world' in depgraph_sets.sets:
+					# For consistent order of traversal for both update
+					# and removal (depclean) actions, sets other that
+					# world are always nested under the world set.
+					world_atoms = list(depgraph_sets.sets['world'])
+					world_atoms.extend(SETPREFIX + s for s in required_sets[root] if s != 'world')
+					depgraph_sets.sets['world'] = InternalPackageSet(initial_atoms=world_atoms)
+					required_set_names = {'world'}
+				else:
+					required_set_names = set(required_sets[root])
 			if "remove" not in self._dynamic_config.myparams and \
 				root == self._frozen_config.target_root and \
 				already_deep:
@@ -6967,7 +6976,7 @@ class depgraph(object):
 				not self._dynamic_config._dep_stack:
 				continue
 			root_config = self._frozen_config.roots[root]
-			for s in required_set_names:
+			for s in sorted(required_set_names):
 				pset = depgraph_sets.sets.get(s)
 				if pset is None:
 					pset = root_config.sets[s]
@@ -6977,10 +6986,10 @@ class depgraph(object):
 
 		self._set_args(args)
 		for arg in self._expand_set_args(args, add_to_digraph=True):
-			for atom in sorted(arg.pset.getAtoms(), reverse=True):
-				self._dynamic_config._dep_stack.append(
-					Dependency(atom=atom, root=arg.root_config.root,
-						parent=arg, depth=self._UNREACHABLE_DEPTH))
+			for atom in sorted(arg.pset.getAtoms()):
+				if not self._add_dep(Dependency(atom=atom, root=arg.root_config.root,
+					parent=arg, depth=self._UNREACHABLE_DEPTH), allow_unsatisfied=True):
+					return 0
 
 		if True:
 			if self._dynamic_config._ignored_deps:

diff --git a/lib/portage/tests/resolver/ResolverPlayground.py b/lib/portage/tests/resolver/ResolverPlayground.py
index cc0aa46e9..0456ce2e2 100644
--- a/lib/portage/tests/resolver/ResolverPlayground.py
+++ b/lib/portage/tests/resolver/ResolverPlayground.py
@@ -1,4 +1,4 @@
-# Copyright 2010-2019 Gentoo Authors
+# Copyright 2010-2020 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 import bz2
@@ -22,9 +22,10 @@ from portage.util import ensure_dirs, normalize_path
 from portage.versions import catsplit
 
 import _emerge
-from _emerge.actions import calc_depclean
+from _emerge.actions import _calc_depclean
 from _emerge.Blocker import Blocker
 from _emerge.create_depgraph_params import create_depgraph_params
+from _emerge.DependencyArg import DependencyArg
 from _emerge.depgraph import backtrack_depgraph
 from _emerge.RootConfig import RootConfig
 
@@ -593,11 +594,16 @@ class ResolverPlayground(object):
 			_emerge.emergelog._disable = True
 
 			if action in ("depclean", "prune"):
-				rval, cleanlist, ordered, req_pkg_count = \
-					calc_depclean(self.settings, self.trees, None,
+				depclean_result = _calc_depclean(self.settings, self.trees, None,
 					options, action, InternalPackageSet(initial_atoms=atoms, allow_wildcard=True), None)
 				result = ResolverPlaygroundDepcleanResult(
-					atoms, rval, cleanlist, ordered, req_pkg_count)
+					atoms,
+					depclean_result.returncode,
+					depclean_result.cleanlist,
+					depclean_result.ordered,
+					depclean_result.req_pkg_count,
+					depclean_result.depgraph,
+				)
 			else:
 				params = create_depgraph_params(options, action)
 				success, depgraph, favorites = backtrack_depgraph(
@@ -780,18 +786,46 @@ class ResolverPlaygroundTestCase(object):
 			return False
 		return True
 
+
+def _mergelist_str(x, depgraph):
+	if isinstance(x, DependencyArg):
+		mergelist_str = x.arg
+	elif isinstance(x, Blocker):
+		mergelist_str = x.atom
+	else:
+		repo_str = ""
+		if x.repo != "test_repo":
+			repo_str = _repo_separator + x.repo
+		build_id_str = ""
+		if (x.type_name == "binary" and
+			x.cpv.build_id is not None):
+			build_id_str = "-%s" % x.cpv.build_id
+		mergelist_str = x.cpv + build_id_str + repo_str
+		if x.built:
+			if x.operation == "merge":
+				desc = x.type_name
+			else:
+				desc = x.operation
+			mergelist_str = "[%s]%s" % (desc, mergelist_str)
+		if x.root != depgraph._frozen_config._running_root.root:
+			mergelist_str += "{targetroot}"
+	return mergelist_str
+
+
 class ResolverPlaygroundResult(object):
 
 	checks = (
 		"success", "mergelist", "use_changes", "license_changes",
 		"unstable_keywords", "slot_collision_solutions",
 		"circular_dependency_solutions", "needed_p_mask_changes",
-		"unsatisfied_deps", "forced_rebuilds", "required_use_unsatisfied"
+		"unsatisfied_deps", "forced_rebuilds", "required_use_unsatisfied",
+		"graph_order",
 		)
 	optional_checks = (
 		"forced_rebuilds",
 		"required_use_unsatisfied",
-		"unsatisfied_deps"
+		"unsatisfied_deps",
+		"graph_order",
 		)
 
 	def __init__(self, atoms, success, mydepgraph, favorites):
@@ -810,30 +844,12 @@ class ResolverPlaygroundResult(object):
 		self.forced_rebuilds = None
 		self.required_use_unsatisfied = None
 
+		self.graph_order = [_mergelist_str(node, self.depgraph) for node in self.depgraph._dynamic_config.digraph]
+
 		if self.depgraph._dynamic_config._serialized_tasks_cache is not None:
 			self.mergelist = []
-			host_root = self.depgraph._frozen_config._running_root.root
 			for x in self.depgraph._dynamic_config._serialized_tasks_cache:
-				if isinstance(x, Blocker):
-					self.mergelist.append(x.atom)
-				else:
-					repo_str = ""
-					if x.repo != "test_repo":
-						repo_str = _repo_separator + x.repo
-					build_id_str = ""
-					if (x.type_name == "binary" and
-						x.cpv.build_id is not None):
-						build_id_str = "-%s" % x.cpv.build_id
-					mergelist_str = x.cpv + build_id_str + repo_str
-					if x.built:
-						if x.operation == "merge":
-							desc = x.type_name
-						else:
-							desc = x.operation
-						mergelist_str = "[%s]%s" % (desc, mergelist_str)
-					if x.root != host_root:
-						mergelist_str += "{targetroot}"
-					self.mergelist.append(mergelist_str)
+				self.mergelist.append(_mergelist_str(x, self.depgraph))
 
 		if self.depgraph._dynamic_config._needed_use_config_changes:
 			self.use_changes = {}
@@ -894,14 +910,17 @@ class ResolverPlaygroundDepcleanResult(object):
 
 	checks = (
 		"success", "cleanlist", "ordered", "req_pkg_count",
+		"graph_order",
 		)
 	optional_checks = (
 		"ordered", "req_pkg_count",
+		"graph_order",
 		)
 
-	def __init__(self, atoms, rval, cleanlist, ordered, req_pkg_count):
+	def __init__(self, atoms, rval, cleanlist, ordered, req_pkg_count, depgraph):
 		self.atoms = atoms
 		self.success = rval == 0
 		self.cleanlist = cleanlist
 		self.ordered = ordered
 		self.req_pkg_count = req_pkg_count
+		self.graph_order = [_mergelist_str(node, depgraph) for node in depgraph._dynamic_config.digraph]

diff --git a/lib/portage/tests/resolver/test_or_choices.py b/lib/portage/tests/resolver/test_or_choices.py
index f31a5ff22..5c6803784 100644
--- a/lib/portage/tests/resolver/test_or_choices.py
+++ b/lib/portage/tests/resolver/test_or_choices.py
@@ -667,12 +667,16 @@ class OrChoicesTestCase(TestCase):
 
 			# Test for bug 649622 (with www-client/w3m installed via
 			# xorg-server dependency), where virtual/w3m was pulled in
-			# only to be removed by the next emerge --depclean.
+			# only to be removed by the next emerge --depclean. Note
+			# that graph_order must be deterministic in order to achieve
+			# deterministic results which are consistent between both
+			# update and removal (depclean) actions.
 			ResolverPlaygroundTestCase(
 				['@world'],
 				options = {'--update': True, '--deep': True},
 				success = True,
 				mergelist=['virtual/w3m-0'],
+				graph_order=['@world', '@system', '@selected', '@profile', '[nomerge]app-misc/neofetch-6.1.0', '[nomerge]mail-client/neomutt-20191207', '[nomerge]www-client/lynx-2.9.0_pre4', '[nomerge]x11-base/xorg-server-1.20.7', '[nomerge]app-text/xmlto-0.0.28-r1', '[nomerge]www-client/w3m-0.5.3_p20190105', 'virtual/w3m-0'],
 			),
 
 		)
@@ -702,12 +706,16 @@ class OrChoicesTestCase(TestCase):
 			# Test for bug 649622, where virtual/w3m is removed by
 			# emerge --depclean immediately after it's installed
 			# by a world update. Since virtual/w3m-0 is not removed
-			# here, this case fails to reproduce bug 649622.
+			# here, this case fails to reproduce bug 649622. Note
+			# that graph_order must be deterministic in order to achieve
+			# deterministic results which are consistent between both
+			# update and removal (depclean) actions.
 			ResolverPlaygroundTestCase(
 				[],
 				options={'--depclean': True},
 				success=True,
 				cleanlist=[],
+				graph_order=['@world', '@system', '@selected', '@profile', '@____depclean_protected_set____', '[nomerge]app-misc/neofetch-6.1.0', '[nomerge]mail-client/neomutt-20191207', '[nomerge]www-client/lynx-2.9.0_pre4', '[nomerge]x11-base/xorg-server-1.20.7', '[nomerge]app-text/xmlto-0.0.28-r1', '[nomerge]www-client/w3m-0.5.3_p20190105', '[nomerge]virtual/w3m-0'],
 			),
 
 		)


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [gentoo-commits] proj/portage:master commit in: lib/_emerge/, lib/portage/tests/resolver/
@ 2020-09-21  5:39 Zac Medico
  0 siblings, 0 replies; 13+ messages in thread
From: Zac Medico @ 2020-09-21  5:39 UTC (permalink / raw
  To: gentoo-commits

commit:     f64310749f176f8921b72ce282b4294efe81c3f0
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Sat Sep 19 21:32:41 2020 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Sun Sep 20 22:27:32 2020 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=f6431074

_slot_confict_backtrack: minimize conflict atoms (bug 743631)

Prefer choices that minimize conflict atoms, so that choices
which satisfy all parents are preferred. This reduces the
minimum necessary backtrack tries from 21 to 7 for the unit
test related to bug 743115.

Bug: https://bugs.gentoo.org/743115
Bug: https://bugs.gentoo.org/743631
Signed-off-by: Zac Medico <zmedico <AT> gentoo.org>

 lib/_emerge/depgraph.py                                        | 6 ++++++
 lib/portage/tests/resolver/test_slot_operator_missed_update.py | 2 +-
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
index 3f864aefc..7281d8692 100644
--- a/lib/_emerge/depgraph.py
+++ b/lib/_emerge/depgraph.py
@@ -1797,6 +1797,12 @@ class depgraph:
 				if parent_atom not in parent_atoms)
 			backtrack_data.append((to_be_masked, conflict_atoms))
 
+		# Prefer choices that minimize conflict atoms. This is intended
+		# to take precedence over the earlier package version sort. The
+		# package version sort is still needed or else choices for the
+		# testOverlapSlotConflict method of VirtualMinimizeChildrenTestCase
+		# become non-deterministic.
+		backtrack_data.sort(key=lambda item: len(item[1]))
 		to_be_masked = backtrack_data[-1][0]
 
 		self._dynamic_config._backtrack_infos.setdefault(

diff --git a/lib/portage/tests/resolver/test_slot_operator_missed_update.py b/lib/portage/tests/resolver/test_slot_operator_missed_update.py
index fce012f62..1ea701003 100644
--- a/lib/portage/tests/resolver/test_slot_operator_missed_update.py
+++ b/lib/portage/tests/resolver/test_slot_operator_missed_update.py
@@ -90,7 +90,7 @@ class BacktrackMissedUpdateTestCase(TestCase):
 			# Bug 743115: missed updates trigger excessive backtracking
 			ResolverPlaygroundTestCase(
 				[">=dev-python/pypy3-7.3.2_rc", "@world"],
-				options={"--update": True, "--deep": True, "--backtrack": 25},
+				options={"--update": True, "--deep": True, "--backtrack": 10},
 				success=True,
 				mergelist=[
 					"dev-python/pypy3-7.3.2_rc2_p37-r1",


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [gentoo-commits] proj/portage:master commit in: lib/_emerge/, lib/portage/tests/resolver/
@ 2020-11-22  6:13 Zac Medico
  0 siblings, 0 replies; 13+ messages in thread
From: Zac Medico @ 2020-11-22  6:13 UTC (permalink / raw
  To: gentoo-commits

commit:     5095c2023595a75e2848f1ad3dbe25b5fb451a44
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Mon Nov 16 05:55:54 2020 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Sun Nov 22 03:19:29 2020 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=5095c202

find_smallest_cycle: enhance search prioritization

Enhance the find_smallest_cycle function to prioritize its
search so that it will minimize the use of installed packages
to break cycles. When installed packages must be used to
break cycles, it will now prefer to do this for runtime
dependencies over buildtime dependencies, since it's
preferable to build against latest versions of buildtime
dependencies whenever possible. This should solve some cases
of bug 199856 which have been triggered by unsafe reliance
on installed packages to break cycles.

The included unit test case demonstrates correct merge order
for a dependency calculation involving 6 independent cycles.
This test case fails with the master branch, due to a buildtime
dependency cycle of 3 packages being merged earlier than cycles
of 2 packages. We can generalize this to say that the master
branch may use an installed package to break an arbitrarily
sized cycle in a somewhat random location, even though that
cycle may be composed of smaller independent cycles which
would be safer to break individually.

Bug: https://bugs.gentoo.org/754903
Signed-off-by: Zac Medico <zmedico <AT> gentoo.org>

 lib/_emerge/DepPriorityNormalRange.py          |  2 +
 lib/_emerge/DepPrioritySatisfiedRange.py       | 53 ++++++++++++++++----------
 lib/_emerge/depgraph.py                        | 43 ++++++++++++---------
 lib/portage/tests/resolver/test_merge_order.py | 10 +++++
 4 files changed, 70 insertions(+), 38 deletions(-)

diff --git a/lib/_emerge/DepPriorityNormalRange.py b/lib/_emerge/DepPriorityNormalRange.py
index 5f3f3da70..10f205a3b 100644
--- a/lib/_emerge/DepPriorityNormalRange.py
+++ b/lib/_emerge/DepPriorityNormalRange.py
@@ -14,6 +14,7 @@ class DepPriorityNormalRange:
 	"""
 	MEDIUM      = 3
 	MEDIUM_SOFT = 2
+	MEDIUM_POST = 2
 	SOFT        = 1
 	NONE        = 0
 
@@ -37,6 +38,7 @@ class DepPriorityNormalRange:
 
 	ignore_medium      = _ignore_runtime
 	ignore_medium_soft = _ignore_runtime_post
+	ignore_medium_post = _ignore_runtime_post
 	ignore_soft        = _ignore_optional
 
 DepPriorityNormalRange.ignore_priority = (

diff --git a/lib/_emerge/DepPrioritySatisfiedRange.py b/lib/_emerge/DepPrioritySatisfiedRange.py
index e056e676f..fb0d7db4e 100644
--- a/lib/_emerge/DepPrioritySatisfiedRange.py
+++ b/lib/_emerge/DepPrioritySatisfiedRange.py
@@ -8,17 +8,18 @@ class DepPrioritySatisfiedRange:
 
 	not satisfied and buildtime                    HARD
 	not satisfied and runtime              7       MEDIUM
-	not satisfied and runtime_post         6       MEDIUM_SOFT
-	satisfied and buildtime_slot_op        5       SOFT
-	satisfied and buildtime                4       SOFT
-	satisfied and runtime                  3       SOFT
-	satisfied and runtime_post             2       SOFT
+	satisfied and buildtime_slot_op        6       MEDIUM_SOFT
+	satisfied and buildtime                5       MEDIUM_SOFT
+	satisfied and runtime                  4       MEDIUM_SOFT
+	runtime_post                           3       MEDIUM_POST
+	satisfied and runtime_post             2       MEDIUM_POST
 	optional                               1       SOFT
 	(none of the above)                    0       NONE
 	"""
 	MEDIUM      = 7
 	MEDIUM_SOFT = 6
-	SOFT        = 5
+	MEDIUM_POST = 3
+	SOFT        = 1
 	NONE        = 0
 
 	@classmethod
@@ -35,42 +36,51 @@ class DepPrioritySatisfiedRange:
 			return True
 		if not priority.satisfied:
 			return False
+		if priority.buildtime or priority.runtime:
+			return False
 		return bool(priority.runtime_post)
 
 	@classmethod
-	def _ignore_satisfied_runtime(cls, priority):
+	def _ignore_runtime_post(cls, priority):
 		if priority.__class__ is not DepPriority:
 			return False
 		if priority.optional:
 			return True
-		if not priority.satisfied:
+		if priority.buildtime or priority.runtime:
 			return False
-		return not priority.buildtime
+		return bool(priority.runtime_post)
 
 	@classmethod
-	def _ignore_satisfied_buildtime(cls, priority):
+	def _ignore_satisfied_runtime(cls, priority):
 		if priority.__class__ is not DepPriority:
 			return False
 		if priority.optional:
 			return True
-		if priority.buildtime_slot_op:
+		if priority.buildtime:
 			return False
+		if not priority.runtime:
+			return True
 		return bool(priority.satisfied)
 
 	@classmethod
-	def _ignore_satisfied_buildtime_slot_op(cls, priority):
+	def _ignore_satisfied_buildtime(cls, priority):
 		if priority.__class__ is not DepPriority:
 			return False
-		return bool(priority.optional or \
-			priority.satisfied)
+		if priority.optional:
+			return True
+		if priority.buildtime_slot_op:
+			return False
+		return bool(priority.satisfied)
 
 	@classmethod
-	def _ignore_runtime_post(cls, priority):
+	def _ignore_satisfied_buildtime_slot_op(cls, priority):
 		if priority.__class__ is not DepPriority:
 			return False
-		return bool(priority.optional or \
-			priority.satisfied or \
-			priority.runtime_post)
+		if priority.optional:
+			return True
+		if priority.satisfied:
+			return True
+		return not priority.buildtime and not priority.runtime
 
 	@classmethod
 	def _ignore_runtime(cls, priority):
@@ -81,17 +91,18 @@ class DepPrioritySatisfiedRange:
 			not priority.buildtime)
 
 	ignore_medium      = _ignore_runtime
-	ignore_medium_soft = _ignore_runtime_post
-	ignore_soft        = _ignore_satisfied_buildtime
+	ignore_medium_soft = _ignore_satisfied_buildtime_slot_op
+	ignore_medium_post = _ignore_runtime_post
+	ignore_soft        = _ignore_optional
 
 
 DepPrioritySatisfiedRange.ignore_priority = (
 	None,
 	DepPrioritySatisfiedRange._ignore_optional,
 	DepPrioritySatisfiedRange._ignore_satisfied_runtime_post,
+	DepPrioritySatisfiedRange._ignore_runtime_post,
 	DepPrioritySatisfiedRange._ignore_satisfied_runtime,
 	DepPrioritySatisfiedRange._ignore_satisfied_buildtime,
 	DepPrioritySatisfiedRange._ignore_satisfied_buildtime_slot_op,
-	DepPrioritySatisfiedRange._ignore_runtime_post,
 	DepPrioritySatisfiedRange._ignore_runtime
 )

diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
index a994caea7..d10474ab3 100644
--- a/lib/_emerge/depgraph.py
+++ b/lib/_emerge/depgraph.py
@@ -7905,7 +7905,7 @@ class depgraph:
 								if check_asap_parent:
 									for node in nodes:
 										parents = mygraph.parent_nodes(node,
-											ignore_priority=DepPrioritySatisfiedRange.ignore_soft)
+											ignore_priority=DepPrioritySatisfiedRange.ignore_medium_soft)
 										if any(x in asap_nodes for x in parents):
 											selected_nodes = [node]
 											break
@@ -7921,7 +7921,7 @@ class depgraph:
 
 			if not selected_nodes:
 
-				def find_smallest_cycle(mergeable_nodes, priority_ranges):
+				def find_smallest_cycle(mergeable_nodes, local_priority_range):
 					if prefer_asap and asap_nodes:
 						nodes = asap_nodes
 					else:
@@ -7938,18 +7938,27 @@ class depgraph:
 					# these smaller independent cycles.
 					smallest_cycle = None
 					ignore_priority = None
-					for node in nodes:
-						if not mygraph.parent_nodes(node):
-							continue
-						for local_priority_range in priority_ranges:
+
+					# Sort nodes for deterministic results.
+					nodes = sorted(nodes)
+					for priority in (local_priority_range.ignore_priority[i] for i in range(
+						local_priority_range.MEDIUM_POST,
+						local_priority_range.MEDIUM_SOFT + 1)):
+						for node in nodes:
+							if not mygraph.parent_nodes(node):
+								continue
 							selected_nodes = set()
-							if gather_deps(local_priority_range.ignore_medium_soft,
+							if gather_deps(priority,
 								mergeable_nodes, selected_nodes, node):
 								if smallest_cycle is None or \
 									len(selected_nodes) < len(smallest_cycle):
 									smallest_cycle = selected_nodes
-									ignore_priority = local_priority_range.ignore_medium_soft
-								break
+									ignore_priority = priority
+
+						# Exit this loop with the lowest possible priority, which
+						# minimizes the use of installed packages to break cycles.
+						if smallest_cycle is not None:
+							break
 
 					return smallest_cycle, ignore_priority
 
@@ -7963,7 +7972,7 @@ class depgraph:
 				for local_priority_range in priority_ranges:
 					mergeable_nodes = set(get_nodes(ignore_priority=local_priority_range.ignore_medium))
 					if mergeable_nodes:
-						selected_nodes, ignore_priority = find_smallest_cycle(mergeable_nodes, priority_ranges)
+						selected_nodes, ignore_priority = find_smallest_cycle(mergeable_nodes, local_priority_range)
 						if selected_nodes:
 							break
 
@@ -8001,19 +8010,19 @@ class depgraph:
 									(selected_nodes[0],), noiselevel=-1)
 
 			if selected_nodes and ignore_priority is not None:
-				# Try to merge ignored medium_soft deps as soon as possible
+				# Try to merge ignored medium_post deps as soon as possible
 				# if they're not satisfied by installed packages.
 				for node in selected_nodes:
 					children = set(mygraph.child_nodes(node))
 					soft = children.difference(
-						mygraph.child_nodes(node,
-						ignore_priority=DepPrioritySatisfiedRange.ignore_soft))
-					medium_soft = children.difference(
 						mygraph.child_nodes(node,
 							ignore_priority = \
-							DepPrioritySatisfiedRange.ignore_medium_soft))
-					medium_soft -= soft
-					for child in medium_soft:
+							DepPrioritySatisfiedRange.ignore_soft))
+					medium_post = children.difference(
+						mygraph.child_nodes(node,
+						ignore_priority=DepPrioritySatisfiedRange.ignore_medium_post))
+					medium_post -= soft
+					for child in medium_post:
 						if child in selected_nodes:
 							continue
 						if child in asap_nodes:

diff --git a/lib/portage/tests/resolver/test_merge_order.py b/lib/portage/tests/resolver/test_merge_order.py
index 0003cd7d8..f81fd2f6f 100644
--- a/lib/portage/tests/resolver/test_merge_order.py
+++ b/lib/portage/tests/resolver/test_merge_order.py
@@ -490,6 +490,16 @@ class MergeOrderTestCase(TestCase):
 				success=True,
 				all_permutations = True,
 				mergelist = ['x11-base/xorg-server-1.14.1', 'media-libs/mesa-9.1.3']),
+			# Test prioritization of the find_smallest_cycle function, which should
+			# minimize the use of installed packages to break cycles. If installed
+			# packages must be used to break cycles, then it should prefer to do this
+			# for runtime dependencies over buildtime dependencies. If a package needs
+			# to be uninstalled in order to solve a blocker, then it should prefer to
+			# do this before it uses an installed package to break a cycle.
+			ResolverPlaygroundTestCase(
+				["app-misc/some-app-a", "app-misc/some-app-b", "app-misc/some-app-c", "app-misc/circ-buildtime-a", "app-misc/blocker-buildtime-unbuilt-a", "media-libs/mesa", "x11-base/xorg-server", "app-misc/circ-direct-a", "app-misc/circ-direct-b", "app-misc/circ-satisfied-a", "app-misc/circ-satisfied-b", "app-misc/circ-satisfied-c"],
+				success = True,
+				mergelist = ['app-misc/circ-post-runtime-a-1', 'app-misc/circ-post-runtime-c-1', 'app-misc/circ-post-runtime-b-1', 'app-misc/some-app-b-1', 'app-misc/circ-runtime-a-1', 'app-misc/circ-runtime-b-1', 'app-misc/circ-runtime-c-1', 'app-misc/some-app-a-1', 'app-misc/blocker-buildtime-unbuilt-a-1', '[uninstall]app-misc/installed-blocker-a-1', '!app-misc/installed-blocker-a', 'app-misc/circ-direct-a-1', 'app-misc/circ-direct-b-1', 'x11-base/xorg-server-1.14.1', 'media-libs/mesa-9.1.3', 'app-misc/circ-buildtime-a-1', 'app-misc/circ-buildtime-b-1', 'app-misc/circ-buildtime-c-1', 'app-misc/some-app-c-1', 'app-misc/circ-satisfied-a-1', 'app-misc/circ-satisfied-b-1', 'app-misc/circ-satisfied-c-1']),
 		)
 
 		playground = ResolverPlayground(ebuilds=ebuilds, installed=installed)


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [gentoo-commits] proj/portage:master commit in: lib/_emerge/, lib/portage/tests/resolver/
@ 2023-06-16  3:34 Sam James
  0 siblings, 0 replies; 13+ messages in thread
From: Sam James @ 2023-06-16  3:34 UTC (permalink / raw
  To: gentoo-commits

commit:     44afa8445dc46464200fe46c1e09e0c7475067bf
Author:     YiFei Zhu <zhuyifei1999 <AT> gmail <DOT> com>
AuthorDate: Mon Jun 12 02:23:09 2023 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Fri Jun 16 03:34:46 2023 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=44afa844

depgraph: Don't ignore downgrades as missed_updates

Missed updates can also come in the form of package downgrades,
when, for example, there are keyword changes. They can cause
rebuilds, and these rebuilds may be not possible due to reasons such
as keywords or masks. In this case, prior to this patch, portage
would cancel the downgrade, but the rebuilds would be requested
endlessly, because bug 439688's backtrack code does not trigger.

To reproduce, on an ACCEPT_KEYWORDS=~amd64 machine, emerge
=dev-libs/openssl=3.0.9, dev-util/rustup, and something else that
depends on openssl. Then un-accept ~amd64 for openssl and rustup.
Prior to this patch, a @world upgrade would cause:

  These are the packages that would be merged, in order:

  Calculating dependencies... done!

  [ebuild  rR    ] dev-libs/libevent-2.1.12-r1:0/2.1-7::gentoo
  [ebuild  rR    ] net-misc/rsync-3.2.7-r2::gentoo
  [...]

  Total: 71 packages (71 reinstalls), Size of downloads: 0 KiB

There are no packages marked "R", only "rR". There are no section
labeled "The following packages are causing rebuilds:" either.

After this patch, we have:

  These are the packages that would be merged, in order:

  Calculating dependencies... done!

  Total: 0 packages, Size of downloads: 0 KiB

  WARNING: One or more updates/rebuilds have been skipped due to a dependency conflict:

  dev-libs/openssl:0

    (dev-libs/openssl-1.1.1u:0/1.1::gentoo, ebuild scheduled for merge)
      dev-libs/openssl:0/3= required by (dev-util/rustup-1.25.2:0/0::gentoo, installed)

I also updated the test from the previous patch to account for
this change. No other tests seems affected.

Bug: https://bugs.gentoo.org/439688
Bug: https://bugs.gentoo.org/622270
Signed-off-by: YiFei Zhu <zhuyifei1999 <AT> gmail.com>
Closes: https://github.com/gentoo/portage/pull/1053
Signed-off-by: Sam James <sam <AT> gentoo.org>

 lib/_emerge/depgraph.py                                        | 4 +---
 lib/portage/tests/resolver/test_slot_conflict_blocked_prune.py | 2 +-
 2 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
index 77133e99c..60e57b226 100644
--- a/lib/_emerge/depgraph.py
+++ b/lib/_emerge/depgraph.py
@@ -1287,9 +1287,7 @@ class depgraph:
                 pkg.root, pkg.slot_atom
             ):
                 any_selected = True
-                if chosen_pkg > pkg or (
-                    not chosen_pkg.installed and chosen_pkg.version == pkg.version
-                ):
+                if not chosen_pkg.installed and chosen_pkg.version == pkg.version:
                     missed_update = False
                     break
             if any_selected and missed_update:

diff --git a/lib/portage/tests/resolver/test_slot_conflict_blocked_prune.py b/lib/portage/tests/resolver/test_slot_conflict_blocked_prune.py
index 14e98cd00..b23126d5f 100644
--- a/lib/portage/tests/resolver/test_slot_conflict_blocked_prune.py
+++ b/lib/portage/tests/resolver/test_slot_conflict_blocked_prune.py
@@ -63,7 +63,7 @@ class SlotConflictBlockedPruneTestCase(TestCase):
                 ["@world"],
                 options={"--deep": True, "--update": True, "--verbose": True},
                 success=True,
-                mergelist=["x11-base/xwayland-23.1.1"],
+                mergelist=[],
             ),
         )
 


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [gentoo-commits] proj/portage:master commit in: lib/_emerge/, lib/portage/tests/resolver/
@ 2023-06-16  3:34 Sam James
  0 siblings, 0 replies; 13+ messages in thread
From: Sam James @ 2023-06-16  3:34 UTC (permalink / raw
  To: gentoo-commits

commit:     224207c7d1988a354d004507bb7ecfb90b4ef097
Author:     YiFei Zhu <zhuyifei1999 <AT> gmail <DOT> com>
AuthorDate: Tue Jun 13 00:47:52 2023 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Fri Jun 16 03:34:46 2023 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=224207c7

depgraph: Do not allow slotted deps to be satisfied by wrong slots

This may be part of what caused the "perl rebuild bug". The
"priority" of perl, when seen by perl modules, may have "satisfied"
set to an installed perl of a wrong slot. Compounding this factor
with bug #756199 where find_smallest_cycle would select a single
node, in this case because the dependency of perl is satisfied and
the priority then gets ignored, the "cycle" becomes a perl module
alone and gets rebuilt early.

I also updated the test from the previous patch to account for
this change. No other tests seems affected.

For a larger scale test, I reproduced this initially with a stage3
chroot from a Jan 1 2022 stage3 snapshot, and testing in an
equivalent dockerfile would work too:

  FROM gentoo/stage3:amd64-openrc-20220101
  RUN emerge-webrsync
  COPY . /portage

Before this patch (USE flags omitted):

  # cd /portage && bin/emerge -puDN @world 2>&1 | grep -i perl
  [ebuild     U  ] app-admin/perl-cleaner-2.30-r1 [2.30]
  [ebuild  rR    ] virtual/perl-File-Temp-0.231.100
  [ebuild  rR    ] dev-perl/Locale-gettext-1.70.0-r1
  [ebuild  rR    ] dev-perl/MIME-Charset-1.12.2-r1
  [ebuild  rR    ] dev-perl/Module-Build-0.423.100
  [ebuild  rR    ] dev-perl/Text-CharWidth-0.40.0-r2
  [ebuild     U  ] dev-lang/perl-5.36.0-r2 [5.34.0-r3]
  [ebuild  N     ] virtual/perl-CPAN-2.330.0
  [ebuild     U  ] virtual/perl-ExtUtils-MakeMaker-7.640.0 [7.620.0]
  [ebuild     U  ] virtual/perl-File-Spec-3.840.0 [3.800.0]
  [...]

After this patch:

  # cd /portage && bin/emerge -puDN @world 2>&1 | grep -i perl
  [ebuild     U  ] app-admin/perl-cleaner-2.30-r1 [2.30]
  [ebuild     U  ] dev-lang/perl-5.36.0-r2:0/5.36 [5.34.0-r3:0/5.34]
  [ebuild  N     ] virtual/perl-CPAN-2.330.0
  [ebuild     U  ] virtual/perl-ExtUtils-MakeMaker-7.640.0 [7.620.0]
  [ebuild     U  ] virtual/perl-Data-Dumper-2.184.0 [2.179.0]
  [ebuild     U  ] virtual/perl-File-Spec-3.840.0 [3.800.0]
  [ebuild     U  ] virtual/perl-Test-Harness-3.440.0-r1 [3.430.0]
  [ebuild  rR    ] dev-perl/Pod-Parser-1.630.0-r1
  [ebuild  rR    ] dev-perl/Text-CharWidth-0.40.0-r2
  [ebuild  rR    ] dev-perl/Text-WrapI18N-0.60.0-r2
  [...]

Bug: https://bugs.gentoo.org/463976
Bug: https://bugs.gentoo.org/592880
Bug: https://bugs.gentoo.org/596664
Bug: https://bugs.gentoo.org/631490
Bug: https://bugs.gentoo.org/764365
Bug: https://bugs.gentoo.org/793992
Signed-off-by: YiFei Zhu <zhuyifei1999 <AT> gmail.com>
Closes: https://github.com/gentoo/portage/pull/1055
Signed-off-by: Sam James <sam <AT> gentoo.org>

 lib/_emerge/depgraph.py                             | 18 ++++++++++++++++++
 lib/portage/tests/resolver/test_perl_rebuild_bug.py |  4 ++--
 2 files changed, 20 insertions(+), 2 deletions(-)

diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
index 60e57b226..28acfed9d 100644
--- a/lib/_emerge/depgraph.py
+++ b/lib/_emerge/depgraph.py
@@ -4042,6 +4042,16 @@ class depgraph:
                         inst_pkg, modified_use=self._pkg_use_enabled(inst_pkg)
                     )
                 ]
+                # Do not allow slotted deps to be satisfied by wrong slots.
+                # Otherwise, slot-operator-dependent packages may rebuild
+                # before the slotted package they are dependent on.
+                if child and atom.slot_operator == "=":
+                    inst_pkgs = [
+                        inst_pkg
+                        for inst_pkg in inst_pkgs
+                        if inst_pkg.slot == child.slot
+                        and inst_pkg.sub_slot == child.sub_slot
+                    ]
                 if inst_pkgs:
                     for inst_pkg in inst_pkgs:
                         if self._pkg_visibility_check(inst_pkg):
@@ -4161,6 +4171,14 @@ class depgraph:
                             inst_pkg, modified_use=self._pkg_use_enabled(inst_pkg)
                         )
                     ]
+                    # Do not allow slotted deps to be satisfied by wrong slots.
+                    if child and atom.slot_operator == "=":
+                        inst_pkgs = [
+                            inst_pkg
+                            for inst_pkg in inst_pkgs
+                            if inst_pkg.slot == child.slot
+                            and inst_pkg.sub_slot == child.sub_slot
+                        ]
                     if inst_pkgs:
                         for inst_pkg in inst_pkgs:
                             if self._pkg_visibility_check(inst_pkg):

diff --git a/lib/portage/tests/resolver/test_perl_rebuild_bug.py b/lib/portage/tests/resolver/test_perl_rebuild_bug.py
index 928fd47d7..7e376f396 100644
--- a/lib/portage/tests/resolver/test_perl_rebuild_bug.py
+++ b/lib/portage/tests/resolver/test_perl_rebuild_bug.py
@@ -94,15 +94,15 @@ class PerlRebuildBugTestCase(TestCase):
                 ambiguous_merge_order=True,
                 merge_order_assertions=(
                     (
-                        "dev-perl/Locale-gettext-1.70.0-r1",
                         "dev-lang/perl-5.36.0-r2",
+                        "dev-perl/Locale-gettext-1.70.0-r1",
                     ),
                 ),
                 mergelist=[
-                    "dev-perl/Locale-gettext-1.70.0-r1",
                     "sys-devel/automake-1.16.5",
                     "sys-libs/zlib-1.2.13-r1",
                     "dev-lang/perl-5.36.0-r2",
+                    "dev-perl/Locale-gettext-1.70.0-r1",
                     "sys-apps/help2man-1.49.3",
                 ],
             ),


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [gentoo-commits] proj/portage:master commit in: lib/_emerge/, lib/portage/tests/resolver/
@ 2023-11-19 17:56 Zac Medico
  0 siblings, 0 replies; 13+ messages in thread
From: Zac Medico @ 2023-11-19 17:56 UTC (permalink / raw
  To: gentoo-commits

commit:     9206d5a75ecd2d9ae0fe63e57d28aa8061b5927e
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Sat Nov 18 17:07:59 2023 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Sun Nov 19 04:15:22 2023 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=9206d5a7

find_smallest_cycle: Increase ignore_priority to find smaller cycles

Fix AlternativesGzipTestCase by increasing ignore_priority in
order to find smaller cycles. This causes some changes in merge
order for MergeOrderTestCase, but they appear to be acceptable
since they prevent temporarily unsatisfied RDEPEND by relying
on satisfied installed build-time dependencies.

Add a test case for bug 690436, since the change to merge order
in MergeOrderTestCase is related (see commit 680276cc4d4f).

Bug: https://bugs.gentoo.org/690436
Bug: https://bugs.gentoo.org/917259
Signed-off-by: Zac Medico <zmedico <AT> gentoo.org>

 lib/_emerge/depgraph.py                            |   7 +-
 lib/portage/tests/resolver/meson.build             |   1 +
 .../tests/resolver/test_alternatives_gzip.py       |   7 +-
 lib/portage/tests/resolver/test_merge_order.py     |  24 ++--
 .../tests/resolver/test_rebuild_ghostscript.py     | 160 +++++++++++++++++++++
 5 files changed, 180 insertions(+), 19 deletions(-)

diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
index e4305c18c9..0d3b37c698 100644
--- a/lib/_emerge/depgraph.py
+++ b/lib/_emerge/depgraph.py
@@ -9345,9 +9345,10 @@ class depgraph:
                                     smallest_cycle = selected_nodes
                                     ignore_priority = priority
 
-                        # Exit this loop with the lowest possible priority, which
-                        # minimizes the use of installed packages to break cycles.
-                        if smallest_cycle is not None:
+                        if smallest_cycle is not None and len(smallest_cycle) == 1:
+                            # The cycle can't get any smaller than this,
+                            # so there is no need to search further since
+                            # we try to minimize ignore_priority.
                             break
 
                     return smallest_cycle, ignore_priority

diff --git a/lib/portage/tests/resolver/meson.build b/lib/portage/tests/resolver/meson.build
index 7d2bd367d4..770027ac47 100644
--- a/lib/portage/tests/resolver/meson.build
+++ b/lib/portage/tests/resolver/meson.build
@@ -49,6 +49,7 @@ py.install_sources(
         'test_profile_default_eapi.py',
         'test_profile_package_set.py',
         'test_rebuild.py',
+        'test_rebuild_ghostscript.py',
         'test_regular_slot_change_without_revbump.py',
         'test_required_use.py',
         'test_runtime_cycle_merge_order.py',

diff --git a/lib/portage/tests/resolver/test_alternatives_gzip.py b/lib/portage/tests/resolver/test_alternatives_gzip.py
index 602ed1756f..7cd1da25f1 100644
--- a/lib/portage/tests/resolver/test_alternatives_gzip.py
+++ b/lib/portage/tests/resolver/test_alternatives_gzip.py
@@ -1,8 +1,6 @@
 # Copyright 2023 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
-import pytest
-
 from portage.tests import TestCase
 from portage.tests.resolver.ResolverPlayground import (
     ResolverPlayground,
@@ -10,7 +8,6 @@ from portage.tests.resolver.ResolverPlayground import (
 )
 
 
-@pytest.mark.xfail()
 class AlternativesGzipTestCase(TestCase):
     def testAlternativesGzip(self):
         """
@@ -19,8 +16,8 @@ class AlternativesGzipTestCase(TestCase):
         find_smallest_cycle selects a large cycle and the topological
         sort produces poor results when leaf_nodes returns
         app-alternatives/gzip as part of a large group of nodes.
-        This problem might be solved by implementing a finer-grained
-        ignore_priority for leaf_nodes calls.
+        This problem was solved by increasing ignore_priority in order
+        to find a smaller cycle.
         """
         ebuilds = {
             "app-alternatives/gzip-1": {

diff --git a/lib/portage/tests/resolver/test_merge_order.py b/lib/portage/tests/resolver/test_merge_order.py
index 940eb3bbbe..671543ca29 100644
--- a/lib/portage/tests/resolver/test_merge_order.py
+++ b/lib/portage/tests/resolver/test_merge_order.py
@@ -382,10 +382,12 @@ class MergeOrderTestCase(TestCase):
                 ambiguous_merge_order=True,
                 # The following merge order assertion reflects optimal order for
                 # a circular relationship which is DEPEND in one direction and
-                # RDEPEND in the other.
-                merge_order_assertions=(
-                    ("app-misc/circ-buildtime-a-1", "app-misc/circ-buildtime-c-1"),
-                ),
+                # RDEPEND in the other. However, it is not respected because
+                # it would result in a temporarily broken RDEPEND, so we instead
+                # rely on satisfied installed build-time dependencies.
+                # merge_order_assertions=(
+                #    ("app-misc/circ-buildtime-a-1", "app-misc/circ-buildtime-c-1"),
+                # ),
                 mergelist=[
                     (
                         "app-misc/circ-buildtime-b-1",
@@ -699,15 +701,15 @@ class MergeOrderTestCase(TestCase):
                     "!app-misc/installed-blocker-a",
                     "app-misc/circ-direct-a-1",
                     "app-misc/circ-direct-b-1",
-                    "x11-base/xorg-server-1.14.1",
-                    "media-libs/mesa-9.1.3",
-                    "app-misc/circ-buildtime-a-1",
-                    "app-misc/circ-buildtime-b-1",
-                    "app-misc/circ-buildtime-c-1",
-                    "app-misc/some-app-c-1",
                     "app-misc/circ-satisfied-a-1",
-                    "app-misc/circ-satisfied-b-1",
                     "app-misc/circ-satisfied-c-1",
+                    "app-misc/circ-satisfied-b-1",
+                    "app-misc/circ-buildtime-c-1",
+                    "app-misc/circ-buildtime-b-1",
+                    "app-misc/circ-buildtime-a-1",
+                    "app-misc/some-app-c-1",
+                    "x11-base/xorg-server-1.14.1",
+                    "media-libs/mesa-9.1.3",
                 ],
             ),
         )

diff --git a/lib/portage/tests/resolver/test_rebuild_ghostscript.py b/lib/portage/tests/resolver/test_rebuild_ghostscript.py
new file mode 100644
index 0000000000..e1d736610e
--- /dev/null
+++ b/lib/portage/tests/resolver/test_rebuild_ghostscript.py
@@ -0,0 +1,160 @@
+# Copyright 2023 Gentoo Authors
+# 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 RebuildGhostscriptTestCase(TestCase):
+    def testRebuildGhostscript(self):
+        """
+        Test bug 703676, where app-text/libspectre was rebuilt before
+        its app-text/ghostscript-gpl DEPEND.
+        """
+        ebuilds = {
+            "app-text/ghostscript-gpl-10.01.1": {
+                "EAPI": "8",
+                "DEPEND": "gtk? ( x11-libs/gtk+:3 )",
+                "RDEPEND": "gtk? ( x11-libs/gtk+:3 )",
+                "IUSE": "gtk",
+            },
+            "app-text/ghostscript-gpl-10.01.2": {
+                "EAPI": "8",
+                "SLOT": "0/10.01",
+                "DEPEND": "dbus? ( sys-apps/dbus ) gtk? ( x11-libs/gtk+:3 )",
+                "RDEPEND": "dbus? ( sys-apps/dbus ) gtk? ( x11-libs/gtk+:3 )",
+                "IUSE": "dbus gtk",
+            },
+            "app-text/libspectre-0.2.11": {
+                "EAPI": "8",
+                "DEPEND": ">=app-text/ghostscript-gpl-9.53.0:=",
+                "RDEPEND": ">=app-text/ghostscript-gpl-9.53.0:=",
+            },
+            "app-text/libspectre-0.2.12": {
+                "EAPI": "8",
+                "DEPEND": ">=app-text/ghostscript-gpl-9.53.0:=",
+                "RDEPEND": ">=app-text/ghostscript-gpl-9.53.0:=",
+            },
+            "net-dns/avahi-0.8-r7": {
+                "EAPI": "8",
+                "DEPEND": "dbus? ( sys-apps/dbus ) gtk? ( x11-libs/gtk+:3 )",
+                "RDEPEND": "dbus? ( sys-apps/dbus ) gtk? ( x11-libs/gtk+:3 )",
+                "IUSE": "dbus gtk",
+            },
+            "net-print/cups-2.4.6": {
+                "EAPI": "8",
+                "DEPEND": "zeroconf? ( >=net-dns/avahi-0.6.31-r2[dbus] )",
+                "RDEPEND": "zeroconf? ( >=net-dns/avahi-0.6.31-r2[dbus] )",
+                "IUSE": "zeroconf",
+            },
+            "sys-apps/dbus-1.15.6": {
+                "EAPI": "8",
+            },
+            "x11-libs/gtk+-3.24.38": {
+                "EAPI": "8",
+                "SLOT": "3",
+                "DEPEND": "cups? ( >=net-print/cups-2.0 )",
+                "RDEPEND": "cups? ( >=net-print/cups-2.0 )",
+                "IUSE": "cups",
+            },
+            "x11-libs/goffice-0.10.55": {
+                "EAPI": "8",
+                "DEPEND": ">=app-text/libspectre-0.2.6:=",
+                "RDEPEND": ">=app-text/libspectre-0.2.6:=",
+            },
+        }
+
+        installed = {
+            "app-text/ghostscript-gpl-10.01.1": {
+                "EAPI": "8",
+                "DEPEND": "dbus? ( sys-apps/dbus ) gtk? ( x11-libs/gtk+:3 )",
+                "RDEPEND": "dbus? ( sys-apps/dbus ) gtk? ( x11-libs/gtk+:3 )",
+                "IUSE": "dbus gtk",
+                "USE": "dbus gtk",
+            },
+            "app-text/libspectre-0.2.11": {
+                "EAPI": "8",
+                "DEPEND": ">=app-text/ghostscript-gpl-9.53.0:0/10.01=",
+                "RDEPEND": ">=app-text/ghostscript-gpl-9.53.0:0/10.01=",
+            },
+            "net-dns/avahi-0.8-r7": {
+                "EAPI": "8",
+                "DEPEND": "dbus? ( sys-apps/dbus ) gtk? ( x11-libs/gtk+:3 )",
+                "RDEPEND": "dbus? ( sys-apps/dbus ) gtk? ( x11-libs/gtk+:3 )",
+                "IUSE": "dbus gtk",
+                "USE": "dbus gtk",
+            },
+            "net-print/cups-2.4.6": {
+                "EAPI": "8",
+                "DEPEND": "zeroconf? ( >=net-dns/avahi-0.6.31-r2[dbus] )",
+                "RDEPEND": "zeroconf? ( >=net-dns/avahi-0.6.31-r2[dbus] )",
+                "IUSE": "zeroconf",
+                "USE": "zeroconf",
+            },
+            "sys-apps/dbus-1.15.6": {
+                "EAPI": "8",
+            },
+            "x11-libs/gtk+-3.24.38": {
+                "EAPI": "8",
+                "SLOT": "3",
+                "DEPEND": "cups? ( >=net-print/cups-2.0 )",
+                "RDEPEND": "cups? ( >=net-print/cups-2.0 )",
+                "IUSE": "cups",
+                "USE": "cups",
+            },
+            "x11-libs/goffice-0.10.55": {
+                "EAPI": "8",
+                "DEPEND": ">=app-text/libspectre-0.2.6:0=",
+                "RDEPEND": ">=app-text/libspectre-0.2.6:0=",
+            },
+        }
+
+        world = [
+            "x11-libs/goffice",
+        ]
+
+        user_config = {
+            "make.conf": ('USE="cups dbus gtk zeroconf"',),
+        }
+
+        test_cases = (
+            ResolverPlaygroundTestCase(
+                ["@world"],
+                options={"--deep": True, "--update": True},
+                success=True,
+                mergelist=[
+                    "app-text/ghostscript-gpl-10.01.2",
+                    "app-text/libspectre-0.2.12",
+                ],
+            ),
+            ResolverPlaygroundTestCase(
+                ["@world"],
+                options={"--emptytree": True},
+                success=True,
+                mergelist=[
+                    "sys-apps/dbus-1.15.6",
+                    "app-text/ghostscript-gpl-10.01.2",
+                    "app-text/libspectre-0.2.12",
+                    "x11-libs/goffice-0.10.55",
+                    "net-dns/avahi-0.8-r7",
+                    "net-print/cups-2.4.6",
+                    "x11-libs/gtk+-3.24.38",
+                ],
+            ),
+        )
+
+        playground = ResolverPlayground(
+            ebuilds=ebuilds,
+            installed=installed,
+            world=world,
+            user_config=user_config,
+        )
+        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()


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [gentoo-commits] proj/portage:master commit in: lib/_emerge/, lib/portage/tests/resolver/
@ 2023-11-28  4:20 Zac Medico
  0 siblings, 0 replies; 13+ messages in thread
From: Zac Medico @ 2023-11-28  4:20 UTC (permalink / raw
  To: gentoo-commits

commit:     49e01d041c74680a81860b819daff812d83df02f
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Tue Nov 28 03:42:17 2023 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Tue Nov 28 03:42:17 2023 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=49e01d04

find_smallest_cycle: Optimize to traverse fewer nodes

If gather_deps is traversing a cycle that is greater than
or equal to the size of the current smallest_cycle, then
abort early. Also abort early if we traverse a node
encountered in a previous cycle for the same ignore_priority,
since that means the two cycles are identical.

On my laptop, this brings the emerge -pe @world time down
to 3m28.884s, compared to 10m44.268s with portage-3.0.55.

Bug: https://bugs.gentoo.org/918682
Signed-off-by: Zac Medico <zmedico <AT> gentoo.org>

 lib/_emerge/depgraph.py                            | 36 ++++++++++++++++++++--
 .../tests/resolver/test_rebuild_ghostscript.py     |  2 +-
 .../resolver/test_runtime_cycle_merge_order.py     |  7 +++--
 3 files changed, 39 insertions(+), 6 deletions(-)

diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
index e4305c18c9..29eadba3d1 100644
--- a/lib/_emerge/depgraph.py
+++ b/lib/_emerge/depgraph.py
@@ -9151,7 +9151,14 @@ class depgraph:
 
                 asap_nodes.extend(libc_pkgs)
 
-        def gather_deps(ignore_priority, mergeable_nodes, selected_nodes, node):
+        def gather_deps(
+            ignore_priority,
+            mergeable_nodes,
+            selected_nodes,
+            node,
+            smallest_cycle=None,
+            traversed_nodes=None,
+        ):
             """
             Recursively gather a group of nodes that RDEPEND on
             eachother. This ensures that they are merged as a group
@@ -9171,10 +9178,24 @@ class depgraph:
                 # RDEPENDs installed first, but ignore uninstalls
                 # (these occur when new portage blocks an older package version).
                 return False
+            if traversed_nodes is not None:
+                if node in traversed_nodes:
+                    # Identical to a previously traversed cycle.
+                    return False
+                traversed_nodes.add(node)
             selected_nodes.add(node)
+            if smallest_cycle is not None and len(selected_nodes) >= len(
+                smallest_cycle
+            ):
+                return False
             for child in mygraph.child_nodes(node, ignore_priority=ignore_priority):
                 if not gather_deps(
-                    ignore_priority, mergeable_nodes, selected_nodes, child
+                    ignore_priority,
+                    mergeable_nodes,
+                    selected_nodes,
+                    child,
+                    smallest_cycle=smallest_cycle,
+                    traversed_nodes=traversed_nodes,
                 ):
                     return False
             return True
@@ -9332,12 +9353,21 @@ class depgraph:
                             local_priority_range.MEDIUM_SOFT + 1,
                         )
                     ):
+                        # Traversed nodes for current priority
+                        traversed_nodes = set()
                         for node in nodes:
                             if not mygraph.parent_nodes(node):
                                 continue
+                            if node in traversed_nodes:
+                                continue
                             selected_nodes = set()
                             if gather_deps(
-                                priority, mergeable_nodes, selected_nodes, node
+                                priority,
+                                mergeable_nodes,
+                                selected_nodes,
+                                node,
+                                smallest_cycle=smallest_cycle,
+                                traversed_nodes=traversed_nodes,
                             ):
                                 if smallest_cycle is None or len(selected_nodes) < len(
                                     smallest_cycle

diff --git a/lib/portage/tests/resolver/test_rebuild_ghostscript.py b/lib/portage/tests/resolver/test_rebuild_ghostscript.py
index 8d7cbb1f92..88dc2b5fc3 100644
--- a/lib/portage/tests/resolver/test_rebuild_ghostscript.py
+++ b/lib/portage/tests/resolver/test_rebuild_ghostscript.py
@@ -137,9 +137,9 @@ class RebuildGhostscriptTestCase(TestCase):
                 mergelist=[
                     "sys-apps/dbus-1.15.6",
                     "x11-libs/gtk+-3.24.38",
+                    "app-text/ghostscript-gpl-10.01.2",
                     "net-print/cups-2.4.6",
                     "net-dns/avahi-0.8-r7",
-                    "app-text/ghostscript-gpl-10.01.2",
                     "app-text/libspectre-0.2.12",
                     "x11-libs/goffice-0.10.55",
                 ],

diff --git a/lib/portage/tests/resolver/test_runtime_cycle_merge_order.py b/lib/portage/tests/resolver/test_runtime_cycle_merge_order.py
index 305757ff47..a955ac3dc3 100644
--- a/lib/portage/tests/resolver/test_runtime_cycle_merge_order.py
+++ b/lib/portage/tests/resolver/test_runtime_cycle_merge_order.py
@@ -56,8 +56,11 @@ class RuntimeCycleMergeOrderTestCase(TestCase):
                     ("app-misc/leaf-b-1", "app-misc/leaf-d-1", "app-misc/leaf-e-1"),
                     ("app-misc/branch-d-1", "app-misc/branch-e-1"),
                     "app-misc/runtime-c-1",
-                    ("app-misc/runtime-cycle-c-1", "app-misc/branch-c-1"),
-                    "app-misc/branch-b-1",
+                    (
+                        "app-misc/branch-b-1",
+                        "app-misc/runtime-cycle-c-1",
+                        "app-misc/branch-c-1",
+                    ),
                     ("app-misc/runtime-cycle-b-1", "app-misc/plugin-b-1"),
                     "app-misc/plugins-consumer-1",
                 ],


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [gentoo-commits] proj/portage:master commit in: lib/_emerge/, lib/portage/tests/resolver/
@ 2023-11-28 22:26 Sam James
  0 siblings, 0 replies; 13+ messages in thread
From: Sam James @ 2023-11-28 22:26 UTC (permalink / raw
  To: gentoo-commits

commit:     2e298ea7ba36801a1cfba6e4cbfc16a7c05ee73d
Author:     Sam James <sam <AT> gentoo <DOT> org>
AuthorDate: Tue Nov 28 06:22:44 2023 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Tue Nov 28 22:07:46 2023 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=2e298ea7

DepPriority{Normal,Satisfied}Range: strengthen _ignore_runtime for runtime slot operators

In the reported bug, net-misc/curl gets merged (binary), then dev-util/cmake gets
bulit (from source) which fails because one of the built curl's dependencies
(net-libs/nghttp2) is missing:
```
[binary   R    ] net-misc/curl-8.4.0::test_repo  USE="http2%*" 0 KiB
[ebuild     U  ] dev-util/cmake-3.27.8::test_repo [3.26.5-r2::test_repo] 0 KiB
[ebuild  N     ] net-libs/nghttp2-1.57.0::test_repo  0 KiB
```

Zac had the idea [0] of strengthening _ignore_runtime to consider runtime slot deps
as well, so we now get:
```
[ebuild     U  ] dev-util/cmake-3.27.8::test_repo [3.26.5-r2::test_repo] 0 KiB
[ebuild  N     ] net-libs/nghttp2-1.57.0::test_repo  0 KiB
[binary   R    ] net-misc/curl-8.4.0::test_repo  USE="http2%*" 0 KiB
```

For DepPrioritySatisfiedRange, we now allow ignoring the dep if:
* it's either a satisfied runtime slot dep, or it's not a runtime slot dep at all, and
* the dep is satisfied or it's optional/not a build time dep.

(i.e. we now prevent ignoring the slot dep unless it's satisfied.)

For DepPriorityNormalRange, we now allow ignoring the dep if:
* it's not a runtime slot dep, and
* it's optional, or
* it's not a buildtime dep.

(i.e. we now prevent ignoring the slot dep.)

We then realise we can't ignore curl's dep on nghttp2 and come up with a better order.

[0] https://github.com/gentoo/portage/pull/1193#issuecomment-1829178126

Bug: https://bugs.gentoo.org/918683
Thanks-to: Zac Medico <zmedico <AT> gentoo.org>
Signed-off-by: Sam James <sam <AT> gentoo.org>

 lib/_emerge/DepPriorityNormalRange.py                       |  8 +++++++-
 lib/_emerge/DepPrioritySatisfiedRange.py                    | 13 +++++++++++--
 .../tests/resolver/test_runtime_cycle_merge_order.py        |  9 ++++-----
 3 files changed, 22 insertions(+), 8 deletions(-)

diff --git a/lib/_emerge/DepPriorityNormalRange.py b/lib/_emerge/DepPriorityNormalRange.py
index a85e1b9c14..d7e4381b47 100644
--- a/lib/_emerge/DepPriorityNormalRange.py
+++ b/lib/_emerge/DepPriorityNormalRange.py
@@ -37,7 +37,13 @@ class DepPriorityNormalRange:
     def _ignore_runtime(cls, priority):
         if priority.__class__ is not DepPriority:
             return False
-        return bool(priority.optional or not priority.buildtime)
+        # If we ever allow "optional" runtime_slot_op, we'll need
+        # to adjust this appropriately. But only build time dependencies
+        # are optional right now, so it's not an issue as-is.
+        return bool(
+            not priority.runtime_slot_op
+            and (priority.optional or not priority.buildtime)
+        )
 
     ignore_medium = _ignore_runtime
     ignore_medium_soft = _ignore_runtime_post

diff --git a/lib/_emerge/DepPrioritySatisfiedRange.py b/lib/_emerge/DepPrioritySatisfiedRange.py
index 0633a5e1c2..0d42e7613d 100644
--- a/lib/_emerge/DepPrioritySatisfiedRange.py
+++ b/lib/_emerge/DepPrioritySatisfiedRange.py
@@ -1,4 +1,4 @@
-# Copyright 1999-2013 Gentoo Foundation
+# Copyright 1999-2023 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 from _emerge.DepPriority import DepPriority
@@ -89,7 +89,16 @@ class DepPrioritySatisfiedRange:
     def _ignore_runtime(cls, priority):
         if priority.__class__ is not DepPriority:
             return False
-        return bool(priority.satisfied or priority.optional or not priority.buildtime)
+        # We could split this up into 2 variants (ignoring satisfied
+        # runtime_slot_op, and not) if we need more granularity for ignore_priority
+        # in future.
+        return bool(
+            (
+                (not priority.runtime_slot_op)
+                or (priority.satisfied and priority.runtime_slot_op)
+            )
+            and (priority.satisfied or priority.optional or not priority.buildtime)
+        )
 
     ignore_medium = _ignore_runtime
     ignore_medium_soft = _ignore_satisfied_buildtime_slot_op

diff --git a/lib/portage/tests/resolver/test_runtime_cycle_merge_order.py b/lib/portage/tests/resolver/test_runtime_cycle_merge_order.py
index 26850ccad2..ed329aa097 100644
--- a/lib/portage/tests/resolver/test_runtime_cycle_merge_order.py
+++ b/lib/portage/tests/resolver/test_runtime_cycle_merge_order.py
@@ -1,4 +1,4 @@
-# Copyright 2016 Gentoo Foundation
+# Copyright 2016-2023 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 from portage.tests import TestCase
@@ -7,8 +7,6 @@ from portage.tests.resolver.ResolverPlayground import (
     ResolverPlaygroundTestCase,
 )
 
-import pytest
-
 
 class RuntimeCycleMergeOrderTestCase(TestCase):
     def testRuntimeCycleMergeOrder(self):
@@ -77,7 +75,6 @@ class RuntimeCycleMergeOrderTestCase(TestCase):
         finally:
             playground.cleanup()
 
-    @pytest.mark.xfail()
     def testBuildtimeRuntimeCycleMergeOrder(self):
         installed = {
             "dev-util/cmake-3.26.5-r2": {
@@ -192,10 +189,12 @@ class RuntimeCycleMergeOrderTestCase(TestCase):
                     "--usepkg": True,
                 },
                 success=True,
+                # It would also work to punt the dev-util/cmake upgrade
+                # until the end, given it's already installed.
                 mergelist=[
+                    "dev-util/cmake-3.27.8",
                     "net-libs/nghttp2-1.57.0",
                     "[binary]net-misc/curl-8.4.0",
-                    "dev-util/cmake-3.27.8",
                 ],
             ),
         )


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [gentoo-commits] proj/portage:master commit in: lib/_emerge/, lib/portage/tests/resolver/
@ 2023-11-28 22:42 Zac Medico
  0 siblings, 0 replies; 13+ messages in thread
From: Zac Medico @ 2023-11-28 22:42 UTC (permalink / raw
  To: gentoo-commits

commit:     3487594cd8f46a5c83caaab3a9425321443e5efc
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Tue Nov 28 04:58:07 2023 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Tue Nov 28 22:41:45 2023 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=3487594c

Increase ignore_priority during topological sort for runtime cycle

Fix AlternativesGzipTestCase by increasing ignore_priority in
order to find smaller groups of leaf nodes during topological
sort for runtime cycles. This causes some changes in merge
order for MergeOrderTestCase, but they appear to be acceptable
since they prevent temporarily unsatisfied RDEPEND by relying
on satisfied installed build-time dependencies.

Bug: https://bugs.gentoo.org/917259
Signed-off-by: Zac Medico <zmedico <AT> gentoo.org>

 lib/_emerge/depgraph.py                            | 27 ++++++++++++++++------
 .../tests/resolver/test_alternatives_gzip.py       |  8 +++----
 lib/portage/tests/resolver/test_merge_order.py     | 20 ++++++++--------
 .../tests/resolver/test_rebuild_ghostscript.py     |  2 +-
 4 files changed, 35 insertions(+), 22 deletions(-)

diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
index 29eadba3d1..da37f980ad 100644
--- a/lib/_emerge/depgraph.py
+++ b/lib/_emerge/depgraph.py
@@ -9478,17 +9478,30 @@ class depgraph:
                 )
                 selected_nodes = []
                 while cycle_digraph:
+                    # Increase ignore_priority in order to find
+                    # smaller groups of leaf nodes. This solves
+                    # bug 917259 which happened because too many
+                    # leaves were selected at once.
+                    smallest_leaves = None
                     for ignore_priority in ignore_priorities:
                         leaves = cycle_digraph.leaf_nodes(
                             ignore_priority=ignore_priority
                         )
-                        if leaves:
-                            cycle_digraph.difference_update(leaves)
-                            selected_nodes.extend(leaves)
-                            break
-                    else:
-                        selected_nodes.extend(cycle_digraph)
-                        break
+                        if leaves and (
+                            smallest_leaves is None
+                            or len(leaves) < len(smallest_leaves)
+                        ):
+                            smallest_leaves = leaves
+                            if len(smallest_leaves) == 1:
+                                break
+
+                    if smallest_leaves is None:
+                        smallest_leaves = [cycle_digraph.order[-1]]
+
+                    # Only harvest one node at a time, in order to
+                    # minimize the number of ignored dependencies.
+                    cycle_digraph.remove(smallest_leaves[0])
+                    selected_nodes.append(smallest_leaves[0])
 
             if not selected_nodes and myblocker_uninstalls:
                 # An Uninstall task needs to be executed in order to

diff --git a/lib/portage/tests/resolver/test_alternatives_gzip.py b/lib/portage/tests/resolver/test_alternatives_gzip.py
index 602ed1756f..e763e84640 100644
--- a/lib/portage/tests/resolver/test_alternatives_gzip.py
+++ b/lib/portage/tests/resolver/test_alternatives_gzip.py
@@ -1,8 +1,6 @@
 # Copyright 2023 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
-import pytest
-
 from portage.tests import TestCase
 from portage.tests.resolver.ResolverPlayground import (
     ResolverPlayground,
@@ -10,7 +8,6 @@ from portage.tests.resolver.ResolverPlayground import (
 )
 
 
-@pytest.mark.xfail()
 class AlternativesGzipTestCase(TestCase):
     def testAlternativesGzip(self):
         """
@@ -19,8 +16,9 @@ class AlternativesGzipTestCase(TestCase):
         find_smallest_cycle selects a large cycle and the topological
         sort produces poor results when leaf_nodes returns
         app-alternatives/gzip as part of a large group of nodes.
-        This problem might be solved by implementing a finer-grained
-        ignore_priority for leaf_nodes calls.
+        This problem was solved by changing the topological sort to
+        increase ignore_priority in order to select a smaller number
+        of leaf nodes at a time.
         """
         ebuilds = {
             "app-alternatives/gzip-1": {

diff --git a/lib/portage/tests/resolver/test_merge_order.py b/lib/portage/tests/resolver/test_merge_order.py
index 940eb3bbbe..e6d45c847b 100644
--- a/lib/portage/tests/resolver/test_merge_order.py
+++ b/lib/portage/tests/resolver/test_merge_order.py
@@ -1,4 +1,4 @@
-# Copyright 2011-2020 Gentoo Authors
+# Copyright 2011-2023 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 from portage.tests import TestCase
@@ -382,10 +382,12 @@ class MergeOrderTestCase(TestCase):
                 ambiguous_merge_order=True,
                 # The following merge order assertion reflects optimal order for
                 # a circular relationship which is DEPEND in one direction and
-                # RDEPEND in the other.
-                merge_order_assertions=(
-                    ("app-misc/circ-buildtime-a-1", "app-misc/circ-buildtime-c-1"),
-                ),
+                # RDEPEND in the other. However, it is not respected because
+                # it would result in a temporarily broken RDEPEND, so we instead
+                # rely on satisfied installed build-time dependencies.
+                # merge_order_assertions=(
+                #    ("app-misc/circ-buildtime-a-1", "app-misc/circ-buildtime-c-1"),
+                # ),
                 mergelist=[
                     (
                         "app-misc/circ-buildtime-b-1",
@@ -691,8 +693,8 @@ class MergeOrderTestCase(TestCase):
                     "app-misc/circ-post-runtime-b-1",
                     "app-misc/some-app-b-1",
                     "app-misc/circ-runtime-a-1",
-                    "app-misc/circ-runtime-b-1",
                     "app-misc/circ-runtime-c-1",
+                    "app-misc/circ-runtime-b-1",
                     "app-misc/some-app-a-1",
                     "app-misc/blocker-buildtime-unbuilt-a-1",
                     "[uninstall]app-misc/installed-blocker-a-1",
@@ -701,13 +703,13 @@ class MergeOrderTestCase(TestCase):
                     "app-misc/circ-direct-b-1",
                     "x11-base/xorg-server-1.14.1",
                     "media-libs/mesa-9.1.3",
-                    "app-misc/circ-buildtime-a-1",
-                    "app-misc/circ-buildtime-b-1",
                     "app-misc/circ-buildtime-c-1",
+                    "app-misc/circ-buildtime-b-1",
+                    "app-misc/circ-buildtime-a-1",
                     "app-misc/some-app-c-1",
                     "app-misc/circ-satisfied-a-1",
-                    "app-misc/circ-satisfied-b-1",
                     "app-misc/circ-satisfied-c-1",
+                    "app-misc/circ-satisfied-b-1",
                 ],
             ),
         )

diff --git a/lib/portage/tests/resolver/test_rebuild_ghostscript.py b/lib/portage/tests/resolver/test_rebuild_ghostscript.py
index 88dc2b5fc3..8ee3349d6f 100644
--- a/lib/portage/tests/resolver/test_rebuild_ghostscript.py
+++ b/lib/portage/tests/resolver/test_rebuild_ghostscript.py
@@ -138,8 +138,8 @@ class RebuildGhostscriptTestCase(TestCase):
                     "sys-apps/dbus-1.15.6",
                     "x11-libs/gtk+-3.24.38",
                     "app-text/ghostscript-gpl-10.01.2",
-                    "net-print/cups-2.4.6",
                     "net-dns/avahi-0.8-r7",
+                    "net-print/cups-2.4.6",
                     "app-text/libspectre-0.2.12",
                     "x11-libs/goffice-0.10.55",
                 ],


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [gentoo-commits] proj/portage:master commit in: lib/_emerge/, lib/portage/tests/resolver/
@ 2023-12-06 20:29 Zac Medico
  0 siblings, 0 replies; 13+ messages in thread
From: Zac Medico @ 2023-12-06 20:29 UTC (permalink / raw
  To: gentoo-commits

commit:     1d856747ada48f8d32c033091b1156cc655efed3
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Wed Dec  6 06:05:46 2023 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Wed Dec  6 20:23:14 2023 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=1d856747

DepPriority{Normal,Satisfied}Range: weaken _ignore_runtime for cross root

When the dependency parent is for a cross root (ROOT != /) package,
weaken _ignore_runtime in order to tolerate runtime cycles that are
less problematic for cross root packages.

The included test case fails with this error without the fix:

 * Error: circular dependencies:

(dev-libs/gmp-6.3.0:0/10.4::test_repo, binary scheduled for merge to '/tmp/tmp25nwdjn7/cross_root/') depends on
 (sys-devel/gcc-13.2.1_p20230826:0/0::test_repo, binary scheduled for merge to '/tmp/tmp25nwdjn7/cross_root/') (runtime)
  (dev-libs/gmp-6.3.0:0/10.4::test_repo, binary scheduled for merge to '/tmp/tmp25nwdjn7/cross_root/') (runtime_slot_op)

It might be possible to break this cycle
by applying the following change:
- dev-libs/gmp-6.3.0 (Change USE: -cxx)

Bug: https://bugs.gentoo.org/919174
Signed-off-by: Zac Medico <zmedico <AT> gentoo.org>

 lib/_emerge/DepPriority.py                         |   4 +-
 lib/_emerge/DepPriorityNormalRange.py              |   4 +-
 lib/_emerge/DepPrioritySatisfiedRange.py           |   1 +
 lib/_emerge/UnmergeDepPriority.py                  |   3 +-
 lib/_emerge/depgraph.py                            |  46 ++++--
 lib/portage/tests/resolver/meson.build             |   1 +
 .../tests/resolver/test_cross_dep_priority.py      | 164 +++++++++++++++++++++
 7 files changed, 208 insertions(+), 15 deletions(-)

diff --git a/lib/_emerge/DepPriority.py b/lib/_emerge/DepPriority.py
index 99d38477e2..8d282b937a 100644
--- a/lib/_emerge/DepPriority.py
+++ b/lib/_emerge/DepPriority.py
@@ -1,11 +1,11 @@
-# Copyright 1999-2013 Gentoo Foundation
+# Copyright 1999-2023 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 from _emerge.AbstractDepPriority import AbstractDepPriority
 
 
 class DepPriority(AbstractDepPriority):
-    __slots__ = ("satisfied", "optional", "ignored")
+    __slots__ = ("cross", "ignored", "optional", "satisfied")
 
     def __int__(self):
         """

diff --git a/lib/_emerge/DepPriorityNormalRange.py b/lib/_emerge/DepPriorityNormalRange.py
index d7e4381b47..cb0e6c26b1 100644
--- a/lib/_emerge/DepPriorityNormalRange.py
+++ b/lib/_emerge/DepPriorityNormalRange.py
@@ -1,4 +1,4 @@
-# Copyright 1999-2011 Gentoo Foundation
+# Copyright 1999-2023 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 from _emerge.DepPriority import DepPriority
@@ -41,7 +41,7 @@ class DepPriorityNormalRange:
         # to adjust this appropriately. But only build time dependencies
         # are optional right now, so it's not an issue as-is.
         return bool(
-            not priority.runtime_slot_op
+            not (priority.runtime_slot_op and not priority.cross)
             and (priority.optional or not priority.buildtime)
         )
 

diff --git a/lib/_emerge/DepPrioritySatisfiedRange.py b/lib/_emerge/DepPrioritySatisfiedRange.py
index 0d42e7613d..b3bc90c2ff 100644
--- a/lib/_emerge/DepPrioritySatisfiedRange.py
+++ b/lib/_emerge/DepPrioritySatisfiedRange.py
@@ -96,6 +96,7 @@ class DepPrioritySatisfiedRange:
             (
                 (not priority.runtime_slot_op)
                 or (priority.satisfied and priority.runtime_slot_op)
+                or priority.cross
             )
             and (priority.satisfied or priority.optional or not priority.buildtime)
         )

diff --git a/lib/_emerge/UnmergeDepPriority.py b/lib/_emerge/UnmergeDepPriority.py
index ff81eff46f..d818bad1b8 100644
--- a/lib/_emerge/UnmergeDepPriority.py
+++ b/lib/_emerge/UnmergeDepPriority.py
@@ -1,4 +1,4 @@
-# Copyright 1999-2013 Gentoo Foundation
+# Copyright 1999-2023 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 from _emerge.AbstractDepPriority import AbstractDepPriority
@@ -6,6 +6,7 @@ from _emerge.AbstractDepPriority import AbstractDepPriority
 
 class UnmergeDepPriority(AbstractDepPriority):
     __slots__ = (
+        "cross",
         "ignored",
         "optional",
         "satisfied",

diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
index 9b09701021..59c78c7354 100644
--- a/lib/_emerge/depgraph.py
+++ b/lib/_emerge/depgraph.py
@@ -3630,7 +3630,7 @@ class depgraph:
                     blocker=False,
                     depth=depth,
                     parent=pkg,
-                    priority=self._priority(runtime=True),
+                    priority=self._priority(cross=self._cross(pkg.root), runtime=True),
                     root=pkg.root,
                 )
                 if not self._add_dep(dep, allow_unsatisfied=allow_unsatisfied):
@@ -3968,17 +3968,26 @@ class depgraph:
         # _dep_disjunctive_stack first, so that choices for build-time
         # deps influence choices for run-time deps (bug 639346).
         deps = (
-            (myroot, edepend["RDEPEND"], self._priority(runtime=True)),
+            (
+                myroot,
+                edepend["RDEPEND"],
+                self._priority(cross=self._cross(pkg.root), runtime=True),
+            ),
             (
                 self._frozen_config._running_root.root,
                 edepend["IDEPEND"],
-                self._priority(runtime=True),
+                self._priority(cross=self._cross(pkg.root), runtime=True),
+            ),
+            (
+                myroot,
+                edepend["PDEPEND"],
+                self._priority(cross=self._cross(pkg.root), runtime_post=True),
             ),
-            (myroot, edepend["PDEPEND"], self._priority(runtime_post=True)),
             (
                 depend_root,
                 edepend["DEPEND"],
                 self._priority(
+                    cross=self._cross(pkg.root),
                     buildtime=True,
                     optional=(pkg.built or ignore_depend_deps),
                     ignored=ignore_depend_deps,
@@ -3988,6 +3997,7 @@ class depgraph:
                 self._frozen_config._running_root.root,
                 edepend["BDEPEND"],
                 self._priority(
+                    cross=self._cross(pkg.root),
                     buildtime=True,
                     optional=(pkg.built or ignore_bdepend_deps),
                     ignored=ignore_bdepend_deps,
@@ -4036,7 +4046,9 @@ class depgraph:
                             self._queue_disjunctive_deps(
                                 pkg,
                                 dep_root,
-                                self._priority(runtime_post=True),
+                                self._priority(
+                                    cross=self._cross(pkg.root), runtime_post=True
+                                ),
                                 test_deps,
                             )
                         )
@@ -4044,7 +4056,9 @@ class depgraph:
                         if test_deps and not self._add_pkg_dep_string(
                             pkg,
                             dep_root,
-                            self._priority(runtime_post=True),
+                            self._priority(
+                                cross=self._cross(pkg.root), runtime_post=True
+                            ),
                             test_deps,
                             allow_unsatisfied,
                         ):
@@ -4359,7 +4373,10 @@ class depgraph:
                     return 0
 
             for atom, child in self._minimize_children(
-                pkg, self._priority(runtime=True), root_config, atoms
+                pkg,
+                self._priority(cross=self._cross(pkg.root), runtime=True),
+                root_config,
+                atoms,
             ):
                 # If this was a specially generated virtual atom
                 # from dep_check, map it back to the original, in
@@ -4369,7 +4386,7 @@ class depgraph:
                 atom = getattr(atom, "_orig_atom", atom)
 
                 # This is a GLEP 37 virtual, so its deps are all runtime.
-                mypriority = self._priority(runtime=True)
+                mypriority = self._priority(cross=self._cross(pkg.root), runtime=True)
                 if not atom.blocker:
                     inst_pkgs = [
                         inst_pkg
@@ -4616,6 +4633,13 @@ class depgraph:
             priority_constructor = DepPriority
         return priority_constructor(**kwargs)
 
+    def _cross(self, eroot):
+        """
+        Returns True if the ROOT for the given EROOT is not /,
+        or EROOT is cross-prefix.
+        """
+        return eroot != self._frozen_config._running_root.root
+
     def _dep_expand(self, root_config, atom_without_category):
         """
         @param root_config: a root config instance
@@ -5788,7 +5812,9 @@ class depgraph:
                             node_priority = priority.copy()
                     else:
                         # virtuals only have runtime deps
-                        node_priority = self._priority(runtime=True)
+                        node_priority = self._priority(
+                            cross=self._cross(node_parent.root), runtime=True
+                        )
 
                     k = Dependency(
                         atom=parent_atom,
@@ -5874,7 +5900,7 @@ class depgraph:
                 pkg._metadata.get("RDEPEND", ""),
                 myuse=self._pkg_use_enabled(pkg),
                 parent=pkg,
-                priority=self._priority(runtime=True),
+                priority=self._priority(cross=self._cross(pkg.root), runtime=True),
             )
         except InvalidDependString as e:
             if not pkg.installed:

diff --git a/lib/portage/tests/resolver/meson.build b/lib/portage/tests/resolver/meson.build
index 770027ac47..77c65a511e 100644
--- a/lib/portage/tests/resolver/meson.build
+++ b/lib/portage/tests/resolver/meson.build
@@ -21,6 +21,7 @@ py.install_sources(
         'test_circular_dependencies.py',
         'test_complete_graph.py',
         'test_complete_if_new_subslot_without_revbump.py',
+        'test_cross_dep_priority.py',
         'test_depclean.py',
         'test_depclean_order.py',
         'test_depclean_slot_unavailable.py',

diff --git a/lib/portage/tests/resolver/test_cross_dep_priority.py b/lib/portage/tests/resolver/test_cross_dep_priority.py
new file mode 100644
index 0000000000..10f2eb36e2
--- /dev/null
+++ b/lib/portage/tests/resolver/test_cross_dep_priority.py
@@ -0,0 +1,164 @@
+# Copyright 2023 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+import shutil
+import subprocess
+import os
+
+import portage
+from portage.tests import TestCase
+from portage.tests.resolver.ResolverPlayground import (
+    ResolverPlayground,
+    ResolverPlaygroundTestCase,
+)
+
+
+class CrossDepPriorityTestCase(TestCase):
+    def testCrossDepPriority(self):
+        """
+        Test bug 919174, where cross-root merge to an empty root
+        failed due to circular dependencies.
+        """
+        ebuilds = {
+            "dev-lang/python-3.11.6": {
+                "EAPI": "8",
+                "DEPEND": "sys-apps/util-linux:=",
+                "RDEPEND": "sys-apps/util-linux:=",
+            },
+            "sys-apps/util-linux-2.38.1-r2": {
+                "EAPI": "8",
+                "DEPEND": "selinux? ( >=sys-libs/libselinux-2.2.2-r4 )",
+                "RDEPEND": "selinux? ( >=sys-libs/libselinux-2.2.2-r4 )",
+                "IUSE": "selinux",
+            },
+            "sys-libs/libselinux-3.5-r1": {
+                "EAPI": "8",
+                "DEPEND": "python? ( dev-lang/python )",
+                "RDEPEND": "python? ( dev-lang/python )",
+                "IUSE": "python",
+            },
+            "dev-libs/gmp-6.3.0": {
+                "EAPI": "8",
+                "SLOT": "0/10.4",
+                "DEPEND": "cxx? ( sys-devel/gcc )",
+                "RDEPEND": "cxx? ( sys-devel/gcc )",
+                "IUSE": "cxx",
+            },
+            "sys-devel/gcc-13.2.1_p20230826": {
+                "EAPI": "8",
+                "DEPEND": ">=dev-libs/gmp-4.3.2:0=",
+                "RDEPEND": ">=dev-libs/gmp-4.3.2:0=",
+            },
+        }
+
+        installed = {
+            "dev-lang/python-3.11.6": {
+                "EAPI": "8",
+                "KEYWORDS": "x86",
+                "DEPEND": "sys-apps/util-linux:0/0=",
+                "RDEPEND": "sys-apps/util-linux:0/0=",
+            },
+            "sys-apps/util-linux-2.38.1-r2": {
+                "EAPI": "8",
+                "KEYWORDS": "x86",
+                "DEPEND": "selinux? ( >=sys-libs/libselinux-2.2.2-r4 )",
+                "RDEPEND": "selinux? ( >=sys-libs/libselinux-2.2.2-r4 )",
+                "IUSE": "selinux",
+                "USE": "selinux",
+            },
+            "sys-libs/libselinux-3.5-r1": {
+                "EAPI": "8",
+                "KEYWORDS": "x86",
+                "DEPEND": "python? ( dev-lang/python )",
+                "RDEPEND": "python? ( dev-lang/python )",
+                "IUSE": "python",
+                "USE": "python",
+            },
+            "dev-libs/gmp-6.3.0": {
+                "EAPI": "8",
+                "KEYWORDS": "x86",
+                "SLOT": "0/10.4",
+                "DEPEND": "cxx? ( sys-devel/gcc )",
+                "RDEPEND": "cxx? ( sys-devel/gcc )",
+                "IUSE": "cxx",
+                "USE": "cxx",
+            },
+            "sys-devel/gcc-13.2.1_p20230826": {
+                "EAPI": "8",
+                "KEYWORDS": "x86",
+                "DEPEND": ">=dev-libs/gmp-4.3.2:0/10.4=",
+                "RDEPEND": ">=dev-libs/gmp-4.3.2:0/10.4=",
+            },
+        }
+
+        world = [
+            "sys-apps/util-linux",
+            "sys-devel/gcc",
+        ]
+
+        user_config = {
+            "make.conf": ('USE="cxx python selinux"',),
+        }
+
+        test_cases = (
+            ResolverPlaygroundTestCase(
+                ["@world"],
+                options={"--emptytree": True},
+                success=True,
+                mergelist=[
+                    "dev-libs/gmp-6.3.0",
+                    "sys-devel/gcc-13.2.1_p20230826",
+                    "sys-apps/util-linux-2.38.1-r2",
+                    "dev-lang/python-3.11.6",
+                    "sys-libs/libselinux-3.5-r1",
+                ],
+            ),
+        )
+
+        playground = ResolverPlayground(
+            ebuilds=ebuilds,
+            installed=installed,
+            world=world,
+            user_config=user_config,
+        )
+        try:
+            for test_case in test_cases:
+                playground.run_TestCase(test_case)
+                self.assertEqual(test_case.test_success, True, test_case.fail_msg)
+
+            # Since ResolverPlayground does not internally support
+            # cross-root, test with emerge.
+            cross_root = os.path.join(playground.settings["EPREFIX"], "cross_root")
+            world_file = os.path.join(
+                cross_root,
+                playground.settings["EPREFIX"].lstrip(os.sep),
+                portage.const.WORLD_FILE,
+            )
+            os.makedirs(os.path.dirname(world_file))
+            shutil.copy(
+                os.path.join(playground.settings["EPREFIX"], portage.const.WORLD_FILE),
+                world_file,
+            )
+            result = subprocess.run(
+                [
+                    "emerge",
+                    f"--root={cross_root}",
+                    "--pretend",
+                    "--verbose",
+                    "--usepkgonly",
+                    "--quickpkg-direct=y",
+                    "@world",
+                ],
+                env=playground.settings.environ(),
+                stdout=subprocess.PIPE,
+                stderr=subprocess.STDOUT,
+            )
+            output = result.stdout.decode(errors="replace")
+            try:
+                self.assertTrue("5 packages (5 new, 5 binaries)" in output)
+                self.assertEqual(result.returncode, os.EX_OK)
+            except Exception:
+                print(output)
+                raise
+        finally:
+            playground.cleanup()


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [gentoo-commits] proj/portage:master commit in: lib/_emerge/, lib/portage/tests/resolver/
@ 2023-12-24 19:30 Zac Medico
  0 siblings, 0 replies; 13+ messages in thread
From: Zac Medico @ 2023-12-24 19:30 UTC (permalink / raw
  To: gentoo-commits

commit:     bf3d091de8702f0c95e5530d03c6e925008ee80a
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Sun Dec 24 05:12:55 2023 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Sun Dec 24 19:29:25 2023 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=bf3d091d

depclean: drop direct circular deps in merge order calculation

Drop direct circular deps in the depclean merge order calculation,
since it does not handle them well as shown by the test case
for bug 916135.

Bug: https://bugs.gentoo.org/916135
Signed-off-by: Zac Medico <zmedico <AT> gentoo.org>

 lib/_emerge/actions.py                            |  8 ++++++--
 lib/portage/tests/resolver/test_depclean_order.py | 10 ++++++----
 2 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/lib/_emerge/actions.py b/lib/_emerge/actions.py
index 86ba7f77a5..13bb75931c 100644
--- a/lib/_emerge/actions.py
+++ b/lib/_emerge/actions.py
@@ -1,4 +1,4 @@
-# Copyright 1999-2021 Gentoo Authors
+# Copyright 1999-2023 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 import collections
@@ -1642,7 +1642,11 @@ def _calc_depclean(settings, trees, ldpath_mtimes, myopts, action, args_set, spi
                                 if mypriority.runtime:
                                     mypriority.runtime_slot_op = True
 
-                            graph.add(child_node, node, priority=mypriority)
+                            # Drop direct circular deps because the unmerge order
+                            # calculation does not handle them well as demonstrated
+                            # by the test case for bug 916135.
+                            if child_node is not node:
+                                graph.add(child_node, node, priority=mypriority)
 
         if debug:
             writemsg_level("\nunmerge digraph:\n\n", noiselevel=-1, level=logging.DEBUG)

diff --git a/lib/portage/tests/resolver/test_depclean_order.py b/lib/portage/tests/resolver/test_depclean_order.py
index 867b1a54ca..23b5e755c3 100644
--- a/lib/portage/tests/resolver/test_depclean_order.py
+++ b/lib/portage/tests/resolver/test_depclean_order.py
@@ -1,8 +1,6 @@
-# Copyright 2013 Gentoo Foundation
+# Copyright 2013-2023 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
-import pytest
-
 from portage.tests import TestCase
 from portage.tests.resolver.ResolverPlayground import (
     ResolverPlayground,
@@ -60,8 +58,12 @@ class SimpleDepcleanTestCase(TestCase):
         finally:
             playground.cleanup()
 
-    @pytest.mark.xfail()
     def testIDEPENDDepclean(self):
+        """
+        Test for bug 916135, where a direct circular dependency caused
+        the unmerge order to fail to account for IDEPEND.
+        """
+
         ebuilds = {
             "dev-util/A-1": {},
             "dev-libs/B-1": {


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [gentoo-commits] proj/portage:master commit in: lib/_emerge/, lib/portage/tests/resolver/
@ 2024-05-26 18:48 Zac Medico
  0 siblings, 0 replies; 13+ messages in thread
From: Zac Medico @ 2024-05-26 18:48 UTC (permalink / raw
  To: gentoo-commits

commit:     96e4f95cc8c0d544d375b28394dafe8809c4bc9b
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Sun May 26 18:23:27 2024 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Sun May 26 18:34:04 2024 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=96e4f95c

Revert "find_smallest_cycle: Optimize to traverse fewer nodes"

This reverts commit 49e01d041c74680a81860b819daff812d83df02f
in order to fix bug 922629. Later we can try to optimize it
again but without breaking testTarMergeOrder.

Bug: https://bugs.gentoo.org/922629
Signed-off-by: Zac Medico <zmedico <AT> gentoo.org>

 lib/_emerge/depgraph.py                            | 36 ++--------------------
 .../tests/resolver/test_rebuild_ghostscript.py     |  2 +-
 .../resolver/test_runtime_cycle_merge_order.py     |  7 ++---
 lib/portage/tests/resolver/test_tar_merge_order.py |  4 ++-
 4 files changed, 9 insertions(+), 40 deletions(-)

diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
index 6b91d5c42d..3adc04bcfb 100644
--- a/lib/_emerge/depgraph.py
+++ b/lib/_emerge/depgraph.py
@@ -9313,14 +9313,7 @@ class depgraph:
 
                 asap_nodes.extend(libc_pkgs)
 
-        def gather_deps(
-            ignore_priority,
-            mergeable_nodes,
-            selected_nodes,
-            node,
-            smallest_cycle=None,
-            traversed_nodes=None,
-        ):
+        def gather_deps(ignore_priority, mergeable_nodes, selected_nodes, node):
             """
             Recursively gather a group of nodes that RDEPEND on
             eachother. This ensures that they are merged as a group
@@ -9340,24 +9333,10 @@ class depgraph:
                 # RDEPENDs installed first, but ignore uninstalls
                 # (these occur when new portage blocks an older package version).
                 return False
-            if traversed_nodes is not None:
-                if node in traversed_nodes:
-                    # Identical to a previously traversed cycle.
-                    return False
-                traversed_nodes.add(node)
             selected_nodes.add(node)
-            if smallest_cycle is not None and len(selected_nodes) >= len(
-                smallest_cycle
-            ):
-                return False
             for child in mygraph.child_nodes(node, ignore_priority=ignore_priority):
                 if not gather_deps(
-                    ignore_priority,
-                    mergeable_nodes,
-                    selected_nodes,
-                    child,
-                    smallest_cycle=smallest_cycle,
-                    traversed_nodes=traversed_nodes,
+                    ignore_priority, mergeable_nodes, selected_nodes, child
                 ):
                     return False
             return True
@@ -9515,21 +9494,12 @@ class depgraph:
                             local_priority_range.MEDIUM_SOFT + 1,
                         )
                     ):
-                        # Traversed nodes for current priority
-                        traversed_nodes = set()
                         for node in nodes:
                             if not mygraph.parent_nodes(node):
                                 continue
-                            if node in traversed_nodes:
-                                continue
                             selected_nodes = set()
                             if gather_deps(
-                                priority,
-                                mergeable_nodes,
-                                selected_nodes,
-                                node,
-                                smallest_cycle=smallest_cycle,
-                                traversed_nodes=traversed_nodes,
+                                priority, mergeable_nodes, selected_nodes, node
                             ):
                                 if smallest_cycle is None or len(selected_nodes) < len(
                                     smallest_cycle

diff --git a/lib/portage/tests/resolver/test_rebuild_ghostscript.py b/lib/portage/tests/resolver/test_rebuild_ghostscript.py
index 8ee3349d6f..dad6a21322 100644
--- a/lib/portage/tests/resolver/test_rebuild_ghostscript.py
+++ b/lib/portage/tests/resolver/test_rebuild_ghostscript.py
@@ -137,9 +137,9 @@ class RebuildGhostscriptTestCase(TestCase):
                 mergelist=[
                     "sys-apps/dbus-1.15.6",
                     "x11-libs/gtk+-3.24.38",
-                    "app-text/ghostscript-gpl-10.01.2",
                     "net-dns/avahi-0.8-r7",
                     "net-print/cups-2.4.6",
+                    "app-text/ghostscript-gpl-10.01.2",
                     "app-text/libspectre-0.2.12",
                     "x11-libs/goffice-0.10.55",
                 ],

diff --git a/lib/portage/tests/resolver/test_runtime_cycle_merge_order.py b/lib/portage/tests/resolver/test_runtime_cycle_merge_order.py
index ed329aa097..a695b25198 100644
--- a/lib/portage/tests/resolver/test_runtime_cycle_merge_order.py
+++ b/lib/portage/tests/resolver/test_runtime_cycle_merge_order.py
@@ -56,11 +56,8 @@ class RuntimeCycleMergeOrderTestCase(TestCase):
                     ("app-misc/leaf-b-1", "app-misc/leaf-d-1", "app-misc/leaf-e-1"),
                     ("app-misc/branch-d-1", "app-misc/branch-e-1"),
                     "app-misc/runtime-c-1",
-                    (
-                        "app-misc/branch-b-1",
-                        "app-misc/runtime-cycle-c-1",
-                        "app-misc/branch-c-1",
-                    ),
+                    ("app-misc/runtime-cycle-c-1", "app-misc/branch-c-1"),
+                    "app-misc/branch-b-1",
                     ("app-misc/runtime-cycle-b-1", "app-misc/plugin-b-1"),
                     "app-misc/plugins-consumer-1",
                 ],

diff --git a/lib/portage/tests/resolver/test_tar_merge_order.py b/lib/portage/tests/resolver/test_tar_merge_order.py
index 7e1a18bc21..4bd9b4df4a 100644
--- a/lib/portage/tests/resolver/test_tar_merge_order.py
+++ b/lib/portage/tests/resolver/test_tar_merge_order.py
@@ -12,7 +12,6 @@ from portage.tests.resolver.ResolverPlayground import (
 
 
 class TarMergeOrderTestCase(TestCase):
-    @pytest.mark.xfail(reason="bug #922629 isn't yet fixed")
     def testTarMergeOrder(self):
         """
         Test for bug #922629 where binary app-arch/tar[acl] was merged
@@ -21,6 +20,9 @@ class TarMergeOrderTestCase(TestCase):
 
         It poorly interacted with @system containing app-alternatives/tar
         as a circular dependency on app-arch/tar.
+
+        Bisect found that commit 49e01d041c74680a81860b819daff812d83df02f
+        triggered the issue.
         """
 
         ebuilds = {


^ permalink raw reply related	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2024-05-26 18:48 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-06-16  3:34 [gentoo-commits] proj/portage:master commit in: lib/_emerge/, lib/portage/tests/resolver/ Sam James
  -- strict thread matches above, loose matches on Subject: below --
2024-05-26 18:48 Zac Medico
2023-12-24 19:30 Zac Medico
2023-12-06 20:29 Zac Medico
2023-11-28 22:42 Zac Medico
2023-11-28 22:26 Sam James
2023-11-28  4:20 Zac Medico
2023-11-19 17:56 Zac Medico
2023-06-16  3:34 Sam James
2020-11-22  6:13 Zac Medico
2020-09-21  5:39 Zac Medico
2020-02-15  0:05 Zac Medico
2019-09-12  1:51 Zac Medico

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox