diff --git a/Cargo.lock b/Cargo.lock index 0603214c9..9c5bb0528 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5450,7 +5450,6 @@ dependencies = [ "uv-static", "uv-types", "uv-warnings", - "uv-workspace", "walkdir", ] diff --git a/crates/uv-bench/benches/uv.rs b/crates/uv-bench/benches/uv.rs index df19354f6..b1958db90 100644 --- a/crates/uv-bench/benches/uv.rs +++ b/crates/uv-bench/benches/uv.rs @@ -91,7 +91,9 @@ mod resolver { }; use uv_dispatch::{BuildDispatch, SharedState}; use uv_distribution::DistributionDatabase; - use uv_distribution_types::{DependencyMetadata, IndexLocations, RequiresPython}; + use uv_distribution_types::{ + DependencyMetadata, ExtraBuildRequires, IndexLocations, RequiresPython, + }; use uv_install_wheel::LinkMode; use uv_pep440::Version; use uv_pep508::{MarkerEnvironment, MarkerEnvironmentBuilder}; @@ -141,7 +143,7 @@ mod resolver { universal: bool, ) -> Result { let build_isolation = BuildIsolation::default(); - let extra_build_requires = uv_distribution::ExtraBuildRequires::default(); + let extra_build_requires = ExtraBuildRequires::default(); let build_options = BuildOptions::default(); let concurrency = Concurrency::default(); let config_settings = ConfigSettings::default(); diff --git a/crates/uv-build-frontend/src/lib.rs b/crates/uv-build-frontend/src/lib.rs index 58cf441ab..cfa6a5f9e 100644 --- a/crates/uv-build-frontend/src/lib.rs +++ b/crates/uv-build-frontend/src/lib.rs @@ -32,7 +32,7 @@ use uv_cache_key::cache_digest; use uv_configuration::Preview; use uv_configuration::{BuildKind, BuildOutput, ConfigSettings, SourceStrategy}; use uv_distribution::BuildRequires; -use uv_distribution_types::{IndexLocations, Requirement, Resolution}; +use uv_distribution_types::{ExtraBuildRequires, IndexLocations, Requirement, Resolution}; use uv_fs::LockedFile; use uv_fs::{PythonExt, Simplified}; use uv_pep440::Version; @@ -43,7 +43,6 @@ 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}; @@ -283,7 +282,7 @@ impl SourceBuild { workspace_cache: &WorkspaceCache, config_settings: ConfigSettings, build_isolation: BuildIsolation<'_>, - extra_build_dependencies: &ExtraBuildDependencies, + extra_build_requires: &ExtraBuildRequires, build_stack: &BuildStack, build_kind: BuildKind, mut environment_variables: FxHashMap, @@ -326,10 +325,9 @@ impl SourceBuild { let extra_build_dependencies: Vec = package_name .as_ref() - .and_then(|name| extra_build_dependencies.get(name).cloned()) + .and_then(|name| extra_build_requires.get(name).cloned()) .unwrap_or_default() .into_iter() - .map(Requirement::from) .collect(); // Create a virtual environment, or install into the shared environment if requested. diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index 0205ca969..e381c895e 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -22,11 +22,11 @@ use uv_configuration::{ }; use uv_configuration::{BuildOutput, Concurrency}; use uv_distribution::DistributionDatabase; -use uv_distribution::ExtraBuildRequires; use uv_distribution_filename::DistFilename; use uv_distribution_types::{ - CachedDist, DependencyMetadata, Identifier, IndexCapabilities, IndexLocations, - IsBuildBackendError, Name, Requirement, Resolution, SourceDist, VersionOrUrlRef, + CachedDist, DependencyMetadata, ExtraBuildRequires, Identifier, IndexCapabilities, + IndexLocations, IsBuildBackendError, Name, Requirement, Resolution, SourceDist, + VersionOrUrlRef, }; use uv_git::GitResolver; use uv_installer::{Installer, Plan, Planner, Preparer, SitePackages}; @@ -223,8 +223,8 @@ impl BuildContext for BuildDispatch<'_> { &self.workspace_cache } - fn extra_build_dependencies(&self) -> &uv_workspace::pyproject::ExtraBuildDependencies { - &self.extra_build_requires.extra_build_dependencies + fn extra_build_requires(&self) -> &ExtraBuildRequires { + self.extra_build_requires } async fn resolve<'data>( @@ -311,7 +311,7 @@ impl BuildContext for BuildDispatch<'_> { self.index_locations, self.config_settings, self.config_settings_package, - self.extra_build_dependencies(), + self.extra_build_requires(), self.cache(), venv, tags, @@ -461,7 +461,7 @@ impl BuildContext for BuildDispatch<'_> { self.workspace_cache(), config_settings, self.build_isolation, - self.extra_build_dependencies(), + self.extra_build_requires(), &build_stack, build_kind, self.build_extra_env_vars.clone(), diff --git a/crates/uv-distribution-filename/src/extension.rs b/crates/uv-distribution-filename/src/extension.rs index 747afc41b..233dfa9a1 100644 --- a/crates/uv-distribution-filename/src/extension.rs +++ b/crates/uv-distribution-filename/src/extension.rs @@ -55,6 +55,14 @@ impl DistExtension { .map_err(|_| ExtensionError::Dist), } } + + /// Return the name for the extension. + pub fn name(&self) -> &'static str { + match self { + Self::Wheel => "whl", + Self::Source(ext) => ext.name(), + } + } } impl SourceDistExtension { diff --git a/crates/uv-distribution-types/src/build_requires.rs b/crates/uv-distribution-types/src/build_requires.rs new file mode 100644 index 000000000..2ec1a164e --- /dev/null +++ b/crates/uv-distribution-types/src/build_requires.rs @@ -0,0 +1,38 @@ +use std::collections::BTreeMap; + +use uv_normalize::PackageName; + +use crate::Requirement; + +/// Lowered extra build dependencies with source resolution applied. +#[derive(Debug, Clone, Default)] +pub struct ExtraBuildRequires(BTreeMap>); + +impl std::ops::Deref for ExtraBuildRequires { + type Target = BTreeMap>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for ExtraBuildRequires { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl IntoIterator for ExtraBuildRequires { + type Item = (PackageName, Vec); + type IntoIter = std::collections::btree_map::IntoIter>; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl FromIterator<(PackageName, Vec)> for ExtraBuildRequires { + fn from_iter)>>(iter: T) -> Self { + Self(iter.into_iter().collect()) + } +} diff --git a/crates/uv-distribution-types/src/lib.rs b/crates/uv-distribution-types/src/lib.rs index 0b25669b0..98cea2582 100644 --- a/crates/uv-distribution-types/src/lib.rs +++ b/crates/uv-distribution-types/src/lib.rs @@ -54,6 +54,7 @@ use uv_redacted::DisplaySafeUrl; pub use crate::annotation::*; pub use crate::any::*; +pub use crate::build_requires::*; pub use crate::buildable::*; pub use crate::cached::*; pub use crate::dependency_metadata::*; @@ -82,6 +83,7 @@ pub use crate::traits::*; mod annotation; mod any; +mod build_requires; mod buildable; mod cached; mod dependency_metadata; diff --git a/crates/uv-distribution-types/src/requirement.rs b/crates/uv-distribution-types/src/requirement.rs index 104cf396c..49507aadf 100644 --- a/crates/uv-distribution-types/src/requirement.rs +++ b/crates/uv-distribution-types/src/requirement.rs @@ -4,7 +4,7 @@ use std::path::Path; use std::str::FromStr; use thiserror::Error; - +use uv_cache_key::{CacheKey, CacheKeyHasher}; use uv_distribution_filename::DistExtension; use uv_fs::{CWD, PortablePath, PortablePathBuf, relative_to}; use uv_git_types::{GitOid, GitReference, GitUrl, GitUrlParseError, OidParseError}; @@ -365,6 +365,107 @@ impl Display for Requirement { } } +impl CacheKey for Requirement { + fn cache_key(&self, state: &mut CacheKeyHasher) { + self.name.as_str().cache_key(state); + + self.groups.len().cache_key(state); + for group in &self.groups { + group.as_str().cache_key(state); + } + + self.extras.len().cache_key(state); + for extra in &self.extras { + extra.as_str().cache_key(state); + } + + if let Some(marker) = self.marker.contents() { + 1u8.cache_key(state); + marker.to_string().cache_key(state); + } else { + 0u8.cache_key(state); + } + + match &self.source { + RequirementSource::Registry { + specifier, + index, + conflict: _, + } => { + 0u8.cache_key(state); + specifier.len().cache_key(state); + for spec in specifier.iter() { + spec.operator().as_str().cache_key(state); + spec.version().cache_key(state); + } + if let Some(index) = index { + 1u8.cache_key(state); + index.url.cache_key(state); + } else { + 0u8.cache_key(state); + } + // `conflict` is intentionally omitted + } + RequirementSource::Url { + location, + subdirectory, + ext, + url, + } => { + 1u8.cache_key(state); + location.cache_key(state); + if let Some(subdirectory) = subdirectory { + 1u8.cache_key(state); + subdirectory.display().to_string().cache_key(state); + } else { + 0u8.cache_key(state); + } + ext.name().cache_key(state); + url.cache_key(state); + } + RequirementSource::Git { + git, + subdirectory, + url, + } => { + 2u8.cache_key(state); + git.to_string().cache_key(state); + if let Some(subdirectory) = subdirectory { + 1u8.cache_key(state); + subdirectory.display().to_string().cache_key(state); + } else { + 0u8.cache_key(state); + } + url.cache_key(state); + } + RequirementSource::Path { + install_path, + ext, + url, + } => { + 3u8.cache_key(state); + install_path.cache_key(state); + ext.name().cache_key(state); + url.cache_key(state); + } + RequirementSource::Directory { + install_path, + editable, + r#virtual, + url, + } => { + 4u8.cache_key(state); + install_path.cache_key(state); + editable.cache_key(state); + r#virtual.cache_key(state); + url.cache_key(state); + } + } + + // `origin` is intentionally omitted + } +} + /// The different locations with can install a distribution from: Version specifier (from an index), /// HTTP(S) URL, git repository, and path. /// diff --git a/crates/uv-distribution/src/index/built_wheel_index.rs b/crates/uv-distribution/src/index/built_wheel_index.rs index d877c2509..321199772 100644 --- a/crates/uv-distribution/src/index/built_wheel_index.rs +++ b/crates/uv-distribution/src/index/built_wheel_index.rs @@ -5,12 +5,12 @@ use uv_cache_info::CacheInfo; use uv_cache_key::cache_digest; use uv_configuration::{ConfigSettings, PackageConfigSettings}; use uv_distribution_types::{ - DirectUrlSourceDist, DirectorySourceDist, GitSourceDist, Hashed, PathSourceDist, + DirectUrlSourceDist, DirectorySourceDist, ExtraBuildRequires, GitSourceDist, Hashed, + PathSourceDist, Requirement, }; use uv_normalize::PackageName; use uv_platform_tags::Tags; use uv_types::HashStrategy; -use uv_workspace::pyproject::ExtraBuildDependencies; use crate::Error; use crate::index::cached_wheel::CachedWheel; @@ -24,7 +24,7 @@ pub struct BuiltWheelIndex<'a> { hasher: &'a HashStrategy, config_settings: &'a ConfigSettings, config_settings_package: &'a PackageConfigSettings, - extra_build_dependencies: &'a ExtraBuildDependencies, + extra_build_requires: &'a ExtraBuildRequires, } impl<'a> BuiltWheelIndex<'a> { @@ -35,7 +35,7 @@ impl<'a> BuiltWheelIndex<'a> { hasher: &'a HashStrategy, config_settings: &'a ConfigSettings, config_settings_package: &'a PackageConfigSettings, - extra_build_dependencies: &'a ExtraBuildDependencies, + extra_build_requires: &'a ExtraBuildRequires, ) -> Self { Self { cache, @@ -43,7 +43,7 @@ impl<'a> BuiltWheelIndex<'a> { hasher, config_settings, config_settings_package, - extra_build_dependencies, + extra_build_requires, } } @@ -74,7 +74,7 @@ impl<'a> BuiltWheelIndex<'a> { // If there are build settings, we need to scope to a cache shard. let config_settings = self.config_settings_for(&source_dist.name); - let extra_build_deps = self.extra_build_dependencies_for(&source_dist.name); + let extra_build_deps = self.extra_build_requires_for(&source_dist.name); let cache_shard = if config_settings.is_empty() && extra_build_deps.is_empty() { cache_shard } else { @@ -113,7 +113,7 @@ impl<'a> BuiltWheelIndex<'a> { // If there are build settings, we need to scope to a cache shard. let config_settings = self.config_settings_for(&source_dist.name); - let extra_build_deps = self.extra_build_dependencies_for(&source_dist.name); + let extra_build_deps = self.extra_build_requires_for(&source_dist.name); let cache_shard = if config_settings.is_empty() && extra_build_deps.is_empty() { cache_shard } else { @@ -163,7 +163,7 @@ impl<'a> BuiltWheelIndex<'a> { // If there are build settings, we need to scope to a cache shard. let config_settings = self.config_settings_for(&source_dist.name); - let extra_build_deps = self.extra_build_dependencies_for(&source_dist.name); + let extra_build_deps = self.extra_build_requires_for(&source_dist.name); let cache_shard = if config_settings.is_empty() && extra_build_deps.is_empty() { cache_shard } else { @@ -191,7 +191,7 @@ impl<'a> BuiltWheelIndex<'a> { // If there are build settings, we need to scope to a cache shard. let config_settings = self.config_settings_for(&source_dist.name); - let extra_build_deps = self.extra_build_dependencies_for(&source_dist.name); + let extra_build_deps = self.extra_build_requires_for(&source_dist.name); let cache_shard = if config_settings.is_empty() && extra_build_deps.is_empty() { cache_shard } else { @@ -268,11 +268,8 @@ impl<'a> BuiltWheelIndex<'a> { } /// Determine the extra build dependencies for the given package name. - fn extra_build_dependencies_for( - &self, - name: &PackageName, - ) -> &[uv_pep508::Requirement] { - self.extra_build_dependencies + fn extra_build_requires_for(&self, name: &PackageName) -> &[Requirement] { + self.extra_build_requires .get(name) .map(Vec::as_slice) .unwrap_or(&[]) diff --git a/crates/uv-distribution/src/lib.rs b/crates/uv-distribution/src/lib.rs index 6371d58af..6ffb2d6d8 100644 --- a/crates/uv-distribution/src/lib.rs +++ b/crates/uv-distribution/src/lib.rs @@ -3,8 +3,9 @@ pub use download::LocalWheel; pub use error::Error; pub use index::{BuiltWheelIndex, RegistryWheelIndex}; pub use metadata::{ - ArchiveMetadata, BuildRequires, ExtraBuildRequires, FlatRequiresDist, LoweredRequirement, - LoweringError, Metadata, MetadataError, RequiresDist, SourcedDependencyGroups, + ArchiveMetadata, BuildRequires, FlatRequiresDist, LoweredExtraBuildDependencies, + LoweredRequirement, LoweringError, Metadata, MetadataError, RequiresDist, + SourcedDependencyGroups, }; pub use reporter::Reporter; pub use source::prune; diff --git a/crates/uv-distribution/src/metadata/build_requires.rs b/crates/uv-distribution/src/metadata/build_requires.rs index 9aa14bd9e..3913d292c 100644 --- a/crates/uv-distribution/src/metadata/build_requires.rs +++ b/crates/uv-distribution/src/metadata/build_requires.rs @@ -2,9 +2,8 @@ use std::collections::BTreeMap; use std::path::Path; use uv_configuration::SourceStrategy; -use uv_distribution_types::{IndexLocations, Requirement}; +use uv_distribution_types::{ExtraBuildRequires, IndexLocations, Requirement}; use uv_normalize::PackageName; -use uv_pypi_types::VerbatimParsedUrl; use uv_workspace::pyproject::{ExtraBuildDependencies, ToolUvSources}; use uv_workspace::{ DiscoveryOptions, MemberDiscovery, ProjectWorkspace, Workspace, WorkspaceCache, @@ -205,14 +204,20 @@ impl BuildRequires { } } -/// Lowered extra build dependencies with source resolution applied. +/// Lowered extra build dependencies. +/// +/// This is a wrapper around [`ExtraBuildRequires`] that provides methods to lower +/// [`ExtraBuildDependencies`] from a workspace context or from already lowered dependencies. #[derive(Debug, Clone, Default)] -pub struct ExtraBuildRequires { - pub extra_build_dependencies: ExtraBuildDependencies, -} +pub struct LoweredExtraBuildDependencies(ExtraBuildRequires); -impl ExtraBuildRequires { - /// Lower extra build dependencies from a workspace, applying source resolution. +impl LoweredExtraBuildDependencies { + /// Return the [`ExtraBuildRequires`] that this was lowered into. + pub fn into_inner(self) -> ExtraBuildRequires { + self.0 + } + + /// Create from a workspace, lowering the extra build dependencies. pub fn from_workspace( extra_build_dependencies: ExtraBuildDependencies, workspace: &Workspace, @@ -241,9 +246,9 @@ impl ExtraBuildRequires { .unwrap_or(&empty_sources); // Lower each package's extra build dependencies - let mut result = ExtraBuildDependencies::default(); + let mut build_requires = ExtraBuildRequires::default(); for (package_name, requirements) in extra_build_dependencies { - let lowered: Vec> = requirements + let lowered: Vec = requirements .into_iter() .flat_map(|requirement| { let requirement_name = requirement.name.clone(); @@ -263,7 +268,7 @@ impl ExtraBuildRequires { ) .map( move |requirement| match requirement { - Ok(requirement) => Ok(requirement.into_inner().into()), + Ok(requirement) => Ok(requirement.into_inner()), Err(err) => Err(MetadataError::LoweringError( requirement_name.clone(), Box::new(err), @@ -272,25 +277,41 @@ impl ExtraBuildRequires { ) }) .collect::, _>>()?; - result.insert(package_name, lowered); + build_requires.insert(package_name, lowered); } - Ok(Self { - extra_build_dependencies: result, - }) - } - SourceStrategy::Disabled => { - // Without source resolution, just return the dependencies as-is - Ok(Self { - extra_build_dependencies, - }) + Ok(Self(build_requires)) } + SourceStrategy::Disabled => Ok(Self( + extra_build_dependencies + .into_iter() + .map(|(name, requirements)| { + ( + name, + requirements.into_iter().map(Requirement::from).collect(), + ) + }) + .collect(), + )), } } - /// Create from pre-lowered dependencies (for non-workspace contexts). - pub fn from_lowered(extra_build_dependencies: ExtraBuildDependencies) -> Self { - Self { - extra_build_dependencies, - } + /// Create from lowered dependencies (for non-workspace contexts, like scripts). + pub fn from_lowered(extra_build_dependencies: ExtraBuildRequires) -> Self { + Self(extra_build_dependencies) + } + + /// Create from unlowered dependencies (e.g., for contexts in the pip CLI). + pub fn from_non_lowered(extra_build_dependencies: ExtraBuildDependencies) -> Self { + Self( + extra_build_dependencies + .into_iter() + .map(|(name, requirements)| { + ( + name, + requirements.into_iter().map(Requirement::from).collect(), + ) + }) + .collect(), + ) } } diff --git a/crates/uv-distribution/src/metadata/mod.rs b/crates/uv-distribution/src/metadata/mod.rs index 3375f9fe2..5deb37c03 100644 --- a/crates/uv-distribution/src/metadata/mod.rs +++ b/crates/uv-distribution/src/metadata/mod.rs @@ -11,7 +11,7 @@ use uv_pypi_types::{HashDigests, ResolutionMetadata}; use uv_workspace::dependency_groups::DependencyGroupError; use uv_workspace::{WorkspaceCache, WorkspaceError}; -pub use crate::metadata::build_requires::{BuildRequires, ExtraBuildRequires}; +pub use crate::metadata::build_requires::{BuildRequires, LoweredExtraBuildDependencies}; pub use crate::metadata::dependency_groups::SourcedDependencyGroups; pub use crate::metadata::lowering::LoweredRequirement; pub use crate::metadata::lowering::LoweringError; diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index d516c6d0d..05d25be42 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -33,7 +33,7 @@ use uv_configuration::{BuildKind, BuildOutput, ConfigSettings, SourceStrategy}; use uv_distribution_filename::{SourceDistExtension, WheelFilename}; use uv_distribution_types::{ BuildableSource, DirectorySourceUrl, GitSourceUrl, HashPolicy, Hashed, IndexUrl, PathSourceUrl, - SourceDist, SourceUrl, + Requirement, SourceDist, SourceUrl, }; use uv_extract::hash::Hasher; use uv_fs::{rename_with_retry, write_atomic}; @@ -405,13 +405,10 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { } /// Determine the extra build dependencies for the given package name. - fn extra_build_dependencies_for( - &self, - name: Option<&PackageName>, - ) -> &[uv_pep508::Requirement] { + fn extra_build_dependencies_for(&self, name: Option<&PackageName>) -> &[Requirement] { name.and_then(|name| { self.build_context - .extra_build_dependencies() + .extra_build_requires() .get(name) .map(Vec::as_slice) }) diff --git a/crates/uv-installer/Cargo.toml b/crates/uv-installer/Cargo.toml index d0f43e770..a78dec23b 100644 --- a/crates/uv-installer/Cargo.toml +++ b/crates/uv-installer/Cargo.toml @@ -35,7 +35,6 @@ uv-redacted = { workspace = true } uv-static = { workspace = true } uv-types = { workspace = true } uv-warnings = { workspace = true } -uv-workspace = { workspace = true } anyhow = { workspace = true } async-channel = { workspace = true } diff --git a/crates/uv-installer/src/plan.rs b/crates/uv-installer/src/plan.rs index ddabf3386..a604d8152 100644 --- a/crates/uv-installer/src/plan.rs +++ b/crates/uv-installer/src/plan.rs @@ -10,15 +10,14 @@ use uv_distribution::{ BuiltWheelIndex, HttpArchivePointer, LocalArchivePointer, RegistryWheelIndex, }; use uv_distribution_types::{ - BuiltDist, CachedDirectUrlDist, CachedDist, Dist, Error, Hashed, IndexLocations, InstalledDist, - Name, RequirementSource, Resolution, ResolvedDist, SourceDist, + BuiltDist, CachedDirectUrlDist, CachedDist, Dist, Error, ExtraBuildRequires, Hashed, + IndexLocations, InstalledDist, Name, RequirementSource, Resolution, ResolvedDist, SourceDist, }; use uv_fs::Simplified; use uv_platform_tags::Tags; use uv_pypi_types::VerbatimParsedUrl; use uv_python::PythonEnvironment; use uv_types::HashStrategy; -use uv_workspace::pyproject::ExtraBuildDependencies; use crate::SitePackages; use crate::satisfies::RequirementSatisfaction; @@ -55,7 +54,7 @@ impl<'a> Planner<'a> { index_locations: &IndexLocations, config_settings: &ConfigSettings, config_settings_package: &PackageConfigSettings, - extra_build_dependencies: &ExtraBuildDependencies, + extra_build_requires: &ExtraBuildRequires, cache: &Cache, venv: &PythonEnvironment, tags: &Tags, @@ -69,7 +68,7 @@ impl<'a> Planner<'a> { hasher, config_settings, config_settings_package, - extra_build_dependencies, + extra_build_requires, ); let mut cached = vec![]; diff --git a/crates/uv-normalize/src/group_name.rs b/crates/uv-normalize/src/group_name.rs index e0a2b7c1f..e39044ad7 100644 --- a/crates/uv-normalize/src/group_name.rs +++ b/crates/uv-normalize/src/group_name.rs @@ -29,6 +29,11 @@ impl GroupName { pub fn from_owned(name: String) -> Result { validate_and_normalize_ref(&name).map(Self) } + + /// Return the underlying group name as a string. + pub fn as_str(&self) -> &str { + &self.0 + } } impl FromStr for GroupName { diff --git a/crates/uv-types/src/traits.rs b/crates/uv-types/src/traits.rs index 6966c850a..f6e51d929 100644 --- a/crates/uv-types/src/traits.rs +++ b/crates/uv-types/src/traits.rs @@ -12,14 +12,13 @@ use uv_configuration::{ }; use uv_distribution_filename::DistFilename; use uv_distribution_types::{ - CachedDist, DependencyMetadata, DistributionId, IndexCapabilities, IndexLocations, - InstalledDist, IsBuildBackendError, Requirement, Resolution, SourceDist, + CachedDist, DependencyMetadata, DistributionId, ExtraBuildRequires, IndexCapabilities, + IndexLocations, InstalledDist, IsBuildBackendError, Requirement, Resolution, SourceDist, }; use uv_git::GitResolver; use uv_pep508::PackageName; use uv_python::{Interpreter, PythonEnvironment}; use uv_workspace::WorkspaceCache; -use uv_workspace::pyproject::ExtraBuildDependencies; use crate::BuildArena; @@ -104,8 +103,8 @@ pub trait BuildContext { /// Workspace discovery caching. fn workspace_cache(&self) -> &WorkspaceCache; - /// Get the extra build dependencies. - fn extra_build_dependencies(&self) -> &ExtraBuildDependencies; + /// Get the extra build requirements. + fn extra_build_requires(&self) -> &ExtraBuildRequires; /// Resolve the given requirements into a ready-to-install set of package versions. fn resolve<'a>( diff --git a/crates/uv/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs index 7ec57e1d6..baaba8b75 100644 --- a/crates/uv/src/commands/build_frontend.rs +++ b/crates/uv/src/commands/build_frontend.rs @@ -19,6 +19,7 @@ use uv_configuration::{ PackageConfigSettings, Preview, SourceStrategy, }; use uv_dispatch::{BuildDispatch, SharedState}; +use uv_distribution::LoweredExtraBuildDependencies; use uv_distribution_filename::{ DistFilename, SourceDistExtension, SourceDistFilename, WheelFilename, }; @@ -563,9 +564,11 @@ async fn build_package( let state = SharedState::default(); let workspace_cache = WorkspaceCache::default(); - // Create a build dispatch. let extra_build_requires = - uv_distribution::ExtraBuildRequires::from_lowered(extra_build_dependencies.clone()); + LoweredExtraBuildDependencies::from_non_lowered(extra_build_dependencies.clone()) + .into_inner(); + + // Create a build dispatch. let build_dispatch = BuildDispatch::new( &client, cache, diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index 26ae9f11c..48bba2532 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -19,6 +19,7 @@ use uv_configuration::{ }; use uv_configuration::{KeyringProviderType, TargetTriple}; use uv_dispatch::{BuildDispatch, SharedState}; +use uv_distribution::LoweredExtraBuildDependencies; use uv_distribution_types::{ DependencyMetadata, HashGeneration, Index, IndexLocations, NameRequirementSpecification, Origin, Requirement, RequiresPython, UnresolvedRequirementSpecification, Verbatim, @@ -480,8 +481,12 @@ pub(crate) async fn pip_compile( .map(|constraint| constraint.requirement.clone()), ); + // Lower the extra build dependencies, if any. let extra_build_requires = - uv_distribution::ExtraBuildRequires::from_lowered(extra_build_dependencies.clone()); + LoweredExtraBuildDependencies::from_non_lowered(extra_build_dependencies.clone()) + .into_inner(); + + // Create a build dispatch. let build_dispatch = BuildDispatch::new( &client, &cache, diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index 6a9ccbd9c..e5b69dbe9 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -15,6 +15,7 @@ use uv_configuration::{ }; use uv_configuration::{KeyringProviderType, TargetTriple}; use uv_dispatch::{BuildDispatch, SharedState}; +use uv_distribution::LoweredExtraBuildDependencies; use uv_distribution_types::{ DependencyMetadata, Index, IndexLocations, NameRequirementSpecification, Origin, Requirement, Resolution, UnresolvedRequirementSpecification, @@ -423,9 +424,12 @@ pub(crate) async fn pip_install( // Initialize any shared state. let state = SharedState::default(); - // Create a build dispatch. + // Lower the extra build dependencies, if any. let extra_build_requires = - uv_distribution::ExtraBuildRequires::from_lowered(extra_build_dependencies.clone()); + LoweredExtraBuildDependencies::from_non_lowered(extra_build_dependencies.clone()) + .into_inner(); + + // Create a build dispatch. let build_dispatch = BuildDispatch::new( &client, &cache, diff --git a/crates/uv/src/commands/pip/operations.rs b/crates/uv/src/commands/pip/operations.rs index f3ddcf671..680eca490 100644 --- a/crates/uv/src/commands/pip/operations.rs +++ b/crates/uv/src/commands/pip/operations.rs @@ -464,7 +464,7 @@ pub(crate) async fn install( build_dispatch.locations(), build_dispatch.config_settings(), build_dispatch.config_settings_package(), - build_dispatch.extra_build_dependencies(), + build_dispatch.extra_build_requires(), cache, venv, tags, diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 70451faad..411d1a0ba 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -14,6 +14,7 @@ use uv_configuration::{ }; use uv_configuration::{KeyringProviderType, TargetTriple}; use uv_dispatch::{BuildDispatch, SharedState}; +use uv_distribution::LoweredExtraBuildDependencies; use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, Origin, Resolution}; use uv_fs::Simplified; use uv_install_wheel::LinkMode; @@ -358,9 +359,12 @@ pub(crate) async fn pip_sync( // Initialize any shared state. let state = SharedState::default(); - // Create a build dispatch. + // Lower the extra build dependencies, if any. let extra_build_requires = - uv_distribution::ExtraBuildRequires::from_lowered(extra_build_dependencies.clone()); + LoweredExtraBuildDependencies::from_non_lowered(extra_build_dependencies.clone()) + .into_inner(); + + // Create a build dispatch. let build_dispatch = BuildDispatch::new( &client, &cache, diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index dbbc5a77b..fce4cce6a 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -22,7 +22,7 @@ use uv_configuration::{ PreviewFeatures, SourceStrategy, }; use uv_dispatch::BuildDispatch; -use uv_distribution::DistributionDatabase; +use uv_distribution::{DistributionDatabase, LoweredExtraBuildDependencies}; use uv_distribution_types::{ Index, IndexName, IndexUrl, IndexUrls, NameRequirementSpecification, Requirement, RequirementSource, UnresolvedRequirement, VersionId, @@ -436,19 +436,22 @@ pub(crate) async fn add( FlatIndex::from_entries(entries, None, &hasher, &settings.resolver.build_options) }; - // Create a build dispatch. + // Lower the extra build dependencies, if any. let extra_build_requires = if let AddTarget::Project(project, _) = &target { - uv_distribution::ExtraBuildRequires::from_workspace( + LoweredExtraBuildDependencies::from_workspace( settings.resolver.extra_build_dependencies.clone(), project.workspace(), &settings.resolver.index_locations, settings.resolver.sources, )? } else { - uv_distribution::ExtraBuildRequires::from_lowered( + LoweredExtraBuildDependencies::from_non_lowered( settings.resolver.extra_build_dependencies.clone(), ) - }; + } + .into_inner(); + + // Create a build dispatch. let build_dispatch = BuildDispatch::new( &client, cache, diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index ad45d126b..fa71b5f62 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -16,7 +16,7 @@ use uv_configuration::{ PreviewFeatures, Reinstall, Upgrade, }; use uv_dispatch::BuildDispatch; -use uv_distribution::DistributionDatabase; +use uv_distribution::{DistributionDatabase, LoweredExtraBuildDependencies}; use uv_distribution_types::{ DependencyMetadata, HashGeneration, Index, IndexLocations, NameRequirementSpecification, Requirement, RequiresPython, UnresolvedRequirementSpecification, @@ -673,9 +673,9 @@ async fn do_lock( FlatIndex::from_entries(entries, None, &hasher, build_options) }; - // Create a build dispatch. + // Lower the extra build dependencies. let extra_build_requires = match &target { - LockTarget::Workspace(workspace) => uv_distribution::ExtraBuildRequires::from_workspace( + LockTarget::Workspace(workspace) => LoweredExtraBuildDependencies::from_workspace( extra_build_dependencies.clone(), workspace, index_locations, @@ -685,7 +685,10 @@ async fn do_lock( // Try to get extra build dependencies from the script metadata script_extra_build_requires((*script).into(), settings)? } - }; + } + .into_inner(); + + // Create a build dispatch. let build_dispatch = BuildDispatch::new( &client, cache, diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 9458919c8..2c799dfe2 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -16,9 +16,9 @@ use uv_configuration::{ PreviewFeatures, Reinstall, Upgrade, }; use uv_dispatch::{BuildDispatch, SharedState}; -use uv_distribution::{DistributionDatabase, LoweredRequirement}; +use uv_distribution::{DistributionDatabase, LoweredExtraBuildDependencies, LoweredRequirement}; use uv_distribution_types::{ - Index, Requirement, RequiresPython, Resolution, UnresolvedRequirement, + ExtraBuildRequires, Index, Requirement, RequiresPython, Resolution, UnresolvedRequirement, UnresolvedRequirementSpecification, }; use uv_fs::{CWD, LockedFile, Simplified}; @@ -46,7 +46,6 @@ use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy}; use uv_virtualenv::remove_virtualenv; use uv_warnings::{warn_user, warn_user_once}; use uv_workspace::dependency_groups::DependencyGroupError; -use uv_workspace::pyproject::ExtraBuildDependencies; use uv_workspace::pyproject::PyProjectToml; use uv_workspace::{RequiresPythonSources, Workspace, WorkspaceCache}; @@ -1741,9 +1740,12 @@ pub(crate) async fn resolve_names( let build_constraints = Constraints::default(); let build_hasher = HashStrategy::default(); - // Create a build dispatch. + // Lower the extra build dependencies, if any. let extra_build_requires = - uv_distribution::ExtraBuildRequires::from_lowered(extra_build_dependencies.clone()); + LoweredExtraBuildDependencies::from_non_lowered(extra_build_dependencies.clone()) + .into_inner(); + + // Create a build dispatch. let build_dispatch = BuildDispatch::new( &client, cache, @@ -1953,9 +1955,12 @@ pub(crate) async fn resolve_environment( let workspace_cache = WorkspaceCache::default(); - // Create a build dispatch. + // Lower the extra build dependencies, if any. let extra_build_requires = - uv_distribution::ExtraBuildRequires::from_lowered(extra_build_dependencies.clone()); + LoweredExtraBuildDependencies::from_non_lowered(extra_build_dependencies.clone()) + .into_inner(); + + // Create a build dispatch. let resolve_dispatch = BuildDispatch::new( &client, cache, @@ -2095,9 +2100,12 @@ pub(crate) async fn sync_environment( FlatIndex::from_entries(entries, Some(tags), &hasher, build_options) }; - // Create a build dispatch. + // Lower the extra build dependencies, if any. let extra_build_requires = - uv_distribution::ExtraBuildRequires::from_lowered(extra_build_dependencies.clone()); + LoweredExtraBuildDependencies::from_non_lowered(extra_build_dependencies.clone()) + .into_inner(); + + // Create a build dispatch. let build_dispatch = BuildDispatch::new( &client, cache, @@ -2174,7 +2182,7 @@ pub(crate) async fn update_environment( spec: RequirementsSpecification, modifications: Modifications, build_constraints: Constraints, - extra_build_requires: uv_distribution::ExtraBuildRequires, + extra_build_requires: ExtraBuildRequires, settings: &ResolverInstallerSettings, network_settings: &NetworkSettings, state: &SharedState, @@ -2618,7 +2626,7 @@ pub(crate) fn script_specification( pub(crate) fn script_extra_build_requires( script: Pep723ItemRef<'_>, settings: &ResolverSettings, -) -> Result { +) -> Result { let script_dir = script.directory()?; let script_indexes = script.indexes(settings.sources); let script_sources = script.sources(settings.sources); @@ -2633,8 +2641,8 @@ pub(crate) fn script_extra_build_requires( .and_then(|uv| uv.extra_build_dependencies.as_ref()) .unwrap_or(&empty); - // Lower the extra build dependencies - let mut extra_build_dependencies = ExtraBuildDependencies::default(); + // Lower the extra build dependencies. + let mut extra_build_requires = ExtraBuildRequires::default(); for (name, requirements) in script_extra_build_dependencies { let lowered_requirements: Vec<_> = requirements .iter() @@ -2647,14 +2655,14 @@ pub(crate) fn script_extra_build_requires( script_indexes, &settings.index_locations, ) - .map_ok(|req| req.into_inner().into()) + .map_ok(uv_distribution::LoweredRequirement::into_inner) }) .collect::, _>>()?; - extra_build_dependencies.insert(name.clone(), lowered_requirements); + extra_build_requires.insert(name.clone(), lowered_requirements); } - Ok(uv_distribution::ExtraBuildRequires::from_lowered( - extra_build_dependencies, + Ok(LoweredExtraBuildDependencies::from_lowered( + extra_build_requires, )) } diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 594a5c28a..9cbfd16fb 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -360,7 +360,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl // Install the script requirements, if necessary. Otherwise, use an isolated environment. if let Some(spec) = script_specification((&script).into(), &settings.resolver)? { let script_extra_build_requires = - script_extra_build_requires((&script).into(), &settings.resolver)?; + script_extra_build_requires((&script).into(), &settings.resolver)?.into_inner(); let environment = ScriptEnvironment::get_or_init( (&script).into(), python.as_deref().map(PythonRequest::parse), diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 88cf221b6..3d7a1011a 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -17,6 +17,7 @@ use uv_configuration::{ Preview, PreviewFeatures, TargetTriple, Upgrade, }; use uv_dispatch::BuildDispatch; +use uv_distribution::LoweredExtraBuildDependencies; use uv_distribution_types::{ DirectorySourceDist, Dist, Index, Requirement, Resolution, ResolvedDist, SourceDist, }; @@ -227,7 +228,7 @@ pub(crate) async fn sync( // Parse the requirements from the script. let spec = script_specification(script.into(), &settings.resolver)?.unwrap_or_default(); let script_extra_build_requires = - script_extra_build_requires(script.into(), &settings.resolver)?; + script_extra_build_requires(script.into(), &settings.resolver)?.into_inner(); // Parse the build constraints from the script. let build_constraints = script @@ -602,12 +603,12 @@ pub(super) async fn do_sync( ); } - // Lower the extra build dependencies with source resolution + // Lower the extra build dependencies with source resolution. let extra_build_requires = match &target { InstallTarget::Workspace { workspace, .. } | InstallTarget::Project { workspace, .. } | InstallTarget::NonProjectWorkspace { workspace, .. } => { - uv_distribution::ExtraBuildRequires::from_workspace( + LoweredExtraBuildDependencies::from_workspace( extra_build_dependencies.clone(), workspace, index_locations, @@ -637,7 +638,8 @@ pub(super) async fn do_sync( }; script_extra_build_requires((*script).into(), &resolver_settings)? } - }; + } + .into_inner(); let client_builder = BaseClientBuilder::new() .retries_from_env()? diff --git a/crates/uv/src/commands/tool/install.rs b/crates/uv/src/commands/tool/install.rs index 4917934c4..b191d0e1e 100644 --- a/crates/uv/src/commands/tool/install.rs +++ b/crates/uv/src/commands/tool/install.rs @@ -10,7 +10,7 @@ use uv_cache_info::Timestamp; use uv_client::BaseClientBuilder; use uv_configuration::{Concurrency, Constraints, DryRun, Preview, Reinstall, Upgrade}; use uv_distribution_types::{ - NameRequirementSpecification, Requirement, RequirementSource, + ExtraBuildRequires, NameRequirementSpecification, Requirement, RequirementSource, UnresolvedRequirementSpecification, }; use uv_normalize::PackageName; @@ -23,7 +23,7 @@ use uv_requirements::{RequirementsSource, RequirementsSpecification}; use uv_settings::{PythonInstallMirrors, ResolverInstallerOptions, ToolOptions}; use uv_tool::InstalledTools; use uv_warnings::warn_user; -use uv_workspace::{WorkspaceCache, pyproject::ExtraBuildDependencies}; +use uv_workspace::WorkspaceCache; use crate::commands::ExitStatus; use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger}; @@ -444,7 +444,7 @@ pub(crate) async fn install( spec, Modifications::Exact, Constraints::from_requirements(build_constraints.iter().cloned()), - uv_distribution::ExtraBuildRequires::from_lowered(ExtraBuildDependencies::default()), + ExtraBuildRequires::default(), &settings, &network_settings, &state, diff --git a/crates/uv/src/commands/tool/upgrade.rs b/crates/uv/src/commands/tool/upgrade.rs index 5e46a4a9d..4c62e15d6 100644 --- a/crates/uv/src/commands/tool/upgrade.rs +++ b/crates/uv/src/commands/tool/upgrade.rs @@ -9,7 +9,7 @@ use tracing::{debug, trace}; use uv_cache::Cache; use uv_client::BaseClientBuilder; use uv_configuration::{Concurrency, Constraints, DryRun, Preview}; -use uv_distribution_types::Requirement; +use uv_distribution_types::{ExtraBuildRequires, Requirement}; use uv_fs::CWD; use uv_normalize::PackageName; use uv_python::{ @@ -20,7 +20,7 @@ use uv_requirements::RequirementsSpecification; use uv_settings::{Combine, PythonInstallMirrors, ResolverInstallerOptions, ToolOptions}; use uv_tool::InstalledTools; use uv_warnings::write_error_chain; -use uv_workspace::{WorkspaceCache, pyproject::ExtraBuildDependencies}; +use uv_workspace::WorkspaceCache; use crate::commands::pip::loggers::{ DefaultInstallLogger, SummaryResolveLogger, UpgradeInstallLogger, @@ -339,7 +339,7 @@ async fn upgrade_tool( spec, Modifications::Exact, build_constraints, - uv_distribution::ExtraBuildRequires::from_lowered(ExtraBuildDependencies::default()), + ExtraBuildRequires::default(), &settings, network_settings, &state, diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 9bfa9d24d..1f48d5cfd 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -15,8 +15,8 @@ use uv_configuration::{ SourceStrategy, }; use uv_dispatch::{BuildDispatch, SharedState}; -use uv_distribution_types::Requirement; use uv_distribution_types::{DependencyMetadata, Index, IndexLocations}; +use uv_distribution_types::{ExtraBuildRequires, Requirement}; use uv_fs::Simplified; use uv_install_wheel::LinkMode; use uv_normalize::DefaultGroups; @@ -29,7 +29,6 @@ use uv_shell::{Shell, shlex_posix, shlex_windows}; use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, BuildStack, HashStrategy}; use uv_virtualenv::OnExisting; use uv_warnings::warn_user; -use uv_workspace::pyproject::ExtraBuildDependencies; use uv_workspace::{DiscoveryOptions, VirtualProject, WorkspaceCache, WorkspaceError}; use crate::commands::ExitStatus; @@ -267,8 +266,7 @@ pub(crate) async fn venv( // Do not allow builds let build_options = BuildOptions::new(NoBinary::None, NoBuild::All); - let extra_build_requires = - uv_distribution::ExtraBuildRequires::from_lowered(ExtraBuildDependencies::default()); + let extra_build_requires = ExtraBuildRequires::default(); // Prep the build context. let build_dispatch = BuildDispatch::new( &client, diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index cb17c7fff..1e46314d4 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -2091,6 +2091,160 @@ fn sync_extra_build_dependencies_sources() -> Result<()> { Ok(()) } +#[test] +fn sync_extra_build_dependencies_index() -> Result<()> { + let context = TestContext::new("3.12").with_filtered_counts(); + + // Write a test package that arbitrarily requires `anyio` at build time + let child = context.temp_dir.child("child"); + child.create_dir_all()?; + let child_pyproject_toml = child.child("pyproject.toml"); + child_pyproject_toml.write_str(indoc! {r#" + [project] + name = "child" + version = "0.1.0" + requires-python = ">=3.9" + + [build-system] + requires = ["hatchling", "anyio"] + backend-path = ["."] + build-backend = "build_backend" + "#})?; + + // Create a build backend that checks for a specific version of anyio + let build_backend = child.child("build_backend.py"); + build_backend.write_str(indoc! {r#" + import os + import sys + from hatchling.build import * + + expected_version = os.environ.get("EXPECTED_ANYIO_VERSION", "") + if not expected_version: + print("`EXPECTED_ANYIO_VERSION` not set", file=sys.stderr) + sys.exit(1) + + try: + import anyio + except ModuleNotFoundError: + print("Missing `anyio` module", file=sys.stderr) + sys.exit(1) + + from importlib.metadata import version + anyio_version = version("anyio") + + if not anyio_version.startswith(expected_version): + print(f"Expected `anyio` version {expected_version} but got {anyio_version}", file=sys.stderr) + sys.exit(1) + + print(f"Found expected `anyio` version {anyio_version}", file=sys.stderr) + "#})?; + child.child("src/child/__init__.py").touch()?; + + let parent = &context.temp_dir; + let pyproject_toml = parent.child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.9" + dependencies = ["child"] + + [tool.uv.sources] + child = { path = "child" } + "#})?; + + // Ensure our build backend is checking the version correctly + uv_snapshot!(context.filters(), context.sync().env("EXPECTED_ANYIO_VERSION", "3.0"), @r" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + Resolved [N] packages in [TIME] + × Failed to build `child @ file://[TEMP_DIR]/child` + ├─▶ The build backend returned an error + ╰─▶ Call to `build_backend.build_wheel` failed (exit status: 1) + + [stderr] + Expected `anyio` version 3.0 but got 4.3.0 + + hint: This usually indicates a problem with the package or the build environment. + help: `child` was included because `parent` (v0.1.0) depends on `child` + "); + + // Ensure that we're resolving to `4.3.0`, the "latest" on PyPI. + uv_snapshot!(context.filters(), context.sync().env("EXPECTED_ANYIO_VERSION", "4.3"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved [N] packages in [TIME] + Prepared [N] packages in [TIME] + Installed [N] packages in [TIME] + + child==0.1.0 (from file://[TEMP_DIR]/child) + "); + + // Pin `anyio` to the Test PyPI. + pyproject_toml.write_str(indoc! {r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.9" + dependencies = ["child"] + + [tool.uv.sources] + child = { path = "child" } + anyio = { index = "test" } + + [tool.uv.extra-build-dependencies] + child = ["anyio"] + + [[tool.uv.index]] + url = "https://test.pypi.org/simple" + name = "test" + explicit = true + "#})?; + + // The child should be rebuilt with `3.5` on reinstall, the "latest" on Test PyPI. + uv_snapshot!(context.filters(), context.sync() + .arg("--reinstall-package").arg("child").env("EXPECTED_ANYIO_VERSION", "4.3"), @r" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. + Resolved [N] packages in [TIME] + × Failed to build `child @ file://[TEMP_DIR]/child` + ├─▶ The build backend returned an error + ╰─▶ Call to `build_backend.build_wheel` failed (exit status: 1) + + [stderr] + Expected `anyio` version 4.3 but got 3.5.0 + + hint: This usually indicates a problem with the package or the build environment. + help: `child` was included because `parent` (v0.1.0) depends on `child` + "); + + uv_snapshot!(context.filters(), context.sync() + .arg("--reinstall-package").arg("child").env("EXPECTED_ANYIO_VERSION", "3.5"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. + Resolved [N] packages in [TIME] + Prepared [N] packages in [TIME] + Uninstalled [N] packages in [TIME] + Installed [N] packages in [TIME] + ~ child==0.1.0 (from file://[TEMP_DIR]/child) + "); + + Ok(()) +} + #[test] fn sync_extra_build_dependencies_sources_from_child() -> Result<()> { let context = TestContext::new("3.12").with_filtered_counts();