make universal resolver fork only when markers are disjoint#4135
make universal resolver fork only when markers are disjoint#4135charliermarsh merged 3 commits intomainfrom
Conversation
17dd0e1 to
7f97d14
Compare
7f97d14 to
fe4ee84
Compare
charliermarsh
left a comment
There was a problem hiding this comment.
This looks good to me! I'd like to see some tests eventually for combinations of forks and markers, like...
flask
flask[dotenv] ; python_version > '3.7'
flask[async] ; python_version <= '3.7'
| @@ -1,5 +1,7 @@ | |||
| //! Given a set of requirements, find a set of compatible packages. | |||
|
|
|||
| #![allow(warnings)] | |||
There was a problem hiding this comment.
I removed it and fixed the lint issues, hope that's ok.
There was a problem hiding this comment.
Ug yeah. Sometimes I add this while writing new code (to silence lots of "unused code" warnings) and forget to remove it. Yes, it's okay!
| // *only* when the markers between two identically named packages are disjoint. | ||
| // So if there are, say, multiple `Extra` packages for the same name, they should | ||
| // all have the same markers (I believe), and thus they are not disjoint and thus | ||
| // they should not provoke a fork. |
There was a problem hiding this comment.
We can probably remove these comments then.
|
I'm gonna merge this so that I can build atop it if need be. |
…ypes This commit does not change any behavior, but instead changes `get_dependencies_forking` (and its caller) to use more structured data types describing the result of forking (or not). This commit is a precursor to forking based only on whether markers between conflicting dependency specifications are disjoint.
fe4ee84 to
12c08cb
Compare
The previous way we did forking was merely by looking at whether there were duplicate dependency specifications (with respect to package name), and if their version constraints were different, we forked. But this is incorrect (and was known to be, it was a shortcut). We only want to fork when we can prove that the resolution produced will never result in installing two different versions of the same package. That in turn can only happen when there are marker expressions attached to conflicting dependency specifications that never overlap. This does slightly change the test output, but only the ordering of packages. This might be because of the fork refactoring. We may want to investigate making the test output more stable, but we punt on that for now.
12c08cb to
ead960a
Compare
| PubGrubPackageInner::Package { name, marker, .. } | ||
| | PubGrubPackageInner::Extra { name, marker, .. } | ||
| | PubGrubPackageInner::Dev { name, marker, .. } => (name, marker.as_ref()), | ||
| PubGrubPackageInner::Marker { name, marker, .. } => (name, Some(marker)), |
There was a problem hiding this comment.
I removed the ref name and ref marker here and now use marker.as_ref(), because the PubGrubPackageInner::Marker arm needs needs to return Option<&Marker> not &Option<Marker>.
The basic idea here is to make it so forking can only ever result in a resolution that, for a particular marker environment, will only install at most one version of a package. We can guarantee this by ensuring we only fork on conflicting dependency specifications only when their corresponding markers are completely disjoint. If they aren't, then resolution must find a single version of the package in the intersection of the two dependency specifications.
A test for this case has been added to packse here: astral-sh/packse#182. Previously, that test would result in a resolution with two different unconditional versions of the same package. With this change, resolution fails (as it should).
A commit-by-commit review should be helpful here, since the first commit is a refactor to make the second commit a bit more digestible.
Closes #3926