Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 15 additions & 21 deletions crates/uv-build-frontend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,13 +465,12 @@ impl SourceBuild {
.or(package_name)
{
let build_requires = uv_pypi_types::BuildRequires {
name: name.clone(),
name: Some(name.clone()),
requires_dist: build_system.requires,
};
let build_requires = BuildRequires::from_project_maybe_workspace(
build_requires,
install_path,
None,
locations,
source_strategy,
LowerBound::Allow,
Expand Down Expand Up @@ -905,25 +904,20 @@ async fn create_pep517_build_environment(
// If necessary, lower the requirements.
let extra_requires = match source_strategy {
SourceStrategy::Enabled => {
if let Some(package_name) = package_name {
let build_requires = uv_pypi_types::BuildRequires {
name: package_name.clone(),
requires_dist: extra_requires,
};
let build_requires = BuildRequires::from_project_maybe_workspace(
build_requires,
install_path,
None,
locations,
source_strategy,
LowerBound::Allow,
)
.await
.map_err(Error::Lowering)?;
build_requires.requires_dist
} else {
extra_requires.into_iter().map(Requirement::from).collect()
}
let build_requires = uv_pypi_types::BuildRequires {
name: package_name.cloned(),
requires_dist: extra_requires,
};
let build_requires = BuildRequires::from_project_maybe_workspace(
build_requires,
install_path,
locations,
source_strategy,
LowerBound::Allow,
)
.await
.map_err(Error::Lowering)?;
build_requires.requires_dist
}
SourceStrategy::Disabled => extra_requires.into_iter().map(Requirement::from).collect(),
};
Expand Down
108 changes: 85 additions & 23 deletions crates/uv-distribution/src/metadata/build_requires.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ use uv_configuration::{LowerBound, SourceStrategy};
use uv_distribution_types::IndexLocations;
use uv_normalize::PackageName;
use uv_workspace::pyproject::ToolUvSources;
use uv_workspace::{DiscoveryOptions, ProjectWorkspace};
use uv_workspace::{DiscoveryOptions, ProjectWorkspace, Workspace};

use crate::metadata::{GitWorkspaceMember, LoweredRequirement, MetadataError};
use crate::metadata::{LoweredRequirement, MetadataError};

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

Expand All @@ -35,46 +35,31 @@ impl BuildRequires {
pub async fn from_project_maybe_workspace(
metadata: uv_pypi_types::BuildRequires,
install_path: &Path,
git_member: Option<&GitWorkspaceMember<'_>>,
locations: &IndexLocations,
sources: SourceStrategy,
lower_bound: LowerBound,
) -> Result<Self, MetadataError> {
// TODO(konsti): Cache workspace discovery.
let discovery_options = if let Some(git_member) = &git_member {
DiscoveryOptions {
stop_discovery_at: Some(
git_member
.fetch_root
.parent()
.expect("git checkout has a parent"),
),
..Default::default()
}
} else {
DiscoveryOptions::default()
};
let Some(project_workspace) =
ProjectWorkspace::from_maybe_project_root(install_path, &discovery_options).await?
ProjectWorkspace::from_maybe_project_root(install_path, &DiscoveryOptions::default())
.await?
else {
return Ok(Self::from_metadata23(metadata));
};

Self::from_project_workspace(
metadata,
&project_workspace,
git_member,
locations,
sources,
lower_bound,
)
}

/// Lower the `build-system.requires` field from a `pyproject.toml` file.
fn from_project_workspace(
pub fn from_project_workspace(
metadata: uv_pypi_types::BuildRequires,
project_workspace: &ProjectWorkspace,
git_member: Option<&GitWorkspaceMember<'_>>,
locations: &IndexLocations,
source_strategy: SourceStrategy,
lower_bound: LowerBound,
Expand Down Expand Up @@ -118,7 +103,7 @@ impl BuildRequires {
let group = None;
LoweredRequirement::from_requirement(
requirement,
&metadata.name,
metadata.name.as_ref(),
project_workspace.project_root(),
project_sources,
project_indexes,
Expand All @@ -127,7 +112,84 @@ impl BuildRequires {
locations,
project_workspace.workspace(),
lower_bound,
git_member,
None,
)
.map(move |requirement| match requirement {
Ok(requirement) => Ok(requirement.into_inner()),
Err(err) => Err(MetadataError::LoweringError(
requirement_name.clone(),
Box::new(err),
)),
})
})
.collect::<Result<Vec<_>, _>>()?,
SourceStrategy::Disabled => requires_dist
.into_iter()
.map(uv_pypi_types::Requirement::from)
.collect(),
};

Ok(Self {
name: metadata.name,
requires_dist,
})
}

/// Lower the `build-system.requires` field from a `pyproject.toml` file.
pub fn from_workspace(
metadata: uv_pypi_types::BuildRequires,
workspace: &Workspace,
locations: &IndexLocations,
source_strategy: SourceStrategy,
lower_bound: LowerBound,
) -> Result<Self, MetadataError> {
// Collect any `tool.uv.index` entries.
let empty = vec![];
let project_indexes = match source_strategy {
SourceStrategy::Enabled => workspace
.pyproject_toml()
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
.and_then(|uv| uv.index.as_deref())
.unwrap_or(&empty),
SourceStrategy::Disabled => &empty,
};

// Collect any `tool.uv.sources` and `tool.uv.dev_dependencies` from `pyproject.toml`.
let empty = BTreeMap::default();
let project_sources = match source_strategy {
SourceStrategy::Enabled => workspace
.pyproject_toml()
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
.and_then(|uv| uv.sources.as_ref())
.map(ToolUvSources::inner)
.unwrap_or(&empty),
SourceStrategy::Disabled => &empty,
};

// Lower the requirements.
let requires_dist = metadata.requires_dist.into_iter();
let requires_dist = match source_strategy {
SourceStrategy::Enabled => requires_dist
.flat_map(|requirement| {
let requirement_name = requirement.name.clone();
let extra = requirement.marker.top_level_extra_name();
let group = None;
LoweredRequirement::from_requirement(
requirement,
None,
workspace.install_path(),
project_sources,
project_indexes,
extra.as_ref(),
group,
locations,
workspace,
lower_bound,
None,
)
.map(move |requirement| match requirement {
Ok(requirement) => Ok(requirement.into_inner()),
Expand Down
20 changes: 12 additions & 8 deletions crates/uv-distribution/src/metadata/lowering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl LoweredRequirement {
/// Combine `project.dependencies` or `project.optional-dependencies` with `tool.uv.sources`.
pub(crate) fn from_requirement<'data>(
requirement: uv_pep508::Requirement<VerbatimParsedUrl>,
project_name: &'data PackageName,
project_name: Option<&'data PackageName>,
project_dir: &'data Path,
project_sources: &'data BTreeMap<PackageName, Sources>,
project_indexes: &'data [Index],
Expand Down Expand Up @@ -89,7 +89,7 @@ impl LoweredRequirement {
}))
// ... except for recursive self-inclusion (extras that activate other extras), e.g.
// `framework[machine_learning]` depends on `framework[cuda]`.
|| &requirement.name == project_name;
|| project_name.is_some_and(|project_name| *project_name == requirement.name);
if !workspace_package_declared {
return Either::Left(std::iter::once(Err(
LoweringError::UndeclaredWorkspacePackage,
Expand All @@ -102,7 +102,7 @@ impl LoweredRequirement {
// Support recursive editable inclusions.
if has_sources
&& requirement.version_or_url.is_none()
&& &requirement.name != project_name
&& !project_name.is_some_and(|project_name| *project_name == requirement.name)
{
warn_user_once!(
"Missing version constraint (e.g., a lower bound) for `{}`",
Expand Down Expand Up @@ -211,11 +211,15 @@ impl LoweredRequirement {
index,
));
};
let conflict = if let Some(extra) = extra {
Some(ConflictItem::from((project_name.clone(), extra)))
} else {
group.map(|group| ConflictItem::from((project_name.clone(), group)))
};
let conflict = project_name.and_then(|project_name| {
if let Some(extra) = extra {
Some(ConflictItem::from((project_name.clone(), extra)))
} else {
group.map(|group| {
ConflictItem::from((project_name.clone(), group))
})
}
});
let source = registry_source(
&requirement,
index.into_url(),
Expand Down
9 changes: 5 additions & 4 deletions crates/uv-distribution/src/metadata/requires_dist.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
use std::collections::BTreeMap;
use std::path::Path;

use crate::metadata::{GitWorkspaceMember, LoweredRequirement, MetadataError};
use crate::Metadata;
use uv_configuration::{LowerBound, SourceStrategy};
use uv_distribution_types::IndexLocations;
use uv_normalize::{ExtraName, GroupName, PackageName, DEV_DEPENDENCIES};
use uv_workspace::dependency_groups::FlatDependencyGroups;
use uv_workspace::pyproject::{Sources, ToolUvSources};
use uv_workspace::{DiscoveryOptions, ProjectWorkspace};

use crate::metadata::{GitWorkspaceMember, LoweredRequirement, MetadataError};
use crate::Metadata;

#[derive(Debug, Clone)]
pub struct RequiresDist {
pub name: PackageName,
Expand Down Expand Up @@ -164,7 +165,7 @@ impl RequiresDist {
let extra = None;
LoweredRequirement::from_requirement(
requirement,
&metadata.name,
Some(&metadata.name),
project_workspace.project_root(),
project_sources,
project_indexes,
Expand Down Expand Up @@ -209,7 +210,7 @@ impl RequiresDist {
let group = None;
LoweredRequirement::from_requirement(
requirement,
&metadata.name,
Some(&metadata.name),
project_workspace.project_root(),
project_sources,
project_indexes,
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-pypi-types/src/metadata/build_requires.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ use crate::VerbatimParsedUrl;
/// See: <https://peps.python.org/pep-0518/>
#[derive(Debug, Clone)]
pub struct BuildRequires {
pub name: PackageName,
pub name: Option<PackageName>,
pub requires_dist: Vec<Requirement<VerbatimParsedUrl>>,
}
9 changes: 9 additions & 0 deletions crates/uv-pypi-types/src/requirement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@ impl Requirement {
let fragment = url.fragment()?;
Hashes::parse_fragment(fragment).ok()
}

/// Set the source file containing the requirement.
#[must_use]
pub fn with_origin(self, origin: RequirementOrigin) -> Self {
Self {
origin: Some(origin),
..self
}
}
}

impl From<Requirement> for uv_pep508::Requirement<VerbatimUrl> {
Expand Down
Loading