From acbd367eadc6de2b5accbab1c288076a21190967 Mon Sep 17 00:00:00 2001 From: Ahmed Ilyas Date: Thu, 8 Aug 2024 03:35:56 +0200 Subject: [PATCH] Support `no-build-isolation-package` (#5894) ## Summary Resolves #5831 ## Test Plan `cargo test` --- crates/uv-build/src/lib.rs | 15 ++-- crates/uv-cli/src/lib.rs | 12 ++++ crates/uv-cli/src/options.rs | 8 +++ crates/uv-settings/src/settings.rs | 25 +++++++ crates/uv-types/src/builds.rs | 29 +++++++- crates/uv/src/commands/pip/compile.rs | 6 +- crates/uv/src/commands/pip/install.rs | 6 +- crates/uv/src/commands/pip/sync.rs | 6 +- crates/uv/src/commands/project/lock.rs | 6 +- crates/uv/src/commands/project/mod.rs | 17 ++++- crates/uv/src/lib.rs | 3 + crates/uv/src/settings.rs | 26 +++++++ crates/uv/tests/pip_install.rs | 93 +++++++++++++++++++++++++ crates/uv/tests/show_settings.rs | 19 +++++ crates/uv/tests/sync.rs | 96 ++++++++++++++++++++++++++ docs/reference/cli.md | 40 +++++++++++ docs/reference/settings.md | 57 +++++++++++++++ uv.schema.json | 20 ++++++ 18 files changed, 468 insertions(+), 16 deletions(-) diff --git a/crates/uv-build/src/lib.rs b/crates/uv-build/src/lib.rs index fe8450938..03a08dbb5 100644 --- a/crates/uv-build/src/lib.rs +++ b/crates/uv-build/src/lib.rs @@ -434,22 +434,25 @@ impl SourceBuild { Self::extract_pep517_backend(&source_tree, setup_py, &default_backend) .map_err(|err| *err)?; + let package_name = project.clone().map(|p| p.name); + // Create a virtual environment, or install into the shared environment if requested. - let venv = match build_isolation { - BuildIsolation::Isolated => uv_virtualenv::create_venv( + let venv = if let Some(venv) = build_isolation.shared_environment(package_name.as_ref()) { + venv.clone() + } else { + uv_virtualenv::create_venv( temp_dir.path(), interpreter.clone(), uv_virtualenv::Prompt::None, false, false, false, - )?, - BuildIsolation::Shared(venv) => venv.clone(), + )? }; // Setup the build environment. If build isolation is disabled, we assume the build // environment is already setup. - if build_isolation.is_isolated() { + if build_isolation.is_isolated(package_name.as_ref()) { let resolved_requirements = Self::get_resolved_requirements( build_context, source_build_context, @@ -499,7 +502,7 @@ impl SourceBuild { // Create the PEP 517 build environment. If build isolation is disabled, we assume the build // environment is already setup. let runner = PythonRunner::new(concurrent_builds); - if build_isolation.is_isolated() { + if build_isolation.is_isolated(package_name.as_ref()) { if let Some(pep517_backend) = &pep517_backend { create_pep517_build_environment( &runner, diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 129639afd..095d9efae 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -3106,6 +3106,12 @@ pub struct ResolverArgs { )] pub no_build_isolation: bool, + /// Disable isolation when building source distributions for a specific package. + /// + /// Assumes that the packages' build dependencies specified by PEP 518 are already installed. + #[arg(long, help_heading = "Build options")] + pub no_build_isolation_package: Vec, + #[arg( long, overrides_with("no_build_isolation"), @@ -3271,6 +3277,12 @@ pub struct ResolverInstallerArgs { )] pub no_build_isolation: bool, + /// Disable isolation when building source distributions for a specific package. + /// + /// Assumes that the packages' build dependencies specified by PEP 518 are already installed. + #[arg(long, help_heading = "Build options")] + pub no_build_isolation_package: Vec, + #[arg( long, overrides_with("no_build_isolation"), diff --git a/crates/uv-cli/src/options.rs b/crates/uv-cli/src/options.rs index 8ea9004ac..87ec4c5f1 100644 --- a/crates/uv-cli/src/options.rs +++ b/crates/uv-cli/src/options.rs @@ -43,6 +43,7 @@ impl From for PipOptions { pre, config_setting, no_build_isolation, + no_build_isolation_package, build_isolation, exclude_newer, link_mode, @@ -63,6 +64,7 @@ impl From for PipOptions { config_settings: config_setting .map(|config_settings| config_settings.into_iter().collect::()), no_build_isolation: flag(no_build_isolation, build_isolation), + no_build_isolation_package: Some(no_build_isolation_package), exclude_newer, link_mode, no_sources: if no_sources { Some(true) } else { None }, @@ -124,6 +126,7 @@ impl From for PipOptions { pre, config_setting, no_build_isolation, + no_build_isolation_package, build_isolation, exclude_newer, link_mode, @@ -148,6 +151,7 @@ impl From for PipOptions { config_settings: config_setting .map(|config_settings| config_settings.into_iter().collect::()), no_build_isolation: flag(no_build_isolation, build_isolation), + no_build_isolation_package: Some(no_build_isolation_package), exclude_newer, link_mode, compile_bytecode: flag(compile_bytecode, no_compile_bytecode), @@ -195,6 +199,7 @@ pub fn resolver_options(resolver_args: ResolverArgs, build_args: BuildArgs) -> R pre, config_setting, no_build_isolation, + no_build_isolation_package, build_isolation, exclude_newer, link_mode, @@ -237,6 +242,7 @@ pub fn resolver_options(resolver_args: ResolverArgs, build_args: BuildArgs) -> R config_settings: config_setting .map(|config_settings| config_settings.into_iter().collect::()), no_build_isolation: flag(no_build_isolation, build_isolation), + no_build_isolation_package: Some(no_build_isolation_package), exclude_newer, link_mode, no_build: flag(no_build, build), @@ -267,6 +273,7 @@ pub fn resolver_installer_options( pre, config_setting, no_build_isolation, + no_build_isolation_package, build_isolation, exclude_newer, link_mode, @@ -313,6 +320,7 @@ pub fn resolver_installer_options( config_settings: config_setting .map(|config_settings| config_settings.into_iter().collect::()), no_build_isolation: flag(no_build_isolation, build_isolation), + no_build_isolation_package: Some(no_build_isolation_package), exclude_newer, link_mode, compile_bytecode: flag(compile_bytecode, no_compile_bytecode), diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs index 4af608fc6..fd631010f 100644 --- a/crates/uv-settings/src/settings.rs +++ b/crates/uv-settings/src/settings.rs @@ -205,6 +205,7 @@ pub struct ResolverOptions { pub no_binary: Option, pub no_binary_package: Option>, pub no_build_isolation: Option, + pub no_build_isolation_package: Option>, pub no_sources: Option, } @@ -350,6 +351,18 @@ pub struct ResolverInstallerOptions { "# )] pub no_build_isolation: Option, + /// Disable isolation when building source distributions for a specific package. + /// + /// Assumes that the packages' build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/) + /// are already installed. + #[option( + default = "[]", + value_type = "Vec", + example = r#" + no-build-isolation-package = ["package1", "package2"] + "# + )] + pub no_build_isolation_package: Option>, /// Limit candidate packages to those that were uploaded prior to the given date. /// /// Accepts both [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html) timestamps (e.g., @@ -717,6 +730,18 @@ pub struct PipOptions { "# )] pub no_build_isolation: Option, + /// Disable isolation when building source distributions for a specific package. + /// + /// Assumes that the packages' build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/) + /// are already installed. + #[option( + default = "[]", + value_type = "Vec", + example = r#" + no-build-isolation-package = ["package1", "package2"] + "# + )] + pub no_build_isolation_package: Option>, /// Validate the Python environment, to detect packages with missing dependencies and other /// issues. #[option( diff --git a/crates/uv-types/src/builds.rs b/crates/uv-types/src/builds.rs index c6ee29ec8..5aec6fd37 100644 --- a/crates/uv-types/src/builds.rs +++ b/crates/uv-types/src/builds.rs @@ -1,3 +1,4 @@ +use pep508_rs::PackageName; use uv_python::PythonEnvironment; /// Whether to enforce build isolation when building source distributions. @@ -6,11 +7,33 @@ pub enum BuildIsolation<'a> { #[default] Isolated, Shared(&'a PythonEnvironment), + SharedPackage(&'a PythonEnvironment, &'a [PackageName]), } impl<'a> BuildIsolation<'a> { - /// Returns `true` if build isolation is enforced. - pub fn is_isolated(&self) -> bool { - matches!(self, Self::Isolated) + /// Returns `true` if build isolation is enforced for the given package name. + pub fn is_isolated(&self, package: Option<&PackageName>) -> bool { + match self { + Self::Isolated => true, + Self::Shared(_) => false, + Self::SharedPackage(_, packages) => { + package.map_or(true, |package| !packages.iter().any(|p| p == package)) + } + } + } + + /// Returns the shared environment for a given package, if build isolation is not enforced. + pub fn shared_environment(&self, package: Option<&PackageName>) -> Option<&PythonEnvironment> { + match self { + Self::Isolated => None, + Self::Shared(env) => Some(env), + Self::SharedPackage(env, packages) => { + if package.is_some_and(|package| packages.iter().any(|p| p == package)) { + Some(env) + } else { + None + } + } + } } } diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index b390ca299..1fa84bf4a 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -76,6 +76,7 @@ pub(crate) async fn pip_compile( config_settings: ConfigSettings, connectivity: Connectivity, no_build_isolation: bool, + no_build_isolation_package: Vec, build_options: BuildOptions, python_version: Option, python_platform: Option, @@ -303,8 +304,11 @@ pub(crate) async fn pip_compile( let build_isolation = if no_build_isolation { environment = PythonEnvironment::from_interpreter(interpreter.clone()); BuildIsolation::Shared(&environment) - } else { + } else if no_build_isolation_package.is_empty() { BuildIsolation::Isolated + } else { + environment = PythonEnvironment::from_interpreter(interpreter.clone()); + BuildIsolation::SharedPackage(&environment, &no_build_isolation_package) }; let build_dispatch = BuildDispatch::new( diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index 681d13086..564246ba0 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -3,6 +3,7 @@ use std::fmt::Write; use anstream::eprint; use itertools::Itertools; use owo_colors::OwoColorize; +use pep508_rs::PackageName; use tracing::{debug, enabled, Level}; use distribution_types::{IndexLocations, Resolution, UnresolvedRequirementSpecification}; @@ -59,6 +60,7 @@ pub(crate) async fn pip_install( connectivity: Connectivity, config_settings: &ConfigSettings, no_build_isolation: bool, + no_build_isolation_package: Vec, build_options: BuildOptions, python_version: Option, python_platform: Option, @@ -289,8 +291,10 @@ pub(crate) async fn pip_install( // Determine whether to enable build isolation. let build_isolation = if no_build_isolation { BuildIsolation::Shared(&environment) - } else { + } else if no_build_isolation_package.is_empty() { BuildIsolation::Isolated + } else { + BuildIsolation::SharedPackage(&environment, &no_build_isolation_package) }; // Initialize any shared state. diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 852724275..fa02faa13 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -3,6 +3,7 @@ use std::fmt::Write; use anstream::eprint; use anyhow::Result; use owo_colors::OwoColorize; +use pep508_rs::PackageName; use tracing::debug; use distribution_types::{IndexLocations, Resolution}; @@ -51,6 +52,7 @@ pub(crate) async fn pip_sync( connectivity: Connectivity, config_settings: &ConfigSettings, no_build_isolation: bool, + no_build_isolation_package: Vec, build_options: BuildOptions, python_version: Option, python_platform: Option, @@ -229,8 +231,10 @@ pub(crate) async fn pip_sync( // Determine whether to enable build isolation. let build_isolation = if no_build_isolation { BuildIsolation::Shared(&environment) - } else { + } else if no_build_isolation_package.is_empty() { BuildIsolation::Isolated + } else { + BuildIsolation::SharedPackage(&environment, &no_build_isolation_package) }; // Initialize any shared state. diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index 95354b761..4608b9e9f 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -229,6 +229,7 @@ async fn do_lock( prerelease, config_setting, no_build_isolation, + no_build_isolation_package, exclude_newer, link_mode, upgrade, @@ -298,8 +299,11 @@ async fn do_lock( let build_isolation = if no_build_isolation { environment = PythonEnvironment::from_interpreter(interpreter.clone()); BuildIsolation::Shared(&environment) - } else { + } else if no_build_isolation_package.is_empty() { BuildIsolation::Isolated + } else { + environment = PythonEnvironment::from_interpreter(interpreter.clone()); + BuildIsolation::SharedPackage(&environment, no_build_isolation_package) }; let options = OptionsBuilder::new() diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index eeafd8905..dba472910 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -406,6 +406,7 @@ pub(crate) async fn resolve_names( prerelease: _, config_setting, no_build_isolation, + no_build_isolation_package, exclude_newer, link_mode, compile_bytecode: _, @@ -436,8 +437,11 @@ pub(crate) async fn resolve_names( let build_isolation = if *no_build_isolation { environment = PythonEnvironment::from_interpreter(interpreter.clone()); BuildIsolation::Shared(&environment) - } else { + } else if no_build_isolation_package.is_empty() { BuildIsolation::Isolated + } else { + environment = PythonEnvironment::from_interpreter(interpreter.clone()); + BuildIsolation::SharedPackage(&environment, no_build_isolation_package) }; // TODO(charlie): These are all default values. We should consider whether we want to make them @@ -505,6 +509,7 @@ pub(crate) async fn resolve_environment<'a>( prerelease, config_setting, no_build_isolation, + no_build_isolation_package, exclude_newer, link_mode, upgrade: _, @@ -548,8 +553,11 @@ pub(crate) async fn resolve_environment<'a>( let build_isolation = if no_build_isolation { environment = PythonEnvironment::from_interpreter(interpreter.clone()); BuildIsolation::Shared(&environment) - } else { + } else if no_build_isolation_package.is_empty() { BuildIsolation::Isolated + } else { + environment = PythonEnvironment::from_interpreter(interpreter.clone()); + BuildIsolation::SharedPackage(&environment, no_build_isolation_package) }; let options = OptionsBuilder::new() @@ -780,6 +788,7 @@ pub(crate) async fn update_environment( prerelease, config_setting, no_build_isolation, + no_build_isolation_package, exclude_newer, link_mode, compile_bytecode, @@ -848,8 +857,10 @@ pub(crate) async fn update_environment( // Determine whether to enable build isolation. let build_isolation = if *no_build_isolation { BuildIsolation::Shared(&venv) - } else { + } else if no_build_isolation_package.is_empty() { BuildIsolation::Isolated + } else { + BuildIsolation::SharedPackage(&venv, no_build_isolation_package) }; let options = OptionsBuilder::new() diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 199cf1e54..511a9967d 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -280,6 +280,7 @@ async fn run(cli: Cli) -> Result { args.settings.config_setting, globals.connectivity, args.settings.no_build_isolation, + args.settings.no_build_isolation_package, args.settings.build_options, args.settings.python_version, args.settings.python_platform, @@ -351,6 +352,7 @@ async fn run(cli: Cli) -> Result { globals.connectivity, &args.settings.config_setting, args.settings.no_build_isolation, + args.settings.no_build_isolation_package, args.settings.build_options, args.settings.python_version, args.settings.python_platform, @@ -441,6 +443,7 @@ async fn run(cli: Cli) -> Result { globals.connectivity, &args.settings.config_setting, args.settings.no_build_isolation, + args.settings.no_build_isolation_package, args.settings.build_options, args.settings.python_version, args.settings.python_platform, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 95c06dbc3..02a804831 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -1531,6 +1531,7 @@ pub(crate) struct ResolverSettings { pub(crate) prerelease: PrereleaseMode, pub(crate) config_setting: ConfigSettings, pub(crate) no_build_isolation: bool, + pub(crate) no_build_isolation_package: Vec, pub(crate) exclude_newer: Option, pub(crate) link_mode: LinkMode, pub(crate) upgrade: Upgrade, @@ -1547,6 +1548,7 @@ pub(crate) struct ResolverSettingsRef<'a> { pub(crate) prerelease: PrereleaseMode, pub(crate) config_setting: &'a ConfigSettings, pub(crate) no_build_isolation: bool, + pub(crate) no_build_isolation_package: &'a [PackageName], pub(crate) exclude_newer: Option, pub(crate) link_mode: LinkMode, pub(crate) upgrade: &'a Upgrade, @@ -1568,6 +1570,7 @@ impl ResolverSettings { prerelease, config_settings, no_build_isolation, + no_build_isolation_package, exclude_newer, link_mode, compile_bytecode: _, @@ -1612,6 +1615,10 @@ impl ResolverSettings { .no_build_isolation .combine(no_build_isolation) .unwrap_or_default(), + no_build_isolation_package: args + .no_build_isolation_package + .combine(no_build_isolation_package) + .unwrap_or_default(), exclude_newer: args.exclude_newer.combine(exclude_newer), link_mode: args.link_mode.combine(link_mode).unwrap_or_default(), upgrade: Upgrade::from_args( @@ -1652,6 +1659,7 @@ impl ResolverSettings { prerelease: self.prerelease, config_setting: &self.config_setting, no_build_isolation: self.no_build_isolation, + no_build_isolation_package: &self.no_build_isolation_package, exclude_newer: self.exclude_newer, link_mode: self.link_mode, upgrade: &self.upgrade, @@ -1676,6 +1684,7 @@ pub(crate) struct ResolverInstallerSettings { pub(crate) prerelease: PrereleaseMode, pub(crate) config_setting: ConfigSettings, pub(crate) no_build_isolation: bool, + pub(crate) no_build_isolation_package: Vec, pub(crate) exclude_newer: Option, pub(crate) link_mode: LinkMode, pub(crate) compile_bytecode: bool, @@ -1694,6 +1703,7 @@ pub(crate) struct ResolverInstallerSettingsRef<'a> { pub(crate) prerelease: PrereleaseMode, pub(crate) config_setting: &'a ConfigSettings, pub(crate) no_build_isolation: bool, + pub(crate) no_build_isolation_package: &'a [PackageName], pub(crate) exclude_newer: Option, pub(crate) link_mode: LinkMode, pub(crate) compile_bytecode: bool, @@ -1720,6 +1730,7 @@ impl ResolverInstallerSettings { prerelease, config_settings, no_build_isolation, + no_build_isolation_package, exclude_newer, link_mode, compile_bytecode, @@ -1764,6 +1775,10 @@ impl ResolverInstallerSettings { .no_build_isolation .combine(no_build_isolation) .unwrap_or_default(), + no_build_isolation_package: args + .no_build_isolation_package + .combine(no_build_isolation_package) + .unwrap_or_default(), exclude_newer: args.exclude_newer.combine(exclude_newer), link_mode: args.link_mode.combine(link_mode).unwrap_or_default(), sources: SourceStrategy::from_args( @@ -1814,6 +1829,7 @@ impl ResolverInstallerSettings { prerelease: self.prerelease, config_setting: &self.config_setting, no_build_isolation: self.no_build_isolation, + no_build_isolation_package: &self.no_build_isolation_package, exclude_newer: self.exclude_newer, link_mode: self.link_mode, compile_bytecode: self.compile_bytecode, @@ -1842,6 +1858,7 @@ pub(crate) struct PipSettings { pub(crate) index_strategy: IndexStrategy, pub(crate) keyring_provider: KeyringProviderType, pub(crate) no_build_isolation: bool, + pub(crate) no_build_isolation_package: Vec, pub(crate) build_options: BuildOptions, pub(crate) allow_empty_requirements: bool, pub(crate) strict: bool, @@ -1900,6 +1917,7 @@ impl PipSettings { no_binary, only_binary, no_build_isolation, + no_build_isolation_package, strict, extra, all_extras, @@ -1952,6 +1970,7 @@ impl PipSettings { prerelease: top_level_prerelease, config_settings: top_level_config_settings, no_build_isolation: top_level_no_build_isolation, + no_build_isolation_package: top_level_no_build_isolation_package, exclude_newer: top_level_exclude_newer, link_mode: top_level_link_mode, compile_bytecode: top_level_compile_bytecode, @@ -1980,6 +1999,8 @@ impl PipSettings { let prerelease = prerelease.combine(top_level_prerelease); let config_settings = config_settings.combine(top_level_config_settings); let no_build_isolation = no_build_isolation.combine(top_level_no_build_isolation); + let no_build_isolation_package = + no_build_isolation_package.combine(top_level_no_build_isolation_package); let exclude_newer = exclude_newer.combine(top_level_exclude_newer); let link_mode = link_mode.combine(top_level_link_mode); let compile_bytecode = compile_bytecode.combine(top_level_compile_bytecode); @@ -2054,6 +2075,10 @@ impl PipSettings { .no_build_isolation .combine(no_build_isolation) .unwrap_or_default(), + no_build_isolation_package: args + .no_build_isolation_package + .combine(no_build_isolation_package) + .unwrap_or_default(), config_setting: args .config_settings .combine(config_settings) @@ -2172,6 +2197,7 @@ impl<'a> From> for ResolverSettingsRef<'a> { prerelease: settings.prerelease, config_setting: settings.config_setting, no_build_isolation: settings.no_build_isolation, + no_build_isolation_package: settings.no_build_isolation_package, exclude_newer: settings.exclude_newer, link_mode: settings.link_mode, upgrade: settings.upgrade, diff --git a/crates/uv/tests/pip_install.rs b/crates/uv/tests/pip_install.rs index fd0fa7a55..433ff0a97 100644 --- a/crates/uv/tests/pip_install.rs +++ b/crates/uv/tests/pip_install.rs @@ -6324,3 +6324,96 @@ fn compatible_build_constraint() -> Result<()> { Ok(()) } + +#[test] +fn install_build_isolation_package() -> Result<()> { + let context = TestContext::new("3.12"); + + // Create an package. + let package = context.temp_dir.child("project"); + package.create_dir_all()?; + let pyproject_toml = package.child("pyproject.toml"); + + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "iniconfig @ https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", + ] + [build-system] + requires = [ + "setuptools >= 40.9.0", + ] + build-backend = "setuptools.build_meta" + "#, + )?; + + // Running `uv pip install` should fail for iniconfig. + let filters = std::iter::once((r"exit code: 1", "exit status: 1")) + .chain(context.filters()) + .collect::>(); + uv_snapshot!(filters, context.pip_install() + .arg("--no-build-isolation-package") + .arg("iniconfig") + .arg(package.path()), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to download and build: `iniconfig @ https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz` + Caused by: Failed to build: `iniconfig @ https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz` + Caused by: Build backend failed to determine metadata through `prepare_metadata_for_build_wheel` with exit status: 1 + --- stdout: + + --- stderr: + Traceback (most recent call last): + File "", line 8, in + ModuleNotFoundError: No module named 'hatchling' + --- + "### + ); + + // Install `hatchinling`, `hatch-vs` for iniconfig + uv_snapshot!(context.filters(), context.pip_install().arg("hatchling").arg("hatch-vcs"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 9 packages in [TIME] + Prepared 9 packages in [TIME] + Installed 9 packages in [TIME] + + hatch-vcs==0.4.0 + + hatchling==1.22.4 + + packaging==24.0 + + pathspec==0.12.1 + + pluggy==1.4.0 + + setuptools==69.2.0 + + setuptools-scm==8.0.4 + + trove-classifiers==2024.3.3 + + typing-extensions==4.10.0 + "###); + + // Running `uv pip install` should succeed. + uv_snapshot!(context.filters(), context.pip_install() + .arg("--no-build-isolation-package") + .arg("iniconfig") + .arg(package.path()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Prepared 2 packages in [TIME] + Installed 2 packages in [TIME] + + iniconfig==2.0.0 (from https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz) + + project==0.1.0 (from file://[TEMP_DIR]/project) + "###); + + Ok(()) +} diff --git a/crates/uv/tests/show_settings.rs b/crates/uv/tests/show_settings.rs index 9e662271b..7421e2c95 100644 --- a/crates/uv/tests/show_settings.rs +++ b/crates/uv/tests/show_settings.rs @@ -122,6 +122,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { index_strategy: FirstIndex, keyring_provider: Disabled, no_build_isolation: false, + no_build_isolation_package: [], build_options: BuildOptions { no_binary: None, no_build: None, @@ -257,6 +258,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { index_strategy: FirstIndex, keyring_provider: Disabled, no_build_isolation: false, + no_build_isolation_package: [], build_options: BuildOptions { no_binary: None, no_build: None, @@ -393,6 +395,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { index_strategy: FirstIndex, keyring_provider: Disabled, no_build_isolation: false, + no_build_isolation_package: [], build_options: BuildOptions { no_binary: None, no_build: None, @@ -561,6 +564,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { index_strategy: FirstIndex, keyring_provider: Disabled, no_build_isolation: false, + no_build_isolation_package: [], build_options: BuildOptions { no_binary: None, no_build: None, @@ -675,6 +679,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { index_strategy: FirstIndex, keyring_provider: Disabled, no_build_isolation: false, + no_build_isolation_package: [], build_options: BuildOptions { no_binary: None, no_build: None, @@ -821,6 +826,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { index_strategy: FirstIndex, keyring_provider: Disabled, no_build_isolation: false, + no_build_isolation_package: [], build_options: BuildOptions { no_binary: None, no_build: None, @@ -1004,6 +1010,7 @@ fn resolve_index_url() -> anyhow::Result<()> { index_strategy: FirstIndex, keyring_provider: Disabled, no_build_isolation: false, + no_build_isolation_package: [], build_options: BuildOptions { no_binary: None, no_build: None, @@ -1186,6 +1193,7 @@ fn resolve_index_url() -> anyhow::Result<()> { index_strategy: FirstIndex, keyring_provider: Disabled, no_build_isolation: false, + no_build_isolation_package: [], build_options: BuildOptions { no_binary: None, no_build: None, @@ -1346,6 +1354,7 @@ fn resolve_find_links() -> anyhow::Result<()> { index_strategy: FirstIndex, keyring_provider: Disabled, no_build_isolation: false, + no_build_isolation_package: [], build_options: BuildOptions { no_binary: None, no_build: None, @@ -1482,6 +1491,7 @@ fn resolve_top_level() -> anyhow::Result<()> { index_strategy: FirstIndex, keyring_provider: Disabled, no_build_isolation: false, + no_build_isolation_package: [], build_options: BuildOptions { no_binary: None, no_build: None, @@ -1656,6 +1666,7 @@ fn resolve_top_level() -> anyhow::Result<()> { index_strategy: FirstIndex, keyring_provider: Disabled, no_build_isolation: false, + no_build_isolation_package: [], build_options: BuildOptions { no_binary: None, no_build: None, @@ -1813,6 +1824,7 @@ fn resolve_top_level() -> anyhow::Result<()> { index_strategy: FirstIndex, keyring_provider: Disabled, no_build_isolation: false, + no_build_isolation_package: [], build_options: BuildOptions { no_binary: None, no_build: None, @@ -1949,6 +1961,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { index_strategy: FirstIndex, keyring_provider: Disabled, no_build_isolation: false, + no_build_isolation_package: [], build_options: BuildOptions { no_binary: None, no_build: None, @@ -2068,6 +2081,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { index_strategy: FirstIndex, keyring_provider: Disabled, no_build_isolation: false, + no_build_isolation_package: [], build_options: BuildOptions { no_binary: None, no_build: None, @@ -2187,6 +2201,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { index_strategy: FirstIndex, keyring_provider: Disabled, no_build_isolation: false, + no_build_isolation_package: [], build_options: BuildOptions { no_binary: None, no_build: None, @@ -2308,6 +2323,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { index_strategy: FirstIndex, keyring_provider: Disabled, no_build_isolation: false, + no_build_isolation_package: [], build_options: BuildOptions { no_binary: None, no_build: None, @@ -2454,6 +2470,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> { index_strategy: FirstIndex, keyring_provider: Disabled, no_build_isolation: false, + no_build_isolation_package: [], build_options: BuildOptions { no_binary: None, no_build: None, @@ -2624,6 +2641,7 @@ fn resolve_both() -> anyhow::Result<()> { index_strategy: FirstIndex, keyring_provider: Disabled, no_build_isolation: false, + no_build_isolation_package: [], build_options: BuildOptions { no_binary: None, no_build: None, @@ -2786,6 +2804,7 @@ fn resolve_config_file() -> anyhow::Result<()> { index_strategy: FirstIndex, keyring_provider: Disabled, no_build_isolation: false, + no_build_isolation_package: [], build_options: BuildOptions { no_binary: None, no_build: None, diff --git a/crates/uv/tests/sync.rs b/crates/uv/tests/sync.rs index f59d6ab7c..8d86bccc3 100644 --- a/crates/uv/tests/sync.rs +++ b/crates/uv/tests/sync.rs @@ -581,3 +581,99 @@ fn sync_reset_state() -> Result<()> { Ok(()) } + +#[test] +fn sync_build_isolation_package() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "iniconfig @ https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", + ] + [build-system] + requires = [ + "setuptools >= 40.9.0", + ] + build-backend = "setuptools.build_meta" + "#, + )?; + + // Running `uv sync` should fail for iniconfig. + let filters = std::iter::once((r"exit code: 1", "exit status: 1")) + .chain(context.filters()) + .collect::>(); + uv_snapshot!(filters, context.sync().arg("--no-build-isolation-package").arg("iniconfig"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + warning: `uv sync` is experimental and may change without warning + error: Failed to download and build: `iniconfig @ https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz` + Caused by: Failed to build: `iniconfig @ https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz` + Caused by: Build backend failed to determine metadata through `prepare_metadata_for_build_wheel` with exit status: 1 + --- stdout: + + --- stderr: + Traceback (most recent call last): + File "", line 8, in + ModuleNotFoundError: No module named 'hatchling' + --- + "###); + + // Install `hatchinling`, `hatch-vs` for iniconfig + uv_snapshot!(context.filters(), context.pip_install().arg("hatchling").arg("hatch-vcs"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 9 packages in [TIME] + Prepared 9 packages in [TIME] + Installed 9 packages in [TIME] + + hatch-vcs==0.4.0 + + hatchling==1.22.4 + + packaging==24.0 + + pathspec==0.12.1 + + pluggy==1.4.0 + + setuptools==69.2.0 + + setuptools-scm==8.0.4 + + trove-classifiers==2024.3.3 + + typing-extensions==4.10.0 + "###); + + // Running `uv sync` should succeed. + uv_snapshot!(context.filters(), context.sync().arg("--no-build-isolation-package").arg("iniconfig"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: `uv sync` is experimental and may change without warning + Resolved 2 packages in [TIME] + Prepared 2 packages in [TIME] + Uninstalled 9 packages in [TIME] + Installed 2 packages in [TIME] + - hatch-vcs==0.4.0 + - hatchling==1.22.4 + + iniconfig==2.0.0 (from https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz) + - packaging==24.0 + - pathspec==0.12.1 + - pluggy==1.4.0 + + project==0.1.0 (from file://[TEMP_DIR]/) + - setuptools==69.2.0 + - setuptools-scm==8.0.4 + - trove-classifiers==2024.3.3 + - typing-extensions==4.10.0 + "###); + + assert!(context.temp_dir.child("uv.lock").exists()); + + Ok(()) +} diff --git a/docs/reference/cli.md b/docs/reference/cli.md index a92dc5490..6b646f725 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -162,6 +162,10 @@ uv run [OPTIONS]
--config-setting, -C config-setting

Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

+
--no-build-isolation-package no-build-isolation-package

Disable isolation when building source distributions for a specific package.

+ +

Assumes that the packages’ build dependencies specified by PEP 518 are already installed.

+
--exclude-newer exclude-newer

Limit candidate packages to those that were uploaded prior to the given date.

Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and UTC dates in the same format (e.g., 2006-12-02).

@@ -435,6 +439,10 @@ uv add [OPTIONS] ...
--config-setting, -C config-setting

Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

+
--no-build-isolation-package no-build-isolation-package

Disable isolation when building source distributions for a specific package.

+ +

Assumes that the packages’ build dependencies specified by PEP 518 are already installed.

+
--exclude-newer exclude-newer

Limit candidate packages to those that were uploaded prior to the given date.

Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and UTC dates in the same format (e.g., 2006-12-02).

@@ -612,6 +620,10 @@ uv remove [OPTIONS] ...
--config-setting, -C config-setting

Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

+
--no-build-isolation-package no-build-isolation-package

Disable isolation when building source distributions for a specific package.

+ +

Assumes that the packages’ build dependencies specified by PEP 518 are already installed.

+
--exclude-newer exclude-newer

Limit candidate packages to those that were uploaded prior to the given date.

Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and UTC dates in the same format (e.g., 2006-12-02).

@@ -785,6 +797,10 @@ uv sync [OPTIONS]
--config-setting, -C config-setting

Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

+
--no-build-isolation-package no-build-isolation-package

Disable isolation when building source distributions for a specific package.

+ +

Assumes that the packages’ build dependencies specified by PEP 518 are already installed.

+
--exclude-newer exclude-newer

Limit candidate packages to those that were uploaded prior to the given date.

Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and UTC dates in the same format (e.g., 2006-12-02).

@@ -956,6 +972,10 @@ uv lock [OPTIONS]
--config-setting, -C config-setting

Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

+
--no-build-isolation-package no-build-isolation-package

Disable isolation when building source distributions for a specific package.

+ +

Assumes that the packages’ build dependencies specified by PEP 518 are already installed.

+
--exclude-newer exclude-newer

Limit candidate packages to those that were uploaded prior to the given date.

Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and UTC dates in the same format (e.g., 2006-12-02).

@@ -1138,6 +1158,10 @@ uv tree [OPTIONS]
--config-setting, -C config-setting

Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

+
--no-build-isolation-package no-build-isolation-package

Disable isolation when building source distributions for a specific package.

+ +

Assumes that the packages’ build dependencies specified by PEP 518 are already installed.

+
--exclude-newer exclude-newer

Limit candidate packages to those that were uploaded prior to the given date.

Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and UTC dates in the same format (e.g., 2006-12-02).

@@ -1384,6 +1408,10 @@ uv tool run [OPTIONS] [COMMAND]
--config-setting, -C config-setting

Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

+
--no-build-isolation-package no-build-isolation-package

Disable isolation when building source distributions for a specific package.

+ +

Assumes that the packages’ build dependencies specified by PEP 518 are already installed.

+
--exclude-newer exclude-newer

Limit candidate packages to those that were uploaded prior to the given date.

Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and UTC dates in the same format (e.g., 2006-12-02).

@@ -1561,6 +1589,10 @@ uv tool install [OPTIONS]
--config-setting, -C config-setting

Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

+
--no-build-isolation-package no-build-isolation-package

Disable isolation when building source distributions for a specific package.

+ +

Assumes that the packages’ build dependencies specified by PEP 518 are already installed.

+
--exclude-newer exclude-newer

Limit candidate packages to those that were uploaded prior to the given date.

Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and UTC dates in the same format (e.g., 2006-12-02).

@@ -2475,6 +2507,10 @@ uv pip compile [OPTIONS] ...
--config-setting, -C config-setting

Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

+
--no-build-isolation-package no-build-isolation-package

Disable isolation when building source distributions for a specific package.

+ +

Assumes that the packages’ build dependencies specified by PEP 518 are already installed.

+
--exclude-newer exclude-newer

Limit candidate packages to those that were uploaded prior to the given date.

Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and UTC dates in the same format (e.g., 2006-12-02).

@@ -2976,6 +3012,10 @@ uv pip install [OPTIONS] |--editable
--config-setting, -C config-setting

Settings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs

+
--no-build-isolation-package no-build-isolation-package

Disable isolation when building source distributions for a specific package.

+ +

Assumes that the packages’ build dependencies specified by PEP 518 are already installed.

+
--exclude-newer exclude-newer

Limit candidate packages to those that were uploaded prior to the given date.

Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and UTC dates in the same format (e.g., 2006-12-02).

diff --git a/docs/reference/settings.md b/docs/reference/settings.md index 4b4cba893..bf5d2621a 100644 --- a/docs/reference/settings.md +++ b/docs/reference/settings.md @@ -476,6 +476,34 @@ are already installed. --- +#### [`no-build-isolation-package`](#no-build-isolation-package) {: #no-build-isolation-package } + +Disable isolation when building source distributions for a specific package. + +Assumes that the packages' build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/) +are already installed. + +**Default value**: `[]` + +**Type**: `Vec` + +**Example usage**: + +=== "pyproject.toml" + + ```toml + [tool.uv] + no-build-isolation-package = ["package1", "package2"] + ``` +=== "uv.toml" + + ```toml + + no-build-isolation-package = ["package1", "package2"] + ``` + +--- + #### [`no-build-package`](#no-build-package) {: #no-build-package } Don't build source distributions for a specific package. @@ -1721,6 +1749,35 @@ are already installed. --- +#### [`no-build-isolation-package`](#pip_no-build-isolation-package) {: #pip_no-build-isolation-package } + + +Disable isolation when building source distributions for a specific package. + +Assumes that the packages' build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/) +are already installed. + +**Default value**: `[]` + +**Type**: `Vec` + +**Example usage**: + +=== "pyproject.toml" + + ```toml + [tool.uv.pip] + no-build-isolation-package = ["package1", "package2"] + ``` +=== "uv.toml" + + ```toml + [pip] + no-build-isolation-package = ["package1", "package2"] + ``` + +--- + #### [`no-deps`](#pip_no-deps) {: #pip_no-deps } diff --git a/uv.schema.json b/uv.schema.json index 23dab82cc..8b3e09f81 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -168,6 +168,16 @@ "null" ] }, + "no-build-isolation-package": { + "description": "Disable isolation when building source distributions for a specific package.\n\nAssumes that the packages' build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/) are already installed.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/PackageName" + } + }, "no-build-package": { "description": "Don't build source distributions for a specific package.", "type": [ @@ -750,6 +760,16 @@ "null" ] }, + "no-build-isolation-package": { + "description": "Disable isolation when building source distributions for a specific package.\n\nAssumes that the packages' build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/) are already installed.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/PackageName" + } + }, "no-deps": { "description": "Ignore package dependencies, instead only add those packages explicitly listed on the command line to the resulting the requirements file.", "type": [