Keep source tree locations as constraints

This commit is contained in:
konstin 2025-09-11 09:27:26 +02:00
parent 97516fa89b
commit 4044860fe3
2 changed files with 33 additions and 9 deletions

View File

@ -1,5 +1,5 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::path::Path; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
@ -26,6 +26,8 @@ pub struct SourceTreeResolution {
pub requirements: Box<[Requirement]>, pub requirements: Box<[Requirement]>,
/// The names of the projects that were resolved. /// The names of the projects that were resolved.
pub project: PackageName, pub project: PackageName,
/// The directory containing the package, usable as requirement source.
pub directory: Box<Path>,
/// The extras used when resolving the requirements. /// The extras used when resolving the requirements.
pub extras: Box<[ExtraName]>, pub extras: Box<[ExtraName]>,
} }
@ -85,7 +87,7 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
/// Infer the dependencies for a directory dependency. /// Infer the dependencies for a directory dependency.
async fn resolve_source_tree(&self, path: &Path) -> Result<SourceTreeResolution> { async fn resolve_source_tree(&self, path: &Path) -> Result<SourceTreeResolution> {
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()); let origin = RequirementOrigin::Project(path.to_path_buf(), metadata.name.clone());
// Determine the extras to include when resolving the requirements. // Determine the extras to include when resolving the requirements.
@ -115,6 +117,7 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
Ok(SourceTreeResolution { Ok(SourceTreeResolution {
requirements, requirements,
directory: directory.into_boxed_path(),
project, project,
extras, 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 /// 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 /// dynamic version since, critically, we don't need to install the package itself; only its
/// dependencies. /// dependencies.
async fn resolve_requires_dist(&self, path: &Path) -> Result<RequiresDist> { async fn resolve_requires_dist(&self, path: &Path) -> Result<(PathBuf, RequiresDist)> {
// Convert to a buildable source. // Convert to a buildable source.
let source_tree = fs_err::canonicalize(path).with_context(|| { let source_tree = fs_err::canonicalize(path).with_context(|| {
format!( 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 // _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 // still extract the requirements without performing a build, unlike in the database where
// we typically construct a "complete" metadata object. // we typically construct a "complete" metadata object.
if let Some(metadata) = self.database.requires_dist(source_tree).await? { if let Some(metadata) = self.database.requires_dist(&source_tree).await? {
return Ok(metadata); return Ok((source_tree.to_path_buf(), metadata));
} }
let Ok(url) = Url::from_directory_path(source_tree).map(DisplaySafeUrl::from) else { 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)))
} }
} }

View File

@ -20,14 +20,15 @@ use uv_dispatch::BuildDispatch;
use uv_distribution::{DistributionDatabase, SourcedDependencyGroups}; use uv_distribution::{DistributionDatabase, SourcedDependencyGroups};
use uv_distribution_types::{ use uv_distribution_types::{
CachedDist, Diagnostic, InstalledDist, LocalDist, NameRequirementSpecification, Requirement, CachedDist, Diagnostic, InstalledDist, LocalDist, NameRequirementSpecification, Requirement,
ResolutionDiagnostic, UnresolvedRequirement, UnresolvedRequirementSpecification, RequirementSource, ResolutionDiagnostic, UnresolvedRequirement,
UnresolvedRequirementSpecification,
}; };
use uv_distribution_types::{DistributionMetadata, InstalledMetadata, Name, Resolution}; use uv_distribution_types::{DistributionMetadata, InstalledMetadata, Name, Resolution};
use uv_fs::Simplified; use uv_fs::Simplified;
use uv_install_wheel::LinkMode; use uv_install_wheel::LinkMode;
use uv_installer::{Plan, Planner, Preparer, SitePackages}; use uv_installer::{Plan, Planner, Preparer, SitePackages};
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_pep508::{MarkerEnvironment, RequirementOrigin}; use uv_pep508::{MarkerEnvironment, MarkerTree, RequirementOrigin, VerbatimUrl};
use uv_platform_tags::Tags; use uv_platform_tags::Tags;
use uv_preview::Preview; use uv_preview::Preview;
use uv_pypi_types::{Conflicts, ResolverMarkerEnvironment}; use uv_pypi_types::{Conflicts, ResolverMarkerEnvironment};
@ -130,6 +131,7 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
let start = std::time::Instant::now(); let start = std::time::Instant::now();
// Resolve the requirements from the provided sources. // Resolve the requirements from the provided sources.
let mut source_tree_requirements = Vec::new();
let requirements = { let requirements = {
// Partition the requirements into named and unnamed requirements. // Partition the requirements into named and unnamed requirements.
let (mut requirements, unnamed): (Vec<_>, Vec<_>) = let (mut requirements, unnamed): (Vec<_>, Vec<_>) =
@ -170,6 +172,8 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
.resolve(source_trees.iter().map(PathBuf::as_path)) .resolve(source_trees.iter().map(PathBuf::as_path))
.await?; .await?;
source_tree_requirements.clone_from(&resolutions);
// If we resolved a single project, use it for the project name. // If we resolved a single project, use it for the project name.
project = project.or_else(|| { project = project.or_else(|| {
if let [resolution] = &resolutions[..] { if let [resolution] = &resolutions[..] {
@ -287,7 +291,24 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
constraints constraints
.into_iter() .into_iter()
.map(|constraint| constraint.requirement) .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 overrides = Overrides::from_requirements(overrides);
let preferences = Preferences::from_iter(preferences, &resolver_env); let preferences = Preferences::from_iter(preferences, &resolver_env);