diff --git a/crates/uv-bench/benches/uv.rs b/crates/uv-bench/benches/uv.rs index 9bdd7adb9..4294f9671 100644 --- a/crates/uv-bench/benches/uv.rs +++ b/crates/uv-bench/benches/uv.rs @@ -103,7 +103,7 @@ mod resolver { ResolverEnvironment, ResolverOutput, }; use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy}; - use uv_workspace::WorkspaceCache; + use uv_workspace::{WorkspaceCache, pyproject::ExtraBuildDependencies}; static MARKERS: LazyLock = LazyLock::new(|| { MarkerEnvironment::try_from(MarkerEnvironmentBuilder { @@ -141,6 +141,7 @@ mod resolver { universal: bool, ) -> Result { let build_isolation = BuildIsolation::default(); + let extra_build_dependencies = ExtraBuildDependencies::default(); let build_options = BuildOptions::default(); let concurrency = Concurrency::default(); let config_settings = ConfigSettings::default(); @@ -185,6 +186,7 @@ mod resolver { IndexStrategy::default(), &config_settings, build_isolation, + &extra_build_dependencies, LinkMode::default(), &build_options, &hashes, diff --git a/crates/uv-build-frontend/src/lib.rs b/crates/uv-build-frontend/src/lib.rs index 5cbaece2e..385e056b7 100644 --- a/crates/uv-build-frontend/src/lib.rs +++ b/crates/uv-build-frontend/src/lib.rs @@ -42,6 +42,7 @@ use uv_static::EnvVars; use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, BuildStack, SourceBuildTrait}; use uv_warnings::warn_user_once; use uv_workspace::WorkspaceCache; +use uv_workspace::pyproject::ExtraBuildDependencies; pub use crate::error::{Error, MissingHeaderCause}; @@ -281,6 +282,7 @@ impl SourceBuild { workspace_cache: &WorkspaceCache, config_settings: ConfigSettings, build_isolation: BuildIsolation<'_>, + extra_build_dependencies: &ExtraBuildDependencies, build_stack: &BuildStack, build_kind: BuildKind, mut environment_variables: FxHashMap, @@ -297,7 +299,6 @@ impl SourceBuild { }; let default_backend: Pep517Backend = DEFAULT_BACKEND.clone(); - // Check if we have a PEP 517 build backend. let (pep517_backend, project) = Self::extract_pep517_backend( &source_tree, @@ -305,6 +306,7 @@ impl SourceBuild { fallback_package_name, locations, source_strategy, + extra_build_dependencies, workspace_cache, &default_backend, ) @@ -506,6 +508,7 @@ impl SourceBuild { package_name: Option<&PackageName>, locations: &IndexLocations, source_strategy: SourceStrategy, + extra_build_dependencies: &ExtraBuildDependencies, workspace_cache: &WorkspaceCache, default_backend: &Pep517Backend, ) -> Result<(Pep517Backend, Option), Box> { @@ -517,17 +520,24 @@ impl SourceBuild { let pyproject_toml: PyProjectToml = PyProjectToml::deserialize(pyproject_toml.into_deserializer()) .map_err(Error::InvalidPyprojectTomlSchema)?; + let name = pyproject_toml + .project + .as_ref() + .map(|project| &project.name) + .or(package_name); + let extra_build_dependencies = name + .as_ref() + .and_then(|name| extra_build_dependencies.get(name).cloned()) + .unwrap_or_default(); + + let backend = if let Some(mut build_system) = pyproject_toml.build_system { + // Apply extra-build-dependencies if there are any + build_system.requires.extend(extra_build_dependencies); - let backend = if let Some(build_system) = pyproject_toml.build_system { // If necessary, lower the requirements. let requirements = match source_strategy { SourceStrategy::Enabled => { - if let Some(name) = pyproject_toml - .project - .as_ref() - .map(|project| &project.name) - .or(package_name) - { + if let Some(name) = name { let build_requires = uv_pypi_types::BuildRequires { name: Some(name.clone()), requires_dist: build_system.requires, @@ -606,7 +616,13 @@ impl SourceBuild { ); } } - default_backend.clone() + let mut backend = default_backend.clone(); + // Apply extra_build_dependencies + // TODO(Gankra): should Sources/Indexes be applied on this path? + backend + .requirements + .extend(extra_build_dependencies.into_iter().map(Requirement::from)); + backend }; Ok((backend, pyproject_toml.project)) } @@ -617,12 +633,21 @@ impl SourceBuild { source_tree.to_path_buf(), ))); } - // If no `pyproject.toml` is present, by default, proceed with a PEP 517 build using // the default backend, to match `build`. `pip` uses `setup.py` directly in this // case, but plans to make PEP 517 builds the default in the future. // See: https://github.com/pypa/pip/issues/9175. - Ok((default_backend.clone(), None)) + let mut backend = default_backend.clone(); + // Apply extra_build_dependencies + // TODO(Gankra): should Sources/Indexes be applied on this path? + let extra_build_dependencies = package_name + .as_ref() + .and_then(|name| extra_build_dependencies.get(name).cloned()) + .unwrap_or_default(); + backend + .requirements + .extend(extra_build_dependencies.into_iter().map(Requirement::from)); + Ok((backend, None)) } Err(err) => Err(Box::new(err.into())), } diff --git a/crates/uv-cli/src/options.rs b/crates/uv-cli/src/options.rs index f522022a1..5aa5bdda8 100644 --- a/crates/uv-cli/src/options.rs +++ b/crates/uv-cli/src/options.rs @@ -323,6 +323,7 @@ pub fn resolver_options( .map(|config_settings| config_settings.into_iter().collect::()), no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"), no_build_isolation_package: Some(no_build_isolation_package), + extra_build_dependencies: None, exclude_newer, link_mode, no_build: flag(no_build, build, "build"), @@ -434,6 +435,7 @@ pub fn resolver_installer_options( } else { Some(no_build_isolation_package) }, + extra_build_dependencies: None, exclude_newer, link_mode, compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode"), diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index 874e412e5..e346152ae 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -40,6 +40,7 @@ use uv_types::{ HashStrategy, InFlight, }; use uv_workspace::WorkspaceCache; +use uv_workspace::pyproject::ExtraBuildDependencies; #[derive(Debug, Error)] pub enum BuildDispatchError { @@ -88,6 +89,7 @@ pub struct BuildDispatch<'a> { shared_state: SharedState, dependency_metadata: &'a DependencyMetadata, build_isolation: BuildIsolation<'a>, + extra_build_dependencies: &'a ExtraBuildDependencies, link_mode: uv_install_wheel::LinkMode, build_options: &'a BuildOptions, config_settings: &'a ConfigSettings, @@ -114,6 +116,7 @@ impl<'a> BuildDispatch<'a> { index_strategy: IndexStrategy, config_settings: &'a ConfigSettings, build_isolation: BuildIsolation<'a>, + extra_build_dependencies: &'a ExtraBuildDependencies, link_mode: uv_install_wheel::LinkMode, build_options: &'a BuildOptions, hasher: &'a HashStrategy, @@ -135,6 +138,7 @@ impl<'a> BuildDispatch<'a> { index_strategy, config_settings, build_isolation, + extra_build_dependencies, link_mode, build_options, hasher, @@ -433,6 +437,7 @@ impl BuildContext for BuildDispatch<'_> { self.workspace_cache(), self.config_settings.clone(), self.build_isolation, + self.extra_build_dependencies, &build_stack, build_kind, self.build_extra_env_vars.clone(), diff --git a/crates/uv-settings/src/combine.rs b/crates/uv-settings/src/combine.rs index 8edbd2a05..9c02b8d07 100644 --- a/crates/uv-settings/src/combine.rs +++ b/crates/uv-settings/src/combine.rs @@ -1,5 +1,5 @@ -use std::num::NonZeroUsize; use std::path::PathBuf; +use std::{collections::BTreeMap, num::NonZeroUsize}; use url::Url; @@ -120,6 +120,21 @@ impl Combine for Option> { } } +impl Combine for Option>> { + /// Combine two maps of vecs by combining their vecs + fn combine(self, other: Option>>) -> Option>> { + match (self, other) { + (Some(mut a), Some(b)) => { + for (key, value) in b { + a.entry(key).or_default().extend(value); + } + Some(a) + } + (a, b) => a.or(b), + } + } +} + impl Combine for Option { /// Combine two maps by merging the map in `self` with the map in `other`, if they're both /// `Some`. diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs index d80ccce2f..5203a6dd0 100644 --- a/crates/uv-settings/src/settings.rs +++ b/crates/uv-settings/src/settings.rs @@ -20,7 +20,7 @@ use uv_redacted::DisplaySafeUrl; use uv_resolver::{AnnotationStyle, ExcludeNewer, ForkStrategy, PrereleaseMode, ResolutionMode}; use uv_static::EnvVars; use uv_torch::TorchMode; -use uv_workspace::pyproject_mut::AddBoundsKind; +use uv_workspace::{pyproject::ExtraBuildDependencies, pyproject_mut::AddBoundsKind}; /// A `pyproject.toml` with an (optional) `[tool.uv]` section. #[allow(dead_code)] @@ -371,6 +371,7 @@ pub struct ResolverOptions { pub no_binary_package: Option>, pub no_build_isolation: Option, pub no_build_isolation_package: Option>, + pub extra_build_dependencies: Option, pub no_sources: Option, } @@ -611,6 +612,20 @@ pub struct ResolverInstallerOptions { "# )] pub no_build_isolation_package: Option>, + /// Additional build dependencies for dependencies. + /// + /// This is intended for enabling more packages to be built with + /// build-isolation, by adding dependencies that they ambiently + /// assume to exist (`setuptools` and `pip` being common). + #[option( + default = "[]", + value_type = "dict", + example = r#" + [extra-build-dependencies] + pytest = ["setuptools"] + "# + )] + pub extra_build_dependencies: Option, /// Limit candidate packages to those that were uploaded prior to a given point in time. /// /// Accepts a superset of [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html) (e.g., @@ -1107,6 +1122,20 @@ pub struct PipOptions { "# )] pub no_build_isolation_package: Option>, + /// Additional build dependencies for dependencies. + /// + /// This is intended for enabling more packages to be built with + /// build-isolation, by adding dependencies that they ambiently + /// assume to exist (`setuptools` and `pip` being common). + #[option( + default = "[]", + value_type = "dict", + example = r#" + [extra-build-dependencies] + pytest = ["setuptools"] + "# + )] + pub extra_build_dependencies: Option, /// Validate the Python environment, to detect packages with missing dependencies and other /// issues. #[option( @@ -1661,6 +1690,7 @@ impl From for ResolverOptions { no_binary_package: value.no_binary_package, no_build_isolation: value.no_build_isolation, no_build_isolation_package: value.no_build_isolation_package, + extra_build_dependencies: value.extra_build_dependencies, no_sources: value.no_sources, } } @@ -1716,6 +1746,7 @@ pub struct ToolOptions { pub config_settings: Option, pub no_build_isolation: Option, pub no_build_isolation_package: Option>, + pub extra_build_dependencies: Option, pub exclude_newer: Option, pub link_mode: Option, pub compile_bytecode: Option, @@ -1743,6 +1774,7 @@ impl From for ToolOptions { config_settings: value.config_settings, no_build_isolation: value.no_build_isolation, no_build_isolation_package: value.no_build_isolation_package, + extra_build_dependencies: value.extra_build_dependencies, exclude_newer: value.exclude_newer, link_mode: value.link_mode, compile_bytecode: value.compile_bytecode, @@ -1772,6 +1804,7 @@ impl From for ResolverInstallerOptions { config_settings: value.config_settings, no_build_isolation: value.no_build_isolation, no_build_isolation_package: value.no_build_isolation_package, + extra_build_dependencies: value.extra_build_dependencies, exclude_newer: value.exclude_newer, link_mode: value.link_mode, compile_bytecode: value.compile_bytecode, @@ -1824,6 +1857,7 @@ pub struct OptionsWire { config_settings: Option, no_build_isolation: Option, no_build_isolation_package: Option>, + extra_build_dependencies: Option, exclude_newer: Option, link_mode: Option, compile_bytecode: Option, @@ -1940,6 +1974,7 @@ impl From for Options { sources, default_groups, dependency_groups, + extra_build_dependencies, dev_dependencies, managed, package, @@ -1979,6 +2014,7 @@ impl From for Options { config_settings, no_build_isolation, no_build_isolation_package, + extra_build_dependencies, exclude_newer, link_mode, compile_bytecode, diff --git a/crates/uv-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index 124a62881..7f4478fb8 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -374,6 +374,21 @@ pub struct ToolUv { )] pub dependency_groups: Option, + /// Additional build dependencies for dependencies. + /// + /// This is intended for enabling more packages to be built with + /// build-isolation, by adding dependencies that they ambiently + /// assume to exist (`setuptools` and `pip` being common). + #[option( + default = "[]", + value_type = "dict", + example = r#" + [tool.uv.extra-build-dependencies] + pytest = ["pip"] + "# + )] + pub extra_build_dependencies: Option, + /// The project's development dependencies. /// /// Development dependencies will be installed by default in `uv run` and `uv sync`, but will @@ -745,6 +760,70 @@ pub struct DependencyGroupSettings { pub requires_python: Option, } +pub type ExtraBuildDependencies = + BTreeMap>>; + +#[derive(Default, Debug, Clone, PartialEq, Eq)] +#[cfg_attr(test, derive(Serialize))] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct ToolUvExtraBuildDependencies(ExtraBuildDependencies); + +impl ToolUvExtraBuildDependencies { + /// Returns the underlying `BTreeMap` of group names to settings. + pub fn inner(&self) -> &ExtraBuildDependencies { + &self.0 + } + + /// Convert the [`ToolUvExtraBuildDependencies`] into its inner `BTreeMap`. + #[must_use] + pub fn into_inner(self) -> ExtraBuildDependencies { + self.0 + } +} + +/// Ensure that all keys in the TOML table are unique. +impl<'de> serde::de::Deserialize<'de> for ToolUvExtraBuildDependencies { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct DependenciesVisitor; + + impl<'de> serde::de::Visitor<'de> for DependenciesVisitor { + type Value = ToolUvExtraBuildDependencies; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a map with unique keys") + } + + fn visit_map(self, mut access: M) -> Result + where + M: serde::de::MapAccess<'de>, + { + let mut groups = BTreeMap::new(); + while let Some((key, value)) = access + .next_entry::>>()? + { + match groups.entry(key) { + std::collections::btree_map::Entry::Occupied(entry) => { + return Err(serde::de::Error::custom(format!( + "duplicate extra-build-dependencies for `{}`", + entry.key() + ))); + } + std::collections::btree_map::Entry::Vacant(entry) => { + entry.insert(value); + } + } + } + Ok(ToolUvExtraBuildDependencies(groups)) + } + } + + deserializer.deserialize_map(DependenciesVisitor) + } +} + #[derive(Deserialize, OptionsMetadata, Default, Debug, Clone, PartialEq, Eq)] #[cfg_attr(test, derive(Serialize))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index 1349d739c..616e2d1b1 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -1868,6 +1868,7 @@ mod tests { "package": null, "default-groups": null, "dependency-groups": null, + "extra-build-dependencies": null, "dev-dependencies": null, "override-dependencies": null, "constraint-dependencies": null, @@ -1964,6 +1965,7 @@ mod tests { "package": null, "default-groups": null, "dependency-groups": null, + "extra-build-dependencies": null, "dev-dependencies": null, "override-dependencies": null, "constraint-dependencies": null, @@ -2175,6 +2177,7 @@ mod tests { "package": null, "default-groups": null, "dependency-groups": null, + "extra-build-dependencies": null, "dev-dependencies": null, "override-dependencies": null, "constraint-dependencies": null, @@ -2283,6 +2286,7 @@ mod tests { "package": null, "default-groups": null, "dependency-groups": null, + "extra-build-dependencies": null, "dev-dependencies": null, "override-dependencies": null, "constraint-dependencies": null, @@ -2404,6 +2408,7 @@ mod tests { "package": null, "default-groups": null, "dependency-groups": null, + "extra-build-dependencies": null, "dev-dependencies": null, "override-dependencies": null, "constraint-dependencies": null, @@ -2499,6 +2504,7 @@ mod tests { "package": null, "default-groups": null, "dependency-groups": null, + "extra-build-dependencies": null, "dev-dependencies": null, "override-dependencies": null, "constraint-dependencies": null, diff --git a/crates/uv/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs index 2cef9a406..0a1b050e3 100644 --- a/crates/uv/src/commands/build_frontend.rs +++ b/crates/uv/src/commands/build_frontend.rs @@ -38,6 +38,7 @@ use uv_requirements::RequirementsSource; use uv_resolver::{ExcludeNewer, FlatIndex}; use uv_settings::PythonInstallMirrors; use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, BuildStack, HashStrategy}; +use uv_workspace::pyproject::ExtraBuildDependencies; use uv_workspace::{DiscoveryOptions, Workspace, WorkspaceCache, WorkspaceError}; use crate::commands::ExitStatus; @@ -199,6 +200,7 @@ async fn build_impl( config_setting, no_build_isolation, no_build_isolation_package, + extra_build_dependencies, exclude_newer, link_mode, upgrade: _, @@ -344,6 +346,7 @@ async fn build_impl( build_constraints, *no_build_isolation, no_build_isolation_package, + extra_build_dependencies, *index_strategy, *keyring_provider, *exclude_newer, @@ -421,6 +424,7 @@ async fn build_package( build_constraints: &[RequirementsSource], no_build_isolation: bool, no_build_isolation_package: &[PackageName], + extra_build_dependencies: &ExtraBuildDependencies, index_strategy: IndexStrategy, keyring_provider: KeyringProviderType, exclude_newer: Option, @@ -568,6 +572,7 @@ async fn build_package( index_strategy, config_setting, build_isolation, + extra_build_dependencies, link_mode, build_options, &hasher, diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index a1846d418..f3023cebb 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -44,6 +44,7 @@ use uv_torch::{TorchMode, TorchStrategy}; use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy}; use uv_warnings::warn_user; use uv_workspace::WorkspaceCache; +use uv_workspace::pyproject::ExtraBuildDependencies; use crate::commands::pip::loggers::DefaultResolveLogger; use crate::commands::pip::{operations, resolution_environment}; @@ -92,6 +93,7 @@ pub(crate) async fn pip_compile( config_settings: ConfigSettings, no_build_isolation: bool, no_build_isolation_package: Vec, + extra_build_dependencies: &ExtraBuildDependencies, build_options: BuildOptions, mut python_version: Option, python_platform: Option, @@ -477,6 +479,7 @@ pub(crate) async fn pip_compile( index_strategy, &config_settings, build_isolation, + extra_build_dependencies, link_mode, &build_options, &build_hashes, diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index aa6e6a6c9..c8221ac40 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -38,6 +38,7 @@ use uv_torch::{TorchMode, TorchStrategy}; use uv_types::{BuildIsolation, HashStrategy}; use uv_warnings::warn_user; use uv_workspace::WorkspaceCache; +use uv_workspace::pyproject::ExtraBuildDependencies; use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger, InstallLogger}; use crate::commands::pip::operations::Modifications; @@ -77,6 +78,7 @@ pub(crate) async fn pip_install( config_settings: &ConfigSettings, no_build_isolation: bool, no_build_isolation_package: Vec, + extra_build_dependencies: &ExtraBuildDependencies, build_options: BuildOptions, modifications: Modifications, python_version: Option, @@ -422,6 +424,7 @@ pub(crate) async fn pip_install( index_strategy, config_settings, build_isolation, + extra_build_dependencies, link_mode, &build_options, &build_hasher, diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 8f26aaea2..a0d521ccf 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -32,6 +32,7 @@ use uv_torch::{TorchMode, TorchStrategy}; use uv_types::{BuildIsolation, HashStrategy}; use uv_warnings::warn_user; use uv_workspace::WorkspaceCache; +use uv_workspace::pyproject::ExtraBuildDependencies; use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger}; use crate::commands::pip::operations::Modifications; @@ -62,6 +63,7 @@ pub(crate) async fn pip_sync( config_settings: &ConfigSettings, no_build_isolation: bool, no_build_isolation_package: Vec, + extra_build_dependencies: &ExtraBuildDependencies, build_options: BuildOptions, python_version: Option, python_platform: Option, @@ -355,6 +357,7 @@ pub(crate) async fn pip_sync( index_strategy, config_settings, build_isolation, + extra_build_dependencies, link_mode, &build_options, &build_hasher, diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 04fd7d822..e59decdb9 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -434,6 +434,7 @@ pub(crate) async fn add( settings.resolver.index_strategy, &settings.resolver.config_setting, build_isolation, + &settings.resolver.extra_build_dependencies, settings.resolver.link_mode, &settings.resolver.build_options, &build_hasher, diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index cd4242833..fc008ae44 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -426,6 +426,7 @@ async fn do_lock( config_setting, no_build_isolation, no_build_isolation_package, + extra_build_dependencies, exclude_newer, link_mode, upgrade, @@ -665,6 +666,7 @@ async fn do_lock( *index_strategy, config_setting, build_isolation, + extra_build_dependencies, *link_mode, build_options, &build_hasher, diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index c327e8a44..2ad63a933 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -1646,6 +1646,7 @@ pub(crate) async fn resolve_names( link_mode, no_build_isolation, no_build_isolation_package, + extra_build_dependencies, prerelease: _, resolution: _, sources, @@ -1704,6 +1705,7 @@ pub(crate) async fn resolve_names( *index_strategy, config_setting, build_isolation, + extra_build_dependencies, *link_mode, build_options, &build_hasher, @@ -1795,6 +1797,7 @@ pub(crate) async fn resolve_environment( config_setting, no_build_isolation, no_build_isolation_package, + extra_build_dependencies, exclude_newer, link_mode, upgrade: _, @@ -1909,6 +1912,7 @@ pub(crate) async fn resolve_environment( *index_strategy, config_setting, build_isolation, + extra_build_dependencies, *link_mode, build_options, &build_hasher, @@ -1975,6 +1979,7 @@ pub(crate) async fn sync_environment( config_setting, no_build_isolation, no_build_isolation_package, + extra_build_dependencies, exclude_newer, link_mode, compile_bytecode, @@ -2044,6 +2049,7 @@ pub(crate) async fn sync_environment( index_strategy, config_setting, build_isolation, + extra_build_dependencies, link_mode, build_options, &build_hasher, @@ -2137,6 +2143,7 @@ pub(crate) async fn update_environment( link_mode, no_build_isolation, no_build_isolation_package, + extra_build_dependencies, prerelease, resolution, sources, @@ -2264,6 +2271,7 @@ pub(crate) async fn update_environment( *index_strategy, config_setting, build_isolation, + extra_build_dependencies, *link_mode, build_options, &build_hasher, diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 6e057446e..36adbb4b1 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -610,6 +610,7 @@ pub(super) async fn do_sync( config_setting, no_build_isolation, no_build_isolation_package, + extra_build_dependencies, exclude_newer, link_mode, compile_bytecode, @@ -744,6 +745,7 @@ pub(super) async fn do_sync( index_strategy, config_setting, build_isolation, + extra_build_dependencies, link_mode, build_options, &build_hasher, diff --git a/crates/uv/src/commands/project/tree.rs b/crates/uv/src/commands/project/tree.rs index d401940d9..a0e4ab51c 100644 --- a/crates/uv/src/commands/project/tree.rs +++ b/crates/uv/src/commands/project/tree.rs @@ -202,6 +202,7 @@ pub(crate) async fn tree( config_setting: _, no_build_isolation: _, no_build_isolation_package: _, + extra_build_dependencies: _, exclude_newer: _, link_mode: _, upgrade: _, diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 9334d844d..7852aa208 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -30,6 +30,7 @@ use uv_settings::PythonInstallMirrors; use uv_shell::{Shell, shlex_posix, shlex_windows}; use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, BuildStack, HashStrategy}; use uv_warnings::warn_user; +use uv_workspace::pyproject::ExtraBuildDependencies; use uv_workspace::{DiscoveryOptions, VirtualProject, WorkspaceCache, WorkspaceError}; use crate::commands::ExitStatus; @@ -343,7 +344,7 @@ async fn venv_impl( // Do not allow builds let build_options = BuildOptions::new(NoBinary::None, NoBuild::All); - + let extra_build_dependencies = ExtraBuildDependencies::default(); // Prep the build context. let build_dispatch = BuildDispatch::new( &client, @@ -357,6 +358,7 @@ async fn venv_impl( index_strategy, &config_settings, BuildIsolation::Isolated, + &extra_build_dependencies, link_mode, &build_options, &build_hasher, diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index ab4aee9e9..1ff6371fc 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -520,6 +520,7 @@ async fn run(mut cli: Cli) -> Result { args.settings.config_setting, args.settings.no_build_isolation, args.settings.no_build_isolation_package, + &args.settings.extra_build_dependencies, args.settings.build_options, args.settings.python_version, args.settings.python_platform, @@ -590,6 +591,7 @@ async fn run(mut cli: Cli) -> Result { &args.settings.config_setting, args.settings.no_build_isolation, args.settings.no_build_isolation_package, + &args.settings.extra_build_dependencies, args.settings.build_options, args.settings.python_version, args.settings.python_platform, @@ -741,6 +743,7 @@ async fn run(mut cli: Cli) -> Result { &args.settings.config_setting, args.settings.no_build_isolation, args.settings.no_build_isolation_package, + &args.settings.extra_build_dependencies, args.settings.build_options, args.modifications, args.settings.python_version, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 004ce5053..13ae0df31 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -44,7 +44,7 @@ use uv_settings::{ use uv_static::EnvVars; use uv_torch::TorchMode; use uv_warnings::warn_user_once; -use uv_workspace::pyproject::DependencyType; +use uv_workspace::pyproject::{DependencyType, ExtraBuildDependencies}; use uv_workspace::pyproject_mut::AddBoundsKind; use crate::commands::ToolRunCommand; @@ -2664,6 +2664,7 @@ pub(crate) struct InstallerSettingsRef<'a> { pub(crate) config_setting: &'a ConfigSettings, pub(crate) no_build_isolation: bool, pub(crate) no_build_isolation_package: &'a [PackageName], + pub(crate) extra_build_dependencies: &'a ExtraBuildDependencies, pub(crate) exclude_newer: Option, pub(crate) link_mode: LinkMode, pub(crate) compile_bytecode: bool, @@ -2689,6 +2690,7 @@ pub(crate) struct ResolverSettings { pub(crate) link_mode: LinkMode, pub(crate) no_build_isolation: bool, pub(crate) no_build_isolation_package: Vec, + pub(crate) extra_build_dependencies: ExtraBuildDependencies, pub(crate) prerelease: PrereleaseMode, pub(crate) resolution: ResolutionMode, pub(crate) sources: SourceStrategy, @@ -2740,6 +2742,7 @@ impl From for ResolverSettings { config_setting: value.config_settings.unwrap_or_default(), no_build_isolation: value.no_build_isolation.unwrap_or_default(), no_build_isolation_package: value.no_build_isolation_package.unwrap_or_default(), + extra_build_dependencies: value.extra_build_dependencies.unwrap_or_default(), exclude_newer: value.exclude_newer, link_mode: value.link_mode.unwrap_or_default(), sources: SourceStrategy::from_args(value.no_sources.unwrap_or_default()), @@ -2828,6 +2831,7 @@ impl From for ResolverInstallerSettings { link_mode: value.link_mode.unwrap_or_default(), no_build_isolation: value.no_build_isolation.unwrap_or_default(), no_build_isolation_package: value.no_build_isolation_package.unwrap_or_default(), + extra_build_dependencies: value.extra_build_dependencies.unwrap_or_default(), prerelease: value.prerelease.unwrap_or_default(), resolution: value.resolution.unwrap_or_default(), sources: SourceStrategy::from_args(value.no_sources.unwrap_or_default()), @@ -2870,6 +2874,7 @@ pub(crate) struct PipSettings { pub(crate) torch_backend: Option, pub(crate) no_build_isolation: bool, pub(crate) no_build_isolation_package: Vec, + pub(crate) extra_build_dependencies: ExtraBuildDependencies, pub(crate) build_options: BuildOptions, pub(crate) allow_empty_requirements: bool, pub(crate) strict: bool, @@ -2936,6 +2941,7 @@ impl PipSettings { only_binary, no_build_isolation, no_build_isolation_package, + extra_build_dependencies, strict, extra, all_extras, @@ -2992,6 +2998,7 @@ impl PipSettings { config_settings: top_level_config_settings, no_build_isolation: top_level_no_build_isolation, no_build_isolation_package: top_level_no_build_isolation_package, + extra_build_dependencies: top_level_extra_build_dependencies, exclude_newer: top_level_exclude_newer, link_mode: top_level_link_mode, compile_bytecode: top_level_compile_bytecode, @@ -3025,6 +3032,8 @@ impl PipSettings { 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 extra_build_dependencies = + extra_build_dependencies.combine(top_level_extra_build_dependencies); 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); @@ -3120,6 +3129,10 @@ impl PipSettings { .no_build_isolation_package .combine(no_build_isolation_package) .unwrap_or_default(), + extra_build_dependencies: args + .extra_build_dependencies + .combine(extra_build_dependencies) + .unwrap_or_default(), config_setting: args .config_settings .combine(config_settings) @@ -3219,6 +3232,7 @@ impl<'a> From<&'a ResolverInstallerSettings> for InstallerSettingsRef<'a> { config_setting: &settings.resolver.config_setting, no_build_isolation: settings.resolver.no_build_isolation, no_build_isolation_package: &settings.resolver.no_build_isolation_package, + extra_build_dependencies: &settings.resolver.extra_build_dependencies, exclude_newer: settings.resolver.exclude_newer, link_mode: settings.resolver.link_mode, compile_bytecode: settings.compile_bytecode, diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs index 7635bd523..9489a3f3f 100644 --- a/crates/uv/tests/it/show_settings.rs +++ b/crates/uv/tests/it/show_settings.rs @@ -179,6 +179,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -360,6 +361,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -542,6 +544,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -756,6 +759,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -906,6 +910,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -1099,6 +1104,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -1339,6 +1345,7 @@ fn resolve_index_url() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -1588,6 +1595,7 @@ fn resolve_index_url() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -1794,6 +1802,7 @@ fn resolve_find_links() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -1966,6 +1975,7 @@ fn resolve_top_level() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -2196,6 +2206,7 @@ fn resolve_top_level() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -2409,6 +2420,7 @@ fn resolve_top_level() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -2580,6 +2592,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -2735,6 +2748,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -2890,6 +2904,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -3047,6 +3062,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -3195,6 +3211,7 @@ fn resolve_tool() -> anyhow::Result<()> { config_settings: None, no_build_isolation: None, no_build_isolation_package: None, + extra_build_dependencies: None, exclude_newer: None, link_mode: Some( Clone, @@ -3234,6 +3251,7 @@ fn resolve_tool() -> anyhow::Result<()> { link_mode: Clone, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, prerelease: IfNecessaryOrExplicit, resolution: LowestDirect, sources: Enabled, @@ -3388,6 +3406,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -3604,6 +3623,7 @@ fn resolve_both() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -3910,6 +3930,7 @@ fn resolve_config_file() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -3987,7 +4008,7 @@ fn resolve_config_file() -> anyhow::Result<()> { | 1 | [project] | ^^^^^^^ - unknown field `project`, expected one of `required-version`, `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `python-install-mirror`, `pypy-install-mirror`, `python-downloads-json-url`, `publish-url`, `trusted-publishing`, `check-url`, `add-bounds`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dependency-groups`, `dev-dependencies`, `build-backend` + unknown field `project`, expected one of `required-version`, `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `extra-build-dependencies`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `python-install-mirror`, `pypy-install-mirror`, `python-downloads-json-url`, `publish-url`, `trusted-publishing`, `check-url`, `add-bounds`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dependency-groups`, `dev-dependencies`, `build-backend` " ); @@ -4159,6 +4180,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -4317,6 +4339,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -4494,6 +4517,7 @@ fn allow_insecure_host() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -4730,6 +4754,7 @@ fn index_priority() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -4945,6 +4970,7 @@ fn index_priority() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -5166,6 +5192,7 @@ fn index_priority() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -5382,6 +5409,7 @@ fn index_priority() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -5605,6 +5633,7 @@ fn index_priority() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -5821,6 +5850,7 @@ fn index_priority() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -5983,6 +6013,7 @@ fn verify_hashes() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -6131,6 +6162,7 @@ fn verify_hashes() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -6277,6 +6309,7 @@ fn verify_hashes() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -6425,6 +6458,7 @@ fn verify_hashes() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -6571,6 +6605,7 @@ fn verify_hashes() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, @@ -6718,6 +6753,7 @@ fn verify_hashes() -> anyhow::Result<()> { torch_backend: None, no_build_isolation: false, no_build_isolation_package: [], + extra_build_dependencies: {}, build_options: BuildOptions { no_binary: None, no_build: None, diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index f9a71fe82..e6eb7e33f 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -1295,6 +1295,106 @@ fn sync_build_isolation_extra() -> Result<()> { Ok(()) } +/// Use dedicated extra groups to install dependencies for `--no-build-isolation-package`. +#[test] +fn sync_build_isolation_fail() -> Result<()> { + let context = TestContext::new("3.12").with_filtered_counts(); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "myproject" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["fasttext==0.9.2"] + + [build-system] + requires = ["setuptools >= 40.9.0"] + build-backend = "setuptools.build_meta" + "#, + )?; + let filters = std::iter::once((r"exit code: 1", "exit status: 1")) + .chain(context.filters()) + .collect::>(); + + // Running `uv sync` should fail due to missing build-dependencies + uv_snapshot!(&filters, context.sync(), @r#" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × Failed to build `fasttext==0.9.2` + ├─▶ The build backend returned an error + ╰─▶ Call to `setuptools.build_meta:__legacy__.build_wheel` failed (exit status: 1) + + [stderr] + [CACHE_DIR]/builds-v0/[TMP]/python: No module named pip + Traceback (most recent call last): + File "", line 38, in __init__ + ModuleNotFoundError: No module named 'pybind11' + + During handling of the above exception, another exception occurred: + + Traceback (most recent call last): + File "", line 14, in + File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 325, in get_requires_for_build_wheel + return self._get_build_requires(config_settings, requirements=['wheel']) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 295, in _get_build_requires + self.run_setup() + File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 487, in run_setup + super().run_setup(setup_script=setup_script) + File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 311, in run_setup + exec(code, locals()) + File "", line 72, in + File "", line 41, in __init__ + RuntimeError: pybind11 install failed. + + hint: This usually indicates a problem with the package or the build environment. + help: `fasttext` (v0.9.2) was included because `myproject` (v0.1.0) depends on `fasttext==0.9.2` + "#); + + // Adding extra-build-dependencies should solve the issue + pyproject_toml.write_str( + r#" + [project] + name = "myproject" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["fasttext==0.9.2"] + + [build-system] + requires = ["setuptools >= 40.9.0"] + build-backend = "setuptools.build_meta" + + [tool.uv.extra-build-dependencies] + fasttext = ["setuptools", "wheel", "pybind11"] + "#, + )?; + + uv_snapshot!(&filters, context.sync(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved [N] packages in [TIME] + Prepared [N] packages in [TIME] + Installed [N] packages in [TIME] + + fasttext==0.9.2 + + myproject==0.1.0 (from file://[TEMP_DIR]/) + + numpy==1.26.4 + + pybind11==2.11.1 + + setuptools==69.2.0 + "); + + assert!(context.temp_dir.child("uv.lock").exists()); + + Ok(()) +} + /// Avoid using incompatible versions for build dependencies that are also part of the resolved /// environment. This is a very subtle issue, but: when locking, we don't enforce platform /// compatibility. So, if we reuse the resolver state to install, and the install itself has to diff --git a/docs/reference/settings.md b/docs/reference/settings.md index 58948c80e..a698559c5 100644 --- a/docs/reference/settings.md +++ b/docs/reference/settings.md @@ -202,6 +202,28 @@ environments = ["sys_platform == 'darwin'"] --- +### [`extra-build-dependencies`](#extra-build-dependencies) {: #extra-build-dependencies } + +Additional build dependencies for dependencies. + +This is intended for enabling more packages to be built with +build-isolation, by adding dependencies that they ambiently +assume to exist (`setuptools` and `pip` being common). + +**Default value**: `[]` + +**Type**: `dict` + +**Example usage**: + +```toml title="pyproject.toml" + +[tool.uv.extra-build-dependencies] +pytest = ["pip"] +``` + +--- + ### [`index`](#index) {: #index } The indexes to use when resolving dependencies. @@ -1070,6 +1092,36 @@ behave consistently across timezones. --- +### [`extra-build-dependencies`](#extra-build-dependencies) {: #extra-build-dependencies } + +Additional build dependencies for dependencies. + +This is intended for enabling more packages to be built with +build-isolation, by adding dependencies that they ambiently +assume to exist (`setuptools` and `pip` being common). + +**Default value**: `[]` + +**Type**: `dict` + +**Example usage**: + +=== "pyproject.toml" + + ```toml + [tool.uv] + [extra-build-dependencies] + pytest = ["setuptools"] + ``` +=== "uv.toml" + + ```toml + [extra-build-dependencies] + pytest = ["setuptools"] + ``` + +--- + ### [`extra-index-url`](#extra-index-url) {: #extra-index-url } Extra URLs of package indexes to use, in addition to `--index-url`. @@ -2504,6 +2556,38 @@ Only applies to `pyproject.toml`, `setup.py`, and `setup.cfg` sources. --- +#### [`extra-build-dependencies`](#pip_extra-build-dependencies) {: #pip_extra-build-dependencies } + + +Additional build dependencies for dependencies. + +This is intended for enabling more packages to be built with +build-isolation, by adding dependencies that they ambiently +assume to exist (`setuptools` and `pip` being common). + +**Default value**: `[]` + +**Type**: `dict` + +**Example usage**: + +=== "pyproject.toml" + + ```toml + [tool.uv.pip] + [extra-build-dependencies] + pytest = ["setuptools"] + ``` +=== "uv.toml" + + ```toml + [pip] + [extra-build-dependencies] + pytest = ["setuptools"] + ``` + +--- + #### [`extra-index-url`](#pip_extra-index-url) {: #pip_extra-index-url } diff --git a/uv.schema.json b/uv.schema.json index dbc4f1168..c46387622 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -203,6 +203,17 @@ } ] }, + "extra-build-dependencies": { + "description": "Additional build dependencies for dependencies.\n\nThis is intended for enabling more packages to be built with build-isolation, by adding dependencies that they ambiently assume to exist (`setuptools` and `pip` being common).", + "anyOf": [ + { + "$ref": "#/definitions/ToolUvExtraBuildDependencies" + }, + { + "type": "null" + } + ] + }, "extra-index-url": { "description": "Extra URLs of package indexes to use, in addition to `--index-url`.\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)\n(the simple repository API), or a local directory laid out in the same format.\n\nAll indexes provided via this flag take priority over the index specified by\n[`index_url`](#index-url) or [`index`](#index) with `default = true`. When multiple indexes\nare provided, earlier values take priority.\n\nTo control uv's resolution strategy when multiple indexes are present, see\n[`index_strategy`](#index-strategy).\n\n(Deprecated: use `index` instead.)", "type": [ @@ -1206,6 +1217,19 @@ "$ref": "#/definitions/ExtraName" } }, + "extra-build-dependencies": { + "description": "Additional build dependencies for dependencies.\n\nThis is intended for enabling more packages to be built with build-isolation, by adding dependencies that they ambiently assume to exist (`setuptools` and `pip` being common).", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/definitions/Requirement" + } + } + }, "extra-index-url": { "description": "Extra URLs of package indexes to use, in addition to `--index-url`.\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)\n(the simple repository API), or a local directory laid out in the same format.\n\nAll indexes provided via this flag take priority over the index specified by\n[`index_url`](#index-url). When multiple indexes are provided, earlier values take priority.\n\nTo control uv's resolution strategy when multiple indexes are present, see\n[`index_strategy`](#index-strategy).", "type": [ @@ -2234,6 +2258,15 @@ "$ref": "#/definitions/DependencyGroupSettings" } }, + "ToolUvExtraBuildDependencies": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/definitions/Requirement" + } + } + }, "ToolUvSources": { "type": "object", "additionalProperties": {