Fix settings rendering for `extra-build-dependencies` (#15161)

## Summary

It would be nice if this rendered as
`[tool.uv.extra-build-dependencies]` and `[extra-build-dependencies]`
(in `uv.toml`), but this is at least correct.

Closes https://github.com/astral-sh/uv/issues/15124.
This commit is contained in:
Charlie Marsh 2025-08-11 22:24:31 +01:00
parent 22f80ca00d
commit 7ee8c9b583
7 changed files with 58 additions and 154 deletions

View File

@ -13,6 +13,14 @@ pub enum ExtraBuildRequiresError {
"`{0}` was declared as an extra build dependency with `match-runtime = true`, but was not found in the resolution"
)]
NotFound(PackageName),
#[error(
"Dependencies marked with `match-runtime = true` cannot include version specifiers, but found: {0}{1}"
)]
VersionSpecifiersNotAllowed(PackageName, Box<RequirementSource>),
#[error(
"Dependencies marked with `match-runtime = true` cannot include URL constraints, but found: {0}{1}"
)]
UrlNotAllowed(PackageName, Box<RequirementSource>),
}
/// Lowered extra build dependencies with source resolution applied.
@ -83,6 +91,26 @@ impl ExtraBuildRequires {
requirement,
match_runtime: true,
} => {
// Reject requirements with `match-runtime = true` that include any form
// of constraint.
if let RequirementSource::Registry { specifier, .. } =
&requirement.source
{
if !specifier.is_empty() {
return Err(
ExtraBuildRequiresError::VersionSpecifiersNotAllowed(
requirement.name.clone(),
Box::new(requirement.source.clone()),
),
);
}
} else {
return Err(ExtraBuildRequiresError::VersionSpecifiersNotAllowed(
requirement.name.clone(),
Box::new(requirement.source.clone()),
));
}
let dist = resolution
.distributions()
.find(|dist| dist.name() == &requirement.name)

View File

@ -785,8 +785,7 @@ pub struct ResolverInstallerSchema {
default = "[]",
value_type = "dict",
example = r#"
[extra-build-dependencies]
pytest = ["setuptools"]
extra-build-dependencies = { pytest = ["setuptools"] }
"#
)]
pub extra_build_dependencies: Option<ExtraBuildDependencies>,
@ -798,8 +797,7 @@ pub struct ResolverInstallerSchema {
default = r#"{}"#,
value_type = r#"dict[str, dict[str, str]]"#,
example = r#"
[tool.uv.extra-build-variables]
flash-attn = { FLASH_ATTENTION_SKIP_CUDA_BUILD = "TRUE" }
extra-build-variables = { flash-attn = { FLASH_ATTENTION_SKIP_CUDA_BUILD = "TRUE" } }
"#
)]
pub extra_build_variables: Option<ExtraBuildVariables>,
@ -1284,8 +1282,7 @@ pub struct PipOptions {
default = "[]",
value_type = "dict",
example = r#"
[extra-build-dependencies]
pytest = ["setuptools"]
extra-build-dependencies = { pytest = ["setuptools"] }
"#
)]
pub extra_build_dependencies: Option<ExtraBuildDependencies>,
@ -1297,8 +1294,7 @@ pub struct PipOptions {
default = r#"{}"#,
value_type = r#"dict[str, dict[str, str]]"#,
example = r#"
[extra-build-variables]
flash-attn = { FLASH_ATTENTION_SKIP_CUDA_BUILD = "TRUE" }
extra-build-variables = { flash-attn = { FLASH_ATTENTION_SKIP_CUDA_BUILD = "TRUE" } }
"#
)]
pub extra_build_variables: Option<ExtraBuildVariables>,

View File

@ -22,14 +22,14 @@ use serde::{Deserialize, Deserializer, Serialize};
use thiserror::Error;
use uv_build_backend::BuildBackendSettings;
use uv_distribution_types::{ExtraBuildVariables, Index, IndexName, RequirementSource};
use uv_distribution_types::{Index, IndexName, RequirementSource};
use uv_fs::{PortablePathBuf, relative_to};
use uv_git_types::GitReference;
use uv_macros::OptionsMetadata;
use uv_normalize::{DefaultGroups, ExtraName, GroupName, PackageName};
use uv_options_metadata::{OptionSet, OptionsMetadata, Visit};
use uv_pep440::{Version, VersionSpecifiers};
use uv_pep508::{MarkerTree, VersionOrUrl};
use uv_pep508::MarkerTree;
use uv_pypi_types::{
Conflicts, DependencyGroups, SchemaConflicts, SupportedEnvironments, VerbatimParsedUrl,
};
@ -428,35 +428,6 @@ pub struct ToolUv {
)]
pub dependency_groups: Option<ToolUvDependencyGroups>,
/// Additional build dependencies for packages.
///
/// This allows extending the PEP 517 build environment for the project's dependencies with
/// additional packages. This is useful for packages that assume the presence of packages, like,
/// `pip`, and do not declare them as build dependencies.
#[option(
default = "[]",
value_type = "dict",
example = r#"
[tool.uv.extra-build-dependencies]
pytest = ["pip"]
"#
)]
pub extra_build_dependencies: Option<ExtraBuildDependencies>,
/// Extra environment variables to set when building certain packages.
///
/// Environment variables will be added to the environment when building the
/// specified packages.
#[option(
default = r#"{}"#,
value_type = r#"dict[str, dict[str, str]]"#,
example = r#"
[tool.uv.extra-build-variables]
flash-attn = { FLASH_ATTENTION_SKIP_CUDA_BUILD = "TRUE" }
"#
)]
pub extra_build_variables: Option<ExtraBuildVariables>,
/// The project's development dependencies.
///
/// Development dependencies will be installed by default in `uv run` and `uv sync`, but will
@ -785,7 +756,7 @@ pub enum ExtraBuildDependencyWire {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(
deny_unknown_fields,
try_from = "ExtraBuildDependencyWire",
from = "ExtraBuildDependencyWire",
into = "ExtraBuildDependencyWire"
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
@ -800,38 +771,19 @@ impl From<ExtraBuildDependency> for uv_pep508::Requirement<VerbatimParsedUrl> {
}
}
#[derive(Error, Debug)]
pub enum ExtraBuildDependencyError {
#[error("Dependencies marked with `match-runtime = true` cannot include version specifiers")]
VersionSpecifiersNotAllowed,
#[error("Dependencies marked with `match-runtime = true` cannot include URL constraints")]
UrlNotAllowed,
}
impl TryFrom<ExtraBuildDependencyWire> for ExtraBuildDependency {
type Error = ExtraBuildDependencyError;
fn try_from(wire: ExtraBuildDependencyWire) -> Result<Self, ExtraBuildDependencyError> {
impl From<ExtraBuildDependencyWire> for ExtraBuildDependency {
fn from(wire: ExtraBuildDependencyWire) -> Self {
match wire {
ExtraBuildDependencyWire::Unannotated(requirement) => Ok(Self {
ExtraBuildDependencyWire::Unannotated(requirement) => Self {
requirement,
match_runtime: false,
}),
},
ExtraBuildDependencyWire::Annotated {
requirement,
match_runtime,
} => match requirement.version_or_url {
// If `match-runtime = true`, reject additional constraints.
Some(VersionOrUrl::VersionSpecifier(..)) if match_runtime => {
Err(ExtraBuildDependencyError::VersionSpecifiersNotAllowed)
}
Some(VersionOrUrl::Url(..)) if match_runtime => {
Err(ExtraBuildDependencyError::UrlNotAllowed)
}
_ => Ok(Self {
} => Self {
requirement,
match_runtime,
}),
},
}
}

View File

@ -1970,8 +1970,6 @@ mod tests {
"package": null,
"default-groups": null,
"dependency-groups": null,
"extra-build-dependencies": null,
"extra-build-variables": null,
"dev-dependencies": null,
"override-dependencies": null,
"constraint-dependencies": null,
@ -2072,8 +2070,6 @@ mod tests {
"package": null,
"default-groups": null,
"dependency-groups": null,
"extra-build-dependencies": null,
"extra-build-variables": null,
"dev-dependencies": null,
"override-dependencies": null,
"constraint-dependencies": null,
@ -2287,8 +2283,6 @@ mod tests {
"package": null,
"default-groups": null,
"dependency-groups": null,
"extra-build-dependencies": null,
"extra-build-variables": null,
"dev-dependencies": null,
"override-dependencies": null,
"constraint-dependencies": null,
@ -2398,8 +2392,6 @@ mod tests {
"package": null,
"default-groups": null,
"dependency-groups": null,
"extra-build-dependencies": null,
"extra-build-variables": null,
"dev-dependencies": null,
"override-dependencies": null,
"constraint-dependencies": null,
@ -2522,8 +2514,6 @@ mod tests {
"package": null,
"default-groups": null,
"dependency-groups": null,
"extra-build-dependencies": null,
"extra-build-variables": null,
"dev-dependencies": null,
"override-dependencies": null,
"constraint-dependencies": null,
@ -2620,8 +2610,6 @@ mod tests {
"package": null,
"default-groups": null,
"dependency-groups": null,
"extra-build-dependencies": null,
"extra-build-variables": null,
"dev-dependencies": null,
"override-dependencies": null,
"constraint-dependencies": null,

View File

@ -13175,26 +13175,16 @@ fn sync_build_dependencies_respect_locked_versions() -> Result<()> {
child = [{ requirement = "anyio>4", match-runtime = true }]
"#})?;
uv_snapshot!(context.filters(), context.sync(), @r#"
uv_snapshot!(context.filters(), context.sync(), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
warning: Failed to parse `pyproject.toml` during settings discovery:
TOML parse error at line 11, column 9
|
11 | child = [{ requirement = "anyio>4", match-runtime = true }]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Dependencies marked with `match-runtime = true` cannot include version specifiers
error: Failed to parse: `pyproject.toml`
Caused by: TOML parse error at line 11, column 9
|
11 | child = [{ requirement = "anyio>4", match-runtime = true }]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Dependencies marked with `match-runtime = true` cannot include version specifiers
"#);
warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning.
Resolved [N] packages in [TIME]
error: Dependencies marked with `match-runtime = true` cannot include version specifiers, but found: anyio>4
");
Ok(())
}

View File

@ -202,49 +202,6 @@ environments = ["sys_platform == 'darwin'"]
---
### [`extra-build-dependencies`](#extra-build-dependencies) {: #extra-build-dependencies }
Additional build dependencies for packages.
This allows extending the PEP 517 build environment for the project's dependencies with
additional packages. This is useful for packages that assume the presence of packages, like,
`pip`, and do not declare them as build dependencies.
**Default value**: `[]`
**Type**: `dict`
**Example usage**:
```toml title="pyproject.toml"
[tool.uv.extra-build-dependencies]
pytest = ["pip"]
```
---
### [`extra-build-variables`](#extra-build-variables) {: #extra-build-variables }
Extra environment variables to set when building certain packages.
Environment variables will be added to the environment when building the
specified packages.
**Default value**: `{}`
**Type**: `dict[str, dict[str, str]]`
**Example usage**:
```toml title="pyproject.toml"
[tool.uv.extra-build-variables]
flash-attn = { FLASH_ATTENTION_SKIP_CUDA_BUILD = "TRUE" }
```
---
### [`index`](#index) {: #index }
The indexes to use when resolving dependencies.
@ -1188,14 +1145,12 @@ additional packages. This is useful for packages that assume the presence of pac
```toml
[tool.uv]
[extra-build-dependencies]
pytest = ["setuptools"]
extra-build-dependencies = { pytest = ["setuptools"] }
```
=== "uv.toml"
```toml
[extra-build-dependencies]
pytest = ["setuptools"]
extra-build-dependencies = { pytest = ["setuptools"] }
```
---
@ -1216,14 +1171,13 @@ specified packages.
=== "pyproject.toml"
```toml
[tool.uv.extra-build-variables]
flash-attn = { FLASH_ATTENTION_SKIP_CUDA_BUILD = "TRUE" }
[tool.uv]
extra-build-variables = { flash-attn = { FLASH_ATTENTION_SKIP_CUDA_BUILD = "TRUE" } }
```
=== "uv.toml"
```toml
[tool.uv.extra-build-variables]
flash-attn = { FLASH_ATTENTION_SKIP_CUDA_BUILD = "TRUE" }
extra-build-variables = { flash-attn = { FLASH_ATTENTION_SKIP_CUDA_BUILD = "TRUE" } }
```
---
@ -2741,15 +2695,13 @@ additional packages. This is useful for packages that assume the presence of pac
```toml
[tool.uv.pip]
[extra-build-dependencies]
pytest = ["setuptools"]
extra-build-dependencies = { pytest = ["setuptools"] }
```
=== "uv.toml"
```toml
[pip]
[extra-build-dependencies]
pytest = ["setuptools"]
extra-build-dependencies = { pytest = ["setuptools"] }
```
---
@ -2772,15 +2724,13 @@ specified packages.
```toml
[tool.uv.pip]
[extra-build-variables]
flash-attn = { FLASH_ATTENTION_SKIP_CUDA_BUILD = "TRUE" }
extra-build-variables = { flash-attn = { FLASH_ATTENTION_SKIP_CUDA_BUILD = "TRUE" } }
```
=== "uv.toml"
```toml
[pip]
[extra-build-variables]
flash-attn = { FLASH_ATTENTION_SKIP_CUDA_BUILD = "TRUE" }
extra-build-variables = { flash-attn = { FLASH_ATTENTION_SKIP_CUDA_BUILD = "TRUE" } }
```
---

2
uv.schema.json generated
View File

@ -226,7 +226,7 @@
]
},
"extra-build-dependencies": {
"description": "Additional build dependencies for packages.\n\nThis allows extending the PEP 517 build environment for the project's dependencies with\nadditional packages. This is useful for packages that assume the presence of packages, like,\n`pip`, and do not declare them as build dependencies.",
"description": "Additional build dependencies for packages.\n\nThis allows extending the PEP 517 build environment for the project's dependencies with\nadditional packages. This is useful for packages that assume the presence of packages like\n`pip`, and do not declare them as build dependencies.",
"anyOf": [
{
"$ref": "#/definitions/ExtraBuildDependencies"