diff --git a/crates/uv-requirements/src/unnamed.rs b/crates/uv-requirements/src/unnamed.rs index 436d30d56bdb..936f6442dffb 100644 --- a/crates/uv-requirements/src/unnamed.rs +++ b/crates/uv-requirements/src/unnamed.rs @@ -12,7 +12,7 @@ use url::Host; use distribution_filename::{DistExtension, SourceDistFilename, WheelFilename}; use distribution_types::{ BuildableSource, DirectSourceUrl, DirectorySourceUrl, GitSourceUrl, PathSourceUrl, - RemoteSource, SourceUrl, UnresolvedRequirement, UnresolvedRequirementSpecification, VersionId, + RemoteSource, SourceUrl, VersionId, }; use pep508_rs::{UnnamedRequirement, VersionOrUrl}; use pypi_types::Requirement; @@ -37,7 +37,7 @@ pub enum NamedRequirementsError { /// Like [`RequirementsSpecification`], but with concrete names for all requirements. pub struct NamedRequirementsResolver<'a, Context: BuildContext> { /// The requirements for the project. - requirements: Vec, + requirements: Vec>, /// Whether to check hashes for distributions. hasher: &'a HashStrategy, /// The in-memory index for resolving dependencies. @@ -49,7 +49,7 @@ pub struct NamedRequirementsResolver<'a, Context: BuildContext> { impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> { /// Instantiate a new [`NamedRequirementsResolver`] for a given set of requirements. pub fn new( - requirements: Vec, + requirements: Vec>, hasher: &'a HashStrategy, index: &'a InMemoryIndex, database: DistributionDatabase<'a, Context>, @@ -81,13 +81,10 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> { } = self; requirements .into_iter() - .map(|entry| async { - match entry.requirement { - UnresolvedRequirement::Named(requirement) => Ok(requirement), - UnresolvedRequirement::Unnamed(requirement) => Ok(Requirement::from( - Self::resolve_requirement(requirement, hasher, index, &database).await?, - )), - } + .map(|requirement| async { + Self::resolve_requirement(requirement, hasher, index, &database) + .await + .map(Requirement::from) }) .collect::>() .try_collect() diff --git a/crates/uv/src/commands/pip/operations.rs b/crates/uv/src/commands/pip/operations.rs index 3663a8fe57a8..839bc941f646 100644 --- a/crates/uv/src/commands/pip/operations.rs +++ b/crates/uv/src/commands/pip/operations.rs @@ -10,7 +10,7 @@ use tracing::debug; use distribution_types::{ CachedDist, Diagnostic, InstalledDist, LocalDist, NameRequirementSpecification, - ResolutionDiagnostic, UnresolvedRequirementSpecification, + ResolutionDiagnostic, UnresolvedRequirement, UnresolvedRequirementSpecification, }; use distribution_types::{ DistributionMetadata, IndexLocations, InstalledMetadata, Name, Resolution, @@ -116,16 +116,33 @@ pub(crate) async fn resolve( // Resolve the requirements from the provided sources. let requirements = { - // Convert from unnamed to named requirements. - let mut requirements = NamedRequirementsResolver::new( - requirements, - hasher, - index, - DistributionDatabase::new(client, build_dispatch, concurrency.downloads), - ) - .with_reporter(ResolverReporter::from(printer)) - .resolve() - .await?; + // Partition the requirements into named and unnamed requirements. + let (mut requirements, unnamed): (Vec<_>, Vec<_>) = + requirements + .into_iter() + .partition_map(|spec| match spec.requirement { + UnresolvedRequirement::Named(requirement) => { + itertools::Either::Left(requirement) + } + UnresolvedRequirement::Unnamed(requirement) => { + itertools::Either::Right(requirement) + } + }); + + // Resolve any unnamed requirements. + if !unnamed.is_empty() { + requirements.extend( + NamedRequirementsResolver::new( + unnamed, + hasher, + index, + DistributionDatabase::new(client, build_dispatch, concurrency.downloads), + ) + .with_reporter(ResolverReporter::from(printer)) + .resolve() + .await?, + ); + } // Resolve any source trees into requirements. if !source_trees.is_empty() { @@ -183,15 +200,37 @@ pub(crate) async fn resolve( }; // Resolve the overrides from the provided sources. - let overrides = NamedRequirementsResolver::new( - overrides, - hasher, - index, - DistributionDatabase::new(client, build_dispatch, concurrency.downloads), - ) - .with_reporter(ResolverReporter::from(printer)) - .resolve() - .await?; + let overrides = { + // Partition the overrides into named and unnamed requirements. + let (mut overrides, unnamed): (Vec<_>, Vec<_>) = + overrides + .into_iter() + .partition_map(|spec| match spec.requirement { + UnresolvedRequirement::Named(requirement) => { + itertools::Either::Left(requirement) + } + UnresolvedRequirement::Unnamed(requirement) => { + itertools::Either::Right(requirement) + } + }); + + // Resolve any unnamed overrides. + if !unnamed.is_empty() { + overrides.extend( + NamedRequirementsResolver::new( + unnamed, + hasher, + index, + DistributionDatabase::new(client, build_dispatch, concurrency.downloads), + ) + .with_reporter(ResolverReporter::from(printer)) + .resolve() + .await?, + ); + } + + overrides + }; // Collect constraints and overrides. let constraints = Constraints::from_requirements( diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index fc266178695a..57f54c5f5e24 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -3,11 +3,13 @@ use std::fmt::Write; use std::path::{Path, PathBuf}; use anyhow::{bail, Context, Result}; +use itertools::Itertools; use owo_colors::OwoColorize; use rustc_hash::{FxBuildHasher, FxHashMap}; use tracing::debug; use cache_key::RepositoryUrl; +use distribution_types::UnresolvedRequirement; use pep508_rs::{ExtraName, Requirement, VersionOrUrl}; use pypi_types::redact_git_credentials; use uv_auth::{store_credentials_from_url, Credentials}; @@ -313,15 +315,37 @@ pub(crate) async fn add( ); // Resolve any unnamed requirements. - let requirements = NamedRequirementsResolver::new( - requirements, - &hasher, - &state.index, - DistributionDatabase::new(&client, &build_dispatch, concurrency.downloads), - ) - .with_reporter(ResolverReporter::from(printer)) - .resolve() - .await?; + let requirements = { + // Partition the requirements into named and unnamed requirements. + let (mut requirements, unnamed): (Vec<_>, Vec<_>) = + requirements + .into_iter() + .partition_map(|spec| match spec.requirement { + UnresolvedRequirement::Named(requirement) => { + itertools::Either::Left(requirement) + } + UnresolvedRequirement::Unnamed(requirement) => { + itertools::Either::Right(requirement) + } + }); + + // Resolve any unnamed requirements. + if !unnamed.is_empty() { + requirements.extend( + NamedRequirementsResolver::new( + unnamed, + &hasher, + &state.index, + DistributionDatabase::new(&client, &build_dispatch, concurrency.downloads), + ) + .with_reporter(ResolverReporter::from(printer)) + .resolve() + .await?, + ); + } + + requirements + }; // Add the requirements to the `pyproject.toml` or script. let mut toml = match &target { diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index ca8d800c38da..81648b744cb7 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -5,7 +5,7 @@ use itertools::Itertools; use owo_colors::OwoColorize; use tracing::debug; -use distribution_types::{Resolution, UnresolvedRequirementSpecification}; +use distribution_types::{Resolution, UnresolvedRequirement, UnresolvedRequirementSpecification}; use pep440_rs::{Version, VersionSpecifiers}; use pep508_rs::MarkerTreeContents; use pypi_types::Requirement; @@ -548,6 +548,22 @@ pub(crate) async fn resolve_names( cache: &Cache, printer: Printer, ) -> anyhow::Result> { + // Partition the requirements into named and unnamed requirements. + let (mut requirements, unnamed): (Vec<_>, Vec<_>) = + requirements + .into_iter() + .partition_map(|spec| match spec.requirement { + UnresolvedRequirement::Named(requirement) => itertools::Either::Left(requirement), + UnresolvedRequirement::Unnamed(requirement) => { + itertools::Either::Right(requirement) + } + }); + + // Short-circuit if there are no unnamed requirements. + if unnamed.is_empty() { + return Ok(requirements); + } + // Extract the project settings. let ResolverInstallerSettings { index_locations, @@ -627,16 +643,20 @@ pub(crate) async fn resolve_names( concurrency, ); - // Initialize the resolver. - let resolver = NamedRequirementsResolver::new( - requirements, - &hasher, - &state.index, - DistributionDatabase::new(&client, &build_dispatch, concurrency.downloads), - ) - .with_reporter(ResolverReporter::from(printer)); + // Resolve the unnamed requirements. + requirements.extend( + NamedRequirementsResolver::new( + unnamed, + &hasher, + &state.index, + DistributionDatabase::new(&client, &build_dispatch, concurrency.downloads), + ) + .with_reporter(ResolverReporter::from(printer)) + .resolve() + .await?, + ); - Ok(resolver.resolve().await?) + Ok(requirements) } /// Run dependency resolution for an interpreter, returning the [`ResolutionGraph`].