mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
Respect pre-release preferences from input files (#5736)
## Summary Right now, if you have a `requirements.txt` with a pre-release, but the `requirements.in` does not have a pre-release marker for that dependency we drop the pre-release. (In the selector, we end up returning `AllowPrerelease::IfNecessary`, the default.) I played with a few ways of solving this... The first was to remove that guard altogether. But if we do that, `universal_transitive_disjoint_prerelease_requirement` fails (we use `1.17.0rc1` in both forks, when it should only apply to one of the two). The second was to do that, but also avoid pushing pre-releases as preferences when we solve a fork. But then `universal_disjoint_prereleases` fails, because we return a different pre-release in each fork. Finally, I settled on allowing existing pre-releases in forks if they have no markers on them, i.e., they are "global" preferences. I believe this is true IFF the preference came from an existing lockfile. Closes https://github.com/astral-sh/uv/issues/5729.
This commit is contained in:
parent
030d477653
commit
35b982446d
3 changed files with 182 additions and 13 deletions
|
@ -206,7 +206,7 @@ impl CandidateSelector {
|
|||
exclusions: &Exclusions,
|
||||
resolver_markers: &ResolverMarkers,
|
||||
) -> Option<Candidate<'a>> {
|
||||
for (_marker, version) in preferences {
|
||||
for (marker, version) in preferences {
|
||||
// Respect the version range for this requirement.
|
||||
if !range.contains(version) {
|
||||
continue;
|
||||
|
@ -240,13 +240,20 @@ impl CandidateSelector {
|
|||
}
|
||||
|
||||
// Respect the pre-release strategy for this fork.
|
||||
if version.any_prerelease()
|
||||
&& self
|
||||
if version.any_prerelease() {
|
||||
let allow = match self
|
||||
.prerelease_strategy
|
||||
.allows(package_name, resolver_markers)
|
||||
!= AllowPrerelease::Yes
|
||||
{
|
||||
continue;
|
||||
{
|
||||
AllowPrerelease::Yes => true,
|
||||
AllowPrerelease::No => false,
|
||||
// If the pre-release is "global" (i.e., provided via a lockfile, rather than
|
||||
// a fork), accept it unless pre-releases are completely banned.
|
||||
AllowPrerelease::IfNecessary => marker.is_none(),
|
||||
};
|
||||
if !allow {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for a remote distribution that matches the preferred version
|
||||
|
|
|
@ -494,7 +494,7 @@ mod tests {
|
|||
];
|
||||
for (i, v1) in versions.iter().enumerate() {
|
||||
for v2 in &versions[i + 1..] {
|
||||
assert_eq!(v1.cmp(v2), Ordering::Less, "less: {v1:?}\ngreater: {v2:?}",);
|
||||
assert_eq!(v1.cmp(v2), Ordering::Less, "less: {v1:?}\ngreater: {v2:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7522,6 +7522,46 @@ fn universal_nested_disjoint_local_requirement() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Respect an existing pre-release preference, even if preferences aren't enabled.
|
||||
#[test]
|
||||
fn existing_prerelease_preference() -> Result<()> {
|
||||
static EXCLUDE_NEWER: &str = "2024-07-17T00:00:00Z";
|
||||
|
||||
let context = TestContext::new("3.12");
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str(indoc::indoc! {r"
|
||||
cffi
|
||||
"})?;
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str(indoc::indoc! {r"
|
||||
cffi==1.17.0rc1
|
||||
pyparser==2.22
|
||||
"})?;
|
||||
|
||||
uv_snapshot!(context.filters(), windows_filters=false, context.pip_compile()
|
||||
.env("UV_EXCLUDE_NEWER", EXCLUDE_NEWER)
|
||||
.arg("requirements.in")
|
||||
.arg("-o")
|
||||
.arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.in -o requirements.txt
|
||||
cffi==1.17.0rc1
|
||||
# via -r requirements.in
|
||||
pycparser==2.22
|
||||
# via cffi
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Requested distinct pre-release strategies with disjoint markers.
|
||||
#[test]
|
||||
fn universal_disjoint_prereleases() -> Result<()> {
|
||||
|
@ -7530,8 +7570,8 @@ fn universal_disjoint_prereleases() -> Result<()> {
|
|||
let context = TestContext::new("3.12");
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str(indoc::indoc! {r"
|
||||
cffi ; os_name == 'linux'
|
||||
cffi >= 1.17.0rc1 ; os_name != 'linux'
|
||||
cffi >= 1.16.0rc1 ; os_name != 'linux'
|
||||
cffi >= 1.16.0rc1, <1.16.0rc2 ; os_name == 'linux'
|
||||
"})?;
|
||||
|
||||
uv_snapshot!(context.filters(), windows_filters=false, context.pip_compile()
|
||||
|
@ -7543,15 +7583,137 @@ fn universal_disjoint_prereleases() -> Result<()> {
|
|||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.in --universal
|
||||
cffi==1.16.0 ; os_name == 'linux'
|
||||
# via -r requirements.in
|
||||
cffi==1.17.0rc1 ; os_name != 'linux'
|
||||
cffi==1.16.0rc1
|
||||
# via -r requirements.in
|
||||
pycparser==2.22
|
||||
# via cffi
|
||||
|
||||
----- stderr -----
|
||||
Resolved 3 packages in [TIME]
|
||||
Resolved 2 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Requested distinct pre-release strategies with disjoint markers.
|
||||
#[test]
|
||||
fn universal_disjoint_prereleases_preference() -> Result<()> {
|
||||
static EXCLUDE_NEWER: &str = "2024-07-17T00:00:00Z";
|
||||
|
||||
let context = TestContext::new("3.12");
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str(indoc::indoc! {r"
|
||||
cffi ; os_name != 'linux'
|
||||
cffi > 1.16.0 ; os_name == 'linux'
|
||||
"})?;
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str(indoc::indoc! {r"
|
||||
cffi==1.17.0rc1
|
||||
pyparser==2.22
|
||||
"})?;
|
||||
|
||||
uv_snapshot!(context.filters(), windows_filters=false, context.pip_compile()
|
||||
.env("UV_EXCLUDE_NEWER", EXCLUDE_NEWER)
|
||||
.arg("requirements.in")
|
||||
.arg("-o")
|
||||
.arg("requirements.txt")
|
||||
.arg("--universal"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.in -o requirements.txt --universal
|
||||
cffi==1.17.0rc1
|
||||
# via -r requirements.in
|
||||
pycparser==2.22
|
||||
# via cffi
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Requested distinct pre-release strategies with disjoint markers.
|
||||
///
|
||||
/// TODO(charlie): This should resolve to two different `cffi` versions, one for each fork.
|
||||
#[test]
|
||||
fn universal_disjoint_prereleases_preference_marker() -> Result<()> {
|
||||
static EXCLUDE_NEWER: &str = "2024-07-17T00:00:00Z";
|
||||
|
||||
let context = TestContext::new("3.12");
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str(indoc::indoc! {r"
|
||||
cffi ; os_name != 'linux'
|
||||
cffi >= 1.16.0rc1 ; os_name == 'linux'
|
||||
"})?;
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str(indoc::indoc! {r"
|
||||
cffi==1.16.0 ; os_name != 'linux'
|
||||
cffi==1.16.0rc1 ; os_name == 'linux'
|
||||
pyparser==2.22
|
||||
"})?;
|
||||
|
||||
uv_snapshot!(context.filters(), windows_filters=false, context.pip_compile()
|
||||
.env("UV_EXCLUDE_NEWER", EXCLUDE_NEWER)
|
||||
.arg("requirements.in")
|
||||
.arg("-o")
|
||||
.arg("requirements.txt")
|
||||
.arg("--universal"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.in -o requirements.txt --universal
|
||||
cffi==1.16.0
|
||||
# via -r requirements.in
|
||||
pycparser==2.22
|
||||
# via cffi
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resolve to a single version as `--prerelease=allow` is provided, even though the first branch
|
||||
/// doesn't include a pre-release marker.
|
||||
#[test]
|
||||
fn universal_disjoint_prereleases_allow() -> Result<()> {
|
||||
static EXCLUDE_NEWER: &str = "2024-07-17T00:00:00Z";
|
||||
|
||||
let context = TestContext::new("3.12");
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str(indoc::indoc! {r"
|
||||
cffi >= 1.15.0, < 1.17.0 ; os_name == 'linux'
|
||||
cffi >= 1.15.0, <= 1.16.0rc2 ; os_name != 'linux'
|
||||
"})?;
|
||||
|
||||
uv_snapshot!(context.filters(), windows_filters=false, context.pip_compile()
|
||||
.env("UV_EXCLUDE_NEWER", EXCLUDE_NEWER)
|
||||
.arg("requirements.in")
|
||||
.arg("--universal")
|
||||
.arg("--prerelease")
|
||||
.arg("allow"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.in --universal --prerelease allow
|
||||
cffi==1.16.0rc2
|
||||
# via -r requirements.in
|
||||
pycparser==2.22
|
||||
# via cffi
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue