From 6a537979bedd8258ea6e6744b43c407d56d235f0 Mon Sep 17 00:00:00 2001 From: Ahmed Ilyas Date: Wed, 31 Jul 2024 00:02:41 +0200 Subject: [PATCH] Support build constraints --- crates/bench/benches/uv.rs | 2 + crates/uv-cli/src/lib.rs | 24 +++++++++++ crates/uv-dev/src/build.rs | 4 ++ crates/uv-dispatch/src/lib.rs | 23 ++++++++-- crates/uv/src/commands/pip/compile.rs | 16 +++++++ crates/uv/src/commands/pip/install.rs | 16 +++++++ crates/uv/src/commands/pip/sync.rs | 16 +++++++ crates/uv/src/commands/project/add.rs | 4 ++ crates/uv/src/commands/project/lock.rs | 6 +++ crates/uv/src/commands/project/mod.rs | 13 ++++++ crates/uv/src/commands/project/sync.rs | 3 ++ crates/uv/src/commands/venv.rs | 3 ++ crates/uv/src/lib.rs | 19 ++++++++ crates/uv/src/settings.rs | 18 ++++++++ crates/uv/tests/pip_compile.rs | 60 ++++++++++++++++++++++++++ crates/uv/tests/pip_install.rs | 55 +++++++++++++++++++++++ crates/uv/tests/pip_sync.rs | 59 +++++++++++++++++++++++++ crates/uv/tests/show_settings.rs | 8 ++++ 18 files changed, 345 insertions(+), 4 deletions(-) diff --git a/crates/bench/benches/uv.rs b/crates/bench/benches/uv.rs index 42c9d2fb804d9..9e262cf2b39d1 100644 --- a/crates/bench/benches/uv.rs +++ b/crates/bench/benches/uv.rs @@ -151,10 +151,12 @@ mod resolver { let python_requirement = PythonRequirement::from_interpreter(interpreter); let options = OptionsBuilder::new().exclude_newer(exclude_newer).build(); + let build_constraints = []; let build_context = BuildDispatch::new( client, &cache, + &build_constraints, interpreter, &index_locations, &flat_index, diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 6d4ca7b62f758..3fd5e09c91a7d 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -520,6 +520,14 @@ pub struct PipCompileArgs { #[arg(long, env = "UV_OVERRIDE", value_delimiter = ' ', value_parser = parse_maybe_file_path)] pub r#override: Vec>, + /// Constrain build requirement versions using the given requirements files. + /// + /// Constraints files are `requirements.txt`-like files that only control the _version_ of a + /// requirement that's installed. However, including a package in a constraints file will _not_ + /// trigger the installation of that package. + #[arg(long, short, env = "UV_BUILD_CONSTRAINT", value_delimiter = ' ', value_parser = parse_maybe_file_path)] + pub build_constraint: Vec>, + /// Include optional dependencies from the extra group name; may be provided more than once. /// /// Only applies to `pyproject.toml`, `setup.py`, and `setup.cfg` sources. @@ -817,6 +825,14 @@ pub struct PipSyncArgs { #[arg(long, short, env = "UV_CONSTRAINT", value_delimiter = ' ', value_parser = parse_maybe_file_path)] pub constraint: Vec>, + /// Constrain build requirement versions using the given requirements files. + /// + /// Constraints files are `requirements.txt`-like files that only control the _version_ of a + /// requirement that's installed. However, including a package in a constraints file will _not_ + /// trigger the installation of that package. + #[arg(long, short, env = "UV_BUILD_CONSTRAINT", value_delimiter = ' ', value_parser = parse_maybe_file_path)] + pub build_constraint: Vec>, + #[command(flatten)] pub installer: InstallerArgs, @@ -1084,6 +1100,14 @@ pub struct PipInstallArgs { #[arg(long, env = "UV_OVERRIDE", value_delimiter = ' ', value_parser = parse_maybe_file_path)] pub r#override: Vec>, + /// Constrain build requirement versions using the given requirements files. + /// + /// Constraints files are `requirements.txt`-like files that only control the _version_ of a + /// requirement that's installed. However, including a package in a constraints file will _not_ + /// trigger the installation of that package. + #[arg(long, short, env = "UV_BUILD_CONSTRAINT", value_delimiter = ' ', value_parser = parse_maybe_file_path)] + pub build_constraint: Vec>, + /// Include optional dependencies from the extra group name; may be provided more than once. /// /// Only applies to `pyproject.toml`, `setup.py`, and `setup.cfg` sources. diff --git a/crates/uv-dev/src/build.rs b/crates/uv-dev/src/build.rs index 5d4b1e5fbe778..b3e79170670e0 100644 --- a/crates/uv-dev/src/build.rs +++ b/crates/uv-dev/src/build.rs @@ -75,9 +75,13 @@ pub(crate) async fn build(args: BuildArgs) -> Result { )?; let build_options = BuildOptions::default(); + // TODO: support build constraints + let build_constraints = []; + let build_dispatch = BuildDispatch::new( &client, &cache, + &build_constraints, python.interpreter(), &index_urls, &flat_index, diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index 77c7039206acb..79c2b210de080 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -17,7 +17,8 @@ use uv_build::{SourceBuild, SourceBuildContext}; use uv_cache::Cache; use uv_client::RegistryClient; use uv_configuration::{ - BuildKind, BuildOptions, ConfigSettings, IndexStrategy, Reinstall, SetupPyStrategy, + BuildKind, BuildOptions, ConfigSettings, Constraints, IndexStrategy, Overrides, Reinstall, + SetupPyStrategy, }; use uv_configuration::{Concurrency, PreviewMode}; use uv_distribution::DistributionDatabase; @@ -25,8 +26,8 @@ use uv_git::GitResolver; use uv_installer::{Installer, Plan, Planner, Preparer, SitePackages}; use uv_python::{Interpreter, PythonEnvironment}; use uv_resolver::{ - ExcludeNewer, FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, PythonRequirement, Resolver, - ResolverMarkers, + ExcludeNewer, Exclusions, FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, Preferences, + PythonRequirement, Resolver, ResolverMarkers, }; use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight}; @@ -35,6 +36,7 @@ use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrateg pub struct BuildDispatch<'a> { client: &'a RegistryClient, cache: &'a Cache, + constraints: Constraints, interpreter: &'a Interpreter, index_locations: &'a IndexLocations, index_strategy: IndexStrategy, @@ -58,6 +60,7 @@ impl<'a> BuildDispatch<'a> { pub fn new( client: &'a RegistryClient, cache: &'a Cache, + constraints: &'a [Requirement], interpreter: &'a Interpreter, index_locations: &'a IndexLocations, flat_index: &'a FlatIndex, @@ -77,6 +80,7 @@ impl<'a> BuildDispatch<'a> { Self { client, cache, + constraints: Constraints::from_requirements(constraints.iter().cloned()), interpreter, index_locations, flat_index, @@ -140,8 +144,19 @@ impl<'a> BuildContext for BuildDispatch<'a> { let python_requirement = PythonRequirement::from_interpreter(self.interpreter); let markers = self.interpreter.markers(); let tags = self.interpreter.tags()?; + let manifest = Manifest::new( + requirements.to_vec(), + self.constraints.clone(), + Overrides::default(), + Vec::new(), + Preferences::default(), + None, + Exclusions::default(), + Vec::new(), + ); + let resolver = Resolver::new( - Manifest::simple(requirements.to_vec()), + manifest, OptionsBuilder::new() .exclude_newer(self.exclude_newer) .index_strategy(self.index_strategy) diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index 3502255aed804..46b883763eb27 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -48,6 +48,7 @@ pub(crate) async fn pip_compile( requirements: &[RequirementsSource], constraints: &[RequirementsSource], overrides: &[RequirementsSource], + build_constraints: &[RequirementsSource], constraints_from_workspace: Vec, overrides_from_workspace: Vec, extras: ExtrasSpecification, @@ -143,6 +144,20 @@ pub(crate) async fn pip_compile( ) .collect(); + // Read build constraints and overrides + let RequirementsSpecification { + constraints: build_constraints, + overrides: _build_overrides, + .. + } = operations::read_requirements( + &[], + build_constraints, + &[], + &ExtrasSpecification::None, + &client_builder, + ) + .await?; + // If all the metadata could be statically resolved, validate that every extra was used. If we // need to resolve metadata via PEP 517, we don't know which extras are used until much later. if source_trees.is_empty() { @@ -304,6 +319,7 @@ pub(crate) async fn pip_compile( let build_dispatch = BuildDispatch::new( &client, &cache, + &build_constraints, &interpreter, &index_locations, &flat_index, diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index d25c4f7cd27d3..f08d20c559738 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -40,6 +40,7 @@ pub(crate) async fn pip_install( requirements: &[RequirementsSource], constraints: &[RequirementsSource], overrides: &[RequirementsSource], + build_constraints: &[RequirementsSource], constraints_from_workspace: Vec, overrides_from_workspace: Vec, extras: &ExtrasSpecification, @@ -105,6 +106,20 @@ pub(crate) async fn pip_install( ) .await?; + // Read build constraints and overrides + let RequirementsSpecification { + constraints: build_constraints, + overrides: _build_overrides, + .. + } = operations::read_requirements( + &[], + build_constraints, + &[], + &ExtrasSpecification::None, + &client_builder, + ) + .await?; + let constraints: Vec = constraints .iter() .cloned() @@ -294,6 +309,7 @@ pub(crate) async fn pip_install( let build_dispatch = BuildDispatch::new( &client, &cache, + &build_constraints, interpreter, &index_locations, &flat_index, diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index aa71e37c967c2..0d1f406342ec4 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -38,6 +38,7 @@ use crate::printer::Printer; pub(crate) async fn pip_sync( requirements: &[RequirementsSource], constraints: &[RequirementsSource], + build_constraints: &[RequirementsSource], reinstall: Reinstall, link_mode: LinkMode, compile: bool, @@ -103,6 +104,20 @@ pub(crate) async fn pip_sync( ) .await?; + // Read build constraints and overrides + let RequirementsSpecification { + constraints: build_constraints, + overrides: _build_overrides, + .. + } = operations::read_requirements( + &[], + build_constraints, + &[], + &ExtrasSpecification::None, + &client_builder, + ) + .await?; + // Validate that the requirements are non-empty. if !allow_empty_requirements { let num_requirements = requirements.len() + source_trees.len(); @@ -240,6 +255,7 @@ pub(crate) async fn pip_sync( let build_dispatch = BuildDispatch::new( &client, &cache, + &build_constraints, interpreter, &index_locations, &flat_index, diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 1e4da31e17966..3fef9a8941e14 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -121,10 +121,14 @@ pub(crate) async fn add( FlatIndex::from_entries(entries, Some(&tags), &hasher, &settings.build_options) }; + // TODO: read locked build constraints + let build_constraints = []; + // Create a build dispatch. let build_dispatch = BuildDispatch::new( &client, cache, + &build_constraints, venv.interpreter(), &settings.index_locations, &flat_index, diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index 1edbbbe94731e..2c67ae2e76637 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -396,10 +396,13 @@ async fn do_lock( // Prefill the index with the lockfile metadata. let index = lock.to_index(workspace.install_path(), upgrade)?; + // TODO: read locked build constraints + let build_constraints = []; // Create a build dispatch. let build_dispatch = BuildDispatch::new( &client, cache, + &build_constraints, interpreter, index_locations, &flat_index, @@ -472,10 +475,13 @@ async fn do_lock( None => { debug!("Starting clean resolution"); + // TODO: read locked build constraints + let build_constraints = []; // Create a build dispatch. let build_dispatch = BuildDispatch::new( &client, cache, + &build_constraints, interpreter, index_locations, &flat_index, diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 999c89db9f8ff..bbccb5cf1f9a8 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -371,10 +371,13 @@ pub(crate) async fn resolve_names( let setup_py = SetupPyStrategy::default(); let flat_index = FlatIndex::default(); + // TODO: read locked build constraints + let build_constraints = []; // Create a build dispatch. let build_dispatch = BuildDispatch::new( &client, cache, + &build_constraints, interpreter, index_locations, &flat_index, @@ -491,10 +494,13 @@ pub(crate) async fn resolve_environment<'a>( FlatIndex::from_entries(entries, Some(tags), &hasher, build_options) }; + // TODO: read locked build constraints + let build_constraints = []; // Create a build dispatch. let resolve_dispatch = BuildDispatch::new( &client, cache, + &build_constraints, interpreter, index_locations, &flat_index, @@ -604,10 +610,13 @@ pub(crate) async fn sync_environment( FlatIndex::from_entries(entries, Some(tags), &hasher, build_options) }; + // TODO: read locked build constraints + let build_constraints = []; // Create a build dispatch. let build_dispatch = BuildDispatch::new( &client, cache, + &build_constraints, interpreter, index_locations, &flat_index, @@ -765,10 +774,14 @@ pub(crate) async fn update_environment( FlatIndex::from_entries(entries, Some(tags), &hasher, build_options) }; + // TODO: read locked build constraints + let build_constraints = []; + // Create a build dispatch. let build_dispatch = BuildDispatch::new( &client, cache, + &build_constraints, interpreter, index_locations, &flat_index, diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 7726cdb2a4d6a..42c01385580b1 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -199,10 +199,13 @@ pub(super) async fn do_sync( FlatIndex::from_entries(entries, Some(tags), &hasher, build_options) }; + // TODO: read locked build constraints + let build_constraints = []; // Create a build dispatch. let build_dispatch = BuildDispatch::new( &client, cache, + &build_constraints, venv.interpreter(), index_locations, &flat_index, diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 67236e56e12d5..d1a18e07a5193 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -252,10 +252,13 @@ async fn venv_impl( // Do not allow builds let build_options = BuildOptions::new(NoBinary::None, NoBuild::All); + let build_constraints = []; + // Prep the build context. let build_dispatch = BuildDispatch::new( &client, cache, + &build_constraints, interpreter, index_locations, &flat_index, diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 3139d978f1caa..f5f230d367549 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -202,11 +202,17 @@ async fn run(cli: Cli) -> Result { .into_iter() .map(RequirementsSource::from_overrides_txt) .collect::>(); + let build_constraints = args + .build_constraint + .into_iter() + .map(RequirementsSource::from_constraints_txt) + .collect::>(); commands::pip_compile( &requirements, &constraints, &overrides, + &build_constraints, args.constraints_from_workspace, args.overrides_from_workspace, args.settings.extras, @@ -282,10 +288,16 @@ async fn run(cli: Cli) -> Result { .into_iter() .map(RequirementsSource::from_constraints_txt) .collect::>(); + let build_constraints = args + .build_constraint + .into_iter() + .map(RequirementsSource::from_constraints_txt) + .collect::>(); commands::pip_sync( &requirements, &constraints, + &build_constraints, args.settings.reinstall, args.settings.link_mode, args.settings.compile_bytecode, @@ -358,10 +370,17 @@ async fn run(cli: Cli) -> Result { .map(RequirementsSource::from_overrides_txt) .collect::>(); + let build_constraints = args + .build_constraint + .into_iter() + .map(RequirementsSource::from_overrides_txt) + .collect::>(); + commands::pip_install( &requirements, &constraints, &overrides, + &build_constraints, args.constraints_from_workspace, args.overrides_from_workspace, &args.settings.extras, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 880f3d4f7c2a6..468ba1ae56258 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -792,6 +792,7 @@ pub(crate) struct PipCompileSettings { pub(crate) r#override: Vec, pub(crate) constraints_from_workspace: Vec, pub(crate) overrides_from_workspace: Vec, + pub(crate) build_constraint: Vec, pub(crate) refresh: Refresh, pub(crate) settings: PipSettings, } @@ -806,6 +807,7 @@ impl PipCompileSettings { extra, all_extras, no_all_extras, + build_constraint, refresh, no_deps, deps, @@ -886,6 +888,10 @@ impl PipCompileSettings { .into_iter() .filter_map(Maybe::into_option) .collect(), + build_constraint: build_constraint + .into_iter() + .filter_map(Maybe::into_option) + .collect(), r#override: r#override .into_iter() .filter_map(Maybe::into_option) @@ -939,6 +945,7 @@ impl PipCompileSettings { pub(crate) struct PipSyncSettings { pub(crate) src_file: Vec, pub(crate) constraint: Vec, + pub(crate) build_constraint: Vec, pub(crate) dry_run: bool, pub(crate) refresh: Refresh, pub(crate) settings: PipSettings, @@ -950,6 +957,7 @@ impl PipSyncSettings { let PipSyncArgs { src_file, constraint, + build_constraint, installer, refresh, require_hashes, @@ -987,6 +995,10 @@ impl PipSyncSettings { .into_iter() .filter_map(Maybe::into_option) .collect(), + build_constraint: build_constraint + .into_iter() + .filter_map(Maybe::into_option) + .collect(), dry_run, refresh: Refresh::from(refresh), settings: PipSettings::combine( @@ -1030,6 +1042,7 @@ pub(crate) struct PipInstallSettings { pub(crate) editable: Vec, pub(crate) constraint: Vec, pub(crate) r#override: Vec, + pub(crate) build_constraint: Vec, pub(crate) dry_run: bool, pub(crate) constraints_from_workspace: Vec, pub(crate) overrides_from_workspace: Vec, @@ -1049,6 +1062,7 @@ impl PipInstallSettings { extra, all_extras, no_all_extras, + build_constraint, refresh, no_deps, deps, @@ -1120,6 +1134,10 @@ impl PipInstallSettings { .into_iter() .filter_map(Maybe::into_option) .collect(), + build_constraint: build_constraint + .into_iter() + .filter_map(Maybe::into_option) + .collect(), dry_run, constraints_from_workspace, overrides_from_workspace, diff --git a/crates/uv/tests/pip_compile.rs b/crates/uv/tests/pip_compile.rs index ba901d067e53e..cd24aaddee700 100644 --- a/crates/uv/tests/pip_compile.rs +++ b/crates/uv/tests/pip_compile.rs @@ -11407,3 +11407,63 @@ fn ignore_invalid_constraint() -> Result<()> { Ok(()) } + +/// Include a `build_constraints.txt` file with an incompatible constraint. +#[test] +fn incompatible_build_constraint() -> Result<()> { + let context = TestContext::new("3.8"); + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("requests==1.2")?; + + let constraints_txt = context.temp_dir.child("build_constraints.txt"); + constraints_txt.write_str("setuptools==1")?; + + uv_snapshot!(context.pip_compile() + .arg("requirements.txt") + .arg("--build-constraint") + .arg("build_constraints.txt"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to download and build `requests==1.2.0` + Caused by: Failed to build: `requests==1.2.0` + Caused by: Failed to install requirements from setup.py build (resolve) + Caused by: No solution found when resolving: setuptools>=40.8.0 + Caused by: Because you require setuptools>=40.8.0 and setuptools==1, we can conclude that the requirements are unsatisfiable. + "### + ); + + Ok(()) +} + +/// Include a `build_constraints.txt` file with a compatible constraint. +#[test] +fn compatible_build_constraint() -> Result<()> { + let context = TestContext::new("3.8"); + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("requests==1.2")?; + + let constraints_txt = context.temp_dir.child("build_constraints.txt"); + constraints_txt.write_str("setuptools>=40")?; + + uv_snapshot!(context.pip_compile() + .arg("requirements.txt") + .arg("--build-constraint") + .arg("build_constraints.txt"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.txt --build-constraint build_constraints.txt + requests==1.2.0 + # via -r requirements.txt + + ----- stderr ----- + Resolved 1 package in [TIME] + "### + ); + + Ok(()) +} diff --git a/crates/uv/tests/pip_install.rs b/crates/uv/tests/pip_install.rs index 7f21b3999915a..6f016010328e4 100644 --- a/crates/uv/tests/pip_install.rs +++ b/crates/uv/tests/pip_install.rs @@ -6244,3 +6244,58 @@ fn install_relocatable() -> Result<()> { Ok(()) } + +/// Include a `build_constraints.txt` file with an incompatible constraint. +#[test] +fn incompatible_build_constraint() -> Result<()> { + let context = TestContext::new("3.8"); + + let constraints_txt = context.temp_dir.child("build_constraints.txt"); + constraints_txt.write_str("setuptools==1")?; + + uv_snapshot!(context.pip_install() + .arg("requests==1.2") + .arg("--build-constraint") + .arg("build_constraints.txt"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to download and build `requests==1.2.0` + Caused by: Failed to build: `requests==1.2.0` + Caused by: Failed to install requirements from setup.py build (resolve) + Caused by: No solution found when resolving: setuptools>=40.8.0 + Caused by: Because you require setuptools>=40.8.0 and setuptools==1, we can conclude that the requirements are unsatisfiable. + "### + ); + + Ok(()) +} + +/// Include a `build_constraints.txt` file with a compatible constraint. +#[test] +fn compatible_build_constraint() -> Result<()> { + let context = TestContext::new("3.8"); + + let constraints_txt = context.temp_dir.child("build_constraints.txt"); + constraints_txt.write_str("setuptools>=40")?; + + uv_snapshot!(context.pip_install() + .arg("requests==1.2") + .arg("--build-constraint") + .arg("build_constraints.txt"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + requests==1.2.0 + "### + ); + + Ok(()) +} diff --git a/crates/uv/tests/pip_sync.rs b/crates/uv/tests/pip_sync.rs index 622937a0652d6..6934ee2d4c996 100644 --- a/crates/uv/tests/pip_sync.rs +++ b/crates/uv/tests/pip_sync.rs @@ -5349,3 +5349,62 @@ fn preserve_markers() -> Result<()> { Ok(()) } + +/// Include a `build_constraints.txt` file with an incompatible constraint. +#[test] +fn incompatible_build_constraint() -> Result<()> { + let context = TestContext::new("3.8"); + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("requests==1.2")?; + + let constraints_txt = context.temp_dir.child("build_constraints.txt"); + constraints_txt.write_str("setuptools==1")?; + + uv_snapshot!(context.pip_sync() + .arg("requirements.txt") + .arg("--build-constraint") + .arg("build_constraints.txt"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to download and build `requests==1.2.0` + Caused by: Failed to build: `requests==1.2.0` + Caused by: Failed to install requirements from setup.py build (resolve) + Caused by: No solution found when resolving: setuptools>=40.8.0 + Caused by: Because you require setuptools>=40.8.0 and setuptools==1, we can conclude that the requirements are unsatisfiable. + "### + ); + + Ok(()) +} + +/// Include a `build_constraints.txt` file with a compatible constraint. +#[test] +fn compatible_build_constraint() -> Result<()> { + let context = TestContext::new("3.8"); + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("requests==1.2")?; + + let constraints_txt = context.temp_dir.child("build_constraints.txt"); + constraints_txt.write_str("setuptools>=40")?; + + uv_snapshot!(context.pip_sync() + .arg("requirements.txt") + .arg("--build-constraint") + .arg("build_constraints.txt"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + requests==1.2.0 + "### + ); + + Ok(()) +} diff --git a/crates/uv/tests/show_settings.rs b/crates/uv/tests/show_settings.rs index 3c498da84a30f..2b090ba7e4137 100644 --- a/crates/uv/tests/show_settings.rs +++ b/crates/uv/tests/show_settings.rs @@ -75,6 +75,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { override: [], constraints_from_workspace: [], overrides_from_workspace: [], + build_constraint: [], refresh: None( Timestamp( SystemTime { @@ -511,6 +512,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { override: [], constraints_from_workspace: [], overrides_from_workspace: [], + build_constraint: [], refresh: None( Timestamp( SystemTime { @@ -928,6 +930,7 @@ fn resolve_index_url() -> anyhow::Result<()> { override: [], constraints_from_workspace: [], overrides_from_workspace: [], + build_constraint: [], refresh: None( Timestamp( SystemTime { @@ -1291,6 +1294,7 @@ fn resolve_find_links() -> anyhow::Result<()> { override: [], constraints_from_workspace: [], overrides_from_workspace: [], + build_constraint: [], refresh: None( Timestamp( SystemTime { @@ -1449,6 +1453,7 @@ fn resolve_top_level() -> anyhow::Result<()> { override: [], constraints_from_workspace: [], overrides_from_workspace: [], + build_constraint: [], refresh: None( Timestamp( SystemTime { @@ -1913,6 +1918,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { override: [], constraints_from_workspace: [], overrides_from_workspace: [], + build_constraint: [], refresh: None( Timestamp( SystemTime { @@ -2414,6 +2420,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> { override: [], constraints_from_workspace: [], overrides_from_workspace: [], + build_constraint: [], refresh: None( Timestamp( SystemTime { @@ -2560,6 +2567,7 @@ fn resolve_both() -> anyhow::Result<()> { override: [], constraints_from_workspace: [], overrides_from_workspace: [], + build_constraint: [], refresh: None( Timestamp( SystemTime {