Add support for per-project build-time environment variables (#15095)

## Summary

E.g., you can now do:

```toml
[tool.uv.extra-build-variables]
flash-attn = { FLASH_ATTENTION_SKIP_CUDA_BUILD = "TRUE" }
```
This commit is contained in:
Charlie Marsh 2025-08-07 00:01:55 +01:00 committed by GitHub
parent fb518380ab
commit 3c1844ca4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 760 additions and 50 deletions

View File

@ -1,6 +1,6 @@
use std::hint::black_box;
use std::str::FromStr;
use std::hint::black_box;
use uv_bench::criterion::{Criterion, criterion_group, criterion_main, measurement::WallTime};
use uv_cache::Cache;
use uv_client::RegistryClientBuilder;
@ -92,7 +92,7 @@ mod resolver {
use uv_dispatch::{BuildDispatch, SharedState};
use uv_distribution::DistributionDatabase;
use uv_distribution_types::{
DependencyMetadata, ExtraBuildRequires, IndexLocations, RequiresPython,
DependencyMetadata, ExtraBuildRequires, ExtraBuildVariables, IndexLocations, RequiresPython,
};
use uv_install_wheel::LinkMode;
use uv_pep440::Version;
@ -144,6 +144,7 @@ mod resolver {
) -> Result<ResolverOutput> {
let build_isolation = BuildIsolation::default();
let extra_build_requires = ExtraBuildRequires::default();
let extra_build_variables = ExtraBuildVariables::default();
let build_options = BuildOptions::default();
let concurrency = Concurrency::default();
let config_settings = ConfigSettings::default();
@ -193,6 +194,7 @@ mod resolver {
&config_settings_package,
build_isolation,
&extra_build_requires,
&extra_build_variables,
LinkMode::default(),
&build_options,
&hashes,

View File

@ -355,6 +355,7 @@ pub fn resolver_options(
no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
no_build_isolation_package: Some(no_build_isolation_package),
extra_build_dependencies: None,
extra_build_variables: None,
exclude_newer: ExcludeNewer::from_args(
exclude_newer,
exclude_newer_package.unwrap_or_default(),
@ -477,6 +478,7 @@ pub fn resolver_installer_options(
Some(no_build_isolation_package)
},
extra_build_dependencies: None,
extra_build_variables: None,
exclude_newer,
exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
link_mode,

View File

@ -24,9 +24,9 @@ use uv_configuration::{BuildOutput, Concurrency};
use uv_distribution::DistributionDatabase;
use uv_distribution_filename::DistFilename;
use uv_distribution_types::{
CachedDist, DependencyMetadata, ExtraBuildRequires, Identifier, IndexCapabilities,
IndexLocations, IsBuildBackendError, Name, Requirement, Resolution, SourceDist,
VersionOrUrlRef,
CachedDist, DependencyMetadata, ExtraBuildRequires, ExtraBuildVariables, Identifier,
IndexCapabilities, IndexLocations, IsBuildBackendError, Name, Requirement, Resolution,
SourceDist, VersionOrUrlRef,
};
use uv_git::GitResolver;
use uv_installer::{Installer, Plan, Planner, Preparer, SitePackages};
@ -90,6 +90,7 @@ pub struct BuildDispatch<'a> {
dependency_metadata: &'a DependencyMetadata,
build_isolation: BuildIsolation<'a>,
extra_build_requires: &'a ExtraBuildRequires,
extra_build_variables: &'a ExtraBuildVariables,
link_mode: uv_install_wheel::LinkMode,
build_options: &'a BuildOptions,
config_settings: &'a ConfigSettings,
@ -119,6 +120,7 @@ impl<'a> BuildDispatch<'a> {
config_settings_package: &'a PackageConfigSettings,
build_isolation: BuildIsolation<'a>,
extra_build_requires: &'a ExtraBuildRequires,
extra_build_variables: &'a ExtraBuildVariables,
link_mode: uv_install_wheel::LinkMode,
build_options: &'a BuildOptions,
hasher: &'a HashStrategy,
@ -142,6 +144,7 @@ impl<'a> BuildDispatch<'a> {
config_settings_package,
build_isolation,
extra_build_requires,
extra_build_variables,
link_mode,
build_options,
hasher,
@ -227,6 +230,10 @@ impl BuildContext for BuildDispatch<'_> {
self.extra_build_requires
}
fn extra_build_variables(&self) -> &ExtraBuildVariables {
self.extra_build_variables
}
async fn resolve<'data>(
&'data self,
requirements: &'data [Requirement],
@ -312,6 +319,7 @@ impl BuildContext for BuildDispatch<'_> {
self.config_settings,
self.config_settings_package,
self.extra_build_requires(),
self.extra_build_variables,
self.cache(),
venv,
tags,
@ -446,6 +454,18 @@ impl BuildContext for BuildDispatch<'_> {
self.config_settings.clone()
};
// Get package-specific environment variables if available.
let mut environment_variables = self.build_extra_env_vars.clone();
if let Some(name) = dist_name {
if let Some(package_vars) = self.extra_build_variables.get(name) {
environment_variables.extend(
package_vars
.iter()
.map(|(key, value)| (OsString::from(key), OsString::from(value))),
);
}
}
let builder = SourceBuild::setup(
source,
subdirectory,
@ -464,7 +484,7 @@ impl BuildContext for BuildDispatch<'_> {
self.extra_build_requires(),
&build_stack,
build_kind,
self.build_extra_env_vars.clone(),
environment_variables,
build_output,
self.concurrency.builds,
self.preview,

View File

@ -1,5 +1,7 @@
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use uv_cache_key::{CacheKey, CacheKeyHasher};
use uv_normalize::PackageName;
@ -104,3 +106,52 @@ impl ExtraBuildRequires {
.collect::<Result<Self, _>>()
}
}
/// A map of extra build variables, from variable name to value.
pub type BuildVariables = BTreeMap<String, String>;
/// Extra environment variables to set during builds, on a per-package basis.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ExtraBuildVariables(BTreeMap<PackageName, BuildVariables>);
impl std::ops::Deref for ExtraBuildVariables {
type Target = BTreeMap<PackageName, BuildVariables>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for ExtraBuildVariables {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl IntoIterator for ExtraBuildVariables {
type Item = (PackageName, BuildVariables);
type IntoIter = std::collections::btree_map::IntoIter<PackageName, BuildVariables>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl FromIterator<(PackageName, BuildVariables)> for ExtraBuildVariables {
fn from_iter<T: IntoIterator<Item = (PackageName, BuildVariables)>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
}
impl CacheKey for ExtraBuildVariables {
fn cache_key(&self, state: &mut CacheKeyHasher) {
for (package, vars) in &self.0 {
package.as_str().cache_key(state);
for (key, value) in vars {
key.cache_key(state);
value.cache_key(state);
}
}
}
}

View File

@ -5,8 +5,8 @@ use uv_cache_info::CacheInfo;
use uv_cache_key::cache_digest;
use uv_configuration::{ConfigSettings, PackageConfigSettings};
use uv_distribution_types::{
DirectUrlSourceDist, DirectorySourceDist, ExtraBuildRequirement, ExtraBuildRequires,
GitSourceDist, Hashed, PathSourceDist,
BuildVariables, DirectUrlSourceDist, DirectorySourceDist, ExtraBuildRequirement,
ExtraBuildRequires, ExtraBuildVariables, GitSourceDist, Hashed, PathSourceDist,
};
use uv_normalize::PackageName;
use uv_platform_tags::Tags;
@ -25,6 +25,7 @@ pub struct BuiltWheelIndex<'a> {
config_settings: &'a ConfigSettings,
config_settings_package: &'a PackageConfigSettings,
extra_build_requires: &'a ExtraBuildRequires,
extra_build_variables: &'a ExtraBuildVariables,
}
impl<'a> BuiltWheelIndex<'a> {
@ -36,6 +37,7 @@ impl<'a> BuiltWheelIndex<'a> {
config_settings: &'a ConfigSettings,
config_settings_package: &'a PackageConfigSettings,
extra_build_requires: &'a ExtraBuildRequires,
extra_build_variables: &'a ExtraBuildVariables,
) -> Self {
Self {
cache,
@ -44,6 +46,7 @@ impl<'a> BuiltWheelIndex<'a> {
config_settings,
config_settings_package,
extra_build_requires,
extra_build_variables,
}
}
@ -75,10 +78,18 @@ impl<'a> BuiltWheelIndex<'a> {
// If there are build settings, we need to scope to a cache shard.
let config_settings = self.config_settings_for(&source_dist.name);
let extra_build_deps = self.extra_build_requires_for(&source_dist.name);
let cache_shard = if config_settings.is_empty() && extra_build_deps.is_empty() {
let extra_build_vars = self.extra_build_variables_for(&source_dist.name);
let cache_shard = if config_settings.is_empty()
&& extra_build_deps.is_empty()
&& extra_build_vars.is_none()
{
cache_shard
} else {
cache_shard.shard(cache_digest(&(&config_settings, extra_build_deps)))
cache_shard.shard(cache_digest(&(
&config_settings,
extra_build_deps,
extra_build_vars,
)))
};
Ok(self.find(&cache_shard))
@ -114,10 +125,18 @@ impl<'a> BuiltWheelIndex<'a> {
// If there are build settings, we need to scope to a cache shard.
let config_settings = self.config_settings_for(&source_dist.name);
let extra_build_deps = self.extra_build_requires_for(&source_dist.name);
let cache_shard = if config_settings.is_empty() && extra_build_deps.is_empty() {
let extra_build_vars = self.extra_build_variables_for(&source_dist.name);
let cache_shard = if config_settings.is_empty()
&& extra_build_deps.is_empty()
&& extra_build_vars.is_none()
{
cache_shard
} else {
cache_shard.shard(cache_digest(&(&config_settings, extra_build_deps)))
cache_shard.shard(cache_digest(&(
&config_settings,
extra_build_deps,
extra_build_vars,
)))
};
Ok(self
@ -164,10 +183,18 @@ impl<'a> BuiltWheelIndex<'a> {
// If there are build settings, we need to scope to a cache shard.
let config_settings = self.config_settings_for(&source_dist.name);
let extra_build_deps = self.extra_build_requires_for(&source_dist.name);
let cache_shard = if config_settings.is_empty() && extra_build_deps.is_empty() {
let extra_build_vars = self.extra_build_variables_for(&source_dist.name);
let cache_shard = if config_settings.is_empty()
&& extra_build_deps.is_empty()
&& extra_build_vars.is_none()
{
cache_shard
} else {
cache_shard.shard(cache_digest(&(&config_settings, extra_build_deps)))
cache_shard.shard(cache_digest(&(
&config_settings,
extra_build_deps,
extra_build_vars,
)))
};
Ok(self
@ -192,10 +219,18 @@ impl<'a> BuiltWheelIndex<'a> {
// If there are build settings, we need to scope to a cache shard.
let config_settings = self.config_settings_for(&source_dist.name);
let extra_build_deps = self.extra_build_requires_for(&source_dist.name);
let cache_shard = if config_settings.is_empty() && extra_build_deps.is_empty() {
let extra_build_vars = self.extra_build_variables_for(&source_dist.name);
let cache_shard = if config_settings.is_empty()
&& extra_build_deps.is_empty()
&& extra_build_vars.is_none()
{
cache_shard
} else {
cache_shard.shard(cache_digest(&(&config_settings, extra_build_deps)))
cache_shard.shard(cache_digest(&(
&config_settings,
extra_build_deps,
extra_build_vars,
)))
};
self.find(&cache_shard)
@ -274,4 +309,9 @@ impl<'a> BuiltWheelIndex<'a> {
.map(Vec::as_slice)
.unwrap_or(&[])
}
/// Determine the extra build variables for the given package name.
fn extra_build_variables_for(&self, name: &PackageName) -> Option<&BuildVariables> {
self.extra_build_variables.get(name)
}
}

View File

@ -32,8 +32,8 @@ use uv_client::{
use uv_configuration::{BuildKind, BuildOutput, ConfigSettings, SourceStrategy};
use uv_distribution_filename::{SourceDistExtension, WheelFilename};
use uv_distribution_types::{
BuildableSource, DirectorySourceUrl, ExtraBuildRequirement, GitSourceUrl, HashPolicy, Hashed,
IndexUrl, PathSourceUrl, SourceDist, SourceUrl,
BuildVariables, BuildableSource, DirectorySourceUrl, ExtraBuildRequirement, GitSourceUrl,
HashPolicy, Hashed, IndexUrl, PathSourceUrl, SourceDist, SourceUrl,
};
use uv_extract::hash::Hasher;
use uv_fs::{rename_with_retry, write_atomic};
@ -415,6 +415,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
.unwrap_or(&[])
}
/// Determine the extra build variables for the given package name.
fn extra_build_variables_for(&self, name: Option<&PackageName>) -> Option<&BuildVariables> {
name.and_then(|name| self.build_context.extra_build_variables().get(name))
}
/// Build a source distribution from a remote URL.
async fn url<'data>(
&self,
@ -452,10 +457,18 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
// If there are build settings or extra build dependencies, we need to scope to a cache shard.
let config_settings = self.config_settings_for(source.name());
let extra_build_deps = self.extra_build_dependencies_for(source.name());
let cache_shard = if config_settings.is_empty() && extra_build_deps.is_empty() {
let extra_build_variables = self.extra_build_variables_for(source.name());
let cache_shard = if config_settings.is_empty()
&& extra_build_deps.is_empty()
&& extra_build_variables.is_none()
{
cache_shard
} else {
cache_shard.shard(cache_digest(&(&config_settings, extra_build_deps)))
cache_shard.shard(cache_digest(&(
&config_settings,
extra_build_deps,
extra_build_variables,
)))
};
// If the cache contains a compatible wheel, return it.
@ -665,10 +678,18 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
// If there are build settings or extra build dependencies, we need to scope to a cache shard.
let config_settings = self.config_settings_for(source.name());
let extra_build_deps = self.extra_build_dependencies_for(source.name());
let cache_shard = if config_settings.is_empty() && extra_build_deps.is_empty() {
let extra_build_variables = self.extra_build_variables_for(source.name());
let cache_shard = if config_settings.is_empty()
&& extra_build_deps.is_empty()
&& extra_build_variables.is_none()
{
cache_shard
} else {
cache_shard.shard(cache_digest(&(&config_settings, extra_build_deps)))
cache_shard.shard(cache_digest(&(
&config_settings,
extra_build_deps,
extra_build_variables,
)))
};
let task = self
@ -843,10 +864,18 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
// If there are build settings or extra build dependencies, we need to scope to a cache shard.
let config_settings = self.config_settings_for(source.name());
let extra_build_deps = self.extra_build_dependencies_for(source.name());
let cache_shard = if config_settings.is_empty() && extra_build_deps.is_empty() {
let extra_build_variables = self.extra_build_variables_for(source.name());
let cache_shard = if config_settings.is_empty()
&& extra_build_deps.is_empty()
&& extra_build_variables.is_none()
{
cache_shard
} else {
cache_shard.shard(cache_digest(&(&config_settings, extra_build_deps)))
cache_shard.shard(cache_digest(&(
&config_settings,
extra_build_deps,
extra_build_variables,
)))
};
// If the cache contains a compatible wheel, return it.
@ -1006,10 +1035,18 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
// If there are build settings or extra build dependencies, we need to scope to a cache shard.
let config_settings = self.config_settings_for(source.name());
let extra_build_deps = self.extra_build_dependencies_for(source.name());
let cache_shard = if config_settings.is_empty() && extra_build_deps.is_empty() {
let extra_build_variables = self.extra_build_variables_for(source.name());
let cache_shard = if config_settings.is_empty()
&& extra_build_deps.is_empty()
&& extra_build_variables.is_none()
{
cache_shard
} else {
cache_shard.shard(cache_digest(&(&config_settings, extra_build_deps)))
cache_shard.shard(cache_digest(&(
&config_settings,
extra_build_deps,
extra_build_variables,
)))
};
// Otherwise, we need to build a wheel.
@ -1149,10 +1186,18 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
// If there are build settings or extra build dependencies, we need to scope to a cache shard.
let config_settings = self.config_settings_for(source.name());
let extra_build_deps = self.extra_build_dependencies_for(source.name());
let cache_shard = if config_settings.is_empty() && extra_build_deps.is_empty() {
let extra_build_variables = self.extra_build_variables_for(source.name());
let cache_shard = if config_settings.is_empty()
&& extra_build_deps.is_empty()
&& extra_build_variables.is_none()
{
cache_shard
} else {
cache_shard.shard(cache_digest(&(&config_settings, extra_build_deps)))
cache_shard.shard(cache_digest(&(
&config_settings,
extra_build_deps,
extra_build_variables,
)))
};
// If the cache contains a compatible wheel, return it.
@ -1338,10 +1383,18 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
// If there are build settings or extra build dependencies, we need to scope to a cache shard.
let config_settings = self.config_settings_for(source.name());
let extra_build_deps = self.extra_build_dependencies_for(source.name());
let cache_shard = if config_settings.is_empty() && extra_build_deps.is_empty() {
let extra_build_variables = self.extra_build_variables_for(source.name());
let cache_shard = if config_settings.is_empty()
&& extra_build_deps.is_empty()
&& extra_build_variables.is_none()
{
cache_shard
} else {
cache_shard.shard(cache_digest(&(&config_settings, extra_build_deps)))
cache_shard.shard(cache_digest(&(
&config_settings,
extra_build_deps,
extra_build_variables,
)))
};
// Otherwise, we need to build a wheel.
@ -1544,10 +1597,18 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
// If there are build settings or extra build dependencies, we need to scope to a cache shard.
let config_settings = self.config_settings_for(source.name());
let extra_build_deps = self.extra_build_dependencies_for(source.name());
let cache_shard = if config_settings.is_empty() && extra_build_deps.is_empty() {
let extra_build_variables = self.extra_build_variables_for(source.name());
let cache_shard = if config_settings.is_empty()
&& extra_build_deps.is_empty()
&& extra_build_variables.is_none()
{
cache_shard
} else {
cache_shard.shard(cache_digest(&(&config_settings, extra_build_deps)))
cache_shard.shard(cache_digest(&(
&config_settings,
extra_build_deps,
extra_build_variables,
)))
};
// If the cache contains a compatible wheel, return it.
@ -1848,10 +1909,18 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
// If there are build settings or extra build dependencies, we need to scope to a cache shard.
let config_settings = self.config_settings_for(source.name());
let extra_build_deps = self.extra_build_dependencies_for(source.name());
let cache_shard = if config_settings.is_empty() && extra_build_deps.is_empty() {
let extra_build_variables = self.extra_build_variables_for(source.name());
let cache_shard = if config_settings.is_empty()
&& extra_build_deps.is_empty()
&& extra_build_variables.is_none()
{
cache_shard
} else {
cache_shard.shard(cache_digest(&(&config_settings, extra_build_deps)))
cache_shard.shard(cache_digest(&(
&config_settings,
extra_build_deps,
extra_build_variables,
)))
};
// Otherwise, we need to build a wheel.

View File

@ -12,8 +12,9 @@ use uv_distribution::{
};
use uv_distribution_filename::WheelFilename;
use uv_distribution_types::{
BuiltDist, CachedDirectUrlDist, CachedDist, Dist, Error, ExtraBuildRequires, Hashed,
IndexLocations, InstalledDist, Name, RequirementSource, Resolution, ResolvedDist, SourceDist,
BuiltDist, CachedDirectUrlDist, CachedDist, Dist, Error, ExtraBuildRequires,
ExtraBuildVariables, Hashed, IndexLocations, InstalledDist, Name, RequirementSource,
Resolution, ResolvedDist, SourceDist,
};
use uv_fs::Simplified;
use uv_platform_tags::{IncompatibleTag, TagCompatibility, Tags};
@ -57,6 +58,7 @@ impl<'a> Planner<'a> {
config_settings: &ConfigSettings,
config_settings_package: &PackageConfigSettings,
extra_build_requires: &ExtraBuildRequires,
extra_build_variables: &ExtraBuildVariables,
cache: &Cache,
venv: &PythonEnvironment,
tags: &Tags,
@ -71,6 +73,7 @@ impl<'a> Planner<'a> {
config_settings,
config_settings_package,
extra_build_requires,
extra_build_variables,
);
let mut cached = vec![];

View File

@ -7,7 +7,9 @@ use uv_configuration::{
ConfigSettings, ExportFormat, IndexStrategy, KeyringProviderType, PackageConfigSettings,
RequiredVersion, TargetTriple, TrustedPublishing,
};
use uv_distribution_types::{Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex};
use uv_distribution_types::{
ExtraBuildVariables, Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex,
};
use uv_install_wheel::LinkMode;
use uv_pypi_types::{SchemaConflicts, SupportedEnvironments};
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
@ -235,3 +237,32 @@ impl Combine for Option<ExtraBuildDependencies> {
}
}
}
impl Combine for ExtraBuildVariables {
fn combine(mut self, other: Self) -> Self {
for (key, value) in other {
match self.entry(key) {
std::collections::btree_map::Entry::Occupied(mut entry) => {
// Combine the maps, with self taking precedence
let existing = entry.get_mut();
for (k, v) in value {
existing.entry(k).or_insert(v);
}
}
std::collections::btree_map::Entry::Vacant(entry) => {
entry.insert(value);
}
}
}
self
}
}
impl Combine for Option<ExtraBuildVariables> {
fn combine(self, other: Self) -> Self {
match (self, other) {
(Some(a), Some(b)) => Some(a.combine(b)),
(a, b) => a.or(b),
}
}
}

View File

@ -318,6 +318,7 @@ fn warn_uv_toml_masked_fields(options: &Options) {
no_build_isolation,
no_build_isolation_package,
extra_build_dependencies,
extra_build_variables,
exclude_newer,
exclude_newer_package,
link_mode,
@ -449,6 +450,9 @@ fn warn_uv_toml_masked_fields(options: &Options) {
if extra_build_dependencies.is_some() {
masked_fields.push("extra-build-dependencies");
}
if extra_build_variables.is_some() {
masked_fields.push("extra-build-variables");
}
if exclude_newer.is_some() {
masked_fields.push("exclude-newer");
}

View File

@ -8,11 +8,11 @@ use uv_configuration::{
PackageNameSpecifier, RequiredVersion, TargetTriple, TrustedHost, TrustedPublishing,
};
use uv_distribution_types::{
Index, IndexUrl, IndexUrlError, PipExtraIndex, PipFindLinks, PipIndex, StaticMetadata,
ExtraBuildVariables, Index, IndexUrl, IndexUrlError, PipExtraIndex, PipFindLinks, PipIndex,
StaticMetadata,
};
use uv_install_wheel::LinkMode;
use uv_macros::{CombineOptions, OptionsMetadata};
use uv_normalize::{ExtraName, PackageName, PipGroupName};
use uv_pep508::Requirement;
use uv_pypi_types::{SupportedEnvironments, VerbatimParsedUrl};
@ -377,6 +377,7 @@ pub struct ResolverOptions {
pub no_build_isolation: Option<bool>,
pub no_build_isolation_package: Option<Vec<PackageName>>,
pub extra_build_dependencies: Option<ExtraBuildDependencies>,
pub extra_build_variables: Option<ExtraBuildVariables>,
pub no_sources: Option<bool>,
}
@ -643,6 +644,19 @@ pub struct ResolverInstallerOptions {
"#
)]
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>,
/// Limit candidate packages to those that were uploaded prior to a given point in time.
///
/// Accepts a superset of [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html) (e.g.,
@ -1164,6 +1178,19 @@ pub struct PipOptions {
"#
)]
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#"
[extra-build-variables]
flash-attn = { FLASH_ATTENTION_SKIP_CUDA_BUILD = "TRUE" }
"#
)]
pub extra_build_variables: Option<ExtraBuildVariables>,
/// Validate the Python environment, to detect packages with missing dependencies and other
/// issues.
#[option(
@ -1749,6 +1776,7 @@ impl From<ResolverInstallerOptions> for ResolverOptions {
no_build_isolation: value.no_build_isolation,
no_build_isolation_package: value.no_build_isolation_package,
extra_build_dependencies: value.extra_build_dependencies,
extra_build_variables: value.extra_build_variables,
no_sources: value.no_sources,
}
}
@ -1815,6 +1843,7 @@ pub struct ToolOptions {
pub no_build_isolation: Option<bool>,
pub no_build_isolation_package: Option<Vec<PackageName>>,
pub extra_build_dependencies: Option<ExtraBuildDependencies>,
pub extra_build_variables: Option<ExtraBuildVariables>,
pub exclude_newer: Option<ExcludeNewerTimestamp>,
pub exclude_newer_package: Option<ExcludeNewerPackage>,
pub link_mode: Option<LinkMode>,
@ -1845,6 +1874,7 @@ impl From<ResolverInstallerOptions> for ToolOptions {
no_build_isolation: value.no_build_isolation,
no_build_isolation_package: value.no_build_isolation_package,
extra_build_dependencies: value.extra_build_dependencies,
extra_build_variables: value.extra_build_variables,
exclude_newer: value.exclude_newer,
exclude_newer_package: value.exclude_newer_package,
link_mode: value.link_mode,
@ -1877,6 +1907,7 @@ impl From<ToolOptions> for ResolverInstallerOptions {
no_build_isolation: value.no_build_isolation,
no_build_isolation_package: value.no_build_isolation_package,
extra_build_dependencies: value.extra_build_dependencies,
extra_build_variables: value.extra_build_variables,
exclude_newer: value.exclude_newer,
exclude_newer_package: value.exclude_newer_package,
link_mode: value.link_mode,
@ -1932,6 +1963,7 @@ pub struct OptionsWire {
no_build_isolation: Option<bool>,
no_build_isolation_package: Option<Vec<PackageName>>,
extra_build_dependencies: Option<ExtraBuildDependencies>,
extra_build_variables: Option<ExtraBuildVariables>,
exclude_newer: Option<ExcludeNewerTimestamp>,
exclude_newer_package: Option<ExcludeNewerPackage>,
link_mode: Option<LinkMode>,
@ -2052,6 +2084,7 @@ impl From<OptionsWire> for Options {
default_groups,
dependency_groups,
extra_build_dependencies,
extra_build_variables,
dev_dependencies,
managed,
package,
@ -2093,6 +2126,7 @@ impl From<OptionsWire> for Options {
no_build_isolation,
no_build_isolation_package,
extra_build_dependencies,
extra_build_variables,
exclude_newer,
exclude_newer_package,
link_mode,

View File

@ -12,8 +12,9 @@ use uv_configuration::{
};
use uv_distribution_filename::DistFilename;
use uv_distribution_types::{
CachedDist, DependencyMetadata, DistributionId, ExtraBuildRequires, IndexCapabilities,
IndexLocations, InstalledDist, IsBuildBackendError, Requirement, Resolution, SourceDist,
CachedDist, DependencyMetadata, DistributionId, ExtraBuildRequires, ExtraBuildVariables,
IndexCapabilities, IndexLocations, InstalledDist, IsBuildBackendError, Requirement, Resolution,
SourceDist,
};
use uv_git::GitResolver;
use uv_pep508::PackageName;
@ -106,6 +107,9 @@ pub trait BuildContext {
/// Get the extra build requirements.
fn extra_build_requires(&self) -> &ExtraBuildRequires;
/// Get the extra build variables.
fn extra_build_variables(&self) -> &ExtraBuildVariables;
/// Resolve the given requirements into a ready-to-install set of package versions.
fn resolve<'a>(
&'a self,

View File

@ -20,8 +20,9 @@ use rustc_hash::{FxBuildHasher, FxHashSet};
use serde::de::{IntoDeserializer, SeqAccess};
use serde::{Deserialize, Deserializer, Serialize};
use thiserror::Error;
use uv_build_backend::BuildBackendSettings;
use uv_distribution_types::{Index, IndexName, RequirementSource};
use uv_distribution_types::{ExtraBuildVariables, Index, IndexName, RequirementSource};
use uv_fs::{PortablePathBuf, relative_to};
use uv_git_types::GitReference;
use uv_macros::OptionsMetadata;
@ -442,6 +443,20 @@ pub struct ToolUv {
)]
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

View File

@ -1971,6 +1971,7 @@ mod tests {
"default-groups": null,
"dependency-groups": null,
"extra-build-dependencies": null,
"extra-build-variables": null,
"dev-dependencies": null,
"override-dependencies": null,
"constraint-dependencies": null,
@ -2072,6 +2073,7 @@ mod tests {
"default-groups": null,
"dependency-groups": null,
"extra-build-dependencies": null,
"extra-build-variables": null,
"dev-dependencies": null,
"override-dependencies": null,
"constraint-dependencies": null,
@ -2286,6 +2288,7 @@ mod tests {
"default-groups": null,
"dependency-groups": null,
"extra-build-dependencies": null,
"extra-build-variables": null,
"dev-dependencies": null,
"override-dependencies": null,
"constraint-dependencies": null,
@ -2396,6 +2399,7 @@ mod tests {
"default-groups": null,
"dependency-groups": null,
"extra-build-dependencies": null,
"extra-build-variables": null,
"dev-dependencies": null,
"override-dependencies": null,
"constraint-dependencies": null,
@ -2519,6 +2523,7 @@ mod tests {
"default-groups": null,
"dependency-groups": null,
"extra-build-dependencies": null,
"extra-build-variables": null,
"dev-dependencies": null,
"override-dependencies": null,
"constraint-dependencies": null,
@ -2616,6 +2621,7 @@ mod tests {
"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

@ -24,7 +24,7 @@ use uv_distribution_filename::{
DistFilename, SourceDistExtension, SourceDistFilename, WheelFilename,
};
use uv_distribution_types::{
DependencyMetadata, Index, IndexLocations, RequiresPython, SourceDist,
DependencyMetadata, ExtraBuildVariables, Index, IndexLocations, RequiresPython, SourceDist,
};
use uv_fs::{Simplified, relative_to};
use uv_install_wheel::LinkMode;
@ -203,6 +203,7 @@ async fn build_impl(
no_build_isolation,
no_build_isolation_package,
extra_build_dependencies,
extra_build_variables,
exclude_newer,
link_mode,
upgrade: _,
@ -350,6 +351,7 @@ async fn build_impl(
*no_build_isolation,
no_build_isolation_package,
extra_build_dependencies,
extra_build_variables,
*index_strategy,
*keyring_provider,
exclude_newer.clone(),
@ -429,6 +431,7 @@ async fn build_package(
no_build_isolation: bool,
no_build_isolation_package: &[PackageName],
extra_build_dependencies: &ExtraBuildDependencies,
extra_build_variables: &ExtraBuildVariables,
index_strategy: IndexStrategy,
keyring_provider: KeyringProviderType,
exclude_newer: ExcludeNewer,
@ -583,6 +586,7 @@ async fn build_package(
config_settings_package,
build_isolation,
&extra_build_requires,
extra_build_variables,
link_mode,
build_options,
&hasher,

View File

@ -21,8 +21,9 @@ use uv_configuration::{KeyringProviderType, TargetTriple};
use uv_dispatch::{BuildDispatch, SharedState};
use uv_distribution::LoweredExtraBuildDependencies;
use uv_distribution_types::{
DependencyMetadata, HashGeneration, Index, IndexLocations, NameRequirementSpecification,
Origin, Requirement, RequiresPython, UnresolvedRequirementSpecification, Verbatim,
DependencyMetadata, ExtraBuildVariables, HashGeneration, Index, IndexLocations,
NameRequirementSpecification, Origin, Requirement, RequiresPython,
UnresolvedRequirementSpecification, Verbatim,
};
use uv_fs::{CWD, Simplified};
use uv_git::ResolvedRepositoryReference;
@ -98,6 +99,7 @@ pub(crate) async fn pip_compile(
no_build_isolation: bool,
no_build_isolation_package: Vec<PackageName>,
extra_build_dependencies: &ExtraBuildDependencies,
extra_build_variables: &ExtraBuildVariables,
build_options: BuildOptions,
mut python_version: Option<PythonVersion>,
python_platform: Option<TargetTriple>,
@ -502,6 +504,7 @@ pub(crate) async fn pip_compile(
&config_settings_package,
build_isolation,
&extra_build_requires,
extra_build_variables,
link_mode,
&build_options,
&build_hashes,

View File

@ -17,8 +17,8 @@ use uv_configuration::{KeyringProviderType, TargetTriple};
use uv_dispatch::{BuildDispatch, SharedState};
use uv_distribution::LoweredExtraBuildDependencies;
use uv_distribution_types::{
DependencyMetadata, Index, IndexLocations, NameRequirementSpecification, Origin, Requirement,
Resolution, UnresolvedRequirementSpecification,
DependencyMetadata, ExtraBuildVariables, Index, IndexLocations, NameRequirementSpecification,
Origin, Requirement, Resolution, UnresolvedRequirementSpecification,
};
use uv_fs::Simplified;
use uv_install_wheel::LinkMode;
@ -81,6 +81,7 @@ pub(crate) async fn pip_install(
no_build_isolation: bool,
no_build_isolation_package: Vec<PackageName>,
extra_build_dependencies: &ExtraBuildDependencies,
extra_build_variables: &ExtraBuildVariables,
build_options: BuildOptions,
modifications: Modifications,
python_version: Option<PythonVersion>,
@ -445,6 +446,7 @@ pub(crate) async fn pip_install(
config_settings_package,
build_isolation,
&extra_build_requires,
extra_build_variables,
link_mode,
&build_options,
&build_hasher,
@ -577,6 +579,7 @@ pub(crate) async fn pip_install(
config_settings_package,
build_isolation,
&extra_build_requires,
extra_build_variables,
link_mode,
&build_options,
&hasher,

View File

@ -465,6 +465,7 @@ pub(crate) async fn install(
build_dispatch.config_settings(),
build_dispatch.config_settings_package(),
build_dispatch.extra_build_requires(),
build_dispatch.extra_build_variables(),
cache,
venv,
tags,

View File

@ -15,7 +15,9 @@ use uv_configuration::{
use uv_configuration::{KeyringProviderType, TargetTriple};
use uv_dispatch::{BuildDispatch, SharedState};
use uv_distribution::LoweredExtraBuildDependencies;
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, Origin, Resolution};
use uv_distribution_types::{
DependencyMetadata, ExtraBuildVariables, Index, IndexLocations, Origin, Resolution,
};
use uv_fs::Simplified;
use uv_install_wheel::LinkMode;
use uv_installer::SitePackages;
@ -70,6 +72,7 @@ pub(crate) async fn pip_sync(
no_build_isolation: bool,
no_build_isolation_package: Vec<PackageName>,
extra_build_dependencies: &ExtraBuildDependencies,
extra_build_variables: &ExtraBuildVariables,
build_options: BuildOptions,
python_version: Option<PythonVersion>,
python_platform: Option<TargetTriple>,
@ -380,6 +383,7 @@ pub(crate) async fn pip_sync(
config_settings_package,
build_isolation,
&extra_build_requires,
extra_build_variables,
link_mode,
&build_options,
&build_hasher,
@ -514,6 +518,7 @@ pub(crate) async fn pip_sync(
config_settings_package,
build_isolation,
&extra_build_requires,
extra_build_variables,
link_mode,
&build_options,
&build_hasher,

View File

@ -452,6 +452,7 @@ pub(crate) async fn add(
.into_inner();
// Create a build dispatch.
let extra_build_variables = settings.resolver.extra_build_variables.clone();
let build_dispatch = BuildDispatch::new(
&client,
cache,
@ -466,6 +467,7 @@ pub(crate) async fn add(
&settings.resolver.config_settings_package,
build_isolation,
&extra_build_requires,
&extra_build_variables,
settings.resolver.link_mode,
&settings.resolver.build_options,
&build_hasher,

View File

@ -436,6 +436,7 @@ async fn do_lock(
no_build_isolation,
no_build_isolation_package,
extra_build_dependencies,
extra_build_variables,
exclude_newer,
link_mode,
upgrade,
@ -706,6 +707,7 @@ async fn do_lock(
config_settings_package,
build_isolation,
&extra_build_requires,
extra_build_variables,
*link_mode,
build_options,
&build_hasher,

View File

@ -1700,6 +1700,7 @@ pub(crate) async fn resolve_names(
no_build_isolation,
no_build_isolation_package,
extra_build_dependencies,
extra_build_variables,
prerelease: _,
resolution: _,
sources,
@ -1767,6 +1768,7 @@ pub(crate) async fn resolve_names(
config_settings_package,
build_isolation,
&extra_build_requires,
extra_build_variables,
*link_mode,
build_options,
&build_hasher,
@ -1860,6 +1862,7 @@ pub(crate) async fn resolve_environment(
no_build_isolation,
no_build_isolation_package,
extra_build_dependencies,
extra_build_variables,
exclude_newer,
link_mode,
upgrade: _,
@ -1982,6 +1985,7 @@ pub(crate) async fn resolve_environment(
config_settings_package,
build_isolation,
&extra_build_requires,
extra_build_variables,
*link_mode,
build_options,
&build_hasher,
@ -2050,6 +2054,7 @@ pub(crate) async fn sync_environment(
no_build_isolation,
no_build_isolation_package,
extra_build_dependencies,
extra_build_variables,
exclude_newer,
link_mode,
compile_bytecode,
@ -2127,6 +2132,7 @@ pub(crate) async fn sync_environment(
config_settings_package,
build_isolation,
&extra_build_requires,
extra_build_variables,
link_mode,
build_options,
&build_hasher,
@ -2221,6 +2227,7 @@ pub(crate) async fn update_environment(
no_build_isolation,
no_build_isolation_package,
extra_build_dependencies: _,
extra_build_variables,
prerelease,
resolution,
sources,
@ -2351,6 +2358,7 @@ pub(crate) async fn update_environment(
config_settings_package,
build_isolation,
&extra_build_requires,
extra_build_variables,
*link_mode,
build_options,
&build_hasher,

View File

@ -586,6 +586,7 @@ pub(super) async fn do_sync(
no_build_isolation,
no_build_isolation_package,
extra_build_dependencies,
extra_build_variables,
exclude_newer,
link_mode,
compile_bytecode,
@ -631,6 +632,7 @@ pub(super) async fn do_sync(
no_build_isolation,
no_build_isolation_package: no_build_isolation_package.to_vec(),
extra_build_dependencies: extra_build_dependencies.clone(),
extra_build_variables: extra_build_variables.clone(),
prerelease: PrereleaseMode::default(),
resolution: ResolutionMode::default(),
sources,
@ -773,6 +775,7 @@ pub(super) async fn do_sync(
config_settings_package,
build_isolation,
&extra_build_requires,
extra_build_variables,
link_mode,
build_options,
&build_hasher,

View File

@ -204,6 +204,7 @@ pub(crate) async fn tree(
no_build_isolation: _,
no_build_isolation_package: _,
extra_build_dependencies: _,
extra_build_variables: _,
exclude_newer: _,
link_mode: _,
upgrade: _,

View File

@ -267,6 +267,7 @@ pub(crate) async fn venv(
// Do not allow builds
let build_options = BuildOptions::new(NoBinary::None, NoBuild::All);
let extra_build_requires = ExtraBuildRequires::default();
let extra_build_variables = uv_distribution_types::ExtraBuildVariables::default();
// Prep the build context.
let build_dispatch = BuildDispatch::new(
&client,
@ -282,6 +283,7 @@ pub(crate) async fn venv(
&config_settings_package,
BuildIsolation::Isolated,
&extra_build_requires,
&extra_build_variables,
link_mode,
&build_options,
&build_hasher,

View File

@ -517,6 +517,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
args.settings.no_build_isolation,
args.settings.no_build_isolation_package,
&args.settings.extra_build_dependencies,
&args.settings.extra_build_variables,
args.settings.build_options,
args.settings.python_version,
args.settings.python_platform,
@ -595,6 +596,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
args.settings.no_build_isolation,
args.settings.no_build_isolation_package,
&args.settings.extra_build_dependencies,
&args.settings.extra_build_variables,
args.settings.build_options,
args.settings.python_version,
args.settings.python_platform,
@ -738,6 +740,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
args.settings.no_build_isolation,
args.settings.no_build_isolation_package,
&args.settings.extra_build_dependencies,
&args.settings.extra_build_variables,
args.settings.build_options,
args.modifications,
args.settings.python_version,

View File

@ -27,7 +27,9 @@ use uv_configuration::{
Reinstall, RequiredVersion, SourceStrategy, TargetTriple, TrustedHost, TrustedPublishing,
Upgrade, VersionControlSystem,
};
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, IndexUrl, Requirement};
use uv_distribution_types::{
DependencyMetadata, ExtraBuildVariables, Index, IndexLocations, IndexUrl, Requirement,
};
use uv_install_wheel::LinkMode;
use uv_normalize::{PackageName, PipGroupName};
use uv_pep508::{ExtraName, MarkerTree, RequirementOrigin};
@ -2721,6 +2723,7 @@ pub(crate) struct InstallerSettingsRef<'a> {
pub(crate) no_build_isolation: bool,
pub(crate) no_build_isolation_package: &'a [PackageName],
pub(crate) extra_build_dependencies: &'a ExtraBuildDependencies,
pub(crate) extra_build_variables: &'a ExtraBuildVariables,
pub(crate) exclude_newer: ExcludeNewer,
pub(crate) link_mode: LinkMode,
pub(crate) compile_bytecode: bool,
@ -2748,6 +2751,7 @@ pub(crate) struct ResolverSettings {
pub(crate) no_build_isolation: bool,
pub(crate) no_build_isolation_package: Vec<PackageName>,
pub(crate) extra_build_dependencies: ExtraBuildDependencies,
pub(crate) extra_build_variables: ExtraBuildVariables,
pub(crate) prerelease: PrereleaseMode,
pub(crate) resolution: ResolutionMode,
pub(crate) sources: SourceStrategy,
@ -2801,6 +2805,7 @@ impl From<ResolverOptions> for ResolverSettings {
no_build_isolation: value.no_build_isolation.unwrap_or_default(),
no_build_isolation_package: value.no_build_isolation_package.unwrap_or_default(),
extra_build_dependencies: value.extra_build_dependencies.unwrap_or_default(),
extra_build_variables: value.extra_build_variables.unwrap_or_default(),
exclude_newer: value.exclude_newer,
link_mode: value.link_mode.unwrap_or_default(),
sources: SourceStrategy::from_args(value.no_sources.unwrap_or_default()),
@ -2899,6 +2904,7 @@ impl From<ResolverInstallerOptions> for ResolverInstallerSettings {
no_build_isolation: value.no_build_isolation.unwrap_or_default(),
no_build_isolation_package: value.no_build_isolation_package.unwrap_or_default(),
extra_build_dependencies: value.extra_build_dependencies.unwrap_or_default(),
extra_build_variables: value.extra_build_variables.unwrap_or_default(),
prerelease: value.prerelease.unwrap_or_default(),
resolution: value.resolution.unwrap_or_default(),
sources: SourceStrategy::from_args(value.no_sources.unwrap_or_default()),
@ -2942,6 +2948,7 @@ pub(crate) struct PipSettings {
pub(crate) no_build_isolation: bool,
pub(crate) no_build_isolation_package: Vec<PackageName>,
pub(crate) extra_build_dependencies: ExtraBuildDependencies,
pub(crate) extra_build_variables: ExtraBuildVariables,
pub(crate) build_options: BuildOptions,
pub(crate) allow_empty_requirements: bool,
pub(crate) strict: bool,
@ -3010,6 +3017,7 @@ impl PipSettings {
no_build_isolation,
no_build_isolation_package,
extra_build_dependencies,
extra_build_variables,
strict,
extra,
all_extras,
@ -3070,6 +3078,7 @@ impl PipSettings {
no_build_isolation: top_level_no_build_isolation,
no_build_isolation_package: top_level_no_build_isolation_package,
extra_build_dependencies: top_level_extra_build_dependencies,
extra_build_variables: top_level_extra_build_variables,
exclude_newer: top_level_exclude_newer,
link_mode: top_level_link_mode,
compile_bytecode: top_level_compile_bytecode,
@ -3108,6 +3117,7 @@ impl PipSettings {
no_build_isolation_package.combine(top_level_no_build_isolation_package);
let extra_build_dependencies =
extra_build_dependencies.combine(top_level_extra_build_dependencies);
let extra_build_variables = extra_build_variables.combine(top_level_extra_build_variables);
let exclude_newer = args
.exclude_newer
.combine(exclude_newer)
@ -3215,6 +3225,10 @@ impl PipSettings {
.extra_build_dependencies
.combine(extra_build_dependencies)
.unwrap_or_default(),
extra_build_variables: args
.extra_build_variables
.combine(extra_build_variables)
.unwrap_or_default(),
config_setting: args
.config_settings
.combine(config_settings)
@ -3323,6 +3337,7 @@ impl<'a> From<&'a ResolverInstallerSettings> for InstallerSettingsRef<'a> {
no_build_isolation: settings.resolver.no_build_isolation,
no_build_isolation_package: &settings.resolver.no_build_isolation_package,
extra_build_dependencies: &settings.resolver.extra_build_dependencies,
extra_build_variables: &settings.resolver.extra_build_variables,
exclude_newer: settings.resolver.exclude_newer.clone(),
link_mode: settings.resolver.link_mode,
compile_bytecode: settings.compile_bytecode,

View File

@ -187,6 +187,9 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -384,6 +387,9 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -582,6 +588,9 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -812,6 +821,9 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -977,6 +989,9 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -1186,6 +1201,9 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -1443,6 +1461,9 @@ fn resolve_index_url() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -1710,6 +1731,9 @@ fn resolve_index_url() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -1932,6 +1956,9 @@ fn resolve_find_links() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -2119,6 +2146,9 @@ fn resolve_top_level() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -2366,6 +2396,9 @@ fn resolve_top_level() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -2596,6 +2629,9 @@ fn resolve_top_level() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -2782,6 +2818,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -2952,6 +2991,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -3122,6 +3164,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -3294,6 +3339,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -3457,6 +3505,7 @@ fn resolve_tool() -> anyhow::Result<()> {
no_build_isolation: None,
no_build_isolation_package: None,
extra_build_dependencies: None,
extra_build_variables: None,
exclude_newer: None,
exclude_newer_package: None,
link_mode: Some(
@ -3508,6 +3557,9 @@ fn resolve_tool() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
prerelease: IfNecessaryOrExplicit,
resolution: LowestDirect,
sources: Enabled,
@ -3669,6 +3721,9 @@ fn resolve_poetry_toml() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -3907,6 +3962,9 @@ fn resolve_both() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -4149,6 +4207,9 @@ fn resolve_both_special_fields() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -4470,6 +4531,9 @@ fn resolve_config_file() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -4555,7 +4619,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
|
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`, `config-settings-package`, `no-build-isolation`, `no-build-isolation-package`, `extra-build-dependencies`, `exclude-newer`, `exclude-newer-package`, `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`, `python-downloads-json-url`, `publish-url`, `trusted-publishing`, `check-url`, `add-bounds`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dependency-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`, `config-settings-package`, `no-build-isolation`, `no-build-isolation-package`, `extra-build-dependencies`, `extra-build-variables`, `exclude-newer`, `exclude-newer-package`, `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`, `python-downloads-json-url`, `publish-url`, `trusted-publishing`, `check-url`, `add-bounds`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dependency-groups`, `dev-dependencies`, `build-backend`
"
);
@ -4733,6 +4797,9 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -4906,6 +4973,9 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -5098,6 +5168,9 @@ fn allow_insecure_host() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -5351,6 +5424,9 @@ fn index_priority() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -5583,6 +5659,9 @@ fn index_priority() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -5821,6 +5900,9 @@ fn index_priority() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -6054,6 +6136,9 @@ fn index_priority() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -6294,6 +6379,9 @@ fn index_priority() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -6527,6 +6615,9 @@ fn index_priority() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -6704,6 +6795,9 @@ fn verify_hashes() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -6867,6 +6961,9 @@ fn verify_hashes() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -7028,6 +7125,9 @@ fn verify_hashes() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -7191,6 +7291,9 @@ fn verify_hashes() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -7352,6 +7455,9 @@ fn verify_hashes() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -7514,6 +7620,9 @@ fn verify_hashes() -> anyhow::Result<()> {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
build_options: BuildOptions {
no_binary: None,
no_build: None,
@ -7685,6 +7794,9 @@ fn preview_features() {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
prerelease: IfNecessaryOrExplicit,
resolution: Highest,
sources: Enabled,
@ -7795,6 +7907,9 @@ fn preview_features() {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
prerelease: IfNecessaryOrExplicit,
resolution: Highest,
sources: Enabled,
@ -7905,6 +8020,9 @@ fn preview_features() {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
prerelease: IfNecessaryOrExplicit,
resolution: Highest,
sources: Enabled,
@ -8015,6 +8133,9 @@ fn preview_features() {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
prerelease: IfNecessaryOrExplicit,
resolution: Highest,
sources: Enabled,
@ -8125,6 +8246,9 @@ fn preview_features() {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
prerelease: IfNecessaryOrExplicit,
resolution: Highest,
sources: Enabled,
@ -8237,6 +8361,9 @@ fn preview_features() {
extra_build_dependencies: ExtraBuildDependencies(
{},
),
extra_build_variables: ExtraBuildVariables(
{},
),
prerelease: IfNecessaryOrExplicit,
resolution: Highest,
sources: Enabled,

View File

@ -12766,3 +12766,141 @@ fn sync_build_dependencies_respect_locked_versions() -> Result<()> {
Ok(())
}
#[test]
fn sync_extra_build_variables() -> Result<()> {
let context = TestContext::new("3.12").with_filtered_counts();
// Create a build backend that asserts that `EXPECTED_ANYIO_VERSION` matches the installed version of `anyio`.
let build_backend = context.temp_dir.child("build_backend.py");
build_backend.write_str(indoc! {r#"
import os
import sys
from hatchling.build import *
expected_version = os.environ.get("EXPECTED_ANYIO_VERSION", "")
if not expected_version:
print("`EXPECTED_ANYIO_VERSION` not set", file=sys.stderr)
sys.exit(1)
try:
import anyio
except ModuleNotFoundError:
print("Missing `anyio` module", file=sys.stderr)
sys.exit(1)
from importlib.metadata import version
anyio_version = version("anyio")
if not anyio_version.startswith(expected_version):
print(f"Expected `anyio` version {expected_version} but got {anyio_version}", file=sys.stderr)
sys.exit(1)
print(f"Found expected `anyio` version {anyio_version}", file=sys.stderr)
"#})?;
// Create a project that will resolve to a non-latest version of `anyio`
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! {r#"
[project]
name = "parent"
version = "0.1.0"
requires-python = ">=3.9"
[build-system]
requires = ["hatchling", "anyio"]
backend-path = ["."]
build-backend = "build_backend"
"#})?;
context.temp_dir.child("src/parent/__init__.py").touch()?;
uv_snapshot!(context.filters(), context.lock(), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved [N] packages in [TIME]
");
// Ensure our build backend is checking the version correctly.
uv_snapshot!(context.filters(), context.sync().env("EXPECTED_ANYIO_VERSION", "3.0"), @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved [N] packages in [TIME]
× Failed to build `parent @ file://[TEMP_DIR]/`
The build backend returned an error
Call to `build_backend.build_editable` failed (exit status: 1)
[stderr]
Expected `anyio` version 3.0 but got 4.3.0
hint: This usually indicates a problem with the package or the build environment.
");
// Set the variable in TOML (to an incorrect value).
pyproject_toml.write_str(indoc! {r#"
[project]
name = "parent"
version = "0.1.0"
requires-python = ">=3.9"
[build-system]
requires = ["hatchling", "anyio"]
backend-path = ["."]
build-backend = "build_backend"
[tool.uv.extra-build-variables]
parent = { EXPECTED_ANYIO_VERSION = "3.0" }
"#})?;
uv_snapshot!(context.filters(), context.sync(), @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved [N] packages in [TIME]
× Failed to build `parent @ file://[TEMP_DIR]/`
The build backend returned an error
Call to `build_backend.build_editable` failed (exit status: 1)
[stderr]
Expected `anyio` version 3.0 but got 4.3.0
hint: This usually indicates a problem with the package or the build environment.
");
// Set the variable in TOML (to a correct value).
pyproject_toml.write_str(indoc! {r#"
[project]
name = "parent"
version = "0.1.0"
requires-python = ">=3.9"
[build-system]
requires = ["hatchling", "anyio"]
backend-path = ["."]
build-backend = "build_backend"
[tool.uv.extra-build-variables]
parent = { EXPECTED_ANYIO_VERSION = "4.3.0" }
"#})?;
uv_snapshot!(context.filters(), context.sync(), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved [N] packages in [TIME]
Prepared [N] packages in [TIME]
Installed [N] packages in [TIME]
+ parent==0.1.0 (from file://[TEMP_DIR]/)
");
Ok(())
}

View File

@ -224,6 +224,27 @@ 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.
@ -1179,6 +1200,34 @@ additional packages. This is useful for packages that assume the presence of pac
---
### [`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**:
=== "pyproject.toml"
```toml
[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-index-url`](#extra-index-url) {: #extra-index-url }
Extra URLs of package indexes to use, in addition to `--index-url`.
@ -2700,6 +2749,37 @@ additional packages. This is useful for packages that assume the presence of pac
---
#### [`extra-build-variables`](#pip_extra-build-variables) {: #pip_extra-build-variables }
<span id="extra-build-variables"></span>
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**:
=== "pyproject.toml"
```toml
[tool.uv.pip]
[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-index-url`](#pip_extra-index-url) {: #pip_extra-index-url }
<span id="extra-index-url"></span>

32
uv.schema.json generated
View File

@ -236,6 +236,17 @@
}
]
},
"extra-build-variables": {
"description": "Extra environment variables to set when building certain packages.\n\nEnvironment variables will be added to the environment when building the\nspecified packages.",
"anyOf": [
{
"$ref": "#/definitions/ExtraBuildVariables"
},
{
"type": "null"
}
]
},
"extra-index-url": {
"description": "Extra URLs of package indexes to use, in addition to `--index-url`.\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)\n(the simple repository API), or a local directory laid out in the same format.\n\nAll indexes provided via this flag take priority over the index specified by\n[`index_url`](#index-url) or [`index`](#index) with `default = true`. When multiple indexes\nare provided, earlier values take priority.\n\nTo control uv's resolution strategy when multiple indexes are present, see\n[`index_strategy`](#index-strategy).\n\n(Deprecated: use `index` instead.)",
"type": [
@ -915,6 +926,16 @@
}
]
},
"ExtraBuildVariables": {
"description": "Extra environment variables to set during builds, on a per-package basis.",
"type": "object",
"additionalProperties": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"ExtraName": {
"description": "The normalized name of an extra dependency.\n\nConverts the name to lowercase and collapses runs of `-`, `_`, and `.` down to a single `-`.\nFor example, `---`, `.`, and `__` are all converted to a single `-`.\n\nSee:\n- <https://peps.python.org/pep-0685/#specification/>\n- <https://packaging.python.org/en/latest/specifications/name-normalization/>",
"type": "string"
@ -1368,6 +1389,17 @@
}
]
},
"extra-build-variables": {
"description": "Extra environment variables to set when building certain packages.\n\nEnvironment variables will be added to the environment when building the\nspecified packages.",
"anyOf": [
{
"$ref": "#/definitions/ExtraBuildVariables"
},
{
"type": "null"
}
]
},
"extra-index-url": {
"description": "Extra URLs of package indexes to use, in addition to `--index-url`.\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)\n(the simple repository API), or a local directory laid out in the same format.\n\nAll indexes provided via this flag take priority over the index specified by\n[`index_url`](#index-url). When multiple indexes are provided, earlier values take priority.\n\nTo control uv's resolution strategy when multiple indexes are present, see\n[`index_strategy`](#index-strategy).",
"type": [