diff --git a/crates/uv-requirements/src/source_tree.rs b/crates/uv-requirements/src/source_tree.rs index a7a99c5a2..043ee51cb 100644 --- a/crates/uv-requirements/src/source_tree.rs +++ b/crates/uv-requirements/src/source_tree.rs @@ -1,5 +1,5 @@ use std::borrow::Cow; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::{Context, Result}; @@ -26,6 +26,8 @@ pub struct SourceTreeResolution { pub requirements: Box<[Requirement]>, /// The names of the projects that were resolved. pub project: PackageName, + /// The directory containing the package, usable as requirement source. + pub directory: Box, /// The extras used when resolving the requirements. pub extras: Box<[ExtraName]>, } @@ -85,7 +87,7 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> { /// Infer the dependencies for a directory dependency. async fn resolve_source_tree(&self, path: &Path) -> Result { - let metadata = self.resolve_requires_dist(path).await?; + let (directory, metadata) = self.resolve_requires_dist(path).await?; let origin = RequirementOrigin::Project(path.to_path_buf(), metadata.name.clone()); // Determine the extras to include when resolving the requirements. @@ -115,6 +117,7 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> { Ok(SourceTreeResolution { requirements, + directory: directory.into_boxed_path(), project, extras, }) @@ -124,7 +127,7 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> { /// requirements without building the distribution, even if the project contains (e.g.) a /// dynamic version since, critically, we don't need to install the package itself; only its /// dependencies. - async fn resolve_requires_dist(&self, path: &Path) -> Result { + async fn resolve_requires_dist(&self, path: &Path) -> Result<(PathBuf, RequiresDist)> { // Convert to a buildable source. let source_tree = fs_err::canonicalize(path).with_context(|| { format!( @@ -144,8 +147,8 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> { // _only_ need the requirements. So, for example, even if the version is dynamic, we can // still extract the requirements without performing a build, unlike in the database where // we typically construct a "complete" metadata object. - if let Some(metadata) = self.database.requires_dist(source_tree).await? { - return Ok(metadata); + if let Some(metadata) = self.database.requires_dist(&source_tree).await? { + return Ok((source_tree.to_path_buf(), metadata)); } let Ok(url) = Url::from_directory_path(source_tree).map(DisplaySafeUrl::from) else { @@ -201,6 +204,6 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> { } }; - Ok(RequiresDist::from(metadata)) + Ok((source_tree.to_path_buf(), RequiresDist::from(metadata))) } } diff --git a/crates/uv/src/commands/pip/operations.rs b/crates/uv/src/commands/pip/operations.rs index de079d1ae..ab97a8289 100644 --- a/crates/uv/src/commands/pip/operations.rs +++ b/crates/uv/src/commands/pip/operations.rs @@ -20,14 +20,15 @@ use uv_dispatch::BuildDispatch; use uv_distribution::{DistributionDatabase, SourcedDependencyGroups}; use uv_distribution_types::{ CachedDist, Diagnostic, InstalledDist, LocalDist, NameRequirementSpecification, Requirement, - ResolutionDiagnostic, UnresolvedRequirement, UnresolvedRequirementSpecification, + RequirementSource, ResolutionDiagnostic, UnresolvedRequirement, + UnresolvedRequirementSpecification, }; use uv_distribution_types::{DistributionMetadata, InstalledMetadata, Name, Resolution}; use uv_fs::Simplified; use uv_install_wheel::LinkMode; use uv_installer::{Plan, Planner, Preparer, SitePackages}; use uv_normalize::PackageName; -use uv_pep508::{MarkerEnvironment, RequirementOrigin}; +use uv_pep508::{MarkerEnvironment, MarkerTree, RequirementOrigin, VerbatimUrl}; use uv_platform_tags::Tags; use uv_preview::Preview; use uv_pypi_types::{Conflicts, ResolverMarkerEnvironment}; @@ -130,6 +131,7 @@ pub(crate) async fn resolve( let start = std::time::Instant::now(); // Resolve the requirements from the provided sources. + let mut source_tree_requirements = Vec::new(); let requirements = { // Partition the requirements into named and unnamed requirements. let (mut requirements, unnamed): (Vec<_>, Vec<_>) = @@ -170,6 +172,8 @@ pub(crate) async fn resolve( .resolve(source_trees.iter().map(PathBuf::as_path)) .await?; + source_tree_requirements.clone_from(&resolutions); + // If we resolved a single project, use it for the project name. project = project.or_else(|| { if let [resolution] = &resolutions[..] { @@ -287,7 +291,24 @@ pub(crate) async fn resolve( constraints .into_iter() .map(|constraint| constraint.requirement) - .chain(upgrade.constraints().cloned()), + .chain(upgrade.constraints().cloned()) + .chain(source_tree_requirements.into_iter().map(|source_tree| { + let url = VerbatimUrl::from_normalized_path(&source_tree.directory) + .expect("Invalid source tree resolution"); + Requirement { + name: source_tree.project, + extras: Box::new([]), + groups: Box::new([]), + marker: MarkerTree::default(), + source: RequirementSource::Directory { + install_path: source_tree.directory, + editable: None, + r#virtual: None, + url, + }, + origin: None, + } + })), ); let overrides = Overrides::from_requirements(overrides); let preferences = Preferences::from_iter(preferences, &resolver_env);