From 8da9df3654f255a2f2e8e83c51257c0699ece9f7 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 2 Oct 2025 09:32:14 -0400 Subject: [PATCH] Avoid rejecting already-installed URL distributions with `--no-sources` (#16094) ## Summary This PR removes a guard that was accidentally included in https://github.com/astral-sh/uv/pull/15234/files#diff-6be6d80fe4821c47b70a372260f55e73b8da8182b8dcad7525d5cd3eb584532b. I meant to remove that logic before merging. Closes https://github.com/astral-sh/uv/issues/16068. --- crates/uv-resolver/src/candidate_selector.rs | 28 +------ crates/uv-resolver/src/resolver/mod.rs | 6 +- crates/uv/tests/it/pip_install.rs | 81 ++++++++++++++++++++ 3 files changed, 86 insertions(+), 29 deletions(-) diff --git a/crates/uv-resolver/src/candidate_selector.rs b/crates/uv-resolver/src/candidate_selector.rs index 01f89cd7c..6f92a3f6b 100644 --- a/crates/uv-resolver/src/candidate_selector.rs +++ b/crates/uv-resolver/src/candidate_selector.rs @@ -6,10 +6,8 @@ use pubgrub::Range; use smallvec::SmallVec; use tracing::{debug, trace}; -use uv_configuration::{IndexStrategy, SourceStrategy}; -use uv_distribution_types::{ - CompatibleDist, IncompatibleDist, IncompatibleSource, IndexUrl, InstalledDistKind, -}; +use uv_configuration::IndexStrategy; +use uv_distribution_types::{CompatibleDist, IncompatibleDist, IncompatibleSource, IndexUrl}; use uv_distribution_types::{DistributionMetadata, IncompatibleWheel, Name, PrioritizedDist}; use uv_normalize::PackageName; use uv_pep440::Version; @@ -28,7 +26,6 @@ pub(crate) struct CandidateSelector { resolution_strategy: ResolutionStrategy, prerelease_strategy: PrereleaseStrategy, index_strategy: IndexStrategy, - source_strategy: SourceStrategy, } impl CandidateSelector { @@ -37,7 +34,6 @@ impl CandidateSelector { options: &Options, manifest: &Manifest, env: &ResolverEnvironment, - source_strategy: SourceStrategy, ) -> Self { Self { resolution_strategy: ResolutionStrategy::from_mode( @@ -53,7 +49,6 @@ impl CandidateSelector { options.dependency_mode, ), index_strategy: options.index_strategy, - source_strategy, } } @@ -124,13 +119,7 @@ impl CandidateSelector { let installed = if reinstall { None } else { - Self::get_installed( - package_name, - range, - installed_packages, - tags, - self.source_strategy, - ) + Self::get_installed(package_name, range, installed_packages, tags) }; // If we're not upgrading, we should prefer the already-installed distribution. @@ -380,7 +369,6 @@ impl CandidateSelector { range: &Range, installed_packages: &'a InstalledPackages, tags: Option<&'a Tags>, - source_strategy: SourceStrategy, ) -> Option> { let installed_dists = installed_packages.get_packages(package_name); match installed_dists.as_slice() { @@ -393,16 +381,6 @@ impl CandidateSelector { return None; } - // When sources are disabled, only allow registry installations to be reused - if matches!(source_strategy, SourceStrategy::Disabled) { - if !matches!(dist.kind, InstalledDistKind::Registry(_)) { - debug!( - "Source strategy is disabled, rejecting non-registry installed distribution: {dist}" - ); - return None; - } - } - // Verify that the installed distribution is compatible with the environment. if tags.is_some_and(|tags| { let Ok(Some(wheel_tags)) = dist.read_tags() else { diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index e8efb1438..28be0a3fe 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -20,7 +20,7 @@ use tokio::sync::oneshot; use tokio_stream::wrappers::ReceiverStream; use tracing::{Level, debug, info, instrument, trace, warn}; -use uv_configuration::{Constraints, Overrides, SourceStrategy}; +use uv_configuration::{Constraints, Overrides}; use uv_distribution::{ArchiveMetadata, DistributionDatabase}; use uv_distribution_types::{ BuiltDist, CompatibleDist, DerivationChain, Dist, DistErrorKind, DistributionMetadata, @@ -201,7 +201,6 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider> build_context.git(), build_context.capabilities(), build_context.locations(), - build_context.sources(), provider, installed_packages, ) @@ -225,7 +224,6 @@ impl git: &GitResolver, capabilities: &IndexCapabilities, locations: &IndexLocations, - source_strategy: SourceStrategy, provider: Provider, installed_packages: InstalledPackages, ) -> Result { @@ -233,7 +231,7 @@ impl index: index.clone(), git: git.clone(), capabilities: capabilities.clone(), - selector: CandidateSelector::for_resolution(&options, &manifest, &env, source_strategy), + selector: CandidateSelector::for_resolution(&options, &manifest, &env), dependency_mode: options.dependency_mode, urls: Urls::from_manifest(&manifest, &env, git, options.dependency_mode), indexes: Indexes::from_manifest(&manifest, &env, options.dependency_mode), diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index d2c7b32dc..4d3312d68 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -12358,6 +12358,87 @@ fn reject_invalid_short_usize_zip64() { ); } +/// Regression test for: +#[test] +fn already_installed_url_dependency_no_sources() -> Result<()> { + let context = TestContext::new("3.12").with_filtered_counts(); + + context + .temp_dir + .child("foo") + .child("pyproject.toml") + .write_str(indoc! {r#" + [project] + name = "foo" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig"] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "#})?; + context + .temp_dir + .child("foo") + .child("src") + .child("foo") + .child("__init__.py") + .touch()?; + + context + .temp_dir + .child("bar") + .child("pyproject.toml") + .write_str(indoc! {r#" + [project] + name = "bar" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["foo"] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "#})?; + context + .temp_dir + .child("bar") + .child("src") + .child("bar") + .child("__init__.py") + .touch()?; + + // Install `foo`. + uv_snapshot!(context.filters(), context.pip_install().arg("./foo"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved [N] packages in [TIME] + Prepared [N] packages in [TIME] + Installed [N] packages in [TIME] + + foo==0.1.0 (from file://[TEMP_DIR]/foo) + + iniconfig==2.0.0 + "); + + // Install `bar` with `--no-sources`. + uv_snapshot!(context.filters(), context.pip_install().arg("./bar").arg("--no-sources"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved [N] packages in [TIME] + Prepared [N] packages in [TIME] + Installed [N] packages in [TIME] + + bar==0.1.0 (from file://[TEMP_DIR]/bar) + "); + + Ok(()) +} + /// Test that build dependencies respect locked versions from the resolution. #[test] fn pip_install_build_dependencies_respect_locked_versions() -> Result<()> {