-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Do not allow pin overrides via requested specs in the CLI #289
Conversation
I tested this PR using a similar approach to my Dockerfile in the related issue. It appears that conflicts to a pinned file only raise a warning using the classic solver (honestly this is a pretty bad bug, and directly conflicts with the docs. I'm surprised the following issue has been around for so long given that conda used to behave correctly before conda/conda#9016) regardless, there is still a difference in behavior between libmamba and classic, where libmamba won't even give a warning about pin conflicts. See below
|
I've been in a call with @costrouc debugging this, and the conclusion so far is that it makes little sense to anticipate conflicting pins as conda classic did (with the corresponding warning we can see in that output). We could follow the classic approach and query the index for packages that are compatible with each spec (pin and user-requested) and estimate the compatibility via their set intersection. If the intersection is not null, then both pins would be compatible. However this implies running some RAM intensive computations (JSON payloads are sent from C++ to python) and the solver will eventually error out if not compatible anyway. So we thought, let's both pin and request the install, and the solver error will report. That sounds good, BUT conda has this notion of "user can override pins if explicitly requested in the CLI", which makes the above statement not as useful. So the current code as of commit af11b5d will always give more precedence to the user requested spec. This will probably fail a couple tests here and there. We'll see and try to decide what to do next. |
Let's make a call in favor of user experience and note the difference in behavior (and respecting the pining via file first) in the conda-libmamba-solver release notes. |
MatchSpec
compatibility properlyif requested.is_name_only_spec and pinned and not pinned.is_name_only_spec: | ||
# for name-only specs, this is a no-op; we already added the pin above | ||
# but we will constrain it again in the install task to have better | ||
# error messages if not solvable | ||
spec_str = self._spec_to_str(pinned) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note this is purely for UX purposes. The solver doesn't care if we do either of:
A:
install("python")
pin("python=3.7")
B:
install("python=3.7")
pin("python=3.7")
However, B will give (as of libmamba 1.5.x
) a clearer error message if the request happens to be unsolvable.
conda_libmamba_solver/state.py
Outdated
# Check we are not trying to remove things that are not installed | ||
if self.is_removing: | ||
not_installed = [ | ||
spec for name, spec in self.requested.items() if name not in self.installed | ||
] | ||
if not_installed: | ||
exc = PackagesNotFoundError(not_installed) | ||
exc.allow_retry = False | ||
raise exc | ||
|
||
# Check if requested and pins overlap | ||
# NOTE: This is a difference with respect to classic logic. classic | ||
# allows pin overrides in the CLI, but we don't. | ||
constraining_requests = { | ||
spec.name for spec in self.requested.values() if not spec.is_name_only_spec | ||
} | ||
constraining_pins = {spec.name for spec in self.pinned.values()} | ||
requested_and_pinned = constraining_requests.intersection(constraining_pins) | ||
if requested_and_pinned: | ||
# libmamba has two types of pins: | ||
# - Pins with constraints: limit which versions of a package can be installed | ||
# - Name-only pins: lock the package as installed; has no effect if not installed | ||
# Below we render name-only pins as their installed version when appropriate. | ||
pinned_specs = [ | ||
(self.installed.get(name, pin) if pin.is_name_only_spec else pin) | ||
for name, pin in sorted(self.pinned.items()) | ||
] | ||
exc = RequestedAndPinnedError( | ||
requested_specs=sorted(self.requested.values(), key=lambda x: x.name), | ||
pinned_specs=pinned_specs, | ||
prefix=self.prefix, | ||
) | ||
exc.allow_retry = False | ||
raise exc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We moved these clauses here (from SolverOutputState
) because at this point we haven't initialized the index
(which means downloading repodata and parsing it, with the implied IO overheads). Since we don't need it to compute these errors, we can exit even earlier and happier users. Imagine waiting precious seconds downloading repodata only to realize you mistyped something in the CLI. Now it's mostly instantaneous.
Co-authored-by: Travis Hathaway <travis.j.hathaway@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me and I tested out locally via docker. Also a fan of having more straightforward rules for how pins/requests interact with each other.
@@ -236,3 +239,126 @@ def test_too_aggressive_update_to_conda_forge_packages(): | |||
data_libmamba = json.loads(p_libmamba.stdout) | |||
assert len(data_classic["actions"]["LINK"]) < 15 | |||
assert len(data_libmamba["actions"]["LINK"]) <= len(data_classic["actions"]["LINK"]) | |||
|
|||
|
|||
@pytest.mark.skipif(context.subdir != "linux-64", reason="Linux-64 only") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ran both of these tests locally with docker and with the latest of main
on mamba and they passed.
* implement some compatibility checks * clean up a bit and add more tests * pre-commit * be explicit about channels * simplify error logic for installed pins * pre-commit
Run some consistency checks to ensure configuration is solid. | ||
""" | ||
# Ensure configured pins match installed builds | ||
for name, pin_spec in self.pinned.items(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An equivalent check is done now in SolverOutputState._prepare_for_solve()
Merging for now. We'll clean up in a follow up PR. |
Description
Closes #286
The original intention of this PR was to address an issue in how we were checking whether pinned specs and explicitly requested specs were compatible or not. After careful investigation, we realized two things:
generalizing a robust solution would require querying the index in several occasions trying to anticipate something that the solver will anyway end up doing. This is RAM intensive and potentially more costly than the solve it self. It's not that bad.As a result, in this PR we have decided to:
--no-pin
.--no-pin
can be used.Checklist - did you ...
news
directory (using the template) for the next release's release notes?