Add support for tool.uv into distribution building (#3904)

With the change, we remove the special casing of workspace dependencies
and resolve `tool.uv` for all git and directory distributions. This
gives us support for non-editable workspace dependencies and path
dependencies in other workspaces. It removes a lot of special casing
around workspaces. These changes are the groundwork for supporting
`tool.uv` with dynamic metadata.

The basis for this change is moving `Requirement` from
`distribution-types` to `pypi-types` and the lowering logic from
`uv-requirements` to `uv-distribution`. This changes should be split out
in separate PRs.

I've included an example workspace `albatross-root-workspace2` where
`bird-feeder` depends on `a` from another workspace `ab`. There's a
bunch of failing tests and regressed error messages that still need
fixing. It does fix the audited package count for the workspace tests.
This commit is contained in:
konsti 2024-05-31 04:42:03 +02:00 committed by GitHub
parent 09f55482a0
commit 081f20c53e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
69 changed files with 1159 additions and 1680 deletions

View file

@ -7,17 +7,26 @@ use futures::stream::FuturesOrdered;
use futures::TryStreamExt;
use url::Url;
use distribution_types::{
BuildableSource, DirectorySourceUrl, HashPolicy, Requirement, SourceUrl, VersionId,
};
use distribution_types::{BuildableSource, DirectorySourceUrl, HashPolicy, SourceUrl, VersionId};
use pep508_rs::RequirementOrigin;
use pypi_types::VerbatimParsedUrl;
use pypi_types::Requirement;
use uv_configuration::ExtrasSpecification;
use uv_distribution::{DistributionDatabase, Reporter};
use uv_fs::Simplified;
use uv_normalize::{ExtraName, PackageName};
use uv_resolver::{InMemoryIndex, MetadataResponse};
use uv_types::{BuildContext, HashStrategy};
#[derive(Debug, Clone)]
pub struct SourceTreeResolution {
/// The requirements sourced from the source trees.
pub requirements: Vec<Requirement>,
/// The names of the projects that were resolved.
pub project: PackageName,
/// The extras used when resolving the requirements.
pub extras: Vec<ExtraName>,
}
/// A resolver for requirements specified via source trees.
///
/// Used, e.g., to determine the input requirements when a user specifies a `pyproject.toml`
@ -63,26 +72,19 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
}
/// Resolve the requirements from the provided source trees.
pub async fn resolve(self) -> Result<Vec<Requirement>> {
let requirements: Vec<_> = self
pub async fn resolve(self) -> Result<Vec<SourceTreeResolution>> {
let resolutions: Vec<_> = self
.source_trees
.iter()
.map(|source_tree| async { self.resolve_source_tree(source_tree).await })
.collect::<FuturesOrdered<_>>()
.try_collect()
.await?;
Ok(requirements
.into_iter()
.flatten()
.map(Requirement::from)
.collect())
Ok(resolutions)
}
/// Infer the package name for a given "unnamed" requirement.
async fn resolve_source_tree(
&self,
path: &Path,
) -> Result<Vec<pep508_rs::Requirement<VerbatimParsedUrl>>> {
/// Infer the dependencies for a directory dependency.
async fn resolve_source_tree(&self, path: &Path) -> Result<SourceTreeResolution> {
// Convert to a buildable source.
let source_tree = fs_err::canonicalize(path).with_context(|| {
format!(
@ -151,40 +153,59 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
}
};
// Extract the origin.
let origin = RequirementOrigin::Project(path.to_path_buf(), metadata.name.clone());
// Determine the extras to include when resolving the requirements.
let extras = match self.extras {
ExtrasSpecification::All => metadata.provides_extras.as_slice(),
ExtrasSpecification::None => &[],
ExtrasSpecification::Some(extras) => extras,
};
// Determine the appropriate requirements to return based on the extras. This involves
// evaluating the `extras` expression in any markers, but preserving the remaining marker
// conditions.
match self.extras {
ExtrasSpecification::None => Ok(metadata
.requires_dist
.into_iter()
.map(|requirement| requirement.with_origin(origin.clone()))
.collect()),
ExtrasSpecification::All => Ok(metadata
.requires_dist
.into_iter()
.map(|requirement| pep508_rs::Requirement {
origin: Some(origin.clone()),
marker: requirement
.marker
.and_then(|marker| marker.simplify_extras(&metadata.provides_extras)),
..requirement
})
.collect()),
ExtrasSpecification::Some(extras) => Ok(metadata
.requires_dist
.into_iter()
.map(|requirement| pep508_rs::Requirement {
origin: Some(origin.clone()),
marker: requirement
.marker
.and_then(|marker| marker.simplify_extras(extras)),
..requirement
})
.collect()),
let mut requirements: Vec<Requirement> = metadata
.requires_dist
.into_iter()
.map(|requirement| Requirement {
origin: Some(origin.clone()),
marker: requirement
.marker
.and_then(|marker| marker.simplify_extras(extras)),
..requirement
})
.collect();
// Resolve any recursive extras.
loop {
// Find the first recursive requirement.
// TODO(charlie): Respect markers on recursive extras.
let Some(index) = requirements.iter().position(|requirement| {
requirement.name == metadata.name && requirement.marker.is_none()
}) else {
break;
};
// Remove the requirement that points to us.
let recursive = requirements.remove(index);
// Re-simplify the requirements.
for requirement in &mut requirements {
requirement.marker = requirement
.marker
.take()
.and_then(|marker| marker.simplify_extras(&recursive.extras));
}
}
let project = metadata.name;
let extras = metadata.provides_extras;
Ok(SourceTreeResolution {
requirements,
project,
extras,
})
}
}