Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 22 additions & 9 deletions crates/uv-resolver/src/lock/export/pylock_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ pub enum PylockTomlError {
"Package `{0}` must include one of: `wheels`, `directory`, `archive`, `sdist`, or `vcs`"
)]
MissingSource(PackageName),
#[error("Package `{0}` does not include a compatible wheel for the current platform")]
MissingWheel(PackageName),
#[error("`packages.wheel` entry for `{0}` must have a `path` or `url`")]
WheelMissingPathUrl(PackageName),
#[error("`packages.sdist` entry for `{0}` must have a `path` or `url`")]
Expand Down Expand Up @@ -863,6 +865,11 @@ impl<'lock> PylockToml {
let root = graph.add_node(Node::Root);

for package in self.packages {
// Omit packages that aren't relevant to the current environment.
if !package.marker.evaluate(markers, &[]) {
continue;
}

match (
package.wheels.is_some(),
package.sdist.is_some(),
Expand Down Expand Up @@ -901,12 +908,12 @@ impl<'lock> PylockToml {
(_, _, _, true, true) => {
return Err(PylockTomlError::VcsWithArchive(package.name.clone()));
}
(false, false, false, false, false) => {
return Err(PylockTomlError::MissingSource(package.name.clone()));
}
_ => {}
}

// Omit packages that aren't relevant to the current environment.
let install = package.marker.evaluate(markers, &[]);

// Search for a matching wheel.
let dist = if let Some(best_wheel) = package.find_best_wheel(tags) {
let hashes = HashDigests::from(best_wheel.hashes.clone());
Expand All @@ -926,7 +933,7 @@ impl<'lock> PylockToml {
Node::Dist {
dist,
hashes,
install,
install: true,
}
} else if let Some(sdist) = package.sdist.as_ref() {
let hashes = HashDigests::from(sdist.hashes.clone());
Expand All @@ -943,7 +950,7 @@ impl<'lock> PylockToml {
Node::Dist {
dist,
hashes,
install,
install: true,
}
} else if let Some(sdist) = package.directory.as_ref() {
let hashes = HashDigests::empty();
Expand All @@ -957,7 +964,7 @@ impl<'lock> PylockToml {
Node::Dist {
dist,
hashes,
install,
install: true,
}
} else if let Some(sdist) = package.vcs.as_ref() {
let hashes = HashDigests::empty();
Expand All @@ -971,7 +978,7 @@ impl<'lock> PylockToml {
Node::Dist {
dist,
hashes,
install,
install: true,
}
} else if let Some(dist) = package.archive.as_ref() {
let hashes = HashDigests::from(dist.hashes.clone());
Expand All @@ -983,10 +990,16 @@ impl<'lock> PylockToml {
Node::Dist {
dist,
hashes,
install,
install: true,
}
} else {
return Err(PylockTomlError::MissingSource(package.name.clone()));
// This is only reachable if the package contains a `wheels` entry (and nothing
// else), but there are no wheels available for the current environment. (If the
// package doesn't contain _any_ of `wheels`, `sdist`, etc., then we error in the
// match above.)
//
// TODO(charlie): Include a hint, like in `uv.lock`.
return Err(PylockTomlError::MissingWheel(package.name.clone()));
};

let index = graph.add_node(dist);
Expand Down
75 changes: 75 additions & 0 deletions crates/uv/tests/it/pip_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5822,3 +5822,78 @@ fn pep_751() -> Result<()> {

Ok(())
}

/// Avoid erroring for packages that only include wheels, and _don't_ include a wheel for the
/// current platform, but are omitted by markers anyway.
///
/// See: <https://github.com/astral-sh/uv/issues/13127>
#[test]
fn pep_751_wheel_only() -> Result<()> {
let context = TestContext::new("3.12");

let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12.0"
dependencies = ["torch"]
"#,
)?;

context
.export()
.arg("-o")
.arg("pylock.toml")
.assert()
.success();

// If there's no compatible wheel for a package we _don't_ need to install (e.g., anything
// CUDA-related), succeed.
uv_snapshot!(context.filters(), context.pip_sync()
.arg("--preview")
.arg("pylock.toml")
.arg("--dry-run")
.arg("--python-platform")
.arg("macos"), @r"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Would download 9 packages
Would install 9 packages
+ filelock==3.13.1
+ fsspec==2024.3.1
+ jinja2==3.1.3
+ markupsafe==2.1.5
+ mpmath==1.3.0
+ networkx==3.2.1
+ sympy==1.12
+ torch==2.2.1
+ typing-extensions==4.10.0
"
);

// However, if there's no compatible wheel for a package that we _do_ need to install, we should
// error
uv_snapshot!(context.filters(), context.pip_sync()
.arg("--preview")
.arg("pylock.toml")
.arg("--dry-run")
.arg("--python-platform")
.arg("macos")
.arg("--python-version")
.arg("3.8"), @r"
success: false
exit_code: 2
----- stdout -----

----- stderr -----
error: Package `torch` does not include a compatible wheel for the current platform
"
);

Ok(())
}
Loading