Skip to content

Commit 218a732

Browse files
committed
depgraph: earlier slot operator backtracking
Trigger backtracking for slot operator issues in the _add_pkg method, allowing irrelevant dependencies to be dropped before REQUIRED_USE checks are enforced. It would not be acceptable to abort depgraph creation here, since that would not scale well for large numbers of slot operator rebuilds. Bug: https://bugs.gentoo.org/964705 Signed-off-by: Zac Medico <[email protected]>
1 parent bdae242 commit 218a732

File tree

2 files changed

+73
-84
lines changed

2 files changed

+73
-84
lines changed

lib/_emerge/depgraph.py

Lines changed: 72 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1935,16 +1935,9 @@ def _process_slot_conflicts(self):
19351935
# conflicts (or by blind luck).
19361936
raise self._unknown_internal_error()
19371937

1938-
# Both _process_slot_conflict and _slot_operator_trigger_reinstalls
1939-
# can call _slot_operator_update_probe, which requires that
1940-
# self._dynamic_config._blocked_pkgs has been initialized by a
1941-
# call to the _validate_blockers method.
19421938
for conflict in self._dynamic_config._package_tracker.slot_conflicts():
19431939
self._process_slot_conflict(conflict)
19441940

1945-
if self._dynamic_config._allow_backtracking:
1946-
self._slot_operator_trigger_reinstalls()
1947-
19481941
def _process_slot_conflict(self, conflict):
19491942
"""
19501943
Process slot conflict data to identify specific atoms which
@@ -2900,50 +2893,50 @@ def _replace_installed_atom(self, inst_pkg):
29002893

29012894
return None
29022895

2903-
def _slot_operator_trigger_reinstalls(self):
2896+
def _slot_operator_trigger_backtracking(self, dep):
29042897
"""
2905-
Search for packages with slot-operator deps on older slots, and schedule
2906-
rebuilds if they can link to a newer slot that's in the graph.
2898+
Trigger backtracking for slot operator issues if needed.
2899+
Return True if this triggers backtracking, and False otherwise.
29072900
"""
2901+
if not self._dynamic_config._allow_backtracking:
2902+
return False
2903+
2904+
atom = dep.atom
2905+
2906+
if not (atom.soname or atom.slot_operator_built):
2907+
new_child_slot = self._slot_change_probe(dep)
2908+
if new_child_slot is not None:
2909+
self._slot_change_backtrack(dep, new_child_slot)
2910+
return True
2911+
2912+
if not (dep.parent and isinstance(dep.parent, Package) and dep.parent.built):
2913+
return False
29082914

29092915
rebuild_if_new_slot = (
29102916
self._dynamic_config.myparams.get("rebuild_if_new_slot", "y") == "y"
29112917
)
29122918

2913-
for slot_key, slot_info in self._dynamic_config._slot_operator_deps.items():
2914-
for dep in slot_info:
2915-
atom = dep.atom
2916-
2917-
if not (atom.soname or atom.slot_operator_built):
2918-
new_child_slot = self._slot_change_probe(dep)
2919-
if new_child_slot is not None:
2920-
self._slot_change_backtrack(dep, new_child_slot)
2921-
continue
2922-
2923-
if not (
2924-
dep.parent and isinstance(dep.parent, Package) and dep.parent.built
2925-
):
2926-
continue
2919+
# If the parent is not installed, check if it needs to be
2920+
# rebuilt against an installed instance, since otherwise
2921+
# it could trigger downgrade of an installed instance as
2922+
# in bug #652938.
2923+
want_update_probe = dep.want_update or not dep.parent.installed
2924+
2925+
# Check for slot update first, since we don't want to
2926+
# trigger reinstall of the child package when a newer
2927+
# slot will be used instead.
2928+
if rebuild_if_new_slot and want_update_probe:
2929+
new_dep = self._slot_operator_update_probe(dep, new_child_slot=True)
2930+
if new_dep is not None:
2931+
self._slot_operator_update_backtrack(dep, new_child_slot=new_dep.child)
2932+
return True
29272933

2928-
# If the parent is not installed, check if it needs to be
2929-
# rebuilt against an installed instance, since otherwise
2930-
# it could trigger downgrade of an installed instance as
2931-
# in bug #652938.
2932-
want_update_probe = dep.want_update or not dep.parent.installed
2933-
2934-
# Check for slot update first, since we don't want to
2935-
# trigger reinstall of the child package when a newer
2936-
# slot will be used instead.
2937-
if rebuild_if_new_slot and want_update_probe:
2938-
new_dep = self._slot_operator_update_probe(dep, new_child_slot=True)
2939-
if new_dep is not None:
2940-
self._slot_operator_update_backtrack(
2941-
dep, new_child_slot=new_dep.child
2942-
)
2934+
if want_update_probe:
2935+
if self._slot_operator_update_probe(dep):
2936+
self._slot_operator_update_backtrack(dep)
2937+
return True
29432938

2944-
if want_update_probe:
2945-
if self._slot_operator_update_probe(dep):
2946-
self._slot_operator_update_backtrack(dep)
2939+
return False
29472940

29482941
def _reinstall_for_flags(
29492942
self, pkg, forced_flags, orig_use, orig_iuse, cur_use, cur_iuse
@@ -3434,44 +3427,6 @@ def _add_pkg(self, pkg, dep):
34343427
raise
34353428
del e
34363429

3437-
# NOTE: REQUIRED_USE checks are delayed until after
3438-
# package selection, since we want to prompt the user
3439-
# for USE adjustment rather than have REQUIRED_USE
3440-
# affect package selection and || dep choices.
3441-
if (
3442-
not pkg.built
3443-
and pkg._metadata.get("REQUIRED_USE")
3444-
and eapi_has_required_use(pkg.eapi)
3445-
):
3446-
required_use_is_sat = check_required_use(
3447-
pkg._metadata["REQUIRED_USE"],
3448-
self._pkg_use_enabled(pkg),
3449-
pkg.iuse.is_valid_flag,
3450-
eapi=pkg.eapi,
3451-
)
3452-
if not required_use_is_sat:
3453-
if dep.atom is not None and dep.parent is not None:
3454-
self._add_parent_atom(pkg, (dep.parent, dep.atom))
3455-
3456-
if arg_atoms:
3457-
for parent_atom in arg_atoms:
3458-
parent, atom = parent_atom
3459-
self._add_parent_atom(pkg, parent_atom)
3460-
3461-
atom = dep.atom
3462-
if atom is None:
3463-
atom = Atom("=" + pkg.cpv)
3464-
self._dynamic_config._unsatisfied_deps_for_display.append(
3465-
((pkg.root, atom), {"myparent": dep.parent, "show_req_use": pkg})
3466-
)
3467-
self._dynamic_config._required_use_unsatisfied = True
3468-
self._dynamic_config._skip_restart = True
3469-
# Add pkg to digraph in order to enable autounmask messages
3470-
# for this package, which is useful when autounmask USE
3471-
# changes have violated REQUIRED_USE.
3472-
self._dynamic_config.digraph.add(pkg, dep.parent, priority=priority)
3473-
return 0
3474-
34753430
if not pkg.onlydeps:
34763431
existing_node, existing_node_matches = self._check_slot_conflict(
34773432
pkg, dep.atom
@@ -3630,6 +3585,43 @@ def _add_pkg(self, pkg, dep):
36303585
and (dep.atom.soname or dep.atom.slot_operator == "=")
36313586
):
36323587
self._add_slot_operator_dep(dep)
3588+
if self._slot_operator_trigger_backtracking(dep):
3589+
# Drop slot operator deps that trigger backtracking, since
3590+
# they may be irrelevant and therefore we don't want to
3591+
# enforce the REQUIRED_USE check that comes below (bug 964705).
3592+
# Since backtracking has been triggered, the _need_restart flag
3593+
# is set and this depgraph is only useful for collecting
3594+
# backtracking parameters at this point, so it is acceptable to
3595+
# drop dependencies as needed. It would not be acceptable to
3596+
# abort depgraph creation here, since that would not scale well
3597+
# for large numbers of slot operator rebuilds.
3598+
return 1
3599+
3600+
# NOTE: REQUIRED_USE checks are delayed until after
3601+
# package selection, since we want to prompt the user
3602+
# for USE adjustment rather than have REQUIRED_USE
3603+
# affect package selection and || dep choices.
3604+
if (
3605+
not pkg.built
3606+
and pkg._metadata.get("REQUIRED_USE")
3607+
and eapi_has_required_use(pkg.eapi)
3608+
):
3609+
required_use_is_sat = check_required_use(
3610+
pkg._metadata["REQUIRED_USE"],
3611+
self._pkg_use_enabled(pkg),
3612+
pkg.iuse.is_valid_flag,
3613+
eapi=pkg.eapi,
3614+
)
3615+
if not required_use_is_sat:
3616+
atom = dep.atom
3617+
if atom is None:
3618+
atom = Atom("=" + pkg.cpv)
3619+
self._dynamic_config._unsatisfied_deps_for_display.append(
3620+
((pkg.root, atom), {"myparent": dep.parent, "show_req_use": pkg})
3621+
)
3622+
self._dynamic_config._required_use_unsatisfied = True
3623+
self._dynamic_config._skip_restart = True
3624+
return 0
36333625

36343626
recurse = deep is True or not self._too_deep(self._depth_increment(depth, n=1))
36353627
dep_stack = self._dynamic_config._dep_stack

lib/portage/tests/resolver/test_binpackage_downgrades_slot_dep.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# Copyright 2025 Gentoo Authors
22
# Distributed under the terms of the GNU General Public License v2
33

4-
import pytest
5-
64
from portage.tests import TestCase
75
from portage.tests.resolver.ResolverPlayground import (
86
ResolverPlayground,
@@ -11,7 +9,6 @@
119

1210

1311
class BinpackageDowngradesSlotDepTestCase(TestCase):
14-
@pytest.mark.xfail()
1512
def testBinpackageDowngradesSlotDep(self):
1613
python_use = "python_targets_python3_12 +python_targets_python3_13"
1714
python_usedep = "python_targets_python3_12(-)?,python_targets_python3_13(-)?"
@@ -92,7 +89,7 @@ def testBinpackageDowngradesSlotDep(self):
9289
binpkgs=binpkgs,
9390
world=world,
9491
user_config=user_config,
95-
debug=True,
92+
debug=False,
9693
)
9794

9895
settings = playground.settings

0 commit comments

Comments
 (0)