Skip to content

Commit 3c1844c

Browse files
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" } ```
1 parent fb51838 commit 3c1844c

30 files changed

Lines changed: 760 additions & 50 deletions

File tree

crates/uv-bench/benches/uv.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
use std::hint::black_box;
12
use std::str::FromStr;
23

3-
use std::hint::black_box;
44
use uv_bench::criterion::{Criterion, criterion_group, criterion_main, measurement::WallTime};
55
use uv_cache::Cache;
66
use uv_client::RegistryClientBuilder;
@@ -92,7 +92,7 @@ mod resolver {
9292
use uv_dispatch::{BuildDispatch, SharedState};
9393
use uv_distribution::DistributionDatabase;
9494
use uv_distribution_types::{
95-
DependencyMetadata, ExtraBuildRequires, IndexLocations, RequiresPython,
95+
DependencyMetadata, ExtraBuildRequires, ExtraBuildVariables, IndexLocations, RequiresPython,
9696
};
9797
use uv_install_wheel::LinkMode;
9898
use uv_pep440::Version;
@@ -144,6 +144,7 @@ mod resolver {
144144
) -> Result<ResolverOutput> {
145145
let build_isolation = BuildIsolation::default();
146146
let extra_build_requires = ExtraBuildRequires::default();
147+
let extra_build_variables = ExtraBuildVariables::default();
147148
let build_options = BuildOptions::default();
148149
let concurrency = Concurrency::default();
149150
let config_settings = ConfigSettings::default();
@@ -193,6 +194,7 @@ mod resolver {
193194
&config_settings_package,
194195
build_isolation,
195196
&extra_build_requires,
197+
&extra_build_variables,
196198
LinkMode::default(),
197199
&build_options,
198200
&hashes,

crates/uv-cli/src/options.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ pub fn resolver_options(
355355
no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
356356
no_build_isolation_package: Some(no_build_isolation_package),
357357
extra_build_dependencies: None,
358+
extra_build_variables: None,
358359
exclude_newer: ExcludeNewer::from_args(
359360
exclude_newer,
360361
exclude_newer_package.unwrap_or_default(),
@@ -477,6 +478,7 @@ pub fn resolver_installer_options(
477478
Some(no_build_isolation_package)
478479
},
479480
extra_build_dependencies: None,
481+
extra_build_variables: None,
480482
exclude_newer,
481483
exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
482484
link_mode,

crates/uv-dispatch/src/lib.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ use uv_configuration::{BuildOutput, Concurrency};
2424
use uv_distribution::DistributionDatabase;
2525
use uv_distribution_filename::DistFilename;
2626
use uv_distribution_types::{
27-
CachedDist, DependencyMetadata, ExtraBuildRequires, Identifier, IndexCapabilities,
28-
IndexLocations, IsBuildBackendError, Name, Requirement, Resolution, SourceDist,
29-
VersionOrUrlRef,
27+
CachedDist, DependencyMetadata, ExtraBuildRequires, ExtraBuildVariables, Identifier,
28+
IndexCapabilities, IndexLocations, IsBuildBackendError, Name, Requirement, Resolution,
29+
SourceDist, VersionOrUrlRef,
3030
};
3131
use uv_git::GitResolver;
3232
use uv_installer::{Installer, Plan, Planner, Preparer, SitePackages};
@@ -90,6 +90,7 @@ pub struct BuildDispatch<'a> {
9090
dependency_metadata: &'a DependencyMetadata,
9191
build_isolation: BuildIsolation<'a>,
9292
extra_build_requires: &'a ExtraBuildRequires,
93+
extra_build_variables: &'a ExtraBuildVariables,
9394
link_mode: uv_install_wheel::LinkMode,
9495
build_options: &'a BuildOptions,
9596
config_settings: &'a ConfigSettings,
@@ -119,6 +120,7 @@ impl<'a> BuildDispatch<'a> {
119120
config_settings_package: &'a PackageConfigSettings,
120121
build_isolation: BuildIsolation<'a>,
121122
extra_build_requires: &'a ExtraBuildRequires,
123+
extra_build_variables: &'a ExtraBuildVariables,
122124
link_mode: uv_install_wheel::LinkMode,
123125
build_options: &'a BuildOptions,
124126
hasher: &'a HashStrategy,
@@ -142,6 +144,7 @@ impl<'a> BuildDispatch<'a> {
142144
config_settings_package,
143145
build_isolation,
144146
extra_build_requires,
147+
extra_build_variables,
145148
link_mode,
146149
build_options,
147150
hasher,
@@ -227,6 +230,10 @@ impl BuildContext for BuildDispatch<'_> {
227230
self.extra_build_requires
228231
}
229232

233+
fn extra_build_variables(&self) -> &ExtraBuildVariables {
234+
self.extra_build_variables
235+
}
236+
230237
async fn resolve<'data>(
231238
&'data self,
232239
requirements: &'data [Requirement],
@@ -312,6 +319,7 @@ impl BuildContext for BuildDispatch<'_> {
312319
self.config_settings,
313320
self.config_settings_package,
314321
self.extra_build_requires(),
322+
self.extra_build_variables,
315323
self.cache(),
316324
venv,
317325
tags,
@@ -446,6 +454,18 @@ impl BuildContext for BuildDispatch<'_> {
446454
self.config_settings.clone()
447455
};
448456

457+
// Get package-specific environment variables if available.
458+
let mut environment_variables = self.build_extra_env_vars.clone();
459+
if let Some(name) = dist_name {
460+
if let Some(package_vars) = self.extra_build_variables.get(name) {
461+
environment_variables.extend(
462+
package_vars
463+
.iter()
464+
.map(|(key, value)| (OsString::from(key), OsString::from(value))),
465+
);
466+
}
467+
}
468+
449469
let builder = SourceBuild::setup(
450470
source,
451471
subdirectory,
@@ -464,7 +484,7 @@ impl BuildContext for BuildDispatch<'_> {
464484
self.extra_build_requires(),
465485
&build_stack,
466486
build_kind,
467-
self.build_extra_env_vars.clone(),
487+
environment_variables,
468488
build_output,
469489
self.concurrency.builds,
470490
self.preview,

crates/uv-distribution-types/src/build_requires.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::collections::BTreeMap;
22

3+
use serde::{Deserialize, Serialize};
4+
35
use uv_cache_key::{CacheKey, CacheKeyHasher};
46
use uv_normalize::PackageName;
57

@@ -104,3 +106,52 @@ impl ExtraBuildRequires {
104106
.collect::<Result<Self, _>>()
105107
}
106108
}
109+
110+
/// A map of extra build variables, from variable name to value.
111+
pub type BuildVariables = BTreeMap<String, String>;
112+
113+
/// Extra environment variables to set during builds, on a per-package basis.
114+
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
115+
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
116+
pub struct ExtraBuildVariables(BTreeMap<PackageName, BuildVariables>);
117+
118+
impl std::ops::Deref for ExtraBuildVariables {
119+
type Target = BTreeMap<PackageName, BuildVariables>;
120+
121+
fn deref(&self) -> &Self::Target {
122+
&self.0
123+
}
124+
}
125+
126+
impl std::ops::DerefMut for ExtraBuildVariables {
127+
fn deref_mut(&mut self) -> &mut Self::Target {
128+
&mut self.0
129+
}
130+
}
131+
132+
impl IntoIterator for ExtraBuildVariables {
133+
type Item = (PackageName, BuildVariables);
134+
type IntoIter = std::collections::btree_map::IntoIter<PackageName, BuildVariables>;
135+
136+
fn into_iter(self) -> Self::IntoIter {
137+
self.0.into_iter()
138+
}
139+
}
140+
141+
impl FromIterator<(PackageName, BuildVariables)> for ExtraBuildVariables {
142+
fn from_iter<T: IntoIterator<Item = (PackageName, BuildVariables)>>(iter: T) -> Self {
143+
Self(iter.into_iter().collect())
144+
}
145+
}
146+
147+
impl CacheKey for ExtraBuildVariables {
148+
fn cache_key(&self, state: &mut CacheKeyHasher) {
149+
for (package, vars) in &self.0 {
150+
package.as_str().cache_key(state);
151+
for (key, value) in vars {
152+
key.cache_key(state);
153+
value.cache_key(state);
154+
}
155+
}
156+
}
157+
}

crates/uv-distribution/src/index/built_wheel_index.rs

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use uv_cache_info::CacheInfo;
55
use uv_cache_key::cache_digest;
66
use uv_configuration::{ConfigSettings, PackageConfigSettings};
77
use uv_distribution_types::{
8-
DirectUrlSourceDist, DirectorySourceDist, ExtraBuildRequirement, ExtraBuildRequires,
9-
GitSourceDist, Hashed, PathSourceDist,
8+
BuildVariables, DirectUrlSourceDist, DirectorySourceDist, ExtraBuildRequirement,
9+
ExtraBuildRequires, ExtraBuildVariables, GitSourceDist, Hashed, PathSourceDist,
1010
};
1111
use uv_normalize::PackageName;
1212
use uv_platform_tags::Tags;
@@ -25,6 +25,7 @@ pub struct BuiltWheelIndex<'a> {
2525
config_settings: &'a ConfigSettings,
2626
config_settings_package: &'a PackageConfigSettings,
2727
extra_build_requires: &'a ExtraBuildRequires,
28+
extra_build_variables: &'a ExtraBuildVariables,
2829
}
2930

3031
impl<'a> BuiltWheelIndex<'a> {
@@ -36,6 +37,7 @@ impl<'a> BuiltWheelIndex<'a> {
3637
config_settings: &'a ConfigSettings,
3738
config_settings_package: &'a PackageConfigSettings,
3839
extra_build_requires: &'a ExtraBuildRequires,
40+
extra_build_variables: &'a ExtraBuildVariables,
3941
) -> Self {
4042
Self {
4143
cache,
@@ -44,6 +46,7 @@ impl<'a> BuiltWheelIndex<'a> {
4446
config_settings,
4547
config_settings_package,
4648
extra_build_requires,
49+
extra_build_variables,
4750
}
4851
}
4952

@@ -75,10 +78,18 @@ impl<'a> BuiltWheelIndex<'a> {
7578
// If there are build settings, we need to scope to a cache shard.
7679
let config_settings = self.config_settings_for(&source_dist.name);
7780
let extra_build_deps = self.extra_build_requires_for(&source_dist.name);
78-
let cache_shard = if config_settings.is_empty() && extra_build_deps.is_empty() {
81+
let extra_build_vars = self.extra_build_variables_for(&source_dist.name);
82+
let cache_shard = if config_settings.is_empty()
83+
&& extra_build_deps.is_empty()
84+
&& extra_build_vars.is_none()
85+
{
7986
cache_shard
8087
} else {
81-
cache_shard.shard(cache_digest(&(&config_settings, extra_build_deps)))
88+
cache_shard.shard(cache_digest(&(
89+
&config_settings,
90+
extra_build_deps,
91+
extra_build_vars,
92+
)))
8293
};
8394

8495
Ok(self.find(&cache_shard))
@@ -114,10 +125,18 @@ impl<'a> BuiltWheelIndex<'a> {
114125
// If there are build settings, we need to scope to a cache shard.
115126
let config_settings = self.config_settings_for(&source_dist.name);
116127
let extra_build_deps = self.extra_build_requires_for(&source_dist.name);
117-
let cache_shard = if config_settings.is_empty() && extra_build_deps.is_empty() {
128+
let extra_build_vars = self.extra_build_variables_for(&source_dist.name);
129+
let cache_shard = if config_settings.is_empty()
130+
&& extra_build_deps.is_empty()
131+
&& extra_build_vars.is_none()
132+
{
118133
cache_shard
119134
} else {
120-
cache_shard.shard(cache_digest(&(&config_settings, extra_build_deps)))
135+
cache_shard.shard(cache_digest(&(
136+
&config_settings,
137+
extra_build_deps,
138+
extra_build_vars,
139+
)))
121140
};
122141

123142
Ok(self
@@ -164,10 +183,18 @@ impl<'a> BuiltWheelIndex<'a> {
164183
// If there are build settings, we need to scope to a cache shard.
165184
let config_settings = self.config_settings_for(&source_dist.name);
166185
let extra_build_deps = self.extra_build_requires_for(&source_dist.name);
167-
let cache_shard = if config_settings.is_empty() && extra_build_deps.is_empty() {
186+
let extra_build_vars = self.extra_build_variables_for(&source_dist.name);
187+
let cache_shard = if config_settings.is_empty()
188+
&& extra_build_deps.is_empty()
189+
&& extra_build_vars.is_none()
190+
{
168191
cache_shard
169192
} else {
170-
cache_shard.shard(cache_digest(&(&config_settings, extra_build_deps)))
193+
cache_shard.shard(cache_digest(&(
194+
&config_settings,
195+
extra_build_deps,
196+
extra_build_vars,
197+
)))
171198
};
172199

173200
Ok(self
@@ -192,10 +219,18 @@ impl<'a> BuiltWheelIndex<'a> {
192219
// If there are build settings, we need to scope to a cache shard.
193220
let config_settings = self.config_settings_for(&source_dist.name);
194221
let extra_build_deps = self.extra_build_requires_for(&source_dist.name);
195-
let cache_shard = if config_settings.is_empty() && extra_build_deps.is_empty() {
222+
let extra_build_vars = self.extra_build_variables_for(&source_dist.name);
223+
let cache_shard = if config_settings.is_empty()
224+
&& extra_build_deps.is_empty()
225+
&& extra_build_vars.is_none()
226+
{
196227
cache_shard
197228
} else {
198-
cache_shard.shard(cache_digest(&(&config_settings, extra_build_deps)))
229+
cache_shard.shard(cache_digest(&(
230+
&config_settings,
231+
extra_build_deps,
232+
extra_build_vars,
233+
)))
199234
};
200235

201236
self.find(&cache_shard)
@@ -274,4 +309,9 @@ impl<'a> BuiltWheelIndex<'a> {
274309
.map(Vec::as_slice)
275310
.unwrap_or(&[])
276311
}
312+
313+
/// Determine the extra build variables for the given package name.
314+
fn extra_build_variables_for(&self, name: &PackageName) -> Option<&BuildVariables> {
315+
self.extra_build_variables.get(name)
316+
}
277317
}

0 commit comments

Comments
 (0)