Add tool.uv.build-constraint-dependencies to pyproject.toml (#11585)

## Summary

Resolves #6913. 

Add `tool.uv.build-constraint-dependencies` to pyproject.toml.
The changes are analogous to the constraint-dependencies feature
implemented in #5248.

Add documentation for `build-constraint-dependencies`

## Test Plan

Add tests for `uv lock`, `uv add`, `uv pip install` and `uv pip
compile`.

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
This commit is contained in:
Chao Ning 2025-02-18 01:58:36 +00:00 committed by GitHub
parent 8996d5c358
commit 8c3a6b2155
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 879 additions and 8 deletions

View file

@ -331,6 +331,7 @@ pub struct ToolUv {
pub top_level: ResolverInstallerOptions, pub top_level: ResolverInstallerOptions,
pub override_dependencies: Option<Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>, pub override_dependencies: Option<Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>,
pub constraint_dependencies: Option<Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>, pub constraint_dependencies: Option<Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>,
pub build_constraint_dependencies: Option<Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>,
pub sources: Option<BTreeMap<PackageName, Sources>>, pub sources: Option<BTreeMap<PackageName, Sources>>,
} }

View file

@ -107,6 +107,9 @@ pub struct Options {
#[cfg_attr(feature = "schemars", schemars(skip))] #[cfg_attr(feature = "schemars", schemars(skip))]
pub constraint_dependencies: Option<Vec<Requirement<VerbatimParsedUrl>>>, pub constraint_dependencies: Option<Vec<Requirement<VerbatimParsedUrl>>>,
#[cfg_attr(feature = "schemars", schemars(skip))]
pub build_constraint_dependencies: Option<Vec<Requirement<VerbatimParsedUrl>>>,
#[cfg_attr(feature = "schemars", schemars(skip))] #[cfg_attr(feature = "schemars", schemars(skip))]
pub environments: Option<SupportedEnvironments>, pub environments: Option<SupportedEnvironments>,
@ -1792,6 +1795,7 @@ pub struct OptionsWire {
// They're respected in both `pyproject.toml` and `uv.toml` files. // They're respected in both `pyproject.toml` and `uv.toml` files.
override_dependencies: Option<Vec<Requirement<VerbatimParsedUrl>>>, override_dependencies: Option<Vec<Requirement<VerbatimParsedUrl>>>,
constraint_dependencies: Option<Vec<Requirement<VerbatimParsedUrl>>>, constraint_dependencies: Option<Vec<Requirement<VerbatimParsedUrl>>>,
build_constraint_dependencies: Option<Vec<Requirement<VerbatimParsedUrl>>>,
environments: Option<SupportedEnvironments>, environments: Option<SupportedEnvironments>,
required_environments: Option<SupportedEnvironments>, required_environments: Option<SupportedEnvironments>,
@ -1858,6 +1862,7 @@ impl From<OptionsWire> for Options {
cache_keys, cache_keys,
override_dependencies, override_dependencies,
constraint_dependencies, constraint_dependencies,
build_constraint_dependencies,
environments, environments,
required_environments, required_environments,
conflicts, conflicts,
@ -1922,6 +1927,7 @@ impl From<OptionsWire> for Options {
cache_keys, cache_keys,
override_dependencies, override_dependencies,
constraint_dependencies, constraint_dependencies,
build_constraint_dependencies,
environments, environments,
required_environments, required_environments,
install_mirrors: PythonInstallMirrors::resolve( install_mirrors: PythonInstallMirrors::resolve(

View file

@ -479,6 +479,37 @@ pub struct ToolUv {
)] )]
pub constraint_dependencies: Option<Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>, pub constraint_dependencies: Option<Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>,
/// Constraints to apply when solving build dependencies.
///
/// Build constraints are used to restrict the versions of build dependencies that are selected
/// when building a package during resolution or installation.
///
/// Including a package as a constraint will _not_ trigger installation of the package during
/// a build; instead, the package must be requested elsewhere in the project's build dependency
/// graph.
///
/// !!! note
/// In `uv lock`, `uv sync`, and `uv run`, uv will only read `build-constraint-dependencies` from
/// the `pyproject.toml` at the workspace root, and will ignore any declarations in other
/// workspace members or `uv.toml` files.
#[cfg_attr(
feature = "schemars",
schemars(
with = "Option<Vec<String>>",
description = "PEP 508-style requirements, e.g., `ruff==0.5.0`, or `ruff @ https://...`."
)
)]
#[option(
default = "[]",
value_type = "list[str]",
example = r#"
# Ensure that the setuptools v60.0.0 is used whenever a package has a build dependency
# on setuptools.
build-constraint-dependencies = ["setuptools==60.0.0"]
"#
)]
pub build_constraint_dependencies: Option<Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>,
/// A list of supported environments against which to resolve dependencies. /// A list of supported environments against which to resolve dependencies.
/// ///
/// By default, uv will resolve for all possible environments during a `uv lock` operation. /// By default, uv will resolve for all possible environments during a `uv lock` operation.

View file

@ -496,6 +496,20 @@ impl Workspace {
constraints.clone() constraints.clone()
} }
/// Returns the set of build constraints for the workspace.
pub fn build_constraints(&self) -> Vec<uv_pep508::Requirement<VerbatimParsedUrl>> {
let Some(build_constraints) = self
.pyproject_toml
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
.and_then(|uv| uv.build_constraint_dependencies.as_ref())
else {
return vec![];
};
build_constraints.clone()
}
/// The path to the workspace root, the directory containing the top level `pyproject.toml` with /// The path to the workspace root, the directory containing the top level `pyproject.toml` with
/// the `uv.tool.workspace`, or the `pyproject.toml` in an implicit single workspace project. /// the `uv.tool.workspace`, or the `pyproject.toml` in an implicit single workspace project.
pub fn install_path(&self) -> &PathBuf { pub fn install_path(&self) -> &PathBuf {
@ -1725,6 +1739,7 @@ mod tests {
"dev-dependencies": null, "dev-dependencies": null,
"override-dependencies": null, "override-dependencies": null,
"constraint-dependencies": null, "constraint-dependencies": null,
"build-constraint-dependencies": null,
"environments": null, "environments": null,
"required-environments": null, "required-environments": null,
"conflicts": null "conflicts": null
@ -1818,6 +1833,7 @@ mod tests {
"dev-dependencies": null, "dev-dependencies": null,
"override-dependencies": null, "override-dependencies": null,
"constraint-dependencies": null, "constraint-dependencies": null,
"build-constraint-dependencies": null,
"environments": null, "environments": null,
"required-environments": null, "required-environments": null,
"conflicts": null "conflicts": null
@ -2025,6 +2041,7 @@ mod tests {
"dev-dependencies": null, "dev-dependencies": null,
"override-dependencies": null, "override-dependencies": null,
"constraint-dependencies": null, "constraint-dependencies": null,
"build-constraint-dependencies": null,
"environments": null, "environments": null,
"required-environments": null, "required-environments": null,
"conflicts": null "conflicts": null
@ -2130,6 +2147,7 @@ mod tests {
"dev-dependencies": null, "dev-dependencies": null,
"override-dependencies": null, "override-dependencies": null,
"constraint-dependencies": null, "constraint-dependencies": null,
"build-constraint-dependencies": null,
"environments": null, "environments": null,
"required-environments": null, "required-environments": null,
"conflicts": null "conflicts": null
@ -2248,6 +2266,7 @@ mod tests {
"dev-dependencies": null, "dev-dependencies": null,
"override-dependencies": null, "override-dependencies": null,
"constraint-dependencies": null, "constraint-dependencies": null,
"build-constraint-dependencies": null,
"environments": null, "environments": null,
"required-environments": null, "required-environments": null,
"conflicts": null "conflicts": null
@ -2340,6 +2359,7 @@ mod tests {
"dev-dependencies": null, "dev-dependencies": null,
"override-dependencies": null, "override-dependencies": null,
"constraint-dependencies": null, "constraint-dependencies": null,
"build-constraint-dependencies": null,
"environments": null, "environments": null,
"required-environments": null, "required-environments": null,
"conflicts": null "conflicts": null

View file

@ -56,6 +56,7 @@ pub(crate) async fn pip_compile(
build_constraints: &[RequirementsSource], build_constraints: &[RequirementsSource],
constraints_from_workspace: Vec<Requirement>, constraints_from_workspace: Vec<Requirement>,
overrides_from_workspace: Vec<Requirement>, overrides_from_workspace: Vec<Requirement>,
build_constraints_from_workspace: Vec<Requirement>,
environments: SupportedEnvironments, environments: SupportedEnvironments,
extras: ExtrasSpecification, extras: ExtrasSpecification,
groups: DevGroupsSpecification, groups: DevGroupsSpecification,
@ -184,8 +185,17 @@ pub(crate) async fn pip_compile(
.collect(); .collect();
// Read build constraints. // Read build constraints.
let build_constraints = let build_constraints: Vec<NameRequirementSpecification> =
operations::read_constraints(build_constraints, &client_builder).await?; operations::read_constraints(build_constraints, &client_builder)
.await?
.iter()
.cloned()
.chain(
build_constraints_from_workspace
.into_iter()
.map(NameRequirementSpecification::from),
)
.collect();
// If all the metadata could be statically resolved, validate that every extra was used. If we // If all the metadata could be statically resolved, validate that every extra was used. If we
// need to resolve metadata via PEP 517, we don't know which extras are used until much later. // need to resolve metadata via PEP 517, we don't know which extras are used until much later.

View file

@ -51,6 +51,7 @@ pub(crate) async fn pip_install(
build_constraints: &[RequirementsSource], build_constraints: &[RequirementsSource],
constraints_from_workspace: Vec<Requirement>, constraints_from_workspace: Vec<Requirement>,
overrides_from_workspace: Vec<Requirement>, overrides_from_workspace: Vec<Requirement>,
build_constraints_from_workspace: Vec<Requirement>,
extras: &ExtrasSpecification, extras: &ExtrasSpecification,
groups: &DevGroupsSpecification, groups: &DevGroupsSpecification,
resolution_mode: ResolutionMode, resolution_mode: ResolutionMode,
@ -123,10 +124,6 @@ pub(crate) async fn pip_install(
) )
.await?; .await?;
// Read build constraints.
let build_constraints =
operations::read_constraints(build_constraints, &client_builder).await?;
let constraints: Vec<NameRequirementSpecification> = constraints let constraints: Vec<NameRequirementSpecification> = constraints
.iter() .iter()
.cloned() .cloned()
@ -147,6 +144,20 @@ pub(crate) async fn pip_install(
) )
.collect(); .collect();
// Read build constraints.
let build_constraints: Vec<NameRequirementSpecification> =
operations::read_constraints(build_constraints, &client_builder)
.await?
.iter()
.cloned()
.chain(
build_constraints_from_workspace
.iter()
.cloned()
.map(NameRequirementSpecification::from),
)
.collect();
// Detect the current Python interpreter. // Detect the current Python interpreter.
let environment = if target.is_some() || prefix.is_some() { let environment = if target.is_some() || prefix.is_some() {
let installation = PythonInstallation::find( let installation = PythonInstallation::find(

View file

@ -366,6 +366,7 @@ async fn do_lock(
let requirements = target.requirements(); let requirements = target.requirements();
let overrides = target.overrides(); let overrides = target.overrides();
let constraints = target.constraints(); let constraints = target.constraints();
let build_constraints = target.build_constraints();
let dependency_groups = target.dependency_groups()?; let dependency_groups = target.dependency_groups()?;
let source_trees = vec![]; let source_trees = vec![];
@ -373,6 +374,7 @@ async fn do_lock(
let requirements = target.lower(requirements, index_locations, sources)?; let requirements = target.lower(requirements, index_locations, sources)?;
let overrides = target.lower(overrides, index_locations, sources)?; let overrides = target.lower(overrides, index_locations, sources)?;
let constraints = target.lower(constraints, index_locations, sources)?; let constraints = target.lower(constraints, index_locations, sources)?;
let build_constraints = target.lower(build_constraints, index_locations, sources)?;
let dependency_groups = dependency_groups let dependency_groups = dependency_groups
.into_iter() .into_iter()
.map(|(name, requirements)| { .map(|(name, requirements)| {
@ -556,9 +558,10 @@ async fn do_lock(
.build(); .build();
let hasher = HashStrategy::Generate(HashGeneration::Url); let hasher = HashStrategy::Generate(HashGeneration::Url);
let build_constraints = Constraints::from_requirements(build_constraints.iter().cloned());
// TODO(charlie): These are all default values. We should consider whether we want to make them // TODO(charlie): These are all default values. We should consider whether we want to make them
// optional on the downstream APIs. // optional on the downstream APIs.
let build_constraints = Constraints::default();
let build_hasher = HashStrategy::default(); let build_hasher = HashStrategy::default();
let extras = ExtrasSpecification::default(); let extras = ExtrasSpecification::default();
let groups = DevGroupsSpecification::default(); let groups = DevGroupsSpecification::default();

View file

@ -79,6 +79,23 @@ impl<'lock> LockTarget<'lock> {
} }
} }
/// Returns the set of build constraints for the [`LockTarget`].
pub(crate) fn build_constraints(self) -> Vec<uv_pep508::Requirement<VerbatimParsedUrl>> {
match self {
Self::Workspace(workspace) => workspace.build_constraints(),
Self::Script(script) => script
.metadata
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
.and_then(|uv| uv.build_constraint_dependencies.as_ref())
.into_iter()
.flatten()
.cloned()
.collect(),
}
}
/// Return the dependency groups that are attached to the target directly, as opposed to being /// Return the dependency groups that are attached to the target directly, as opposed to being
/// attached to any members within the target. /// attached to any members within the target.
pub(crate) fn dependency_groups( pub(crate) fn dependency_groups(

View file

@ -389,6 +389,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
&build_constraints, &build_constraints,
args.constraints_from_workspace, args.constraints_from_workspace,
args.overrides_from_workspace, args.overrides_from_workspace,
args.build_constraints_from_workspace,
args.environments, args.environments,
args.settings.extras, args.settings.extras,
args.settings.groups, args.settings.groups,
@ -561,6 +562,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
&build_constraints, &build_constraints,
args.constraints_from_workspace, args.constraints_from_workspace,
args.overrides_from_workspace, args.overrides_from_workspace,
args.build_constraints_from_workspace,
&args.settings.extras, &args.settings.extras,
&args.settings.groups, &args.settings.groups,
args.settings.resolution, args.settings.resolution,

View file

@ -1540,6 +1540,7 @@ pub(crate) struct PipCompileSettings {
pub(crate) build_constraints: Vec<PathBuf>, pub(crate) build_constraints: Vec<PathBuf>,
pub(crate) constraints_from_workspace: Vec<Requirement>, pub(crate) constraints_from_workspace: Vec<Requirement>,
pub(crate) overrides_from_workspace: Vec<Requirement>, pub(crate) overrides_from_workspace: Vec<Requirement>,
pub(crate) build_constraints_from_workspace: Vec<Requirement>,
pub(crate) environments: SupportedEnvironments, pub(crate) environments: SupportedEnvironments,
pub(crate) refresh: Refresh, pub(crate) refresh: Refresh,
pub(crate) settings: PipSettings, pub(crate) settings: PipSettings,
@ -1626,6 +1627,20 @@ impl PipCompileSettings {
Vec::new() Vec::new()
}; };
let build_constraints_from_workspace = if let Some(configuration) = &filesystem {
configuration
.build_constraint_dependencies
.clone()
.unwrap_or_default()
.into_iter()
.map(|requirement| {
Requirement::from(requirement.with_origin(RequirementOrigin::Workspace))
})
.collect()
} else {
Vec::new()
};
let environments = if let Some(configuration) = &filesystem { let environments = if let Some(configuration) = &filesystem {
configuration.environments.clone().unwrap_or_default() configuration.environments.clone().unwrap_or_default()
} else { } else {
@ -1648,6 +1663,7 @@ impl PipCompileSettings {
.collect(), .collect(),
constraints_from_workspace, constraints_from_workspace,
overrides_from_workspace, overrides_from_workspace,
build_constraints_from_workspace,
environments, environments,
refresh: Refresh::from(refresh), refresh: Refresh::from(refresh),
settings: PipSettings::combine( settings: PipSettings::combine(
@ -1783,6 +1799,7 @@ pub(crate) struct PipInstallSettings {
pub(crate) dry_run: DryRun, pub(crate) dry_run: DryRun,
pub(crate) constraints_from_workspace: Vec<Requirement>, pub(crate) constraints_from_workspace: Vec<Requirement>,
pub(crate) overrides_from_workspace: Vec<Requirement>, pub(crate) overrides_from_workspace: Vec<Requirement>,
pub(crate) build_constraints_from_workspace: Vec<Requirement>,
pub(crate) modifications: Modifications, pub(crate) modifications: Modifications,
pub(crate) refresh: Refresh, pub(crate) refresh: Refresh,
pub(crate) settings: PipSettings, pub(crate) settings: PipSettings,
@ -1858,6 +1875,20 @@ impl PipInstallSettings {
Vec::new() Vec::new()
}; };
let build_constraints_from_workspace = if let Some(configuration) = &filesystem {
configuration
.build_constraint_dependencies
.clone()
.unwrap_or_default()
.into_iter()
.map(|requirement| {
Requirement::from(requirement.with_origin(RequirementOrigin::Workspace))
})
.collect()
} else {
Vec::new()
};
Self { Self {
package, package,
requirements, requirements,
@ -1877,6 +1908,7 @@ impl PipInstallSettings {
dry_run: DryRun::from_args(dry_run), dry_run: DryRun::from_args(dry_run),
constraints_from_workspace, constraints_from_workspace,
overrides_from_workspace, overrides_from_workspace,
build_constraints_from_workspace,
modifications: if flag(exact, inexact).unwrap_or(false) { modifications: if flag(exact, inexact).unwrap_or(false) {
Modifications::Exact Modifications::Exact
} else { } else {

View file

@ -9714,3 +9714,59 @@ fn repeated_index_cli_reversed() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn add_with_build_constraints() -> Result<()> {
let context = TestContext::new("3.8");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! {r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.8"
dependencies = []
[tool.uv]
build-constraint-dependencies = ["setuptools==1"]
"#})?;
uv_snapshot!(context.filters(), context.add().arg("requests==1.2"), @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× Failed to download and build `requests==1.2.0`
Failed to resolve requirements from `setup.py` build
No solution found when resolving: `setuptools>=40.8.0`
Because you require setuptools>=40.8.0 and setuptools==1, we can conclude that your requirements are unsatisfiable.
help: `requests` (v1.2.0) was included because `project` (v0.1.0) depends on `requests==1.2`
");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! {r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.8"
dependencies = []
[tool.uv]
build-constraint-dependencies = ["setuptools>=40"]
"#})?;
uv_snapshot!(context.filters(), context.add().arg("requests==1.2"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ requests==1.2.0
"###);
Ok(())
}

View file

@ -1883,6 +1883,146 @@ fn lock_project_with_constraint_sources() -> Result<()> {
Ok(()) Ok(())
} }
/// Lock a project with `uv.tool.build-constraint-dependencies`.
#[test]
fn lock_project_with_build_constraints() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio==3.7.0"]
# This should be ignored because none of the dependencies requires `setuptools`
[tool.uv]
build-constraint-dependencies = ["setuptools==1"]
"#,
)?;
uv_snapshot!(context.filters(), context.lock(), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 4 packages in [TIME]
"###);
// Re-run with `--locked`.
uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 4 packages in [TIME]
"###);
// Install the base dependencies from the lockfile.
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Prepared 3 packages in [TIME]
Installed 3 packages in [TIME]
+ anyio==3.7.0
+ idna==3.6
+ sniffio==1.3.1
"###);
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.8"
dependencies = ["requests==1.2"]
# This should fail the operation, since `requests` depends on `setuptools` at build time.
[tool.uv]
build-constraint-dependencies = ["setuptools==1"]
"#,
)?;
uv_snapshot!(context.filters(), context.lock(), @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× Failed to download and build `requests==1.2.0`
Failed to resolve requirements from `setup.py` build
No solution found when resolving: `setuptools>=40.8.0`
Because you require setuptools>=40.8.0 and setuptools==1, we can conclude that your requirements are unsatisfiable.
help: `requests` (v1.2.0) was included because `project` (v0.1.0) depends on `requests==1.2`
");
Ok(())
}
/// Lock a project with `uv.tool.build-constraint-dependencies` that reference `tool.uv.sources`.
#[test]
fn lock_project_with_build_constraint_sources() -> Result<()> {
let context = TestContext::new("3.9");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.8"
dependencies = ["requests==1.2"]
[tool.uv]
build-constraint-dependencies = ["setuptools==75.8.0"]
[tool.uv.sources]
setuptools = { url = "https://files.pythonhosted.org/packages/69/8a/b9dc7678803429e4a3bc9ba462fa3dd9066824d3c607490235c6a796be5a/setuptools-75.8.0-py3-none-any.whl" }
"#,
)?;
uv_snapshot!(context.filters(), context.lock(), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
"###);
// Re-run with `--locked`.
uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
"###);
// Install the base dependencies from the lockfile.
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ requests==1.2.0
"###);
Ok(())
}
/// Lock a project with a dependency that has an extra. /// Lock a project with a dependency that has an extra.
#[test] #[test]
fn lock_dependency_extra() -> Result<()> { fn lock_dependency_extra() -> Result<()> {

View file

@ -13572,6 +13572,259 @@ fn compatible_build_constraint() -> Result<()> {
Ok(()) Ok(())
} }
/// Include `build-constraint-dependencies` in pyproject.toml with an incompatible constraint.
#[test]
fn incompatible_build_constraint_in_pyproject_toml() -> Result<()> {
let context = TestContext::new("3.8");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[build-system]
requires = ["setuptools"]
[project]
name = "project"
version = "0.1.0"
dependencies = [
"requests==1.2",
]
[tool.uv]
build-constraint-dependencies = [
"setuptools==1",
]
"#,
)?;
uv_snapshot!(context.pip_compile()
.arg("pyproject.toml"), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× Failed to download and build `requests==1.2.0`
Failed to resolve requirements from `setup.py` build
No solution found when resolving: `setuptools>=40.8.0`
Because you require setuptools>=40.8.0 and setuptools==1, we can conclude that your requirements are unsatisfiable.
"###
);
Ok(())
}
/// Include `build-constraint-dependencies` in pyproject.toml with a compatible constraint.
#[test]
fn compatible_build_constraint_in_pyproject_toml() -> Result<()> {
let context = TestContext::new("3.8");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[build-system]
requires = ["setuptools"]
[project]
name = "project"
version = "0.1.0"
dependencies = [
"requests==1.2",
]
[tool.uv]
build-constraint-dependencies = [
"setuptools>=40",
]
"#,
)?;
uv_snapshot!(context.pip_compile()
.arg("pyproject.toml"), @r"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] pyproject.toml
requests==1.2.0
# via project (pyproject.toml)
----- stderr -----
Resolved 1 package in [TIME]
"
);
Ok(())
}
/// Merge `build_constraints.txt` with `build-constraint-dependencies` in pyproject.toml with an incompatible constraint.
#[test]
fn incompatible_build_constraint_merged_with_pyproject_toml() -> Result<()> {
let context = TestContext::new("3.8");
// incompatible setuptools version in pyproject.toml, compatible in build_constraints.txt
let constraints_txt = context.temp_dir.child("build_constraints.txt");
constraints_txt.write_str("setuptools>=40")?;
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[build-system]
requires = ["setuptools"]
[project]
name = "project"
version = "0.1.0"
dependencies = [
"requests==1.2",
]
[tool.uv]
build-constraint-dependencies = [
"setuptools==1",
]
"#,
)?;
uv_snapshot!(context.pip_compile()
.arg("pyproject.toml")
.arg("--build-constraint")
.arg("build_constraints.txt"), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× Failed to download and build `requests==1.2.0`
Failed to resolve requirements from `setup.py` build
No solution found when resolving: `setuptools>=40.8.0`
Because you require setuptools>=40 and setuptools==1, we can conclude that your requirements are unsatisfiable.
"###
);
// compatible setuptools version in pyproject.toml, incompatible in build_constraints.txt
let constraints_txt = context.temp_dir.child("build_constraints.txt");
constraints_txt.write_str("setuptools==1")?;
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[build-system]
requires = ["setuptools"]
[project]
name = "project"
version = "0.1.0"
dependencies = [
"requests==1.2",
]
[tool.uv]
build-constraint-dependencies = [
"setuptools>=40",
]
"#,
)?;
uv_snapshot!(context.pip_compile()
.arg("pyproject.toml")
.arg("--build-constraint")
.arg("build_constraints.txt"), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× Failed to download and build `requests==1.2.0`
Failed to resolve requirements from `setup.py` build
No solution found when resolving: `setuptools>=40.8.0`
Because you require setuptools==1 and setuptools>=40, we can conclude that your requirements are unsatisfiable.
"###
);
Ok(())
}
/// Merge CLI args `build_constraints.txt` with `build-constraint-dependencies` in pyproject.toml with a compatible constraint.
#[test]
fn compatible_build_constraint_merged_with_pyproject_toml() -> Result<()> {
let context = TestContext::new("3.8");
// incompatible setuptools version in pyproject.toml, compatible in build_constraints.txt
let constraints_txt = context.temp_dir.child("build_constraints.txt");
constraints_txt.write_str("setuptools>=40")?;
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[build-system]
requires = ["setuptools"]
[project]
name = "project"
version = "0.1.0"
dependencies = [
"requests==1.2",
]
[tool.uv]
build-constraint-dependencies = [
"setuptools>=1",
]
"#,
)?;
uv_snapshot!(context.pip_compile()
.arg("pyproject.toml")
.arg("--build-constraint")
.arg("build_constraints.txt"), @r"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] pyproject.toml --build-constraint build_constraints.txt
requests==1.2.0
# via project (pyproject.toml)
----- stderr -----
Resolved 1 package in [TIME]
"
);
// compatible setuptools version in pyproject.toml, incompatible in build_constraints.txt
let constraints_txt = context.temp_dir.child("build_constraints.txt");
constraints_txt.write_str("setuptools>=1")?;
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[build-system]
requires = ["setuptools"]
[project]
name = "project"
version = "0.1.0"
dependencies = [
"requests==1.2",
]
[tool.uv]
build-constraint-dependencies = [
"setuptools>=40",
]
"#,
)?;
uv_snapshot!(context.pip_compile()
.arg("pyproject.toml")
.arg("--build-constraint")
.arg("build_constraints.txt"), @r"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] pyproject.toml --build-constraint build_constraints.txt
requests==1.2.0
# via project (pyproject.toml)
----- stderr -----
Resolved 1 package in [TIME]
"
);
Ok(())
}
/// Ensure that we treat invalid extra markers as `false`, i.e., in projects that define /// Ensure that we treat invalid extra markers as `false`, i.e., in projects that define
/// non-spec-compliant extras. /// non-spec-compliant extras.
#[test] #[test]

View file

@ -7797,6 +7797,166 @@ fn compatible_build_constraint() -> Result<()> {
Ok(()) Ok(())
} }
/// Include `build-constraint-dependencies` in pyproject.toml with an incompatible constraint.
#[test]
fn incompatible_build_constraint_in_pyproject_toml() -> Result<()> {
let context = TestContext::new("3.8");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[tool.uv]
build-constraint-dependencies = [
"setuptools==1",
]
"#,
)?;
uv_snapshot!(context.pip_install()
.arg("requests==1.2"), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× Failed to download and build `requests==1.2.0`
Failed to resolve requirements from `setup.py` build
No solution found when resolving: `setuptools>=40.8.0`
Because you require setuptools>=40.8.0 and setuptools==1, we can conclude that your requirements are unsatisfiable.
"###
);
Ok(())
}
/// Include a `build_constraints.txt` file with a compatible constraint.
#[test]
fn compatible_build_constraint_in_pyproject_toml() -> Result<()> {
let context = TestContext::new("3.8");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[tool.uv]
build-constraint-dependencies = [
"setuptools==40.8.0",
]
"#,
)?;
uv_snapshot!(context.pip_install()
.arg("requests==1.2"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ requests==1.2.0
"###
);
Ok(())
}
/// Merge `build_constraints.txt` with `build-constraint-dependencies` in pyproject.toml with an incompatible constraint.
#[test]
fn incompatible_build_constraint_merged_with_pyproject_toml() -> Result<()> {
let context = TestContext::new("3.8");
// Incompatible setuptools version in pyproject.toml, compatible in build_constraints.txt.
let constraints_txt = context.temp_dir.child("build_constraints.txt");
constraints_txt.write_str("setuptools>=40")?;
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[tool.uv]
build-constraint-dependencies = [
"setuptools==1",
]
"#,
)?;
uv_snapshot!(context.pip_install()
.arg("requests==1.2")
.arg("--build-constraint")
.arg("build_constraints.txt"), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× Failed to download and build `requests==1.2.0`
Failed to resolve requirements from `setup.py` build
No solution found when resolving: `setuptools>=40.8.0`
Because you require setuptools>=40 and setuptools==1, we can conclude that your requirements are unsatisfiable.
"###
);
// Compatible setuptools version in pyproject.toml, incompatible in build_constraints.txt.
let constraints_txt = context.temp_dir.child("build_constraints.txt");
constraints_txt.write_str("setuptools==1")?;
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[tool.uv]
build-constraint-dependencies = [
"setuptools>=40",
]
"#,
)?;
uv_snapshot!(context.pip_install()
.arg("requests==1.2")
.arg("--build-constraint")
.arg("build_constraints.txt"), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× Failed to download and build `requests==1.2.0`
Failed to resolve requirements from `setup.py` build
No solution found when resolving: `setuptools>=40.8.0`
Because you require setuptools==1 and setuptools>=40, we can conclude that your requirements are unsatisfiable.
"###
);
Ok(())
}
/// Merge `build_constraints.txt` with `build-constraint-dependencies` in pyproject.toml with a compatible constraint.
#[test]
fn compatible_build_constraint_merged_with_pyproject_toml() -> Result<()> {
let context = TestContext::new("3.8");
let constraints_txt = context.temp_dir.child("build_constraints.txt");
constraints_txt.write_str("setuptools>=40")?;
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[tool.uv]
build-constraint-dependencies = [
"setuptools>=1",
]
"#,
)?;
uv_snapshot!(context.pip_install()
.arg("requests==1.2")
.arg("--build-constraint")
.arg("build_constraints.txt"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ requests==1.2.0
"###
);
Ok(())
}
#[test] #[test]
fn install_build_isolation_package() -> Result<()> { fn install_build_isolation_package() -> Result<()> {
let context = TestContext::new("3.12"); let context = TestContext::new("3.12");

View file

@ -90,6 +90,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -261,6 +262,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -433,6 +435,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -637,6 +640,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -810,6 +814,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -962,6 +967,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -1158,6 +1164,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -1360,6 +1367,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -1616,6 +1624,7 @@ fn resolve_find_links() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -1811,6 +1820,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -1969,6 +1979,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -2169,6 +2180,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -2393,6 +2405,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -2541,6 +2554,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -2689,6 +2703,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -2839,6 +2854,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -3167,6 +3183,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -3343,6 +3360,7 @@ fn resolve_both() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -3637,6 +3655,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -3793,7 +3812,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
| |
1 | [project] 1 | [project]
| ^^^^^^^ | ^^^^^^^
unknown field `project`, expected one of `required-version`, `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `python-install-mirror`, `pypy-install-mirror`, `publish-url`, `trusted-publishing`, `check-url`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dev-dependencies`, `build-backend` unknown field `project`, expected one of `required-version`, `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `python-install-mirror`, `pypy-install-mirror`, `publish-url`, `trusted-publishing`, `check-url`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dev-dependencies`, `build-backend`
"### "###
); );
@ -3909,6 +3928,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -4060,6 +4080,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -4230,6 +4251,7 @@ fn allow_insecure_host() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -4392,6 +4414,7 @@ fn index_priority() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -4594,6 +4617,7 @@ fn index_priority() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -4802,6 +4826,7 @@ fn index_priority() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -5005,6 +5030,7 @@ fn index_priority() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -5215,6 +5241,7 @@ fn index_priority() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -5418,6 +5445,7 @@ fn index_priority() -> anyhow::Result<()> {
build_constraints: [], build_constraints: [],
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments( environments: SupportedEnvironments(
[], [],
), ),
@ -5637,6 +5665,7 @@ fn verify_hashes() -> anyhow::Result<()> {
dry_run: Disabled, dry_run: Disabled,
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
modifications: Sufficient, modifications: Sufficient,
refresh: None( refresh: None(
Timestamp( Timestamp(
@ -5779,6 +5808,7 @@ fn verify_hashes() -> anyhow::Result<()> {
dry_run: Disabled, dry_run: Disabled,
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
modifications: Sufficient, modifications: Sufficient,
refresh: None( refresh: None(
Timestamp( Timestamp(
@ -5919,6 +5949,7 @@ fn verify_hashes() -> anyhow::Result<()> {
dry_run: Disabled, dry_run: Disabled,
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
modifications: Sufficient, modifications: Sufficient,
refresh: None( refresh: None(
Timestamp( Timestamp(
@ -6061,6 +6092,7 @@ fn verify_hashes() -> anyhow::Result<()> {
dry_run: Disabled, dry_run: Disabled,
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
modifications: Sufficient, modifications: Sufficient,
refresh: None( refresh: None(
Timestamp( Timestamp(
@ -6201,6 +6233,7 @@ fn verify_hashes() -> anyhow::Result<()> {
dry_run: Disabled, dry_run: Disabled,
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
modifications: Sufficient, modifications: Sufficient,
refresh: None( refresh: None(
Timestamp( Timestamp(
@ -6342,6 +6375,7 @@ fn verify_hashes() -> anyhow::Result<()> {
dry_run: Disabled, dry_run: Disabled,
constraints_from_workspace: [], constraints_from_workspace: [],
overrides_from_workspace: [], overrides_from_workspace: [],
build_constraints_from_workspace: [],
modifications: Sufficient, modifications: Sufficient,
refresh: None( refresh: None(
Timestamp( Timestamp(

View file

@ -125,6 +125,38 @@ $ uv pip compile requirements.in --constraint constraints.txt
Note that multiple constraints can be defined in each file and multiple files can be used. Note that multiple constraints can be defined in each file and multiple files can be used.
uv will also read `constraint-dependencies` from the `pyproject.toml` at the workspace root, and
append them to those specified in the constraints file.
## Adding build constraints
Similar to `constraints`, but specifically for build-time dependencies, including those required
when building runtime dependencies.
Build constraint files are `requirements.txt`-like files that only control the _version_ of a
build-time requirement. However, including a package in a build constraints file will _not_ trigger
its installation at build time; instead, constraints apply only when the package is required as a
direct or transitive build-time dependency. Build constraints can be used to add bounds to
dependencies that are not explicitly declared as build-time dependencies of the current project.
For example, if a package defines its build dependencies as follows:
```toml title="pyproject.toml"
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
```
Build constraints could be used to ensure that a specific version of `setuptools` is used for every
package in the workspace:
```python title="build-constraints.txt"
setuptools==75.0.0
```
uv will also read `build-constraint-dependencies` from the `pyproject.toml` at the workspace root,
and append them to those specified in the build constraints file.
## Overriding dependency versions ## Overriding dependency versions
Overrides files are `requirements.txt`-like files that force a specific version of a requirement to Overrides files are `requirements.txt`-like files that force a specific version of a requirement to

View file

@ -1,4 +1,35 @@
## Project metadata ## Project metadata
### [`build-constraint-dependencies`](#build-constraint-dependencies) {: #build-constraint-dependencies }
Constraints to apply when solving build dependencies.
Build constraints are used to restrict the versions of build dependencies that are selected
when building a package during resolution or installation.
Including a package as a constraint will _not_ trigger installation of the package during
a build; instead, the package must be requested elsewhere in the project's build dependency
graph.
!!! note
In `uv lock`, `uv sync`, and `uv run`, uv will only read `build-constraint-dependencies` from
the `pyproject.toml` at the workspace root, and will ignore any declarations in other
workspace members or `uv.toml` files.
**Default value**: `[]`
**Type**: `list[str]`
**Example usage**:
```toml title="pyproject.toml"
[tool.uv]
# Ensure that the setuptools v60.0.0 is used whenever a package has a build dependency
# on setuptools.
build-constraint-dependencies = ["setuptools==60.0.0"]
```
---
### [`conflicts`](#conflicts) {: #conflicts } ### [`conflicts`](#conflicts) {: #conflicts }
Declare collections of extras or dependency groups that are conflicting Declare collections of extras or dependency groups that are conflicting

View file

@ -288,6 +288,28 @@ uv will avoid using an old version of `apache-beam`.
Constraints can also be defined for indirect dependencies using `constraints.txt` files or the Constraints can also be defined for indirect dependencies using `constraints.txt` files or the
[`constraint-dependencies`](../settings.md#constraint-dependencies) setting. [`constraint-dependencies`](../settings.md#constraint-dependencies) setting.
### Old Version of a build dependency is used
If a package fails to build because `uv` selects an incompatible or outdated version of a build-time
dependency, you can enforce constraints specifically for build dependencies. The
[`build-constraint-dependencies`](../settings.md#build-constraint-dependencies) setting (or an
analogous `build-constraints.txt` file) can be used to ensure that `uv` selects an appropriate
version of a given build requirements.
For example, the issue described in
[#5551](https://github.com/astral-sh/uv/issues/5551#issuecomment-2256055975) could be addressed by
specifying a build constraint that excludes `setuptools` version `72.0.0`:
```toml title="pyproject.toml"
[tool.uv]
# Prevent setuptools version 72.0.0 from being used as a build dependency.
build-constraint-dependencies = ["setuptools!=72.0.0"]
```
The build constraint will thus ensure that any package requiring `setuptools` during the build
process will avoid using the problematic version, preventing build failures caused by incompatible
build dependencies.
### Package is only needed for an unused platform ### Package is only needed for an unused platform
If locking fails due to building a package from a platform you do not need to support, consider If locking fails due to building a package from a platform you do not need to support, consider

10
uv.schema.json generated
View file

@ -14,6 +14,16 @@
"$ref": "#/definitions/TrustedHost" "$ref": "#/definitions/TrustedHost"
} }
}, },
"build-constraint-dependencies": {
"description": "PEP 508-style requirements, e.g., `ruff==0.5.0`, or `ruff @ https://...`.",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"cache-dir": { "cache-dir": {
"description": "Path to the cache directory.\n\nDefaults to `$HOME/Library/Caches/uv` on macOS, `$XDG_CACHE_HOME/uv` or `$HOME/.cache/uv` on Linux, and `%LOCALAPPDATA%\\uv\\cache` on Windows.", "description": "Path to the cache directory.\n\nDefaults to `$HOME/Library/Caches/uv` on macOS, `$XDG_CACHE_HOME/uv` or `$HOME/.cache/uv` on Linux, and `%LOCALAPPDATA%\\uv\\cache` on Windows.",
"type": [ "type": [