mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-20 03:49:54 +00:00
Validate that PEP 751 entries don't include multiple sources (#12993)
## Summary The spec defines these as mutually exclusive, so we now error when trying to install such a package.
This commit is contained in:
parent
cda72b297f
commit
ffcd5eb14f
2 changed files with 99 additions and 1 deletions
|
|
@ -37,7 +37,27 @@ use crate::{Installable, LockError, RequiresPython};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum PylockTomlError {
|
pub enum PylockTomlError {
|
||||||
#[error("`packages` entry for `{0}` must contain one of: `wheels`, `directory`, `archive`, `sdist`, or `vcs`")]
|
#[error("Package `{0}` includes both a registry (`packages.wheels`) and a directory source (`packages.directory`)")]
|
||||||
|
WheelWithDirectory(PackageName),
|
||||||
|
#[error("Package `{0}` includes both a registry (`packages.wheels`) and a VCS source (`packages.vcs`)")]
|
||||||
|
WheelWithVcs(PackageName),
|
||||||
|
#[error("Package `{0}` includes both a registry (`packages.wheels`) and an archive source (`packages.archive`)")]
|
||||||
|
WheelWithArchive(PackageName),
|
||||||
|
#[error("Package `{0}` includes both a registry (`packages.sdist`) and a directory source (`packages.directory`)")]
|
||||||
|
SdistWithDirectory(PackageName),
|
||||||
|
#[error("Package `{0}` includes both a registry (`packages.sdist`) and a VCS source (`packages.vcs`)")]
|
||||||
|
SdistWithVcs(PackageName),
|
||||||
|
#[error("Package `{0}` includes both a registry (`packages.sdist`) and an archive source (`packages.archive`)")]
|
||||||
|
SdistWithArchive(PackageName),
|
||||||
|
#[error("Package `{0}` includes both a directory (`packages.directory`) and a VCS source (`packages.vcs`)")]
|
||||||
|
DirectoryWithVcs(PackageName),
|
||||||
|
#[error("Package `{0}` includes both a directory (`packages.directory`) and an archive source (`packages.archive`)")]
|
||||||
|
DirectoryWithArchive(PackageName),
|
||||||
|
#[error("Package `{0}` includes both a VCS (`packages.vcs`) and an archive source (`packages.archive`)")]
|
||||||
|
VcsWithArchive(PackageName),
|
||||||
|
#[error(
|
||||||
|
"Package `{0}` must include one of: `wheels`, `directory`, `archive`, `sdist`, or `vcs`"
|
||||||
|
)]
|
||||||
MissingSource(PackageName),
|
MissingSource(PackageName),
|
||||||
#[error("`packages.wheel` entry for `{0}` must have a `path` or `url`")]
|
#[error("`packages.wheel` entry for `{0}` must have a `path` or `url`")]
|
||||||
WheelMissingPathUrl(PackageName),
|
WheelMissingPathUrl(PackageName),
|
||||||
|
|
@ -558,6 +578,47 @@ impl<'lock> PylockToml {
|
||||||
let root = graph.add_node(Node::Root);
|
let root = graph.add_node(Node::Root);
|
||||||
|
|
||||||
for package in self.packages {
|
for package in self.packages {
|
||||||
|
match (
|
||||||
|
package.wheels.is_some(),
|
||||||
|
package.sdist.is_some(),
|
||||||
|
package.directory.is_some(),
|
||||||
|
package.vcs.is_some(),
|
||||||
|
package.archive.is_some(),
|
||||||
|
) {
|
||||||
|
// `packages.wheels` is mutually exclusive with `packages.directory`, `packages.vcs`, and `packages.archive`.
|
||||||
|
(true, _, true, _, _) => {
|
||||||
|
return Err(PylockTomlError::WheelWithDirectory(package.name.clone()));
|
||||||
|
}
|
||||||
|
(true, _, _, true, _) => {
|
||||||
|
return Err(PylockTomlError::WheelWithVcs(package.name.clone()));
|
||||||
|
}
|
||||||
|
(true, _, _, _, true) => {
|
||||||
|
return Err(PylockTomlError::WheelWithArchive(package.name.clone()));
|
||||||
|
}
|
||||||
|
// `packages.sdist` is mutually exclusive with `packages.directory`, `packages.vcs`, and `packages.archive`.
|
||||||
|
(_, true, true, _, _) => {
|
||||||
|
return Err(PylockTomlError::SdistWithDirectory(package.name.clone()));
|
||||||
|
}
|
||||||
|
(_, true, _, true, _) => {
|
||||||
|
return Err(PylockTomlError::SdistWithVcs(package.name.clone()));
|
||||||
|
}
|
||||||
|
(_, true, _, _, true) => {
|
||||||
|
return Err(PylockTomlError::SdistWithArchive(package.name.clone()));
|
||||||
|
}
|
||||||
|
// `packages.directory` is mutually exclusive with `packages.vcs`, and `packages.archive`.
|
||||||
|
(_, _, true, true, _) => {
|
||||||
|
return Err(PylockTomlError::DirectoryWithVcs(package.name.clone()));
|
||||||
|
}
|
||||||
|
(_, _, true, _, true) => {
|
||||||
|
return Err(PylockTomlError::DirectoryWithArchive(package.name.clone()));
|
||||||
|
}
|
||||||
|
// `packages.vcs` is mutually exclusive with `packages.archive`.
|
||||||
|
(_, _, _, true, true) => {
|
||||||
|
return Err(PylockTomlError::VcsWithArchive(package.name.clone()));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
// Omit packages that aren't relevant to the current environment.
|
// Omit packages that aren't relevant to the current environment.
|
||||||
let install = package.marker.evaluate(markers, &[]);
|
let install = package.marker.evaluate(markers, &[]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11071,3 +11071,40 @@ fn pep_751_mix() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pep_751_multiple_sources() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
let pylock_toml = context.temp_dir.child("pylock.toml");
|
||||||
|
pylock_toml.write_str(r#"
|
||||||
|
# This file was autogenerated by uv via the following command:
|
||||||
|
# uv export --cache-dir [CACHE_DIR] -o pylock.toml
|
||||||
|
lock-version = "1.0"
|
||||||
|
created-by = "uv"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
[[packages]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.10.0"
|
||||||
|
index = "https://pypi.org/simple"
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/16/3a/0d26ce356c7465a19c9ea8814b960f8a36c3b0d07c323176620b7b483e44/typing_extensions-4.10.0.tar.gz", size = 77558, hashes = { sha256 = "b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb" } }
|
||||||
|
wheels = [{ url = "https://files.pythonhosted.org/packages/f9/de/dc04a3ea60b22624b51c703a84bbe0184abcd1d0b9bc8074b5d6b7ab90bb/typing_extensions-4.10.0-py3-none-any.whl", size = 33926, hashes = { sha256 = "69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475" } }]
|
||||||
|
archive = { path = "iniconfig-2.0.0-py3-none-any.whl", hashes = { sha256 = "c5185871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" } }
|
||||||
|
"#)?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.pip_install()
|
||||||
|
.arg("--preview")
|
||||||
|
.arg("-r")
|
||||||
|
.arg("pylock.toml"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: Package `typing-extensions` includes both a registry (`packages.wheels`) and an archive source (`packages.archive`)
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue