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:
Charlie Marsh 2024-03-26 21:12:29 -04:00 committed by GitHub
parent 0b08ba1e67
commit 32d8ee8ba3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 435 additions and 54 deletions

1
Cargo.lock generated
View file

@ -2980,6 +2980,7 @@ dependencies = [
"uv-client",
"uv-fs",
"uv-normalize",
"uv-types",
"uv-warnings",
]

View file

@ -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 }

View file

@ -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(())
}

View file

@ -161,4 +161,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -75,4 +75,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -61,4 +61,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -66,4 +66,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -10,4 +10,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -108,4 +108,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -50,4 +50,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -25,4 +25,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -296,4 +296,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -61,4 +61,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -66,4 +66,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -161,4 +161,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -75,4 +75,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -61,4 +61,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -10,4 +10,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -108,4 +108,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -50,4 +50,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -25,4 +25,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -296,4 +296,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -61,4 +61,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -93,4 +93,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -66,4 +66,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -93,4 +93,6 @@ RequirementsTxt {
extra_index_urls: [],
find_links: [],
no_index: false,
no_binary: None,
only_binary: None,
}

View file

@ -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)

View file

@ -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 {

View file

@ -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());

View file

@ -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,

View file

@ -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?;

View file

@ -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,

View file

@ -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.
///