mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
Support recursive extras with marker in pip compile -r pyproject.toml
(#9535)
## Summary Closes https://github.com/astral-sh/uv/issues/9530.
This commit is contained in:
parent
891e02d586
commit
69811837e5
3 changed files with 209 additions and 54 deletions
|
@ -1,10 +1,13 @@
|
|||
use std::borrow::Cow;
|
||||
use std::collections::VecDeque;
|
||||
use std::path::Path;
|
||||
use std::slice;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use futures::stream::FuturesOrdered;
|
||||
use futures::TryStreamExt;
|
||||
use rustc_hash::FxHashSet;
|
||||
use url::Url;
|
||||
|
||||
use uv_configuration::ExtrasSpecification;
|
||||
|
@ -14,7 +17,7 @@ use uv_distribution_types::{
|
|||
};
|
||||
use uv_fs::Simplified;
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
use uv_pep508::RequirementOrigin;
|
||||
use uv_pep508::{MarkerTree, RequirementOrigin};
|
||||
use uv_pypi_types::Requirement;
|
||||
use uv_resolver::{InMemoryIndex, MetadataResponse};
|
||||
use uv_types::{BuildContext, HashStrategy};
|
||||
|
@ -89,16 +92,13 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
|
|||
let origin = RequirementOrigin::Project(path.to_path_buf(), metadata.name.clone());
|
||||
|
||||
// Determine the extras to include when resolving the requirements.
|
||||
let extras: Vec<_> = self
|
||||
let extras = self
|
||||
.extras
|
||||
.extra_names(metadata.provides_extras.iter())
|
||||
.cloned()
|
||||
.collect();
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// 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.
|
||||
let mut requirements: Vec<Requirement> = metadata
|
||||
let dependencies = metadata
|
||||
.requires_dist
|
||||
.into_iter()
|
||||
.map(|requirement| Requirement {
|
||||
|
@ -106,30 +106,61 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
|
|||
marker: requirement.marker.simplify_extras(&extras),
|
||||
..requirement
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Transitively process all extras that are recursively included, starting with the current
|
||||
// extra.
|
||||
let mut requirements = dependencies.clone();
|
||||
let mut seen = FxHashSet::<(ExtraName, MarkerTree)>::default();
|
||||
let mut queue: VecDeque<_> = requirements
|
||||
.iter()
|
||||
.filter(|req| req.name == metadata.name)
|
||||
.flat_map(|req| {
|
||||
req.extras
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|extra| (extra, req.marker.clone().simplify_extras(&extras)))
|
||||
})
|
||||
.collect();
|
||||
while let Some((extra, marker)) = queue.pop_front() {
|
||||
if !seen.insert((extra.clone(), marker.clone())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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_true()
|
||||
}) 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
|
||||
.clone()
|
||||
.simplify_extras(&recursive.extras);
|
||||
// Find the requirements for the extra.
|
||||
for requirement in &dependencies {
|
||||
if requirement.marker.top_level_extra_name().as_ref() == Some(&extra) {
|
||||
let requirement = {
|
||||
let mut marker = marker.clone();
|
||||
marker.and(requirement.marker.clone());
|
||||
Requirement {
|
||||
name: requirement.name.clone(),
|
||||
extras: requirement.extras.clone(),
|
||||
source: requirement.source.clone(),
|
||||
origin: requirement.origin.clone(),
|
||||
marker: marker.simplify_extras(slice::from_ref(&extra)),
|
||||
}
|
||||
};
|
||||
if requirement.name == metadata.name {
|
||||
// Add each transitively included extra.
|
||||
queue.extend(
|
||||
requirement
|
||||
.extras
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|extra| (extra, requirement.marker.clone())),
|
||||
);
|
||||
} else {
|
||||
// Add the requirements for that extra.
|
||||
requirements.push(requirement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Drop all the self-requirements now that we flattened them out.
|
||||
requirements.retain(|req| req.name != metadata.name);
|
||||
|
||||
let project = metadata.name;
|
||||
let extras = metadata.provides_extras;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue