Skip to content

make universal resolver fork only when markers are disjoint#4135

Merged
charliermarsh merged 3 commits intomainfrom
ag/fork-markers
Jun 7, 2024
Merged

make universal resolver fork only when markers are disjoint#4135
charliermarsh merged 3 commits intomainfrom
ag/fork-markers

Conversation

@BurntSushi
Copy link
Copy Markdown
Member

@BurntSushi BurntSushi commented Jun 7, 2024

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

Copy link
Copy Markdown
Member

@charliermarsh charliermarsh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed it and fixed the lint issues, hope that's ok.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably remove these comments then.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed them...

@charliermarsh
Copy link
Copy Markdown
Member

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.
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.
PubGrubPackageInner::Package { name, marker, .. }
| PubGrubPackageInner::Extra { name, marker, .. }
| PubGrubPackageInner::Dev { name, marker, .. } => (name, marker.as_ref()),
PubGrubPackageInner::Marker { name, marker, .. } => (name, Some(marker)),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>.

@charliermarsh charliermarsh enabled auto-merge (squash) June 7, 2024 23:33
@charliermarsh charliermarsh added the preview Experimental behavior label Jun 7, 2024
@charliermarsh charliermarsh merged commit c46fa74 into main Jun 7, 2024
@charliermarsh charliermarsh deleted the ag/fork-markers branch June 7, 2024 23:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview Experimental behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

universal-lock: check that markers are disjoint before forking

2 participants