Skip to content

Commit 7169b2c

Browse files
Respect sources in overrides and constraints (#9455)
## Summary We still only respect overrides and constraints in the workspace root -- which we may want to change -- but overrides and constraints are now correctly lowered. Closes #8148.
1 parent 8c8a1f0 commit 7169b2c

File tree

11 files changed

+413
-117
lines changed

11 files changed

+413
-117
lines changed

crates/uv-build-frontend/src/lib.rs

+15-21
Original file line numberDiff line numberDiff line change
@@ -465,13 +465,12 @@ impl SourceBuild {
465465
.or(package_name)
466466
{
467467
let build_requires = uv_pypi_types::BuildRequires {
468-
name: name.clone(),
468+
name: Some(name.clone()),
469469
requires_dist: build_system.requires,
470470
};
471471
let build_requires = BuildRequires::from_project_maybe_workspace(
472472
build_requires,
473473
install_path,
474-
None,
475474
locations,
476475
source_strategy,
477476
LowerBound::Allow,
@@ -905,25 +904,20 @@ async fn create_pep517_build_environment(
905904
// If necessary, lower the requirements.
906905
let extra_requires = match source_strategy {
907906
SourceStrategy::Enabled => {
908-
if let Some(package_name) = package_name {
909-
let build_requires = uv_pypi_types::BuildRequires {
910-
name: package_name.clone(),
911-
requires_dist: extra_requires,
912-
};
913-
let build_requires = BuildRequires::from_project_maybe_workspace(
914-
build_requires,
915-
install_path,
916-
None,
917-
locations,
918-
source_strategy,
919-
LowerBound::Allow,
920-
)
921-
.await
922-
.map_err(Error::Lowering)?;
923-
build_requires.requires_dist
924-
} else {
925-
extra_requires.into_iter().map(Requirement::from).collect()
926-
}
907+
let build_requires = uv_pypi_types::BuildRequires {
908+
name: package_name.cloned(),
909+
requires_dist: extra_requires,
910+
};
911+
let build_requires = BuildRequires::from_project_maybe_workspace(
912+
build_requires,
913+
install_path,
914+
locations,
915+
source_strategy,
916+
LowerBound::Allow,
917+
)
918+
.await
919+
.map_err(Error::Lowering)?;
920+
build_requires.requires_dist
927921
}
928922
SourceStrategy::Disabled => extra_requires.into_iter().map(Requirement::from).collect(),
929923
};

crates/uv-distribution/src/metadata/build_requires.rs

+85-23
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ use uv_configuration::{LowerBound, SourceStrategy};
55
use uv_distribution_types::IndexLocations;
66
use uv_normalize::PackageName;
77
use uv_workspace::pyproject::ToolUvSources;
8-
use uv_workspace::{DiscoveryOptions, ProjectWorkspace};
8+
use uv_workspace::{DiscoveryOptions, ProjectWorkspace, Workspace};
99

10-
use crate::metadata::{GitWorkspaceMember, LoweredRequirement, MetadataError};
10+
use crate::metadata::{LoweredRequirement, MetadataError};
1111

1212
/// Lowered requirements from a `[build-system.requires]` field in a `pyproject.toml` file.
1313
#[derive(Debug, Clone)]
1414
pub struct BuildRequires {
15-
pub name: PackageName,
15+
pub name: Option<PackageName>,
1616
pub requires_dist: Vec<uv_pypi_types::Requirement>,
1717
}
1818

@@ -35,46 +35,31 @@ impl BuildRequires {
3535
pub async fn from_project_maybe_workspace(
3636
metadata: uv_pypi_types::BuildRequires,
3737
install_path: &Path,
38-
git_member: Option<&GitWorkspaceMember<'_>>,
3938
locations: &IndexLocations,
4039
sources: SourceStrategy,
4140
lower_bound: LowerBound,
4241
) -> Result<Self, MetadataError> {
4342
// TODO(konsti): Cache workspace discovery.
44-
let discovery_options = if let Some(git_member) = &git_member {
45-
DiscoveryOptions {
46-
stop_discovery_at: Some(
47-
git_member
48-
.fetch_root
49-
.parent()
50-
.expect("git checkout has a parent"),
51-
),
52-
..Default::default()
53-
}
54-
} else {
55-
DiscoveryOptions::default()
56-
};
5743
let Some(project_workspace) =
58-
ProjectWorkspace::from_maybe_project_root(install_path, &discovery_options).await?
44+
ProjectWorkspace::from_maybe_project_root(install_path, &DiscoveryOptions::default())
45+
.await?
5946
else {
6047
return Ok(Self::from_metadata23(metadata));
6148
};
6249

6350
Self::from_project_workspace(
6451
metadata,
6552
&project_workspace,
66-
git_member,
6753
locations,
6854
sources,
6955
lower_bound,
7056
)
7157
}
7258

7359
/// Lower the `build-system.requires` field from a `pyproject.toml` file.
74-
fn from_project_workspace(
60+
pub fn from_project_workspace(
7561
metadata: uv_pypi_types::BuildRequires,
7662
project_workspace: &ProjectWorkspace,
77-
git_member: Option<&GitWorkspaceMember<'_>>,
7863
locations: &IndexLocations,
7964
source_strategy: SourceStrategy,
8065
lower_bound: LowerBound,
@@ -118,7 +103,7 @@ impl BuildRequires {
118103
let group = None;
119104
LoweredRequirement::from_requirement(
120105
requirement,
121-
&metadata.name,
106+
metadata.name.as_ref(),
122107
project_workspace.project_root(),
123108
project_sources,
124109
project_indexes,
@@ -127,7 +112,84 @@ impl BuildRequires {
127112
locations,
128113
project_workspace.workspace(),
129114
lower_bound,
130-
git_member,
115+
None,
116+
)
117+
.map(move |requirement| match requirement {
118+
Ok(requirement) => Ok(requirement.into_inner()),
119+
Err(err) => Err(MetadataError::LoweringError(
120+
requirement_name.clone(),
121+
Box::new(err),
122+
)),
123+
})
124+
})
125+
.collect::<Result<Vec<_>, _>>()?,
126+
SourceStrategy::Disabled => requires_dist
127+
.into_iter()
128+
.map(uv_pypi_types::Requirement::from)
129+
.collect(),
130+
};
131+
132+
Ok(Self {
133+
name: metadata.name,
134+
requires_dist,
135+
})
136+
}
137+
138+
/// Lower the `build-system.requires` field from a `pyproject.toml` file.
139+
pub fn from_workspace(
140+
metadata: uv_pypi_types::BuildRequires,
141+
workspace: &Workspace,
142+
locations: &IndexLocations,
143+
source_strategy: SourceStrategy,
144+
lower_bound: LowerBound,
145+
) -> Result<Self, MetadataError> {
146+
// Collect any `tool.uv.index` entries.
147+
let empty = vec![];
148+
let project_indexes = match source_strategy {
149+
SourceStrategy::Enabled => workspace
150+
.pyproject_toml()
151+
.tool
152+
.as_ref()
153+
.and_then(|tool| tool.uv.as_ref())
154+
.and_then(|uv| uv.index.as_deref())
155+
.unwrap_or(&empty),
156+
SourceStrategy::Disabled => &empty,
157+
};
158+
159+
// Collect any `tool.uv.sources` and `tool.uv.dev_dependencies` from `pyproject.toml`.
160+
let empty = BTreeMap::default();
161+
let project_sources = match source_strategy {
162+
SourceStrategy::Enabled => workspace
163+
.pyproject_toml()
164+
.tool
165+
.as_ref()
166+
.and_then(|tool| tool.uv.as_ref())
167+
.and_then(|uv| uv.sources.as_ref())
168+
.map(ToolUvSources::inner)
169+
.unwrap_or(&empty),
170+
SourceStrategy::Disabled => &empty,
171+
};
172+
173+
// Lower the requirements.
174+
let requires_dist = metadata.requires_dist.into_iter();
175+
let requires_dist = match source_strategy {
176+
SourceStrategy::Enabled => requires_dist
177+
.flat_map(|requirement| {
178+
let requirement_name = requirement.name.clone();
179+
let extra = requirement.marker.top_level_extra_name();
180+
let group = None;
181+
LoweredRequirement::from_requirement(
182+
requirement,
183+
None,
184+
workspace.install_path(),
185+
project_sources,
186+
project_indexes,
187+
extra.as_ref(),
188+
group,
189+
locations,
190+
workspace,
191+
lower_bound,
192+
None,
131193
)
132194
.map(move |requirement| match requirement {
133195
Ok(requirement) => Ok(requirement.into_inner()),

crates/uv-distribution/src/metadata/lowering.rs

+12-8
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ impl LoweredRequirement {
3737
/// Combine `project.dependencies` or `project.optional-dependencies` with `tool.uv.sources`.
3838
pub(crate) fn from_requirement<'data>(
3939
requirement: uv_pep508::Requirement<VerbatimParsedUrl>,
40-
project_name: &'data PackageName,
40+
project_name: Option<&'data PackageName>,
4141
project_dir: &'data Path,
4242
project_sources: &'data BTreeMap<PackageName, Sources>,
4343
project_indexes: &'data [Index],
@@ -89,7 +89,7 @@ impl LoweredRequirement {
8989
}))
9090
// ... except for recursive self-inclusion (extras that activate other extras), e.g.
9191
// `framework[machine_learning]` depends on `framework[cuda]`.
92-
|| &requirement.name == project_name;
92+
|| project_name.is_some_and(|project_name| *project_name == requirement.name);
9393
if !workspace_package_declared {
9494
return Either::Left(std::iter::once(Err(
9595
LoweringError::UndeclaredWorkspacePackage,
@@ -102,7 +102,7 @@ impl LoweredRequirement {
102102
// Support recursive editable inclusions.
103103
if has_sources
104104
&& requirement.version_or_url.is_none()
105-
&& &requirement.name != project_name
105+
&& !project_name.is_some_and(|project_name| *project_name == requirement.name)
106106
{
107107
warn_user_once!(
108108
"Missing version constraint (e.g., a lower bound) for `{}`",
@@ -211,11 +211,15 @@ impl LoweredRequirement {
211211
index,
212212
));
213213
};
214-
let conflict = if let Some(extra) = extra {
215-
Some(ConflictItem::from((project_name.clone(), extra)))
216-
} else {
217-
group.map(|group| ConflictItem::from((project_name.clone(), group)))
218-
};
214+
let conflict = project_name.and_then(|project_name| {
215+
if let Some(extra) = extra {
216+
Some(ConflictItem::from((project_name.clone(), extra)))
217+
} else {
218+
group.map(|group| {
219+
ConflictItem::from((project_name.clone(), group))
220+
})
221+
}
222+
});
219223
let source = registry_source(
220224
&requirement,
221225
index.into_url(),

crates/uv-distribution/src/metadata/requires_dist.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
use std::collections::BTreeMap;
22
use std::path::Path;
33

4-
use crate::metadata::{GitWorkspaceMember, LoweredRequirement, MetadataError};
5-
use crate::Metadata;
64
use uv_configuration::{LowerBound, SourceStrategy};
75
use uv_distribution_types::IndexLocations;
86
use uv_normalize::{ExtraName, GroupName, PackageName, DEV_DEPENDENCIES};
97
use uv_workspace::dependency_groups::FlatDependencyGroups;
108
use uv_workspace::pyproject::{Sources, ToolUvSources};
119
use uv_workspace::{DiscoveryOptions, ProjectWorkspace};
1210

11+
use crate::metadata::{GitWorkspaceMember, LoweredRequirement, MetadataError};
12+
use crate::Metadata;
13+
1314
#[derive(Debug, Clone)]
1415
pub struct RequiresDist {
1516
pub name: PackageName,
@@ -164,7 +165,7 @@ impl RequiresDist {
164165
let extra = None;
165166
LoweredRequirement::from_requirement(
166167
requirement,
167-
&metadata.name,
168+
Some(&metadata.name),
168169
project_workspace.project_root(),
169170
project_sources,
170171
project_indexes,
@@ -209,7 +210,7 @@ impl RequiresDist {
209210
let group = None;
210211
LoweredRequirement::from_requirement(
211212
requirement,
212-
&metadata.name,
213+
Some(&metadata.name),
213214
project_workspace.project_root(),
214215
project_sources,
215216
project_indexes,

crates/uv-pypi-types/src/metadata/build_requires.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ use crate::VerbatimParsedUrl;
88
/// See: <https://peps.python.org/pep-0518/>
99
#[derive(Debug, Clone)]
1010
pub struct BuildRequires {
11-
pub name: PackageName,
11+
pub name: Option<PackageName>,
1212
pub requires_dist: Vec<Requirement<VerbatimParsedUrl>>,
1313
}

crates/uv-pypi-types/src/requirement.rs

+9
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ impl Requirement {
8686
let fragment = url.fragment()?;
8787
Hashes::parse_fragment(fragment).ok()
8888
}
89+
90+
/// Set the source file containing the requirement.
91+
#[must_use]
92+
pub fn with_origin(self, origin: RequirementOrigin) -> Self {
93+
Self {
94+
origin: Some(origin),
95+
..self
96+
}
97+
}
8998
}
9099

91100
impl From<Requirement> for uv_pep508::Requirement<VerbatimUrl> {

0 commit comments

Comments
 (0)