Make uv build fail

Failure test
This commit is contained in:
Zsolt Dollenstein 2025-08-31 13:34:47 +02:00
parent 38f1d1adb0
commit 88acedafb9
No known key found for this signature in database
5 changed files with 133 additions and 11 deletions

View File

@ -97,6 +97,10 @@ pub enum Error {
UnmatchedRuntime(PackageName, PackageName),
#[error("Build requires for `{0}` missing for metadata declared in `pyproject.toml`")]
MissingBuildRequirementForMetadata(PackageName),
#[error(
"Build requirement `{0}` was declared with `match-runtime = true`, but there is no runtime environment to match against"
)]
UnmatchedRuntimeMetadata(PackageName),
}
impl IsBuildBackendError for Error {
@ -114,6 +118,7 @@ impl IsBuildBackendError for Error {
| Self::NoSourceDistBuilds
| Self::CyclicBuildDependency(_)
| Self::UnmatchedRuntime(_, _)
| Self::UnmatchedRuntimeMetadata(_)
| Self::MissingBuildRequirementForMetadata(_) => false,
Self::CommandFailed(_, _)
| Self::BuildBackend(_)

View File

@ -404,6 +404,7 @@ impl SourceBuild {
};
let resolved_requirements = Self::get_resolved_requirements(
build_kind,
build_context,
source_build_context,
&DEFAULT_BACKEND,
@ -528,10 +529,25 @@ impl SourceBuild {
}
fn apply_build_metadata<'a>(
build_kind: BuildKind,
metadata: &BTreeMap<PackageName, BuildDependencyMetadata>,
build_requires: impl Iterator<Item = &'a Requirement>,
top_level_resolution: &Resolution,
build_context: &impl BuildContext,
) -> Result<Vec<Requirement>, Box<Error>> {
let top_level_resolution = build_context.top_level_resolution();
if top_level_resolution.is_none() {
for (package, metadata) in metadata {
if let Some(true) = metadata.match_runtime {
if !matches!(build_kind, BuildKind::Sdist) {
return Err(Box::new(Error::UnmatchedRuntimeMetadata(package.clone())));
}
}
}
// Nothing needs runtime matching, carry on
return Ok(Vec::new());
}
// SAFETY: check above guarantees that top_level_resolution is Some
let top_level_resolution = top_level_resolution.unwrap();
let dists = top_level_resolution.distributions();
let dists_by_name = dists
.filter_map(|dist| metadata.get(dist.name()).map(|_| (dist.name(), dist)))
@ -578,6 +594,7 @@ impl SourceBuild {
}
async fn get_resolved_requirements(
build_kind: BuildKind,
build_context: &impl BuildContext,
source_build_context: SourceBuildContext,
default_backend: &Pep517Backend,
@ -622,16 +639,15 @@ impl SourceBuild {
};
if let Some(build_dep_metadata) = build_dep_metadata {
if let Some(resolution) = build_context.top_level_resolution() {
let mut reqs_with_metadata = Self::apply_build_metadata(
&build_dep_metadata.0,
requirements.iter(),
resolution,
)
.map_err(|err| *err)?;
reqs_with_metadata.extend(requirements.into_owned().into_iter());
requirements = Cow::Owned(reqs_with_metadata);
}
let mut reqs_with_metadata = Self::apply_build_metadata(
build_kind,
&build_dep_metadata.0,
requirements.iter(),
build_context,
)
.map_err(|err| *err)?;
reqs_with_metadata.extend(requirements.into_owned().into_iter());
requirements = Cow::Owned(reqs_with_metadata);
}
build_context

View File

@ -2084,3 +2084,56 @@ fn venv_included_in_sdist() -> Result<()> {
Ok(())
}
/// Runtime matching causes building a wheel to fail
#[test]
fn runtime_matching() -> Result<()> {
let context = TestContext::new("3.12");
let project = context.temp_dir.child("project");
project
.child("src")
.child("project")
.child("__init__.py")
.touch()?;
project.child("README").touch()?;
let pyproject_toml = project.child("pyproject.toml");
pyproject_toml.write_str(indoc! {r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
[tool.uv.build-dependencies-metadata.hatchling]
match-runtime = true
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
"#})?;
uv_snapshot!(context.filters(), context.build().current_dir(&project), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
Building source distribution...
Building wheel from source distribution...
× Failed to build `[TEMP_DIR]/project`
Build requirement `hatchling` was declared with `match-runtime = true`, but there is no runtime environment to match against
");
// But building a sdist is fine
uv_snapshot!(context.filters(), context.build().arg("--sdist").current_dir(&project), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Building source distribution...
Successfully built dist/project-0.1.0.tar.gz
");
Ok(())
}

View File

@ -30,6 +30,26 @@ build-constraint-dependencies = ["setuptools==60.0.0"]
---
### [`build-dependencies-metadata`](#build-dependencies-metadata) {: #build-dependencies-metadata }
Metadata for build dependencies.
This allows specifying metadata for build dependencies, such as runtime matching.
**Default value**: `None`
**Type**: `dict`
**Example usage**:
```toml title="pyproject.toml"
[tool.uv]
[build-dependencies-metadata.package1]
match-runtime = true
```
---
### [`conflicts`](#conflicts) {: #conflicts }
Declare collections of extras or dependency groups that are conflicting

28
uv.schema.json generated
View File

@ -46,6 +46,17 @@
"type": "string"
}
},
"build-dependencies-metadata": {
"description": "Metadata for build dependencies.\n\nThis allows specifying metadata for build dependencies, such as runtime matching.",
"anyOf": [
{
"$ref": "#/definitions/BuildDependenciesMetadata"
},
{
"type": "null"
}
]
},
"cache-dir": {
"description": "Path to the cache directory.\n\nDefaults to `$XDG_CACHE_HOME/uv` or `$HOME/.cache/uv` on Linux and macOS, and\n`%LOCALAPPDATA%\\uv\\cache` on Windows.",
"type": [
@ -759,6 +770,23 @@
}
}
},
"BuildDependenciesMetadata": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/BuildDependencyMetadata"
}
},
"BuildDependencyMetadata": {
"type": "object",
"properties": {
"match_runtime": {
"type": [
"boolean",
"null"
]
}
}
},
"CacheKey": {
"anyOf": [
{