From 714deff1a048a21e359137b4cc8a20b6d3928d16 Mon Sep 17 00:00:00 2001 From: James Le Cuirot Date: Thu, 19 Sep 2024 13:12:06 +0100 Subject: [PATCH 1/4] depgraph: Simplify creation on uninstall tasks Signed-off-by: James Le Cuirot --- lib/_emerge/Package.py | 4 ++-- lib/_emerge/depgraph.py | 26 +++----------------------- 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/lib/_emerge/Package.py b/lib/_emerge/Package.py index 79011380d5..562f177406 100644 --- a/lib/_emerge/Package.py +++ b/lib/_emerge/Package.py @@ -405,7 +405,7 @@ def _validate_deps(self): except InvalidData as e: self._invalid_metadata(k + ".syntax", f"{k}: {e}") - def copy(self): + def copy(self, operation=None): return Package( built=self.built, cpv=self.cpv, @@ -413,7 +413,7 @@ def copy(self): installed=self.installed, metadata=self._raw_metadata, onlydeps=self.onlydeps, - operation=self.operation, + operation=operation or self.operation, root_config=self.root_config, type_name=self.type_name, ) diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py index 2acd8f2e13..05bfbbc3e1 100644 --- a/lib/_emerge/depgraph.py +++ b/lib/_emerge/depgraph.py @@ -8936,15 +8936,7 @@ def _validate_blockers(self): if not unresolved_blocks and depends_on_order: for inst_pkg, inst_task in depends_on_order: - uninst_task = Package( - built=inst_pkg.built, - cpv=inst_pkg.cpv, - installed=inst_pkg.installed, - metadata=inst_pkg._metadata, - operation="uninstall", - root_config=inst_pkg.root_config, - type_name=inst_pkg.type_name, - ) + uninst_task = inst_pkg.copy(operation="uninstall") # Enforce correct merge order with a hard dep. self._dynamic_config.digraph.addnode( uninst_task, inst_task, priority=BlockerDepPriority.instance @@ -9965,20 +9957,8 @@ def find_smallest_cycle(mergeable_nodes, local_priority_range): if inst_pkg: # The package will be replaced by this one, so remove # the corresponding Uninstall task if necessary. - inst_pkg = inst_pkg[0] - uninst_task = Package( - built=inst_pkg.built, - cpv=inst_pkg.cpv, - installed=inst_pkg.installed, - metadata=inst_pkg._metadata, - operation="uninstall", - root_config=inst_pkg.root_config, - type_name=inst_pkg.type_name, - ) - try: - mygraph.remove(uninst_task) - except KeyError: - pass + uninst_task = inst_pkg[0].copy(operation="uninstall") + mygraph.discard(uninst_task) if ( uninst_task is not None From a4bc418f9c6a00c7b67209f37cad990e144d7478 Mon Sep 17 00:00:00 2001 From: James Le Cuirot Date: Wed, 18 Sep 2024 12:21:55 +0100 Subject: [PATCH 2/4] WIP Auto resolve cyclic USE conflicts by trying the first suggestion It only works when there is one conflicting cycle. It gets too complicated when there is more than one. Signed-off-by: James Le Cuirot --- lib/_emerge/depgraph.py | 28 ++++++++++++++++++--- lib/_emerge/resolver/circular_dependency.py | 2 +- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py index 05bfbbc3e1..7ca5b3caf3 100644 --- a/lib/_emerge/depgraph.py +++ b/lib/_emerge/depgraph.py @@ -9901,8 +9901,6 @@ def find_smallest_cycle(mergeable_nodes, local_priority_range): continue if not selected_nodes: - self._dynamic_config._circular_deps_for_display = mygraph - unsolved_cycle = False if self._dynamic_config._allow_backtracking: backtrack_infos = self._dynamic_config._backtrack_infos @@ -9927,11 +9925,33 @@ def find_smallest_cycle(mergeable_nodes, local_priority_range): ) if unsolved_cycle or not self._dynamic_config._allow_backtracking: + self._dynamic_config._circular_deps_for_display = mygraph self._dynamic_config._skip_restart = True + raise self._unknown_internal_error() else: - self._dynamic_config._need_restart = True + handler = circular_dependency_handler(self, mygraph) - raise self._unknown_internal_error() + if handler.solutions and len(handler.cycles) == 2: + pkg = list(handler.solutions.keys())[0] + parent, solution = list(handler.solutions[pkg])[0] + solution = list(solution)[0] + enabled = set(parent.use.enabled) + + if solution[1]: + enabled.add(solution[0]) + else: + enabled.remove(solution[0]) + + selected_nodes = [parent.with_use(enabled), pkg, parent] + ignored_uninstall_tasks = set( + uninst_task + for uninst_task in ignored_uninstall_tasks + if uninst_task.cp != pkg.cp or uninst_task.slot != pkg.slot + ) + else: + self._dynamic_config._circular_deps_for_display = mygraph + self._dynamic_config._need_restart = True + raise self._unknown_internal_error() # At this point, we've succeeded in selecting one or more nodes, so # reset state variables for leaf node selection. diff --git a/lib/_emerge/resolver/circular_dependency.py b/lib/_emerge/resolver/circular_dependency.py index 6c21423083..09b1afebfb 100644 --- a/lib/_emerge/resolver/circular_dependency.py +++ b/lib/_emerge/resolver/circular_dependency.py @@ -293,7 +293,7 @@ def _find_suggestions(self): " (This change might require USE changes on parent packages.)" ) suggestions.append(msg) - final_solutions.setdefault(pkg, set()).add(solution) + final_solutions.setdefault(pkg, set()).add((parent, solution)) return final_solutions, suggestions From 1fc0810be82be7e7de692e14462d8878cbdf1103 Mon Sep 17 00:00:00 2001 From: James Le Cuirot Date: Thu, 19 Sep 2024 17:24:56 +0100 Subject: [PATCH 3/4] WIP Poor attempt at making it handle more than one conflict The order is wrong and some of the rebuilds randomly disappear. Signed-off-by: James Le Cuirot --- lib/_emerge/depgraph.py | 52 ++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py index 7ca5b3caf3..69ecfa9970 100644 --- a/lib/_emerge/depgraph.py +++ b/lib/_emerge/depgraph.py @@ -9224,6 +9224,7 @@ def _serialize_tasks(self): complete = "complete" in self._dynamic_config.myparams ignore_world = self._dynamic_config.myparams.get("ignore_world", False) asap_nodes = [] + changed_pkgs = {} def get_nodes(**kwargs): """ @@ -9929,29 +9930,38 @@ def find_smallest_cycle(mergeable_nodes, local_priority_range): self._dynamic_config._skip_restart = True raise self._unknown_internal_error() else: - handler = circular_dependency_handler(self, mygraph) - - if handler.solutions and len(handler.cycles) == 2: - pkg = list(handler.solutions.keys())[0] - parent, solution = list(handler.solutions[pkg])[0] - solution = list(solution)[0] - enabled = set(parent.use.enabled) - - if solution[1]: - enabled.add(solution[0]) + uniq_selected_nodes = set() + while True: + handler = circular_dependency_handler(self, mygraph) + + if handler.solutions: + pkg = list(handler.solutions.keys())[0] + parent, solution = list(handler.solutions[pkg])[0] + solution = list(solution)[0] + changed_pkg = changed_pkgs.get(parent, parent) + enabled = set(changed_pkg.use.enabled) + + if solution[1]: + enabled.add(solution[0]) + else: + enabled.remove(solution[0]) + + changed_pkgs[parent] = changed_pkg.with_use(enabled) + uniq_selected_nodes.update((pkg, parent)) + mygraph.remove_edge(pkg, parent) + ignored_uninstall_tasks = set( + uninst_task + for uninst_task in ignored_uninstall_tasks + if uninst_task.cp != pkg.cp or uninst_task.slot != pkg.slot + ) + elif uniq_selected_nodes: + break else: - enabled.remove(solution[0]) + self._dynamic_config._circular_deps_for_display = mygraph + self._dynamic_config._need_restart = True + raise self._unknown_internal_error() - selected_nodes = [parent.with_use(enabled), pkg, parent] - ignored_uninstall_tasks = set( - uninst_task - for uninst_task in ignored_uninstall_tasks - if uninst_task.cp != pkg.cp or uninst_task.slot != pkg.slot - ) - else: - self._dynamic_config._circular_deps_for_display = mygraph - self._dynamic_config._need_restart = True - raise self._unknown_internal_error() + selected_nodes = list(changed_pkgs.values()) + list(uniq_selected_nodes) # At this point, we've succeeded in selecting one or more nodes, so # reset state variables for leaf node selection. From e9a80f8adbcb224f9dd99d00e99236c3ec1074dd Mon Sep 17 00:00:00 2001 From: James Le Cuirot Date: Mon, 23 Sep 2024 17:13:31 +0100 Subject: [PATCH 4/4] WIP Only attempt auto conflict resolution if it involves no new deps Signed-off-by: James Le Cuirot --- lib/_emerge/depgraph.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py index 69ecfa9970..f4bb55e05a 100644 --- a/lib/_emerge/depgraph.py +++ b/lib/_emerge/depgraph.py @@ -9932,6 +9932,7 @@ def find_smallest_cycle(mergeable_nodes, local_priority_range): else: uniq_selected_nodes = set() while True: + changed_pkg = None handler = circular_dependency_handler(self, mygraph) if handler.solutions: @@ -9946,14 +9947,26 @@ def find_smallest_cycle(mergeable_nodes, local_priority_range): else: enabled.remove(solution[0]) - changed_pkgs[parent] = changed_pkg.with_use(enabled) - uniq_selected_nodes.update((pkg, parent)) - mygraph.remove_edge(pkg, parent) - ignored_uninstall_tasks = set( - uninst_task - for uninst_task in ignored_uninstall_tasks - if uninst_task.cp != pkg.cp or uninst_task.slot != pkg.slot - ) + # To avoid unnecessarily complexity, only try to + # automatically resolve the conflict if the solution + # does not pull in additional dependencies. + before = self._flatten_atoms(parent, parent.use.enabled) + after = self._flatten_atoms(changed_pkg, frozenset(enabled)) + + if before.issuperset(after): + changed_pkgs[parent] = changed_pkg.with_use(enabled) + uniq_selected_nodes.update((pkg, parent)) + mygraph.remove_edge(pkg, parent) + ignored_uninstall_tasks = set( + uninst_task + for uninst_task in ignored_uninstall_tasks + if uninst_task.cp != pkg.cp or uninst_task.slot != pkg.slot + ) + else: + changed_pkg = None + + if changed_pkg is not None: + pass elif uniq_selected_nodes: break else: