diff --git a/Cargo.lock b/Cargo.lock index 28a850fcf312..0720cc702f16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4787,6 +4787,7 @@ name = "uv-distribution" version = "0.0.1" dependencies = [ "anyhow", + "cache-key", "distribution-filename", "distribution-types", "fs-err", diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index 52da7fdff277..386c282c08fc 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -223,6 +223,7 @@ impl<'a> BuildContext for BuildDispatch<'a> { &BuildOptions::default(), self.hasher, self.index_locations, + self.config_settings, self.cache(), venv, &markers, diff --git a/crates/uv-distribution/Cargo.toml b/crates/uv-distribution/Cargo.toml index 4b798c025379..26d445d8a211 100644 --- a/crates/uv-distribution/Cargo.toml +++ b/crates/uv-distribution/Cargo.toml @@ -13,6 +13,7 @@ license = { workspace = true } workspace = true [dependencies] +cache-key = { workspace = true } distribution-filename = { workspace = true } distribution-types = { workspace = true } install-wheel-rs = { workspace = true } diff --git a/crates/uv-distribution/src/index/built_wheel_index.rs b/crates/uv-distribution/src/index/built_wheel_index.rs index a7e83f7ecc7f..1f5caf94ce4b 100644 --- a/crates/uv-distribution/src/index/built_wheel_index.rs +++ b/crates/uv-distribution/src/index/built_wheel_index.rs @@ -7,6 +7,7 @@ use distribution_types::{ use platform_tags::Tags; use uv_cache::{Cache, CacheBucket, CacheShard, WheelCache}; use uv_cache_info::CacheInfo; +use uv_configuration::ConfigSettings; use uv_fs::symlinks; use uv_types::HashStrategy; @@ -16,15 +17,22 @@ pub struct BuiltWheelIndex<'a> { cache: &'a Cache, tags: &'a Tags, hasher: &'a HashStrategy, + build_configuration: &'a ConfigSettings, } impl<'a> BuiltWheelIndex<'a> { /// Initialize an index of built distributions. - pub fn new(cache: &'a Cache, tags: &'a Tags, hasher: &'a HashStrategy) -> Self { + pub fn new( + cache: &'a Cache, + tags: &'a Tags, + hasher: &'a HashStrategy, + build_configuration: &'a ConfigSettings, + ) -> Self { Self { cache, tags, hasher, + build_configuration, } } @@ -53,6 +61,13 @@ impl<'a> BuiltWheelIndex<'a> { let cache_shard = cache_shard.shard(revision.id()); + // If there are build settings, we need to scope to a cache shard. + let cache_shard = if self.build_configuration.is_empty() { + cache_shard + } else { + cache_shard.shard(cache_key::cache_digest(self.build_configuration)) + }; + Ok(self.find(&cache_shard)) } /// Return the most compatible [`CachedWheel`] for a given source distribution at a local path. @@ -83,6 +98,13 @@ impl<'a> BuiltWheelIndex<'a> { let cache_shard = cache_shard.shard(revision.id()); + // If there are build settings, we need to scope to a cache shard. + let cache_shard = if self.build_configuration.is_empty() { + cache_shard + } else { + cache_shard.shard(cache_key::cache_digest(self.build_configuration)) + }; + Ok(self .find(&cache_shard) .map(|wheel| wheel.with_cache_info(cache_info))) @@ -125,6 +147,13 @@ impl<'a> BuiltWheelIndex<'a> { let cache_shard = cache_shard.shard(revision.id()); + // If there are build settings, we need to scope to a cache shard. + let cache_shard = if self.build_configuration.is_empty() { + cache_shard + } else { + cache_shard.shard(cache_key::cache_digest(self.build_configuration)) + }; + Ok(self .find(&cache_shard) .map(|wheel| wheel.with_cache_info(cache_info))) @@ -144,6 +173,13 @@ impl<'a> BuiltWheelIndex<'a> { WheelCache::Git(&source_dist.url, &git_sha.to_short_string()).root(), ); + // If there are build settings, we need to scope to a cache shard. + let cache_shard = if self.build_configuration.is_empty() { + cache_shard + } else { + cache_shard.shard(cache_key::cache_digest(self.build_configuration)) + }; + self.find(&cache_shard) } diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index 483e4619b587..cf908f2963ac 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -426,6 +426,14 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { // freshness, since entries have to be fresher than the revision itself. let cache_shard = cache_shard.shard(revision.id()); + // If there are build settings, we need to scope to a cache shard. + let config_settings = self.build_context.config_settings(); + let cache_shard = if config_settings.is_empty() { + cache_shard + } else { + cache_shard.shard(cache_key::cache_digest(config_settings)) + }; + // If the cache contains a compatible wheel, return it. if let Some(built_wheel) = BuiltWheelMetadata::find_in_cache(tags, &cache_shard) { return Ok(built_wheel.with_hashes(revision.into_hashes())); @@ -519,6 +527,14 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { }); } + // If there are build settings, we need to scope to a cache shard. + let config_settings = self.build_context.config_settings(); + let cache_shard = if config_settings.is_empty() { + cache_shard + } else { + cache_shard.shard(cache_key::cache_digest(config_settings)) + }; + // Otherwise, we either need to build the metadata. // If the backend supports `prepare_metadata_for_build_wheel`, use it. if let Some(metadata) = self @@ -671,6 +687,14 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { // freshness, since entries have to be fresher than the revision itself. let cache_shard = cache_shard.shard(revision.id()); + // If there are build settings, we need to scope to a cache shard. + let config_settings = self.build_context.config_settings(); + let cache_shard = if config_settings.is_empty() { + cache_shard + } else { + cache_shard.shard(cache_key::cache_digest(config_settings)) + }; + // If the cache contains a compatible wheel, return it. if let Some(built_wheel) = BuiltWheelMetadata::find_in_cache(tags, &cache_shard) { return Ok(built_wheel); @@ -781,6 +805,14 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { }); } + // If there are build settings, we need to scope to a cache shard. + let config_settings = self.build_context.config_settings(); + let cache_shard = if config_settings.is_empty() { + cache_shard + } else { + cache_shard.shard(cache_key::cache_digest(config_settings)) + }; + // Otherwise, we need to build a wheel. let task = self .reporter @@ -897,6 +929,14 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { // freshness, since entries have to be fresher than the revision itself. let cache_shard = cache_shard.shard(revision.id()); + // If there are build settings, we need to scope to a cache shard. + let config_settings = self.build_context.config_settings(); + let cache_shard = if config_settings.is_empty() { + cache_shard + } else { + cache_shard.shard(cache_key::cache_digest(config_settings)) + }; + // If the cache contains a compatible wheel, return it. if let Some(built_wheel) = BuiltWheelMetadata::find_in_cache(tags, &cache_shard) { return Ok(built_wheel); @@ -1020,6 +1060,14 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { )); } + // If there are build settings, we need to scope to a cache shard. + let config_settings = self.build_context.config_settings(); + let cache_shard = if config_settings.is_empty() { + cache_shard + } else { + cache_shard.shard(cache_key::cache_digest(config_settings)) + }; + // Otherwise, we need to build a wheel. let task = self .reporter @@ -1131,6 +1179,14 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { let _lock = lock_shard(&cache_shard).await?; + // If there are build settings, we need to scope to a cache shard. + let config_settings = self.build_context.config_settings(); + let cache_shard = if config_settings.is_empty() { + cache_shard + } else { + cache_shard.shard(cache_key::cache_digest(config_settings)) + }; + // If the cache contains a compatible wheel, return it. if let Some(built_wheel) = BuiltWheelMetadata::find_in_cache(tags, &cache_shard) { return Ok(built_wheel); @@ -1257,6 +1313,14 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { )); } + // If there are build settings, we need to scope to a cache shard. + let config_settings = self.build_context.config_settings(); + let cache_shard = if config_settings.is_empty() { + cache_shard + } else { + cache_shard.shard(cache_key::cache_digest(config_settings)) + }; + // Otherwise, we need to build a wheel. let task = self .reporter diff --git a/crates/uv-installer/src/plan.rs b/crates/uv-installer/src/plan.rs index 2c5c73d38cd4..26bcb051d4c6 100644 --- a/crates/uv-installer/src/plan.rs +++ b/crates/uv-installer/src/plan.rs @@ -15,7 +15,7 @@ use platform_tags::Tags; use pypi_types::{Requirement, RequirementSource, ResolverMarkerEnvironment}; use uv_cache::{Cache, CacheBucket, WheelCache}; use uv_cache_info::{CacheInfo, Timestamp}; -use uv_configuration::{BuildOptions, Reinstall}; +use uv_configuration::{BuildOptions, ConfigSettings, Reinstall}; use uv_distribution::{ BuiltWheelIndex, HttpArchivePointer, LocalArchivePointer, RegistryWheelIndex, }; @@ -57,6 +57,7 @@ impl<'a> Planner<'a> { build_options: &BuildOptions, hasher: &HashStrategy, index_locations: &IndexLocations, + config_settings: &ConfigSettings, cache: &Cache, venv: &PythonEnvironment, markers: &ResolverMarkerEnvironment, @@ -64,7 +65,7 @@ impl<'a> Planner<'a> { ) -> Result { // Index all the already-downloaded wheels in the cache. let mut registry_index = RegistryWheelIndex::new(cache, tags, index_locations, hasher); - let built_index = BuiltWheelIndex::new(cache, tags, hasher); + let built_index = BuiltWheelIndex::new(cache, tags, hasher, config_settings); let mut cached = vec![]; let mut remote = vec![]; diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index 9c423467fc08..760b48b01835 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -408,6 +408,7 @@ pub(crate) async fn pip_install( link_mode, compile, &index_locations, + config_settings, &hasher, &markers, &tags, diff --git a/crates/uv/src/commands/pip/operations.rs b/crates/uv/src/commands/pip/operations.rs index 9801290c4215..3663a8fe57a8 100644 --- a/crates/uv/src/commands/pip/operations.rs +++ b/crates/uv/src/commands/pip/operations.rs @@ -21,7 +21,8 @@ use pypi_types::ResolverMarkerEnvironment; use uv_cache::Cache; use uv_client::{BaseClientBuilder, RegistryClient}; use uv_configuration::{ - BuildOptions, Concurrency, Constraints, ExtrasSpecification, Overrides, Reinstall, Upgrade, + BuildOptions, Concurrency, ConfigSettings, Constraints, ExtrasSpecification, Overrides, + Reinstall, Upgrade, }; use uv_dispatch::BuildDispatch; use uv_distribution::DistributionDatabase; @@ -349,6 +350,7 @@ pub(crate) async fn install( link_mode: LinkMode, compile: bool, index_urls: &IndexLocations, + config_settings: &ConfigSettings, hasher: &HashStrategy, markers: &ResolverMarkerEnvironment, tags: &Tags, @@ -376,6 +378,7 @@ pub(crate) async fn install( build_options, hasher, index_urls, + config_settings, cache, venv, markers, diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 313aec7a6cdf..aec9433e2652 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -360,6 +360,7 @@ pub(crate) async fn pip_sync( link_mode, compile, &index_locations, + config_settings, &hasher, &markers, &tags, diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 828928fecf67..21bf86acf4ac 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -892,6 +892,7 @@ pub(crate) async fn sync_environment( link_mode, compile_bytecode, index_locations, + config_setting, &hasher, &markers, tags, @@ -1125,6 +1126,7 @@ pub(crate) async fn update_environment( *link_mode, *compile_bytecode, index_locations, + config_setting, &hasher, &markers, tags, diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 90f11776dad1..a027cee44404 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -305,6 +305,7 @@ pub(super) async fn do_sync( link_mode, compile_bytecode, index_locations, + config_setting, &hasher, &markers, tags, diff --git a/crates/uv/tests/pip_install.rs b/crates/uv/tests/pip_install.rs index 6015bcd7331f..b00d4f58eb30 100644 --- a/crates/uv/tests/pip_install.rs +++ b/crates/uv/tests/pip_install.rs @@ -2764,9 +2764,37 @@ fn config_settings() { .join("__editable___setuptools_editable_0_1_0_finder.py"); assert!(finder.exists()); - // Install the editable package with `--editable_mode=compat`. - let context = TestContext::new("3.12"); + // Reinstalling with `--editable_mode=compat` should be a no-op; changes in build configuration + // don't invalidate the environment. + uv_snapshot!(context.filters(), context.pip_install() + .arg("-e") + .arg(context.workspace_root.join("scripts/packages/setuptools_editable")) + .arg("-C") + .arg("editable_mode=compat") + , @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Audited 1 package in [TIME] + "### + ); + + // Uninstall the package. + uv_snapshot!(context.filters(), context.pip_uninstall() + .arg("setuptools-editable"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Uninstalled 1 package in [TIME] + - setuptools-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/setuptools_editable) + "###); + // Install the editable package with `--editable_mode=compat`. We should ignore the cached + // build configuration and rebuild. uv_snapshot!(context.filters(), context.pip_install() .arg("-e") .arg(context.workspace_root.join("scripts/packages/setuptools_editable")) @@ -2779,9 +2807,8 @@ fn config_settings() { ----- stderr ----- Resolved 2 packages in [TIME] - Prepared 2 packages in [TIME] - Installed 2 packages in [TIME] - + iniconfig==2.0.0 + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + setuptools-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/setuptools_editable) "### );