mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
Add --no-binary
and --only-binary
support to requirements.txt
(#2680)
## Summary Closes https://github.com/astral-sh/uv/issues/1461.
This commit is contained in:
parent
0b08ba1e67
commit
32d8ee8ba3
33 changed files with 435 additions and 54 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2980,6 +2980,7 @@ dependencies = [
|
|||
"uv-client",
|
||||
"uv-fs",
|
||||
"uv-normalize",
|
||||
"uv-types",
|
||||
"uv-warnings",
|
||||
]
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ pep508_rs = { workspace = true, features = ["rkyv", "serde", "non-pep508-extensi
|
|||
uv-client = { workspace = true }
|
||||
uv-fs = { workspace = true }
|
||||
uv-normalize = { workspace = true }
|
||||
uv-types = { workspace = true }
|
||||
uv-warnings = { workspace = true }
|
||||
|
||||
async-recursion = { workspace = true }
|
||||
|
|
|
@ -38,6 +38,7 @@ use std::borrow::Cow;
|
|||
use std::fmt::{Display, Formatter};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
use async_recursion::async_recursion;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -54,6 +55,7 @@ use uv_client::BaseClient;
|
|||
use uv_client::BaseClientBuilder;
|
||||
use uv_fs::{normalize_url_path, Simplified};
|
||||
use uv_normalize::ExtraName;
|
||||
use uv_types::{NoBinary, NoBuild, PackageNameSpecifier};
|
||||
use uv_warnings::warn_user;
|
||||
|
||||
/// We emit one of those for each requirements.txt entry
|
||||
|
@ -82,6 +84,10 @@ enum RequirementsTxtStatement {
|
|||
FindLinks(FindLink),
|
||||
/// `--no-index`
|
||||
NoIndex,
|
||||
/// `--no-binary`
|
||||
NoBinary(NoBinary),
|
||||
/// `only-binary`
|
||||
OnlyBinary(NoBuild),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
@ -328,6 +334,10 @@ pub struct RequirementsTxt {
|
|||
pub find_links: Vec<FindLink>,
|
||||
/// Whether to ignore the index, specified with `--no-index`.
|
||||
pub no_index: bool,
|
||||
/// Whether to disallow wheels, specified with `--no-binary`.
|
||||
pub no_binary: NoBinary,
|
||||
/// Whether to allow only wheels, specified with `--only-binary`.
|
||||
pub only_binary: NoBuild,
|
||||
}
|
||||
|
||||
impl RequirementsTxt {
|
||||
|
@ -516,6 +526,12 @@ impl RequirementsTxt {
|
|||
RequirementsTxtStatement::NoIndex => {
|
||||
data.no_index = true;
|
||||
}
|
||||
RequirementsTxtStatement::NoBinary(no_binary) => {
|
||||
data.no_binary.extend(no_binary);
|
||||
}
|
||||
RequirementsTxtStatement::OnlyBinary(only_binary) => {
|
||||
data.only_binary.extend(only_binary);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(data)
|
||||
|
@ -531,6 +547,8 @@ impl RequirementsTxt {
|
|||
extra_index_urls,
|
||||
find_links,
|
||||
no_index,
|
||||
no_binary,
|
||||
only_binary,
|
||||
} = other;
|
||||
self.requirements.extend(requirements);
|
||||
self.constraints.extend(constraints);
|
||||
|
@ -541,6 +559,8 @@ impl RequirementsTxt {
|
|||
self.extra_index_urls.extend(extra_index_urls);
|
||||
self.find_links.extend(find_links);
|
||||
self.no_index = self.no_index || no_index;
|
||||
self.no_binary.extend(no_binary);
|
||||
self.only_binary.extend(only_binary);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -622,6 +642,28 @@ fn parse_entry(
|
|||
}
|
||||
})?;
|
||||
RequirementsTxtStatement::FindLinks(path_or_url)
|
||||
} else if s.eat_if("--no-binary") {
|
||||
let given = parse_value(content, s, |c: char| !['\n', '\r'].contains(&c))?;
|
||||
let specifier = PackageNameSpecifier::from_str(given).map_err(|err| {
|
||||
RequirementsTxtParserError::NoBinary {
|
||||
source: err,
|
||||
specifier: given.to_string(),
|
||||
start,
|
||||
end: s.cursor(),
|
||||
}
|
||||
})?;
|
||||
RequirementsTxtStatement::NoBinary(NoBinary::from_arg(specifier))
|
||||
} else if s.eat_if("--only-binary") {
|
||||
let given = parse_value(content, s, |c: char| !['\n', '\r'].contains(&c))?;
|
||||
let specifier = PackageNameSpecifier::from_str(given).map_err(|err| {
|
||||
RequirementsTxtParserError::NoBinary {
|
||||
source: err,
|
||||
specifier: given.to_string(),
|
||||
start,
|
||||
end: s.cursor(),
|
||||
}
|
||||
})?;
|
||||
RequirementsTxtStatement::OnlyBinary(NoBuild::from_arg(specifier))
|
||||
} else if s.at(char::is_ascii_alphanumeric) || s.at(|char| matches!(char, '.' | '/' | '$')) {
|
||||
let (requirement, hashes) = parse_requirement_and_hashes(s, content, working_dir)?;
|
||||
RequirementsTxtStatement::RequirementEntry(RequirementEntry {
|
||||
|
@ -867,6 +909,18 @@ pub enum RequirementsTxtParserError {
|
|||
InvalidEditablePath(String),
|
||||
UnsupportedUrl(String),
|
||||
MissingRequirementPrefix(String),
|
||||
NoBinary {
|
||||
source: uv_normalize::InvalidNameError,
|
||||
specifier: String,
|
||||
start: usize,
|
||||
end: usize,
|
||||
},
|
||||
OnlyBinary {
|
||||
source: uv_normalize::InvalidNameError,
|
||||
specifier: String,
|
||||
start: usize,
|
||||
end: usize,
|
||||
},
|
||||
UnnamedConstraint {
|
||||
start: usize,
|
||||
end: usize,
|
||||
|
@ -918,6 +972,28 @@ impl RequirementsTxtParserError {
|
|||
},
|
||||
Self::UnsupportedUrl(url) => Self::UnsupportedUrl(url),
|
||||
Self::MissingRequirementPrefix(given) => Self::MissingRequirementPrefix(given),
|
||||
Self::NoBinary {
|
||||
source,
|
||||
specifier,
|
||||
start,
|
||||
end,
|
||||
} => Self::NoBinary {
|
||||
source,
|
||||
specifier,
|
||||
start: start + offset,
|
||||
end: end + offset,
|
||||
},
|
||||
Self::OnlyBinary {
|
||||
source,
|
||||
specifier,
|
||||
start,
|
||||
end,
|
||||
} => Self::OnlyBinary {
|
||||
source,
|
||||
specifier,
|
||||
start: start + offset,
|
||||
end: end + offset,
|
||||
},
|
||||
Self::UnnamedConstraint { start, end } => Self::UnnamedConstraint {
|
||||
start: start + offset,
|
||||
end: end + offset,
|
||||
|
@ -969,6 +1045,12 @@ impl Display for RequirementsTxtParserError {
|
|||
Self::MissingRequirementPrefix(given) => {
|
||||
write!(f, "Requirement `{given}` looks like a requirements file but was passed as a package name. Did you mean `-r {given}`?")
|
||||
}
|
||||
Self::NoBinary { specifier, .. } => {
|
||||
write!(f, "Invalid specifier for `--no-binary`: {specifier}")
|
||||
}
|
||||
Self::OnlyBinary { specifier, .. } => {
|
||||
write!(f, "Invalid specifier for `--only-binary`: {specifier}")
|
||||
}
|
||||
Self::UnnamedConstraint { .. } => {
|
||||
write!(f, "Unnamed requirements are not allowed as constraints")
|
||||
}
|
||||
|
@ -1011,6 +1093,8 @@ impl std::error::Error for RequirementsTxtParserError {
|
|||
Self::InvalidEditablePath(_) => None,
|
||||
Self::UnsupportedUrl(_) => None,
|
||||
Self::MissingRequirementPrefix(_) => None,
|
||||
Self::NoBinary { source, .. } => Some(source),
|
||||
Self::OnlyBinary { source, .. } => Some(source),
|
||||
Self::UnnamedConstraint { .. } => None,
|
||||
Self::UnsupportedRequirement { source, .. } => Some(source),
|
||||
Self::Pep508 { source, .. } => Some(source),
|
||||
|
@ -1055,6 +1139,20 @@ impl Display for RequirementsTxtFileError {
|
|||
self.file.user_display(),
|
||||
)
|
||||
}
|
||||
RequirementsTxtParserError::NoBinary { specifier, .. } => {
|
||||
write!(
|
||||
f,
|
||||
"Invalid specifier for `--no-binary` in `{}`: {specifier}",
|
||||
self.file.user_display(),
|
||||
)
|
||||
}
|
||||
RequirementsTxtParserError::OnlyBinary { specifier, .. } => {
|
||||
write!(
|
||||
f,
|
||||
"Invalid specifier for `--only-binary` in `{}`: {specifier}",
|
||||
self.file.user_display(),
|
||||
)
|
||||
}
|
||||
RequirementsTxtParserError::UnnamedConstraint { .. } => {
|
||||
write!(
|
||||
f,
|
||||
|
@ -1653,6 +1751,69 @@ mod test {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn nested_no_binary() -> Result<()> {
|
||||
let temp_dir = assert_fs::TempDir::new()?;
|
||||
|
||||
let requirements_txt = temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str(indoc! {"
|
||||
flask
|
||||
--no-binary :none:
|
||||
-r child.txt
|
||||
"})?;
|
||||
|
||||
let child = temp_dir.child("child.txt");
|
||||
child.write_str(indoc! {"
|
||||
--no-binary flask
|
||||
"})?;
|
||||
|
||||
let requirements = RequirementsTxt::parse(
|
||||
requirements_txt.path(),
|
||||
temp_dir.path(),
|
||||
&BaseClientBuilder::new(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
insta::assert_debug_snapshot!(requirements, @r###"
|
||||
RequirementsTxt {
|
||||
requirements: [
|
||||
RequirementEntry {
|
||||
requirement: Pep508(
|
||||
Requirement {
|
||||
name: PackageName(
|
||||
"flask",
|
||||
),
|
||||
extras: [],
|
||||
version_or_url: None,
|
||||
marker: None,
|
||||
},
|
||||
),
|
||||
hashes: [],
|
||||
editable: false,
|
||||
},
|
||||
],
|
||||
constraints: [],
|
||||
editables: [],
|
||||
index_url: None,
|
||||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: Packages(
|
||||
[
|
||||
PackageName(
|
||||
"flask",
|
||||
),
|
||||
],
|
||||
),
|
||||
only_binary: None,
|
||||
}
|
||||
"###);
|
||||
|
||||
|
@ -1689,37 +1850,39 @@ mod test {
|
|||
.unwrap();
|
||||
|
||||
insta::assert_debug_snapshot!(requirements, @r###"
|
||||
RequirementsTxt {
|
||||
requirements: [],
|
||||
constraints: [],
|
||||
editables: [
|
||||
EditableRequirement {
|
||||
url: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/foo/bar",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
given: Some(
|
||||
"/foo/bar",
|
||||
),
|
||||
RequirementsTxt {
|
||||
requirements: [],
|
||||
constraints: [],
|
||||
editables: [
|
||||
EditableRequirement {
|
||||
url: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/foo/bar",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
extras: [],
|
||||
path: "/foo/bar",
|
||||
given: Some(
|
||||
"/foo/bar",
|
||||
),
|
||||
},
|
||||
],
|
||||
index_url: None,
|
||||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: true,
|
||||
}
|
||||
"###);
|
||||
extras: [],
|
||||
path: "/foo/bar",
|
||||
},
|
||||
],
|
||||
index_url: None,
|
||||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: true,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -161,4 +161,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -75,4 +75,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -61,4 +61,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -66,4 +66,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -10,4 +10,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -108,4 +108,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -50,4 +50,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -25,4 +25,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -296,4 +296,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -61,4 +61,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -66,4 +66,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -161,4 +161,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -75,4 +75,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -61,4 +61,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -10,4 +10,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -108,4 +108,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -50,4 +50,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -25,4 +25,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -296,4 +296,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -61,4 +61,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -93,4 +93,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -66,4 +66,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -93,4 +93,6 @@ RequirementsTxt {
|
|||
extra_index_urls: [],
|
||||
find_links: [],
|
||||
no_index: false,
|
||||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ use requirements_txt::{EditableRequirement, FindLink, RequirementsTxt};
|
|||
use uv_client::BaseClientBuilder;
|
||||
use uv_fs::Simplified;
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
use uv_types::{NoBinary, NoBuild};
|
||||
|
||||
use crate::pyproject::{Pep621Metadata, PyProjectToml};
|
||||
use crate::{ExtrasSpecification, RequirementsSource};
|
||||
|
@ -39,6 +40,10 @@ pub struct RequirementsSpecification {
|
|||
pub no_index: bool,
|
||||
/// The `--find-links` locations to use for fetching packages.
|
||||
pub find_links: Vec<FlatIndexLocation>,
|
||||
/// The `--no-binary` flags to enforce when selecting distributions.
|
||||
pub no_binary: NoBinary,
|
||||
/// The `--no-build` flags to enforce when selecting distributions.
|
||||
pub no_build: NoBuild,
|
||||
}
|
||||
|
||||
impl RequirementsSpecification {
|
||||
|
@ -65,6 +70,8 @@ impl RequirementsSpecification {
|
|||
extra_index_urls: vec![],
|
||||
no_index: false,
|
||||
find_links: vec![],
|
||||
no_binary: NoBinary::default(),
|
||||
no_build: NoBuild::default(),
|
||||
}
|
||||
}
|
||||
RequirementsSource::Editable(name) => {
|
||||
|
@ -82,6 +89,8 @@ impl RequirementsSpecification {
|
|||
extra_index_urls: vec![],
|
||||
no_index: false,
|
||||
find_links: vec![],
|
||||
no_binary: NoBinary::default(),
|
||||
no_build: NoBuild::default(),
|
||||
}
|
||||
}
|
||||
RequirementsSource::RequirementsTxt(path) => {
|
||||
|
@ -114,6 +123,8 @@ impl RequirementsSpecification {
|
|||
FindLink::Path(path) => FlatIndexLocation::Path(path),
|
||||
})
|
||||
.collect(),
|
||||
no_binary: requirements_txt.no_binary,
|
||||
no_build: requirements_txt.only_binary,
|
||||
}
|
||||
}
|
||||
RequirementsSource::PyprojectToml(path) => {
|
||||
|
@ -151,6 +162,8 @@ impl RequirementsSpecification {
|
|||
extra_index_urls: vec![],
|
||||
no_index: false,
|
||||
find_links: vec![],
|
||||
no_binary: NoBinary::default(),
|
||||
no_build: NoBuild::default(),
|
||||
}
|
||||
} else {
|
||||
let path = fs_err::canonicalize(path)?;
|
||||
|
@ -172,6 +185,8 @@ impl RequirementsSpecification {
|
|||
extra_index_urls: vec![],
|
||||
no_index: false,
|
||||
find_links: vec![],
|
||||
no_binary: NoBinary::default(),
|
||||
no_build: NoBuild::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -195,6 +210,8 @@ impl RequirementsSpecification {
|
|||
extra_index_urls: vec![],
|
||||
no_index: false,
|
||||
find_links: vec![],
|
||||
no_binary: NoBinary::default(),
|
||||
no_build: NoBuild::default(),
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -240,6 +257,8 @@ impl RequirementsSpecification {
|
|||
spec.no_index |= source.no_index;
|
||||
spec.extra_index_urls.extend(source.extra_index_urls);
|
||||
spec.find_links.extend(source.find_links);
|
||||
spec.no_binary.extend(source.no_binary);
|
||||
spec.no_build.extend(source.no_build);
|
||||
}
|
||||
|
||||
// Read all constraints, treating _everything_ as a constraint.
|
||||
|
@ -273,6 +292,8 @@ impl RequirementsSpecification {
|
|||
spec.no_index |= source.no_index;
|
||||
spec.extra_index_urls.extend(source.extra_index_urls);
|
||||
spec.find_links.extend(source.find_links);
|
||||
spec.no_binary.extend(source.no_binary);
|
||||
spec.no_build.extend(source.no_build);
|
||||
}
|
||||
|
||||
// Read all overrides, treating both requirements _and_ constraints as overrides.
|
||||
|
@ -306,6 +327,8 @@ impl RequirementsSpecification {
|
|||
spec.no_index |= source.no_index;
|
||||
spec.extra_index_urls.extend(source.extra_index_urls);
|
||||
spec.find_links.extend(source.find_links);
|
||||
spec.no_binary.extend(source.no_binary);
|
||||
spec.no_build.extend(source.no_build);
|
||||
}
|
||||
|
||||
Ok(spec)
|
||||
|
|
|
@ -47,9 +47,10 @@ impl Display for BuildKind {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub enum NoBinary {
|
||||
/// Allow installation of any wheel.
|
||||
#[default]
|
||||
None,
|
||||
|
||||
/// Do not allow installation from any wheels.
|
||||
|
@ -60,7 +61,7 @@ pub enum NoBinary {
|
|||
}
|
||||
|
||||
impl NoBinary {
|
||||
/// Determine the binary installation strategy to use.
|
||||
/// Determine the binary installation strategy to use for the given arguments.
|
||||
pub fn from_args(no_binary: Vec<PackageNameSpecifier>) -> Self {
|
||||
let combined = PackageNameSpecifiers::from_iter(no_binary.into_iter());
|
||||
match combined {
|
||||
|
@ -69,6 +70,54 @@ impl NoBinary {
|
|||
PackageNameSpecifiers::Packages(packages) => Self::Packages(packages),
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine the binary installation strategy to use for the given argument.
|
||||
pub fn from_arg(no_binary: PackageNameSpecifier) -> Self {
|
||||
Self::from_args(vec![no_binary])
|
||||
}
|
||||
|
||||
/// Combine a set of [`NoBinary`] values.
|
||||
#[must_use]
|
||||
pub fn combine(self, other: Self) -> Self {
|
||||
match (self, other) {
|
||||
// If both are `None`, the result is `None`.
|
||||
(Self::None, Self::None) => Self::None,
|
||||
// If either is `All`, the result is `All`.
|
||||
(Self::All, _) | (_, Self::All) => Self::All,
|
||||
// If one is `None`, the result is the other.
|
||||
(Self::Packages(a), Self::None) => Self::Packages(a),
|
||||
(Self::None, Self::Packages(b)) => Self::Packages(b),
|
||||
// If both are `Packages`, the result is the union of the two.
|
||||
(Self::Packages(mut a), Self::Packages(b)) => {
|
||||
a.extend(b);
|
||||
Self::Packages(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extend a [`NoBinary`] value with another.
|
||||
pub fn extend(&mut self, other: Self) {
|
||||
match (&mut *self, other) {
|
||||
// If either is `All`, the result is `All`.
|
||||
(Self::All, _) | (_, Self::All) => *self = Self::All,
|
||||
// If both are `None`, the result is `None`.
|
||||
(Self::None, Self::None) => {
|
||||
// Nothing to do.
|
||||
}
|
||||
// If one is `None`, the result is the other.
|
||||
(Self::Packages(_), Self::None) => {
|
||||
// Nothing to do.
|
||||
}
|
||||
(Self::None, Self::Packages(b)) => {
|
||||
// Take ownership of `b`.
|
||||
*self = Self::Packages(b);
|
||||
}
|
||||
// If both are `Packages`, the result is the union of the two.
|
||||
(Self::Packages(a), Self::Packages(b)) => {
|
||||
a.extend(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NoBinary {
|
||||
|
@ -78,9 +127,10 @@ impl NoBinary {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub enum NoBuild {
|
||||
/// Allow building wheels from any source distribution.
|
||||
#[default]
|
||||
None,
|
||||
|
||||
/// Do not allow building wheels from any source distribution.
|
||||
|
@ -91,7 +141,7 @@ pub enum NoBuild {
|
|||
}
|
||||
|
||||
impl NoBuild {
|
||||
/// Determine the build strategy to use.
|
||||
/// Determine the build strategy to use for the given arguments.
|
||||
pub fn from_args(only_binary: Vec<PackageNameSpecifier>, no_build: bool) -> Self {
|
||||
if no_build {
|
||||
Self::All
|
||||
|
@ -104,6 +154,54 @@ impl NoBuild {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine the build strategy to use for the given argument.
|
||||
pub fn from_arg(no_build: PackageNameSpecifier) -> Self {
|
||||
Self::from_args(vec![no_build], false)
|
||||
}
|
||||
|
||||
/// Combine a set of [`NoBuild`] values.
|
||||
#[must_use]
|
||||
pub fn combine(self, other: Self) -> Self {
|
||||
match (self, other) {
|
||||
// If both are `None`, the result is `None`.
|
||||
(Self::None, Self::None) => Self::None,
|
||||
// If either is `All`, the result is `All`.
|
||||
(Self::All, _) | (_, Self::All) => Self::All,
|
||||
// If one is `None`, the result is the other.
|
||||
(Self::Packages(a), Self::None) => Self::Packages(a),
|
||||
(Self::None, Self::Packages(b)) => Self::Packages(b),
|
||||
// If both are `Packages`, the result is the union of the two.
|
||||
(Self::Packages(mut a), Self::Packages(b)) => {
|
||||
a.extend(b);
|
||||
Self::Packages(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extend a [`NoBuild`] value with another.
|
||||
pub fn extend(&mut self, other: Self) {
|
||||
match (&mut *self, other) {
|
||||
// If either is `All`, the result is `All`.
|
||||
(Self::All, _) | (_, Self::All) => *self = Self::All,
|
||||
// If both are `None`, the result is `None`.
|
||||
(Self::None, Self::None) => {
|
||||
// Nothing to do.
|
||||
}
|
||||
// If one is `None`, the result is the other.
|
||||
(Self::Packages(_), Self::None) => {
|
||||
// Nothing to do.
|
||||
}
|
||||
(Self::None, Self::Packages(b)) => {
|
||||
// Take ownership of `b`.
|
||||
*self = Self::Packages(b);
|
||||
}
|
||||
// If both are `Packages`, the result is the union of the two.
|
||||
(Self::Packages(a), Self::Packages(b)) => {
|
||||
a.extend(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NoBuild {
|
||||
|
|
|
@ -68,7 +68,7 @@ pub(crate) async fn pip_compile(
|
|||
config_settings: ConfigSettings,
|
||||
connectivity: Connectivity,
|
||||
no_build_isolation: bool,
|
||||
no_build: &NoBuild,
|
||||
no_build: NoBuild,
|
||||
python_version: Option<PythonVersion>,
|
||||
exclude_newer: Option<DateTime<Utc>>,
|
||||
annotation_style: AnnotationStyle,
|
||||
|
@ -105,6 +105,8 @@ pub(crate) async fn pip_compile(
|
|||
extra_index_urls,
|
||||
no_index,
|
||||
find_links,
|
||||
no_binary: _,
|
||||
no_build: specified_no_build,
|
||||
} = RequirementsSpecification::from_sources(
|
||||
requirements,
|
||||
constraints,
|
||||
|
@ -231,6 +233,9 @@ pub(crate) async fn pip_compile(
|
|||
BuildIsolation::Isolated
|
||||
};
|
||||
|
||||
// Combine the `--no-build` flags.
|
||||
let no_build = no_build.combine(specified_no_build);
|
||||
|
||||
let build_dispatch = BuildDispatch::new(
|
||||
&client,
|
||||
&cache,
|
||||
|
@ -242,7 +247,7 @@ pub(crate) async fn pip_compile(
|
|||
setup_py,
|
||||
&config_settings,
|
||||
build_isolation,
|
||||
no_build,
|
||||
&no_build,
|
||||
&NoBinary::None,
|
||||
)
|
||||
.with_options(OptionsBuilder::new().exclude_newer(exclude_newer).build());
|
||||
|
|
|
@ -70,8 +70,8 @@ pub(crate) async fn pip_install(
|
|||
connectivity: Connectivity,
|
||||
config_settings: &ConfigSettings,
|
||||
no_build_isolation: bool,
|
||||
no_build: &NoBuild,
|
||||
no_binary: &NoBinary,
|
||||
no_build: NoBuild,
|
||||
no_binary: NoBinary,
|
||||
strict: bool,
|
||||
exclude_newer: Option<DateTime<Utc>>,
|
||||
python: Option<String>,
|
||||
|
@ -100,6 +100,8 @@ pub(crate) async fn pip_install(
|
|||
extra_index_urls,
|
||||
no_index,
|
||||
find_links,
|
||||
no_binary: specified_no_binary,
|
||||
no_build: specified_no_build,
|
||||
extras: _,
|
||||
} = read_requirements(
|
||||
requirements,
|
||||
|
@ -213,6 +215,10 @@ pub(crate) async fn pip_install(
|
|||
BuildIsolation::Isolated
|
||||
};
|
||||
|
||||
// Combine the `--no-binary` and `--no-build` flags.
|
||||
let no_binary = no_binary.combine(specified_no_binary);
|
||||
let no_build = no_build.combine(specified_no_build);
|
||||
|
||||
// Create a shared in-memory index.
|
||||
let index = InMemoryIndex::default();
|
||||
|
||||
|
@ -231,8 +237,8 @@ pub(crate) async fn pip_install(
|
|||
setup_py,
|
||||
config_settings,
|
||||
build_isolation,
|
||||
no_build,
|
||||
no_binary,
|
||||
&no_build,
|
||||
&no_binary,
|
||||
)
|
||||
.with_options(OptionsBuilder::new().exclude_newer(exclude_newer).build());
|
||||
|
||||
|
@ -336,8 +342,8 @@ pub(crate) async fn pip_install(
|
|||
setup_py,
|
||||
config_settings,
|
||||
build_isolation,
|
||||
no_build,
|
||||
no_binary,
|
||||
&no_build,
|
||||
&no_binary,
|
||||
)
|
||||
.with_options(OptionsBuilder::new().exclude_newer(exclude_newer).build())
|
||||
};
|
||||
|
@ -348,7 +354,7 @@ pub(crate) async fn pip_install(
|
|||
editables,
|
||||
site_packages,
|
||||
reinstall,
|
||||
no_binary,
|
||||
&no_binary,
|
||||
link_mode,
|
||||
compile,
|
||||
&index_locations,
|
||||
|
|
|
@ -49,8 +49,8 @@ pub(crate) async fn pip_sync(
|
|||
connectivity: Connectivity,
|
||||
config_settings: &ConfigSettings,
|
||||
no_build_isolation: bool,
|
||||
no_build: &NoBuild,
|
||||
no_binary: &NoBinary,
|
||||
no_build: NoBuild,
|
||||
no_binary: NoBinary,
|
||||
strict: bool,
|
||||
python: Option<String>,
|
||||
system: bool,
|
||||
|
@ -78,6 +78,8 @@ pub(crate) async fn pip_sync(
|
|||
extra_index_urls,
|
||||
no_index,
|
||||
find_links,
|
||||
no_binary: specified_no_binary,
|
||||
no_build: specified_no_build,
|
||||
} = RequirementsSpecification::from_simple_sources(sources, &client_builder).await?;
|
||||
|
||||
// Validate that the requirements are non-empty.
|
||||
|
@ -165,6 +167,10 @@ pub(crate) async fn pip_sync(
|
|||
BuildIsolation::Isolated
|
||||
};
|
||||
|
||||
// Combine the `--no-binary` and `--no-build` flags.
|
||||
let no_binary = no_binary.combine(specified_no_binary);
|
||||
let no_build = no_build.combine(specified_no_build);
|
||||
|
||||
// Prep the build context.
|
||||
let build_dispatch = BuildDispatch::new(
|
||||
&client,
|
||||
|
@ -177,8 +183,8 @@ pub(crate) async fn pip_sync(
|
|||
setup_py,
|
||||
config_settings,
|
||||
build_isolation,
|
||||
no_build,
|
||||
no_binary,
|
||||
&no_build,
|
||||
&no_binary,
|
||||
);
|
||||
|
||||
// Convert from unnamed to named requirements.
|
||||
|
@ -231,7 +237,7 @@ pub(crate) async fn pip_sync(
|
|||
.build(
|
||||
site_packages,
|
||||
reinstall,
|
||||
no_binary,
|
||||
&no_binary,
|
||||
&index_locations,
|
||||
&cache,
|
||||
&venv,
|
||||
|
@ -267,8 +273,8 @@ pub(crate) async fn pip_sync(
|
|||
&client,
|
||||
venv.interpreter(),
|
||||
&flat_index,
|
||||
no_binary,
|
||||
no_build,
|
||||
&no_binary,
|
||||
&no_build,
|
||||
)
|
||||
.with_reporter(FinderReporter::from(printer).with_length(remote.len() as u64));
|
||||
let resolution = wheel_finder.resolve(&remote).await?;
|
||||
|
|
|
@ -1538,7 +1538,7 @@ async fn run() -> Result<ExitStatus> {
|
|||
Connectivity::Online
|
||||
},
|
||||
args.no_build_isolation,
|
||||
&no_build,
|
||||
no_build,
|
||||
args.python_version,
|
||||
args.exclude_newer,
|
||||
args.annotation_style,
|
||||
|
@ -1594,8 +1594,8 @@ async fn run() -> Result<ExitStatus> {
|
|||
},
|
||||
&config_settings,
|
||||
args.no_build_isolation,
|
||||
&no_build,
|
||||
&no_binary,
|
||||
no_build,
|
||||
no_binary,
|
||||
args.strict,
|
||||
args.python,
|
||||
args.system,
|
||||
|
@ -1690,8 +1690,8 @@ async fn run() -> Result<ExitStatus> {
|
|||
},
|
||||
&config_settings,
|
||||
args.no_build_isolation,
|
||||
&no_build,
|
||||
&no_binary,
|
||||
no_build,
|
||||
no_binary,
|
||||
args.strict,
|
||||
args.exclude_newer,
|
||||
args.python,
|
||||
|
|
|
@ -1408,6 +1408,38 @@ fn reinstall_no_binary() {
|
|||
context.assert_command("import anyio").success();
|
||||
}
|
||||
|
||||
/// Respect `--only-binary` flags in `requirements.txt`
|
||||
#[test]
|
||||
fn only_binary_requirements_txt() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt
|
||||
.write_str(indoc! {r"
|
||||
django_allauth==0.51.0
|
||||
--only-binary django_allauth
|
||||
"
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
uv_snapshot!(command(&context)
|
||||
.arg("-r")
|
||||
.arg("requirements.txt")
|
||||
.arg("--strict"), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because django-allauth==0.51.0 is unusable because no wheels
|
||||
are usable and building from source is disabled and you require
|
||||
django-allauth==0.51.0, we can conclude that the requirements are
|
||||
unsatisfiable.
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
/// Install a package into a virtual environment, and ensuring that the executable permissions
|
||||
/// are retained.
|
||||
///
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue