Add test coverage for build tag prioritization (#9680)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / build binary | linux (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / integration test | free-threaded on linux (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on opensuse (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86_64 (push) Blocked by required conditions
CI / check system | python3.10 on windows (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on linux (push) Blocked by required conditions
CI / check system | conda3.8 on linux (push) Blocked by required conditions
CI / check system | conda3.11 on macos (push) Blocked by required conditions
CI / check system | conda3.8 on macos (push) Blocked by required conditions
CI / check system | conda3.11 on windows (push) Blocked by required conditions
CI / check system | conda3.8 on windows (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions

## Summary

See: https://github.com/astral-sh/uv/pull/3781 and
https://github.com/astral-sh/uv/pull/9677.
This commit is contained in:
Charlie Marsh 2024-12-06 09:32:19 -05:00 committed by GitHub
parent b5022efef9
commit 3aaa9594be
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 205 additions and 18 deletions

View file

@ -34,10 +34,18 @@ pub enum FindLinksDirectoryError {
VerbatimUrl(#[from] uv_pep508::VerbatimUrlError),
}
/// An entry in a `--find-links` index.
#[derive(Debug, Clone)]
pub struct FlatIndexEntry {
pub filename: DistFilename,
pub file: File,
pub index: IndexUrl,
}
#[derive(Debug, Default, Clone)]
pub struct FlatIndexEntries {
/// The list of `--find-links` entries.
pub entries: Vec<(DistFilename, File, IndexUrl)>,
pub entries: Vec<FlatIndexEntry>,
/// Whether any `--find-links` entries could not be resolved due to a lack of network
/// connectivity.
pub offline: bool,
@ -45,7 +53,7 @@ pub struct FlatIndexEntries {
impl FlatIndexEntries {
/// Create a [`FlatIndexEntries`] from a list of `--find-links` entries.
fn from_entries(entries: Vec<(DistFilename, File, IndexUrl)>) -> Self {
fn from_entries(entries: Vec<FlatIndexEntry>) -> Self {
Self {
entries,
offline: false,
@ -130,6 +138,9 @@ impl<'a> FlatIndexClient<'a> {
while let Some(entries) = fetches.next().await.transpose()? {
results.extend(entries);
}
results
.entries
.sort_by(|a, b| a.filename.cmp(&b.filename).then(a.index.cmp(&b.index)));
Ok(results)
}
@ -211,11 +222,11 @@ impl<'a> FlatIndexClient<'a> {
.expect("archived version always deserializes")
})
.filter_map(|file| {
Some((
DistFilename::try_from_normalized_filename(&file.filename)?,
Some(FlatIndexEntry {
filename: DistFilename::try_from_normalized_filename(&file.filename)?,
file,
flat_index.clone(),
))
index: flat_index.clone(),
})
})
.collect();
Ok(FlatIndexEntries::from_entries(files))
@ -283,7 +294,11 @@ impl<'a> FlatIndexClient<'a> {
);
continue;
};
dists.push((filename, file, flat_index.clone()));
dists.push(FlatIndexEntry {
filename,
file,
index: flat_index.clone(),
});
}
Ok(FlatIndexEntries::from_entries(dists))
}

View file

@ -15,7 +15,7 @@ mod extension;
mod source_dist;
mod wheel;
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum DistFilename {
SourceDistFilename(SourceDistFilename),
WheelFilename(WheelFilename),

View file

@ -14,6 +14,8 @@ use uv_pep440::{Version, VersionParseError};
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
rkyv::Archive,

View file

@ -11,7 +11,18 @@ use uv_platform_tags::{TagCompatibility, Tags};
use crate::{BuildTag, BuildTagError};
#[derive(Debug, Clone, Eq, PartialEq, Hash, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
#[derive(
Debug,
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Hash,
rkyv::Archive,
rkyv::Deserialize,
rkyv::Serialize,
)]
#[rkyv(derive(Debug))]
pub struct WheelFilename {
pub name: PackageName,

View file

@ -40,16 +40,16 @@ impl FlatIndex {
) -> Self {
// Collect compatible distributions.
let mut index = FxHashMap::default();
for (filename, file, url) in entries.entries {
let distributions = index.entry(filename.name().clone()).or_default();
for entry in entries.entries {
let distributions = index.entry(entry.filename.name().clone()).or_default();
Self::add_file(
distributions,
file,
filename,
entry.file,
entry.filename,
tags,
hasher,
build_options,
url,
entry.index,
);
}

View file

@ -12,7 +12,8 @@ use predicates::prelude::predicate;
use url::Url;
use crate::common::{
self, build_vendor_links_url, decode_token, get_bin, uv_snapshot, venv_bin_path, TestContext,
self, build_vendor_links_url, decode_token, get_bin, uv_snapshot, venv_bin_path,
venv_to_interpreter, TestContext,
};
use uv_fs::Simplified;
use uv_static::EnvVars;
@ -7516,3 +7517,40 @@ fn test_dynamic_version_sdist_wrong_version() -> Result<()> {
Ok(())
}
/// Install a package with multiple wheels at the same version, differing only in the build tag. We
/// should choose the wheel with the highest build tag.
#[test]
fn build_tag() {
let context = TestContext::new("3.12");
uv_snapshot!(context.filters(), context.pip_install()
.arg("build-tag")
.arg("--find-links")
.arg(context.workspace_root.join("scripts/links/")), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ build-tag==1.0.0
"###
);
// Ensure that we choose the highest build tag (5).
uv_snapshot!(Command::new(venv_to_interpreter(&context.venv))
.arg("-B")
.arg("-c")
.arg("import build_tag; build_tag.main()")
.current_dir(&context.temp_dir), @r###"
success: true
exit_code: 0
----- stdout -----
5
----- stderr -----
"###);
}

View file

@ -1,13 +1,13 @@
use anyhow::Result;
use assert_cmd::prelude::*;
use assert_fs::{fixture::ChildPath, prelude::*};
use indoc::indoc;
use indoc::{formatdoc, indoc};
use insta::assert_snapshot;
use crate::common::{download_to_disk, uv_snapshot, venv_bin_path, TestContext};
use predicates::prelude::predicate;
use tempfile::tempdir_in;
use crate::common::{download_to_disk, uv_snapshot, venv_bin_path, TestContext};
use uv_fs::Simplified;
use uv_static::EnvVars;
#[test]
@ -5589,3 +5589,124 @@ fn sync_git_path_dependency() -> Result<()> {
Ok(())
}
/// Sync a package with multiple wheels at the same version, differing only in the build tag. We
/// should choose the wheel with the highest build tag.
#[test]
fn sync_build_tag() -> Result<()> {
let context = TestContext::new("3.12");
// Populate the `--find-links` entries.
fs_err::create_dir_all(context.temp_dir.join("links"))?;
for entry in fs_err::read_dir(context.workspace_root.join("scripts/links"))? {
let entry = entry?;
let path = entry.path();
if path
.file_name()
.and_then(|file_name| file_name.to_str())
.is_some_and(|file_name| file_name.starts_with("build_tag-"))
{
let dest = context
.temp_dir
.join("links")
.join(path.file_name().unwrap());
fs_err::copy(&path, &dest)?;
}
}
context
.temp_dir
.child("pyproject.toml")
.write_str(&formatdoc! { r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["build-tag"]
[tool.uv]
find-links = ["{}"]
"#,
context.temp_dir.join("links/").portable_display(),
})?;
uv_snapshot!(context.filters(), context.lock(), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
"###);
let lock = fs_err::read_to_string(context.temp_dir.child("uv.lock")).unwrap();
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
lock, @r###"
version = 1
requires-python = ">=3.12"
[options]
exclude-newer = "2024-03-25T00:00:00Z"
[[package]]
name = "build-tag"
version = "1.0.0"
source = { registry = "links" }
wheels = [
{ path = "build_tag-1.0.0-1-py2.py3-none-any.whl" },
{ path = "build_tag-1.0.0-3-py2.py3-none-any.whl" },
{ path = "build_tag-1.0.0-5-py2.py3-none-any.whl" },
]
[[package]]
name = "project"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "build-tag" },
]
[package.metadata]
requires-dist = [{ name = "build-tag" }]
"###
);
});
// Re-run with `--locked`.
uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
"###);
// Install from the lockfile.
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Installed 1 package in [TIME]
+ build-tag==1.0.0
"###);
// Ensure that we choose the highest build tag (5).
uv_snapshot!(context.filters(), context.run().arg("--no-sync").arg("python").arg("-c").arg("import build_tag; build_tag.main()"), @r###"
success: true
exit_code: 0
----- stdout -----
5
----- stderr -----
"###);
Ok(())
}

Binary file not shown.

Binary file not shown.

Binary file not shown.