From 92cb2c3c912ad47ef8cbcf384ee1f4bde6526789 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 5 Sep 2024 11:23:27 -0400 Subject: [PATCH] Accept --build-constraints in uv build --- crates/uv-cli/src/lib.rs | 9 +++++ crates/uv/src/commands/build.rs | 11 ++++-- crates/uv/src/lib.rs | 8 +++++ crates/uv/src/settings.rs | 6 ++++ crates/uv/tests/build.rs | 60 +++++++++++++++++++++++++++++++++ docs/reference/cli.md | 5 +++ 6 files changed, 97 insertions(+), 2 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index ef46cf7b3b14..fe91416972ea 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -1979,6 +1979,15 @@ pub struct BuildArgs { #[arg(long)] pub wheel: bool, + /// Constrain build dependencies using the given requirements files when building + /// distributions. + /// + /// Constraints files are `requirements.txt`-like files that only control the _version_ of a + /// build dependency that's installed. However, including a package in a constraints file will + /// _not_ trigger the inclusion of that package on its own. + #[arg(long, short, env = "UV_BUILD_CONSTRAINT", value_delimiter = ' ', value_parser = parse_maybe_file_path)] + pub build_constraint: Vec>, + /// The Python interpreter to use for the build environment. /// /// By default, builds are executed in isolated virtual environments. The diff --git a/crates/uv/src/commands/build.rs b/crates/uv/src/commands/build.rs index 67f54676cb1e..8558c77f1bfb 100644 --- a/crates/uv/src/commands/build.rs +++ b/crates/uv/src/commands/build.rs @@ -5,6 +5,7 @@ use crate::printer::Printer; use crate::settings::{ResolverSettings, ResolverSettingsRef}; use std::borrow::Cow; +use crate::commands::pip::operations; use anyhow::Result; use distribution_filename::SourceDistExtension; use owo_colors::OwoColorize; @@ -20,6 +21,7 @@ use uv_python::{ EnvironmentPreference, PythonDownloads, PythonEnvironment, PythonInstallation, PythonPreference, PythonRequest, PythonVersionFile, VersionRequest, }; +use uv_requirements::RequirementsSource; use uv_resolver::{FlatIndex, RequiresPython}; use uv_types::{BuildContext, BuildIsolation, HashStrategy}; use uv_workspace::{DiscoveryOptions, Workspace}; @@ -32,6 +34,7 @@ pub(crate) async fn build( output_dir: Option, sdist: bool, wheel: bool, + build_constraints: Vec, python: Option, settings: ResolverSettings, no_config: bool, @@ -49,6 +52,7 @@ pub(crate) async fn build( output_dir.as_deref(), sdist, wheel, + &build_constraints, python.as_deref(), settings.as_ref(), no_config, @@ -88,6 +92,7 @@ async fn build_impl( output_dir: Option<&Path>, sdist: bool, wheel: bool, + build_constraints: &[RequirementsSource], python_request: Option<&str>, settings: ResolverSettingsRef<'_>, no_config: bool, @@ -225,6 +230,10 @@ async fn build_impl( store_credentials_from_url(url); } + // Read build constraints. + let build_constraints = + operations::read_constraints(build_constraints, &client_builder).await?; + // Initialize the registry client. let client = RegistryClientBuilder::new(cache.clone()) .native_tls(native_tls) @@ -251,8 +260,6 @@ async fn build_impl( // TODO(charlie): These are all default values. We should consider whether we want to make them // optional on the downstream APIs. - let build_constraints = Constraints::default(); - let build_hasher = HashStrategy::default(); let hasher = HashStrategy::None; // Resolve the flat indexes from `--find-links`. diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index ec211c035a47..e10abee99561 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -670,12 +670,20 @@ async fn run(cli: Cli) -> Result { .combine(Refresh::from(args.settings.upgrade.clone())), ); + // Resolve the build constraints. + let build_constraints = args + .build_constraint + .into_iter() + .map(RequirementsSource::from_constraints_txt) + .collect::>(); + commands::build( args.src, args.package, args.out_dir, args.sdist, args.wheel, + build_constraints, args.python, args.settings, cli.no_config, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 084373c90206..4063d91f005b 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -1629,6 +1629,7 @@ pub(crate) struct BuildSettings { pub(crate) out_dir: Option, pub(crate) sdist: bool, pub(crate) wheel: bool, + pub(crate) build_constraint: Vec, pub(crate) python: Option, pub(crate) refresh: Refresh, pub(crate) settings: ResolverSettings, @@ -1643,6 +1644,7 @@ impl BuildSettings { package, sdist, wheel, + build_constraint, python, build, refresh, @@ -1655,6 +1657,10 @@ impl BuildSettings { out_dir, sdist, wheel, + build_constraint: build_constraint + .into_iter() + .filter_map(Maybe::into_option) + .collect(), python, refresh: Refresh::from(refresh), settings: ResolverSettings::combine(resolver_options(resolver, build), filesystem), diff --git a/crates/uv/tests/build.rs b/crates/uv/tests/build.rs index 7c7f52b14182..5bba20ba2ace 100644 --- a/crates/uv/tests/build.rs +++ b/crates/uv/tests/build.rs @@ -1146,3 +1146,63 @@ fn workspace() -> Result<()> { Ok(()) } + +#[test] +fn build_constraints() -> Result<()> { + let context = TestContext::new("3.12"); + let filters = context + .filters() + .into_iter() + .chain([ + (r"exit code: 1", "exit status: 1"), + (r"bdist\.[^/\\\s]+-[^/\\\s]+", "bdist.linux-x86_64"), + (r"\\\.", ""), + ]) + .collect::>(); + + let project = context.temp_dir.child("project"); + + let constraints = project.child("constraints.txt"); + constraints.write_str("setuptools==0.1.0")?; + + let pyproject_toml = project.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["anyio==3.7.0"] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + "#, + )?; + + project.child("src").child("__init__.py").touch()?; + project.child("README").touch()?; + + uv_snapshot!(&filters, context.build().arg("--build-constraint").arg("constraints.txt").current_dir(&project), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Building source distribution... + error: Failed to install requirements from `build-system.requires` (resolve) + Caused by: No solution found when resolving: setuptools>=42 + Caused by: Because you require setuptools>=42 and setuptools==0.1.0, we can conclude that your requirements are unsatisfiable. + "###); + + project + .child("dist") + .child("project-0.1.0.tar.gz") + .assert(predicate::path::missing()); + project + .child("dist") + .child("project-0.1.0-py3-none-any.whl") + .assert(predicate::path::missing()); + + Ok(()) +} diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 85f107e0d86b..5e6839e9123d 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -6225,6 +6225,11 @@ uv build [OPTIONS] [SRC]

WARNING: Hosts included in this list will not be verified against the system’s certificate store. Only use --allow-insecure-host in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.

May also be set with the UV_INSECURE_HOST environment variable.

+
--build-constraint, -b build-constraint

Constrain build dependencies using the given requirements files when building distributions.

+ +

Constraints files are requirements.txt-like files that only control the version of a build dependency that’s installed. However, including a package in a constraints file will not trigger the inclusion of that package on its own.

+ +

May also be set with the UV_BUILD_CONSTRAINT environment variable.

--cache-dir cache-dir

Path to the cache directory.

Defaults to $HOME/Library/Caches/uv on macOS, $XDG_CACHE_HOME/uv or $HOME/.cache/uv on Linux, and %LOCALAPPDATA%\uv\cache on Windows.