From 671b9c1bbb8d7c4d4e237cac17ad2cd3f5870aa1 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Mon, 6 Nov 2023 20:45:58 +0100 Subject: [PATCH] Avoid issues related with pins persistence (#355) --- conda_libmamba_solver/solver.py | 12 +++++----- news/355-pins-persistence-issues | 19 ++++++++++++++++ tests/test_solvers.py | 38 ++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 news/355-pins-persistence-issues diff --git a/conda_libmamba_solver/solver.py b/conda_libmamba_solver/solver.py index 4083e4de..6b14203f 100644 --- a/conda_libmamba_solver/solver.py +++ b/conda_libmamba_solver/solver.py @@ -270,7 +270,7 @@ def _solving_loop( for attempt in range(1, max_attempts): log.debug("Starting solver attempt %s", attempt) try: - solved = self._solve_attempt(in_state, out_state, index) + solved = self._solve_attempt(in_state, out_state, index, attempt=attempt) if solved: break except (UnsatisfiableError, PackagesNotFoundError): @@ -345,10 +345,11 @@ def _solve_attempt( in_state: SolverInputState, out_state: SolverOutputState, index: LibMambaIndexHelper, + attempt: int = 1, ): self._setup_solver(index) - log.debug("New solver attempt") + log.debug("New solver attempt: #%d", attempt) log.debug("Current conflicts (including learnt ones): %s", out_state.conflicts) # ## First, we need to obtain the list of specs ### @@ -365,12 +366,13 @@ def _solve_attempt( log.debug("Computed specs: %s", out_state.specs) # ## Convert to tasks - out_state.pins.clear() n_pins = 0 tasks = self._specs_to_tasks(in_state, out_state) for (task_name, task_type), specs in tasks.items(): log.debug("Adding task %s with specs %s", task_name, specs) - if task_name == "ADD_PIN": + if task_name == "ADD_PIN" and attempt == 1: + # pins only need to be added once; since they persist in the pool + # adding them more times results in issues like #354 for spec in specs: n_pins += 1 self.solver.add_pin(spec) @@ -393,7 +395,7 @@ def _solve_attempt( new_conflicts = self._maybe_raise_for_problems( problems, old_conflicts, out_state.pins, index._channels ) - log.debug("Attempt failed with %s conflicts", len(new_conflicts)) + log.debug("Attempt %d failed with %s conflicts", attempt, len(new_conflicts)) out_state.conflicts.update(new_conflicts.items(), reason="New conflict found") return False diff --git a/news/355-pins-persistence-issues b/news/355-pins-persistence-issues new file mode 100644 index 00000000..d7e7a96f --- /dev/null +++ b/news/355-pins-persistence-issues @@ -0,0 +1,19 @@ +### Enhancements + +* + +### Bug fixes + +* Configure pinned specs just once to avoid solver bugs related with their persistence (i.e. inability to downgrade environments if pinned specs are present and a transient dependency needs to be removed). (#354 via #355) + +### Deprecations + +* + +### Docs + +* + +### Other + +* diff --git a/tests/test_solvers.py b/tests/test_solvers.py index 9e202bbc..5539524d 100644 --- a/tests/test_solvers.py +++ b/tests/test_solvers.py @@ -458,3 +458,41 @@ def test_python_update_should_not_uninstall_history(): "--dry-run", no_capture=True, ) + + +def test_python_downgrade_with_pins_removes_truststore(): + """ + https://github.com/conda/conda-libmamba-solver/issues/354 + """ + channels = "--override-channels", "-c", "conda-forge" + solver = "--solver", "libmamba" + with make_temp_env("python=3.10", "conda", *channels, *solver) as prefix: + zstd_version = PrefixData(prefix).get("zstd").version + for pin in (None, "zstd", f"zstd={zstd_version}"): + env = os.environ.copy() + if pin: + env["CONDA_PINNED_PACKAGES"] = pin + p = conda_subprocess( + Commands.INSTALL, + "-p", + prefix, + *channels, + *solver, + "--dry-run", + "--json", + "python=3.9", + env=env, + ) + assert p.returncode == 0 + data = json.loads(p.stdout) + assert data.get("success") + assert data.get("dry_run") + assertions = 0 + link_dict = {pkg["name"]: pkg for pkg in data["actions"]["LINK"]} + unlink_dict = {pkg["name"]: pkg for pkg in data["actions"]["UNLINK"]} + assert link_dict["python"]["version"].startswith("3.9.") + assert "truststore" in unlink_dict + if pin: + # shouldn't have changed! + assert "zstd" not in link_dict + assert "zstd" not in unlink_dict