From e724ddc63f14b9378672c16433dbfba534c6cb84 Mon Sep 17 00:00:00 2001
From: Charlie Marsh
Date: Thu, 17 Jul 2025 21:27:54 -0400
Subject: [PATCH] Allow `--config-settings-package` to apply configuration
settings at the package level (#14573)
## Summary
Closes https://github.com/astral-sh/uv/issues/14564.
Closes https://github.com/astral-sh/uv/issues/10940.
---
crates/uv-bench/benches/uv.rs | 6 +-
crates/uv-cli/src/lib.rs | 37 +++-
crates/uv-cli/src/options.rs | 32 ++-
.../uv-configuration/src/config_settings.rs | 184 ++++++++++++++++++
crates/uv-dispatch/src/lib.rs | 25 ++-
.../src/index/built_wheel_index.rs | 42 ++--
crates/uv-distribution/src/source/mod.rs | 51 +++--
crates/uv-installer/src/plan.rs | 11 +-
crates/uv-settings/src/combine.rs | 15 +-
crates/uv-settings/src/settings.rs | 34 +++-
crates/uv-types/src/traits.rs | 7 +-
crates/uv/src/commands/build_frontend.rs | 6 +-
crates/uv/src/commands/pip/compile.rs | 5 +-
crates/uv/src/commands/pip/install.rs | 6 +-
crates/uv/src/commands/pip/operations.rs | 4 +-
crates/uv/src/commands/pip/sync.rs | 6 +-
crates/uv/src/commands/project/add.rs | 1 +
crates/uv/src/commands/project/lock.rs | 2 +
crates/uv/src/commands/project/mod.rs | 10 +
crates/uv/src/commands/project/sync.rs | 3 +
crates/uv/src/commands/project/tree.rs | 1 +
crates/uv/src/commands/venv.rs | 4 +-
crates/uv/src/lib.rs | 3 +
crates/uv/src/settings.rs | 22 ++-
crates/uv/tests/it/pip_install.rs | 119 ++++++++++-
crates/uv/tests/it/show_settings.rs | 108 +++++++++-
crates/uv/tests/it/sync.rs | 143 ++++++++++++++
docs/reference/cli.md | 15 ++
docs/reference/settings.md | 54 +++++
uv.schema.json | 29 +++
30 files changed, 927 insertions(+), 58 deletions(-)
diff --git a/crates/uv-bench/benches/uv.rs b/crates/uv-bench/benches/uv.rs
index 9bdd7adb9..8380ccd60 100644
--- a/crates/uv-bench/benches/uv.rs
+++ b/crates/uv-bench/benches/uv.rs
@@ -86,8 +86,8 @@ mod resolver {
use uv_cache::Cache;
use uv_client::RegistryClient;
use uv_configuration::{
- BuildOptions, Concurrency, ConfigSettings, Constraints, IndexStrategy, PreviewMode,
- SourceStrategy,
+ BuildOptions, Concurrency, ConfigSettings, Constraints, IndexStrategy,
+ PackageConfigSettings, PreviewMode, SourceStrategy,
};
use uv_dispatch::{BuildDispatch, SharedState};
use uv_distribution::DistributionDatabase;
@@ -144,6 +144,7 @@ mod resolver {
let build_options = BuildOptions::default();
let concurrency = Concurrency::default();
let config_settings = ConfigSettings::default();
+ let config_settings_package = PackageConfigSettings::default();
let exclude_newer = Some(
jiff::civil::date(2024, 9, 1)
.to_zoned(jiff::tz::TimeZone::UTC)
@@ -184,6 +185,7 @@ mod resolver {
state,
IndexStrategy::default(),
&config_settings,
+ &config_settings_package,
build_isolation,
LinkMode::default(),
&build_options,
diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs
index 9d7cfa6e0..d6560014f 100644
--- a/crates/uv-cli/src/lib.rs
+++ b/crates/uv-cli/src/lib.rs
@@ -10,8 +10,9 @@ use clap::{Args, Parser, Subcommand};
use uv_cache::CacheArgs;
use uv_configuration::{
- ConfigSettingEntry, ExportFormat, IndexStrategy, KeyringProviderType, PackageNameSpecifier,
- ProjectBuildBackend, TargetTriple, TrustedHost, TrustedPublishing, VersionControlSystem,
+ ConfigSettingEntry, ConfigSettingPackageEntry, ExportFormat, IndexStrategy,
+ KeyringProviderType, PackageNameSpecifier, ProjectBuildBackend, TargetTriple, TrustedHost,
+ TrustedPublishing, VersionControlSystem,
};
use uv_distribution_types::{Index, IndexUrl, Origin, PipExtraIndex, PipFindLinks, PipIndex};
use uv_normalize::{ExtraName, GroupName, PackageName, PipGroupName};
@@ -4693,6 +4694,14 @@ pub struct ToolUpgradeArgs {
)]
pub config_setting: Option>,
+ /// Settings to pass to the PEP 517 build backend for a specific package, specified as `PACKAGE:KEY=VALUE` pairs.
+ #[arg(
+ long,
+ alias = "config-settings-package",
+ help_heading = "Build options"
+ )]
+ pub config_setting_package: Option>,
+
/// Disable isolation when building source distributions.
///
/// Assumes that build dependencies specified by PEP 518 are already installed.
@@ -5484,6 +5493,14 @@ pub struct InstallerArgs {
)]
pub config_setting: Option>,
+ /// Settings to pass to the PEP 517 build backend for a specific package, specified as `PACKAGE:KEY=VALUE` pairs.
+ #[arg(
+ long,
+ alias = "config-settings-package",
+ help_heading = "Build options"
+ )]
+ pub config_settings_package: Option>,
+
/// Disable isolation when building source distributions.
///
/// Assumes that build dependencies specified by PEP 518 are already installed.
@@ -5671,6 +5688,14 @@ pub struct ResolverArgs {
)]
pub config_setting: Option>,
+ /// Settings to pass to the PEP 517 build backend for a specific package, specified as `PACKAGE:KEY=VALUE` pairs.
+ #[arg(
+ long,
+ alias = "config-settings-package",
+ help_heading = "Build options"
+ )]
+ pub config_settings_package: Option>,
+
/// Disable isolation when building source distributions.
///
/// Assumes that build dependencies specified by PEP 518 are already installed.
@@ -5860,6 +5885,14 @@ pub struct ResolverInstallerArgs {
)]
pub config_setting: Option>,
+ /// Settings to pass to the PEP 517 build backend for a specific package, specified as `PACKAGE:KEY=VALUE` pairs.
+ #[arg(
+ long,
+ alias = "config-settings-package",
+ help_heading = "Build options"
+ )]
+ pub config_settings_package: Option>,
+
/// Disable isolation when building source distributions.
///
/// Assumes that build dependencies specified by PEP 518 are already installed.
diff --git a/crates/uv-cli/src/options.rs b/crates/uv-cli/src/options.rs
index f522022a1..d2e651a19 100644
--- a/crates/uv-cli/src/options.rs
+++ b/crates/uv-cli/src/options.rs
@@ -1,7 +1,7 @@
use anstream::eprintln;
use uv_cache::Refresh;
-use uv_configuration::ConfigSettings;
+use uv_configuration::{ConfigSettings, PackageConfigSettings};
use uv_resolver::PrereleaseMode;
use uv_settings::{Combine, PipOptions, ResolverInstallerOptions, ResolverOptions};
use uv_warnings::owo_colors::OwoColorize;
@@ -62,6 +62,7 @@ impl From for PipOptions {
pre,
fork_strategy,
config_setting,
+ config_settings_package,
no_build_isolation,
no_build_isolation_package,
build_isolation,
@@ -84,6 +85,11 @@ impl From for PipOptions {
},
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::()),
+ config_settings_package: config_settings_package.map(|config_settings| {
+ config_settings
+ .into_iter()
+ .collect::()
+ }),
no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
no_build_isolation_package: Some(no_build_isolation_package),
exclude_newer,
@@ -104,6 +110,7 @@ impl From for PipOptions {
index_strategy,
keyring_provider,
config_setting,
+ config_settings_package,
no_build_isolation,
build_isolation,
exclude_newer,
@@ -120,6 +127,11 @@ impl From for PipOptions {
keyring_provider,
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::()),
+ config_settings_package: config_settings_package.map(|config_settings| {
+ config_settings
+ .into_iter()
+ .collect::()
+ }),
no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
exclude_newer,
link_mode,
@@ -147,6 +159,7 @@ impl From for PipOptions {
pre,
fork_strategy,
config_setting,
+ config_settings_package,
no_build_isolation,
no_build_isolation_package,
build_isolation,
@@ -173,6 +186,11 @@ impl From for PipOptions {
fork_strategy,
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::()),
+ config_settings_package: config_settings_package.map(|config_settings| {
+ config_settings
+ .into_iter()
+ .collect::()
+ }),
no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
no_build_isolation_package: Some(no_build_isolation_package),
exclude_newer,
@@ -260,6 +278,7 @@ pub fn resolver_options(
pre,
fork_strategy,
config_setting,
+ config_settings_package,
no_build_isolation,
no_build_isolation_package,
build_isolation,
@@ -321,6 +340,11 @@ pub fn resolver_options(
dependency_metadata: None,
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::()),
+ config_settings_package: config_settings_package.map(|config_settings| {
+ config_settings
+ .into_iter()
+ .collect::()
+ }),
no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
no_build_isolation_package: Some(no_build_isolation_package),
exclude_newer,
@@ -353,6 +377,7 @@ pub fn resolver_installer_options(
pre,
fork_strategy,
config_setting,
+ config_settings_package,
no_build_isolation,
no_build_isolation_package,
build_isolation,
@@ -428,6 +453,11 @@ pub fn resolver_installer_options(
dependency_metadata: None,
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::()),
+ config_settings_package: config_settings_package.map(|config_settings| {
+ config_settings
+ .into_iter()
+ .collect::()
+ }),
no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
no_build_isolation_package: if no_build_isolation_package.is_empty() {
None
diff --git a/crates/uv-configuration/src/config_settings.rs b/crates/uv-configuration/src/config_settings.rs
index cd1d67196..c6238deb2 100644
--- a/crates/uv-configuration/src/config_settings.rs
+++ b/crates/uv-configuration/src/config_settings.rs
@@ -3,6 +3,7 @@ use std::{
str::FromStr,
};
use uv_cache_key::CacheKeyHasher;
+use uv_normalize::PackageName;
#[derive(Debug, Clone)]
pub struct ConfigSettingEntry {
@@ -28,6 +29,32 @@ impl FromStr for ConfigSettingEntry {
}
}
+#[derive(Debug, Clone)]
+pub struct ConfigSettingPackageEntry {
+ /// The package name to apply the setting to.
+ package: PackageName,
+ /// The config setting entry.
+ setting: ConfigSettingEntry,
+}
+
+impl FromStr for ConfigSettingPackageEntry {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result {
+ let Some((package_str, config_str)) = s.split_once(':') else {
+ return Err(format!(
+ "Invalid config setting: {s} (expected `PACKAGE:KEY=VALUE`)"
+ ));
+ };
+
+ let package = PackageName::from_str(package_str.trim())
+ .map_err(|e| format!("Invalid package name: {e}"))?;
+ let setting = ConfigSettingEntry::from_str(config_str)?;
+
+ Ok(Self { package, setting })
+ }
+}
+
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema), schemars(untagged))]
enum ConfigSettingValue {
@@ -212,6 +239,111 @@ impl<'de> serde::Deserialize<'de> for ConfigSettings {
}
}
+/// Settings to pass to PEP 517 build backends on a per-package basis.
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
+pub struct PackageConfigSettings(BTreeMap);
+
+impl FromIterator for PackageConfigSettings {
+ fn from_iter>(iter: T) -> Self {
+ let mut package_configs: BTreeMap> = BTreeMap::new();
+
+ for entry in iter {
+ package_configs
+ .entry(entry.package)
+ .or_default()
+ .push(entry.setting);
+ }
+
+ let configs = package_configs
+ .into_iter()
+ .map(|(package, entries)| (package, entries.into_iter().collect()))
+ .collect();
+
+ Self(configs)
+ }
+}
+
+impl PackageConfigSettings {
+ /// Returns the config settings for a specific package, if any.
+ pub fn get(&self, package: &PackageName) -> Option<&ConfigSettings> {
+ self.0.get(package)
+ }
+
+ /// Returns `true` if there are no package-specific settings.
+ pub fn is_empty(&self) -> bool {
+ self.0.is_empty()
+ }
+
+ /// Merge two sets of package config settings, with the values in `self` taking precedence.
+ #[must_use]
+ pub fn merge(mut self, other: PackageConfigSettings) -> PackageConfigSettings {
+ for (package, settings) in other.0 {
+ match self.0.entry(package) {
+ Entry::Vacant(vacant) => {
+ vacant.insert(settings);
+ }
+ Entry::Occupied(mut occupied) => {
+ let merged = occupied.get().clone().merge(settings);
+ occupied.insert(merged);
+ }
+ }
+ }
+ self
+ }
+}
+
+impl uv_cache_key::CacheKey for PackageConfigSettings {
+ fn cache_key(&self, state: &mut CacheKeyHasher) {
+ for (package, settings) in &self.0 {
+ package.to_string().cache_key(state);
+ settings.cache_key(state);
+ }
+ }
+}
+
+impl serde::Serialize for PackageConfigSettings {
+ fn serialize(&self, serializer: S) -> Result {
+ use serde::ser::SerializeMap;
+
+ let mut map = serializer.serialize_map(Some(self.0.len()))?;
+ for (key, value) in &self.0 {
+ map.serialize_entry(&key.to_string(), value)?;
+ }
+ map.end()
+ }
+}
+
+impl<'de> serde::Deserialize<'de> for PackageConfigSettings {
+ fn deserialize>(deserializer: D) -> Result {
+ struct Visitor;
+
+ impl<'de> serde::de::Visitor<'de> for Visitor {
+ type Value = PackageConfigSettings;
+
+ fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+ formatter.write_str("a map from package name to config settings")
+ }
+
+ fn visit_map>(
+ self,
+ mut map: A,
+ ) -> Result {
+ let mut config = BTreeMap::default();
+ while let Some((key, value)) = map.next_entry::()? {
+ let package = PackageName::from_str(&key).map_err(|e| {
+ serde::de::Error::custom(format!("Invalid package name: {e}"))
+ })?;
+ config.insert(package, value);
+ }
+ Ok(PackageConfigSettings(config))
+ }
+ }
+
+ deserializer.deserialize_map(Visitor)
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -291,4 +423,56 @@ mod tests {
);
assert_eq!(settings.escape_for_python(), r#"{"key":"val\\1 {}value"}"#);
}
+
+ #[test]
+ fn parse_config_setting_package_entry() {
+ // Test valid parsing
+ let entry = ConfigSettingPackageEntry::from_str("numpy:editable_mode=compat").unwrap();
+ assert_eq!(entry.package.as_ref(), "numpy");
+ assert_eq!(entry.setting.key, "editable_mode");
+ assert_eq!(entry.setting.value, "compat");
+
+ // Test with package name containing hyphens
+ let entry = ConfigSettingPackageEntry::from_str("my-package:some_key=value").unwrap();
+ assert_eq!(entry.package.as_ref(), "my-package");
+ assert_eq!(entry.setting.key, "some_key");
+ assert_eq!(entry.setting.value, "value");
+
+ // Test with spaces around values
+ let entry = ConfigSettingPackageEntry::from_str(" numpy : key = value ").unwrap();
+ assert_eq!(entry.package.as_ref(), "numpy");
+ assert_eq!(entry.setting.key, "key");
+ assert_eq!(entry.setting.value, "value");
+ }
+
+ #[test]
+ fn collect_config_settings_package() {
+ let settings: PackageConfigSettings = vec![
+ ConfigSettingPackageEntry::from_str("numpy:editable_mode=compat").unwrap(),
+ ConfigSettingPackageEntry::from_str("numpy:another_key=value").unwrap(),
+ ConfigSettingPackageEntry::from_str("scipy:build_option=fast").unwrap(),
+ ]
+ .into_iter()
+ .collect();
+
+ let numpy_settings = settings
+ .get(&PackageName::from_str("numpy").unwrap())
+ .unwrap();
+ assert_eq!(
+ numpy_settings.0.get("editable_mode"),
+ Some(&ConfigSettingValue::String("compat".to_string()))
+ );
+ assert_eq!(
+ numpy_settings.0.get("another_key"),
+ Some(&ConfigSettingValue::String("value".to_string()))
+ );
+
+ let scipy_settings = settings
+ .get(&PackageName::from_str("scipy").unwrap())
+ .unwrap();
+ assert_eq!(
+ scipy_settings.0.get("build_option"),
+ Some(&ConfigSettingValue::String("fast".to_string()))
+ );
+ }
}
diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs
index 874e412e5..2e34b583d 100644
--- a/crates/uv-dispatch/src/lib.rs
+++ b/crates/uv-dispatch/src/lib.rs
@@ -17,8 +17,8 @@ use uv_build_frontend::{SourceBuild, SourceBuildContext};
use uv_cache::Cache;
use uv_client::RegistryClient;
use uv_configuration::{
- BuildKind, BuildOptions, ConfigSettings, Constraints, IndexStrategy, PreviewMode, Reinstall,
- SourceStrategy,
+ BuildKind, BuildOptions, ConfigSettings, Constraints, IndexStrategy, PackageConfigSettings,
+ PreviewMode, Reinstall, SourceStrategy,
};
use uv_configuration::{BuildOutput, Concurrency};
use uv_distribution::DistributionDatabase;
@@ -91,6 +91,7 @@ pub struct BuildDispatch<'a> {
link_mode: uv_install_wheel::LinkMode,
build_options: &'a BuildOptions,
config_settings: &'a ConfigSettings,
+ config_settings_package: &'a PackageConfigSettings,
hasher: &'a HashStrategy,
exclude_newer: Option,
source_build_context: SourceBuildContext,
@@ -113,6 +114,7 @@ impl<'a> BuildDispatch<'a> {
shared_state: SharedState,
index_strategy: IndexStrategy,
config_settings: &'a ConfigSettings,
+ config_settings_package: &'a PackageConfigSettings,
build_isolation: BuildIsolation<'a>,
link_mode: uv_install_wheel::LinkMode,
build_options: &'a BuildOptions,
@@ -134,6 +136,7 @@ impl<'a> BuildDispatch<'a> {
dependency_metadata,
index_strategy,
config_settings,
+ config_settings_package,
build_isolation,
link_mode,
build_options,
@@ -200,6 +203,10 @@ impl BuildContext for BuildDispatch<'_> {
self.config_settings
}
+ fn config_settings_package(&self) -> &PackageConfigSettings {
+ self.config_settings_package
+ }
+
fn sources(&self) -> SourceStrategy {
self.sources
}
@@ -295,6 +302,7 @@ impl BuildContext for BuildDispatch<'_> {
self.hasher,
self.index_locations,
self.config_settings,
+ self.config_settings_package,
self.cache(),
venv,
tags,
@@ -418,6 +426,17 @@ impl BuildContext for BuildDispatch<'_> {
build_stack.insert(dist.distribution_id());
}
+ // Get package-specific config settings if available; otherwise, use global settings.
+ let config_settings = if let Some(name) = dist_name {
+ if let Some(package_settings) = self.config_settings_package.get(name) {
+ package_settings.clone().merge(self.config_settings.clone())
+ } else {
+ self.config_settings.clone()
+ }
+ } else {
+ self.config_settings.clone()
+ };
+
let builder = SourceBuild::setup(
source,
subdirectory,
@@ -431,7 +450,7 @@ impl BuildContext for BuildDispatch<'_> {
self.index_locations,
sources,
self.workspace_cache(),
- self.config_settings.clone(),
+ config_settings,
self.build_isolation,
&build_stack,
build_kind,
diff --git a/crates/uv-distribution/src/index/built_wheel_index.rs b/crates/uv-distribution/src/index/built_wheel_index.rs
index 9752e7e4f..90ce5deed 100644
--- a/crates/uv-distribution/src/index/built_wheel_index.rs
+++ b/crates/uv-distribution/src/index/built_wheel_index.rs
@@ -1,10 +1,12 @@
+use std::borrow::Cow;
use uv_cache::{Cache, CacheBucket, CacheShard, WheelCache};
use uv_cache_info::CacheInfo;
use uv_cache_key::cache_digest;
-use uv_configuration::ConfigSettings;
+use uv_configuration::{ConfigSettings, PackageConfigSettings};
use uv_distribution_types::{
DirectUrlSourceDist, DirectorySourceDist, GitSourceDist, Hashed, PathSourceDist,
};
+use uv_normalize::PackageName;
use uv_platform_tags::Tags;
use uv_types::HashStrategy;
@@ -18,7 +20,8 @@ pub struct BuiltWheelIndex<'a> {
cache: &'a Cache,
tags: &'a Tags,
hasher: &'a HashStrategy,
- build_configuration: &'a ConfigSettings,
+ config_settings: &'a ConfigSettings,
+ config_settings_package: &'a PackageConfigSettings,
}
impl<'a> BuiltWheelIndex<'a> {
@@ -27,13 +30,15 @@ impl<'a> BuiltWheelIndex<'a> {
cache: &'a Cache,
tags: &'a Tags,
hasher: &'a HashStrategy,
- build_configuration: &'a ConfigSettings,
+ config_settings: &'a ConfigSettings,
+ config_settings_package: &'a PackageConfigSettings,
) -> Self {
Self {
cache,
tags,
hasher,
- build_configuration,
+ config_settings,
+ config_settings_package,
}
}
@@ -63,10 +68,11 @@ impl<'a> BuiltWheelIndex<'a> {
let cache_shard = cache_shard.shard(revision.id());
// If there are build settings, we need to scope to a cache shard.
- let cache_shard = if self.build_configuration.is_empty() {
+ let config_settings = self.config_settings_for(&source_dist.name);
+ let cache_shard = if config_settings.is_empty() {
cache_shard
} else {
- cache_shard.shard(cache_digest(self.build_configuration))
+ cache_shard.shard(cache_digest(&config_settings))
};
Ok(self.find(&cache_shard))
@@ -100,10 +106,11 @@ impl<'a> BuiltWheelIndex<'a> {
let cache_shard = cache_shard.shard(revision.id());
// If there are build settings, we need to scope to a cache shard.
- let cache_shard = if self.build_configuration.is_empty() {
+ let config_settings = self.config_settings_for(&source_dist.name);
+ let cache_shard = if config_settings.is_empty() {
cache_shard
} else {
- cache_shard.shard(cache_digest(self.build_configuration))
+ cache_shard.shard(cache_digest(&config_settings))
};
Ok(self
@@ -148,10 +155,11 @@ impl<'a> BuiltWheelIndex<'a> {
let cache_shard = cache_shard.shard(revision.id());
// If there are build settings, we need to scope to a cache shard.
- let cache_shard = if self.build_configuration.is_empty() {
+ let config_settings = self.config_settings_for(&source_dist.name);
+ let cache_shard = if config_settings.is_empty() {
cache_shard
} else {
- cache_shard.shard(cache_digest(self.build_configuration))
+ cache_shard.shard(cache_digest(&config_settings))
};
Ok(self
@@ -174,10 +182,11 @@ impl<'a> BuiltWheelIndex<'a> {
);
// If there are build settings, we need to scope to a cache shard.
- let cache_shard = if self.build_configuration.is_empty() {
+ let config_settings = self.config_settings_for(&source_dist.name);
+ let cache_shard = if config_settings.is_empty() {
cache_shard
} else {
- cache_shard.shard(cache_digest(self.build_configuration))
+ cache_shard.shard(cache_digest(&config_settings))
};
self.find(&cache_shard)
@@ -239,4 +248,13 @@ impl<'a> BuiltWheelIndex<'a> {
candidate
}
+
+ /// Determine the [`ConfigSettings`] for the given package name.
+ fn config_settings_for(&self, name: &PackageName) -> Cow<'_, ConfigSettings> {
+ if let Some(package_settings) = self.config_settings_package.get(name) {
+ Cow::Owned(package_settings.clone().merge(self.config_settings.clone()))
+ } else {
+ Cow::Borrowed(self.config_settings)
+ }
+ }
}
diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs
index 1308e3d77..080a1e52d 100644
--- a/crates/uv-distribution/src/source/mod.rs
+++ b/crates/uv-distribution/src/source/mod.rs
@@ -29,7 +29,7 @@ use uv_cache_key::cache_digest;
use uv_client::{
CacheControl, CachedClientError, Connectivity, DataWithCachePolicy, RegistryClient,
};
-use uv_configuration::{BuildKind, BuildOutput, SourceStrategy};
+use uv_configuration::{BuildKind, BuildOutput, ConfigSettings, SourceStrategy};
use uv_distribution_filename::{SourceDistExtension, WheelFilename};
use uv_distribution_types::{
BuildableSource, DirectorySourceUrl, GitSourceUrl, HashPolicy, Hashed, PathSourceUrl,
@@ -373,6 +373,23 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
Ok(metadata)
}
+ /// Determine the [`ConfigSettings`] for the given package name.
+ fn config_settings_for(&self, name: Option<&PackageName>) -> Cow<'_, ConfigSettings> {
+ if let Some(name) = name {
+ if let Some(package_settings) = self.build_context.config_settings_package().get(name) {
+ Cow::Owned(
+ package_settings
+ .clone()
+ .merge(self.build_context.config_settings().clone()),
+ )
+ } else {
+ Cow::Borrowed(self.build_context.config_settings())
+ }
+ } else {
+ Cow::Borrowed(self.build_context.config_settings())
+ }
+ }
+
/// Build a source distribution from a remote URL.
async fn url<'data>(
&self,
@@ -407,11 +424,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
let source_dist_entry = cache_shard.entry(SOURCE);
// If there are build settings, we need to scope to a cache shard.
- let config_settings = self.build_context.config_settings();
+ let config_settings = self.config_settings_for(source.name());
let cache_shard = if config_settings.is_empty() {
cache_shard
} else {
- cache_shard.shard(cache_digest(config_settings))
+ cache_shard.shard(cache_digest(&&config_settings))
};
// If the cache contains a compatible wheel, return it.
@@ -580,11 +597,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
}
// If there are build settings, we need to scope to a cache shard.
- let config_settings = self.build_context.config_settings();
+ let config_settings = self.config_settings_for(source.name());
let cache_shard = if config_settings.is_empty() {
cache_shard
} else {
- cache_shard.shard(cache_digest(config_settings))
+ cache_shard.shard(cache_digest(&config_settings))
};
// Otherwise, we either need to build the metadata.
@@ -779,11 +796,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
let source_entry = cache_shard.entry(SOURCE);
// If there are build settings, we need to scope to a cache shard.
- let config_settings = self.build_context.config_settings();
+ let config_settings = self.config_settings_for(source.name());
let cache_shard = if config_settings.is_empty() {
cache_shard
} else {
- cache_shard.shard(cache_digest(config_settings))
+ cache_shard.shard(cache_digest(&config_settings))
};
// If the cache contains a compatible wheel, return it.
@@ -941,11 +958,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
}
// If there are build settings, we need to scope to a cache shard.
- let config_settings = self.build_context.config_settings();
+ let config_settings = self.config_settings_for(source.name());
let cache_shard = if config_settings.is_empty() {
cache_shard
} else {
- cache_shard.shard(cache_digest(config_settings))
+ cache_shard.shard(cache_digest(&config_settings))
};
// Otherwise, we need to build a wheel.
@@ -1083,11 +1100,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
let cache_shard = cache_shard.shard(revision.id());
// If there are build settings, we need to scope to a cache shard.
- let config_settings = self.build_context.config_settings();
+ let config_settings = self.config_settings_for(source.name());
let cache_shard = if config_settings.is_empty() {
cache_shard
} else {
- cache_shard.shard(cache_digest(config_settings))
+ cache_shard.shard(cache_digest(&config_settings))
};
// If the cache contains a compatible wheel, return it.
@@ -1271,11 +1288,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
}
// If there are build settings, we need to scope to a cache shard.
- let config_settings = self.build_context.config_settings();
+ let config_settings = self.config_settings_for(source.name());
let cache_shard = if config_settings.is_empty() {
cache_shard
} else {
- cache_shard.shard(cache_digest(config_settings))
+ cache_shard.shard(cache_digest(&config_settings))
};
// Otherwise, we need to build a wheel.
@@ -1476,11 +1493,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
let _lock = cache_shard.lock().await.map_err(Error::CacheWrite)?;
// If there are build settings, we need to scope to a cache shard.
- let config_settings = self.build_context.config_settings();
+ let config_settings = self.config_settings_for(source.name());
let cache_shard = if config_settings.is_empty() {
cache_shard
} else {
- cache_shard.shard(cache_digest(config_settings))
+ cache_shard.shard(cache_digest(&config_settings))
};
// If the cache contains a compatible wheel, return it.
@@ -1779,11 +1796,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
}
// If there are build settings, we need to scope to a cache shard.
- let config_settings = self.build_context.config_settings();
+ let config_settings = self.config_settings_for(source.name());
let cache_shard = if config_settings.is_empty() {
cache_shard
} else {
- cache_shard.shard(cache_digest(config_settings))
+ cache_shard.shard(cache_digest(&config_settings))
};
// Otherwise, we need to build a wheel.
diff --git a/crates/uv-installer/src/plan.rs b/crates/uv-installer/src/plan.rs
index e030e9b4d..69e10befc 100644
--- a/crates/uv-installer/src/plan.rs
+++ b/crates/uv-installer/src/plan.rs
@@ -4,7 +4,7 @@ use tracing::{debug, warn};
use uv_cache::{Cache, CacheBucket, WheelCache};
use uv_cache_info::Timestamp;
-use uv_configuration::{BuildOptions, ConfigSettings, Reinstall};
+use uv_configuration::{BuildOptions, ConfigSettings, PackageConfigSettings, Reinstall};
use uv_distribution::{
BuiltWheelIndex, HttpArchivePointer, LocalArchivePointer, RegistryWheelIndex,
};
@@ -52,6 +52,7 @@ impl<'a> Planner<'a> {
hasher: &HashStrategy,
index_locations: &IndexLocations,
config_settings: &ConfigSettings,
+ config_settings_package: &PackageConfigSettings,
cache: &Cache,
venv: &PythonEnvironment,
tags: &Tags,
@@ -59,7 +60,13 @@ impl<'a> Planner<'a> {
// Index all the already-downloaded wheels in the cache.
let mut registry_index =
RegistryWheelIndex::new(cache, tags, index_locations, hasher, config_settings);
- let built_index = BuiltWheelIndex::new(cache, tags, hasher, config_settings);
+ let built_index = BuiltWheelIndex::new(
+ cache,
+ tags,
+ hasher,
+ config_settings,
+ config_settings_package,
+ );
let mut cached = vec![];
let mut remote = vec![];
diff --git a/crates/uv-settings/src/combine.rs b/crates/uv-settings/src/combine.rs
index 8edbd2a05..738b00ffe 100644
--- a/crates/uv-settings/src/combine.rs
+++ b/crates/uv-settings/src/combine.rs
@@ -4,8 +4,8 @@ use std::path::PathBuf;
use url::Url;
use uv_configuration::{
- ConfigSettings, ExportFormat, IndexStrategy, KeyringProviderType, RequiredVersion,
- TargetTriple, TrustedPublishing,
+ ConfigSettings, ExportFormat, IndexStrategy, KeyringProviderType, PackageConfigSettings,
+ RequiredVersion, TargetTriple, TrustedPublishing,
};
use uv_distribution_types::{Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex};
use uv_install_wheel::LinkMode;
@@ -131,6 +131,17 @@ impl Combine for Option {
}
}
+impl Combine for Option {
+ /// Combine two maps by merging the map in `self` with the map in `other`, if they're both
+ /// `Some`.
+ fn combine(self, other: Option) -> Option {
+ match (self, other) {
+ (Some(a), Some(b)) => Some(a.merge(b)),
+ (a, b) => a.or(b),
+ }
+ }
+}
+
impl Combine for serde::de::IgnoredAny {
fn combine(self, _other: Self) -> Self {
self
diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs
index e057cb40a..9eb765a1e 100644
--- a/crates/uv-settings/src/settings.rs
+++ b/crates/uv-settings/src/settings.rs
@@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize};
use uv_cache_info::CacheKey;
use uv_configuration::{
- ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier, RequiredVersion,
- TargetTriple, TrustedHost, TrustedPublishing,
+ ConfigSettings, IndexStrategy, KeyringProviderType, PackageConfigSettings,
+ PackageNameSpecifier, RequiredVersion, TargetTriple, TrustedHost, TrustedPublishing,
};
use uv_distribution_types::{
Index, IndexUrl, IndexUrlError, PipExtraIndex, PipFindLinks, PipIndex, StaticMetadata,
@@ -361,6 +361,7 @@ pub struct ResolverOptions {
pub fork_strategy: Option,
pub dependency_metadata: Option>,
pub config_settings: Option,
+ pub config_settings_package: Option,
pub exclude_newer: Option,
pub link_mode: Option,
pub upgrade: Option,
@@ -587,6 +588,18 @@ pub struct ResolverInstallerOptions {
"#
)]
pub config_settings: Option,
+ /// Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend for specific packages,
+ /// specified as `KEY=VALUE` pairs.
+ ///
+ /// Accepts a map from package names to string key-value pairs.
+ #[option(
+ default = "{}",
+ value_type = "dict",
+ example = r#"
+ config-settings-package = { numpy = { editable_mode = "compat" } }
+ "#
+ )]
+ pub config_settings_package: Option,
/// Disable isolation when building source distributions.
///
/// Assumes that build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/)
@@ -1333,6 +1346,16 @@ pub struct PipOptions {
"#
)]
pub config_settings: Option,
+ /// Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend for specific packages,
+ /// specified as `KEY=VALUE` pairs.
+ #[option(
+ default = "{}",
+ value_type = "dict",
+ example = r#"
+ config-settings-package = { numpy = { editable_mode = "compat" } }
+ "#
+ )]
+ pub config_settings_package: Option,
/// The minimum Python version that should be supported by the resolved requirements (e.g.,
/// `3.8` or `3.8.17`).
///
@@ -1651,6 +1674,7 @@ impl From for ResolverOptions {
fork_strategy: value.fork_strategy,
dependency_metadata: value.dependency_metadata,
config_settings: value.config_settings,
+ config_settings_package: value.config_settings_package,
exclude_newer: value.exclude_newer,
link_mode: value.link_mode,
upgrade: value.upgrade,
@@ -1714,6 +1738,7 @@ pub struct ToolOptions {
pub fork_strategy: Option,
pub dependency_metadata: Option>,
pub config_settings: Option,
+ pub config_settings_package: Option,
pub no_build_isolation: Option,
pub no_build_isolation_package: Option>,
pub exclude_newer: Option,
@@ -1741,6 +1766,7 @@ impl From for ToolOptions {
fork_strategy: value.fork_strategy,
dependency_metadata: value.dependency_metadata,
config_settings: value.config_settings,
+ config_settings_package: value.config_settings_package,
no_build_isolation: value.no_build_isolation,
no_build_isolation_package: value.no_build_isolation_package,
exclude_newer: value.exclude_newer,
@@ -1770,6 +1796,7 @@ impl From for ResolverInstallerOptions {
fork_strategy: value.fork_strategy,
dependency_metadata: value.dependency_metadata,
config_settings: value.config_settings,
+ config_settings_package: value.config_settings_package,
no_build_isolation: value.no_build_isolation,
no_build_isolation_package: value.no_build_isolation_package,
exclude_newer: value.exclude_newer,
@@ -1822,6 +1849,7 @@ pub struct OptionsWire {
fork_strategy: Option,
dependency_metadata: Option>,
config_settings: Option,
+ config_settings_package: Option,
no_build_isolation: Option,
no_build_isolation_package: Option>,
exclude_newer: Option,
@@ -1911,6 +1939,7 @@ impl From for Options {
fork_strategy,
dependency_metadata,
config_settings,
+ config_settings_package,
no_build_isolation,
no_build_isolation_package,
exclude_newer,
@@ -1977,6 +2006,7 @@ impl From for Options {
fork_strategy,
dependency_metadata,
config_settings,
+ config_settings_package,
no_build_isolation,
no_build_isolation_package,
exclude_newer,
diff --git a/crates/uv-types/src/traits.rs b/crates/uv-types/src/traits.rs
index a95367fef..e3f4ee012 100644
--- a/crates/uv-types/src/traits.rs
+++ b/crates/uv-types/src/traits.rs
@@ -7,7 +7,9 @@ use anyhow::Result;
use rustc_hash::FxHashSet;
use uv_cache::Cache;
-use uv_configuration::{BuildKind, BuildOptions, BuildOutput, ConfigSettings, SourceStrategy};
+use uv_configuration::{
+ BuildKind, BuildOptions, BuildOutput, ConfigSettings, PackageConfigSettings, SourceStrategy,
+};
use uv_distribution_filename::DistFilename;
use uv_distribution_types::{
CachedDist, DependencyMetadata, DistributionId, IndexCapabilities, IndexLocations,
@@ -87,6 +89,9 @@ pub trait BuildContext {
/// The [`ConfigSettings`] used to build distributions.
fn config_settings(&self) -> &ConfigSettings;
+ /// The [`ConfigSettings`] used to build a specific package.
+ fn config_settings_package(&self) -> &PackageConfigSettings;
+
/// Whether to incorporate `tool.uv.sources` when resolving requirements.
fn sources(&self) -> SourceStrategy;
diff --git a/crates/uv/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs
index a830f7aef..b3f9e5c89 100644
--- a/crates/uv/src/commands/build_frontend.rs
+++ b/crates/uv/src/commands/build_frontend.rs
@@ -16,7 +16,7 @@ use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
BuildKind, BuildOptions, BuildOutput, Concurrency, ConfigSettings, Constraints,
DependencyGroupsWithDefaults, HashCheckingMode, IndexStrategy, KeyringProviderType,
- PreviewMode, SourceStrategy,
+ PackageConfigSettings, PreviewMode, SourceStrategy,
};
use uv_dispatch::{BuildDispatch, SharedState};
use uv_distribution_filename::{
@@ -197,6 +197,7 @@ async fn build_impl(
fork_strategy: _,
dependency_metadata,
config_setting,
+ config_settings_package,
no_build_isolation,
no_build_isolation_package,
exclude_newer,
@@ -357,6 +358,7 @@ async fn build_impl(
dependency_metadata,
*link_mode,
config_setting,
+ config_settings_package,
preview,
);
async {
@@ -434,6 +436,7 @@ async fn build_package(
dependency_metadata: &DependencyMetadata,
link_mode: LinkMode,
config_setting: &ConfigSettings,
+ config_settings_package: &PackageConfigSettings,
preview: PreviewMode,
) -> Result, Error> {
let output_dir = if let Some(output_dir) = output_dir {
@@ -568,6 +571,7 @@ async fn build_package(
state.clone(),
index_strategy,
config_setting,
+ config_settings_package,
build_isolation,
link_mode,
build_options,
diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs
index c40716763..a5116327b 100644
--- a/crates/uv/src/commands/pip/compile.rs
+++ b/crates/uv/src/commands/pip/compile.rs
@@ -14,7 +14,8 @@ use uv_cache::Cache;
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, Constraints, ExportFormat, ExtrasSpecification,
- IndexStrategy, NoBinary, NoBuild, PreviewMode, Reinstall, SourceStrategy, Upgrade,
+ IndexStrategy, NoBinary, NoBuild, PackageConfigSettings, PreviewMode, Reinstall,
+ SourceStrategy, Upgrade,
};
use uv_configuration::{KeyringProviderType, TargetTriple};
use uv_dispatch::{BuildDispatch, SharedState};
@@ -90,6 +91,7 @@ pub(crate) async fn pip_compile(
keyring_provider: KeyringProviderType,
network_settings: &NetworkSettings,
config_settings: ConfigSettings,
+ config_settings_package: PackageConfigSettings,
no_build_isolation: bool,
no_build_isolation_package: Vec,
build_options: BuildOptions,
@@ -477,6 +479,7 @@ pub(crate) async fn pip_compile(
state,
index_strategy,
&config_settings,
+ &config_settings_package,
build_isolation,
link_mode,
&build_options,
diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs
index bbfe99c50..79e18bd98 100644
--- a/crates/uv/src/commands/pip/install.rs
+++ b/crates/uv/src/commands/pip/install.rs
@@ -11,7 +11,8 @@ use uv_cache::Cache;
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, Constraints, DryRun, ExtrasSpecification,
- HashCheckingMode, IndexStrategy, PreviewMode, Reinstall, SourceStrategy, Upgrade,
+ HashCheckingMode, IndexStrategy, PackageConfigSettings, PreviewMode, Reinstall, SourceStrategy,
+ Upgrade,
};
use uv_configuration::{KeyringProviderType, TargetTriple};
use uv_dispatch::{BuildDispatch, SharedState};
@@ -75,6 +76,7 @@ pub(crate) async fn pip_install(
hash_checking: Option,
installer_metadata: bool,
config_settings: &ConfigSettings,
+ config_settings_package: &PackageConfigSettings,
no_build_isolation: bool,
no_build_isolation_package: Vec,
build_options: BuildOptions,
@@ -422,6 +424,7 @@ pub(crate) async fn pip_install(
state.clone(),
index_strategy,
config_settings,
+ config_settings_package,
build_isolation,
link_mode,
&build_options,
@@ -513,6 +516,7 @@ pub(crate) async fn pip_install(
compile,
&index_locations,
config_settings,
+ config_settings_package,
&hasher,
&tags,
&client,
diff --git a/crates/uv/src/commands/pip/operations.rs b/crates/uv/src/commands/pip/operations.rs
index 55ab2aa1b..117321c14 100644
--- a/crates/uv/src/commands/pip/operations.rs
+++ b/crates/uv/src/commands/pip/operations.rs
@@ -13,7 +13,7 @@ use uv_cache::Cache;
use uv_client::{BaseClientBuilder, RegistryClient};
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, Constraints, DependencyGroups, DryRun,
- ExtrasSpecification, Overrides, Reinstall, Upgrade,
+ ExtrasSpecification, Overrides, PackageConfigSettings, Reinstall, Upgrade,
};
use uv_dispatch::BuildDispatch;
use uv_distribution::{DistributionDatabase, SourcedDependencyGroups};
@@ -445,6 +445,7 @@ pub(crate) async fn install(
compile: bool,
index_urls: &IndexLocations,
config_settings: &ConfigSettings,
+ config_settings_package: &PackageConfigSettings,
hasher: &HashStrategy,
tags: &Tags,
client: &RegistryClient,
@@ -470,6 +471,7 @@ pub(crate) async fn install(
hasher,
index_urls,
config_settings,
+ config_settings_package,
cache,
venv,
tags,
diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs
index 6858ddad0..61999825e 100644
--- a/crates/uv/src/commands/pip/sync.rs
+++ b/crates/uv/src/commands/pip/sync.rs
@@ -9,7 +9,8 @@ use uv_cache::Cache;
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, Constraints, DryRun, ExtrasSpecification,
- HashCheckingMode, IndexStrategy, PreviewMode, Reinstall, SourceStrategy, Upgrade,
+ HashCheckingMode, IndexStrategy, PackageConfigSettings, PreviewMode, Reinstall, SourceStrategy,
+ Upgrade,
};
use uv_configuration::{KeyringProviderType, TargetTriple};
use uv_dispatch::{BuildDispatch, SharedState};
@@ -60,6 +61,7 @@ pub(crate) async fn pip_sync(
allow_empty_requirements: bool,
installer_metadata: bool,
config_settings: &ConfigSettings,
+ config_settings_package: &PackageConfigSettings,
no_build_isolation: bool,
no_build_isolation_package: Vec,
build_options: BuildOptions,
@@ -355,6 +357,7 @@ pub(crate) async fn pip_sync(
state.clone(),
index_strategy,
config_settings,
+ config_settings_package,
build_isolation,
link_mode,
&build_options,
@@ -448,6 +451,7 @@ pub(crate) async fn pip_sync(
compile,
&index_locations,
config_settings,
+ config_settings_package,
&hasher,
&tags,
&client,
diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs
index 28cc2dcd5..12535f859 100644
--- a/crates/uv/src/commands/project/add.rs
+++ b/crates/uv/src/commands/project/add.rs
@@ -436,6 +436,7 @@ pub(crate) async fn add(
state.clone().into_inner(),
settings.resolver.index_strategy,
&settings.resolver.config_setting,
+ &settings.resolver.config_settings_package,
build_isolation,
settings.resolver.link_mode,
&settings.resolver.build_options,
diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs
index e23bd97c2..706c86593 100644
--- a/crates/uv/src/commands/project/lock.rs
+++ b/crates/uv/src/commands/project/lock.rs
@@ -432,6 +432,7 @@ async fn do_lock(
fork_strategy,
dependency_metadata,
config_setting,
+ config_settings_package,
no_build_isolation,
no_build_isolation_package,
exclude_newer,
@@ -674,6 +675,7 @@ async fn do_lock(
state.fork().into_inner(),
*index_strategy,
config_setting,
+ config_settings_package,
build_isolation,
*link_mode,
build_options,
diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs
index cce02a70b..becd2a26e 100644
--- a/crates/uv/src/commands/project/mod.rs
+++ b/crates/uv/src/commands/project/mod.rs
@@ -1674,6 +1674,7 @@ pub(crate) async fn resolve_names(
ResolverSettings {
build_options,
config_setting,
+ config_settings_package,
dependency_metadata,
exclude_newer,
fork_strategy: _,
@@ -1742,6 +1743,7 @@ pub(crate) async fn resolve_names(
state.clone(),
*index_strategy,
config_setting,
+ config_settings_package,
build_isolation,
*link_mode,
build_options,
@@ -1832,6 +1834,7 @@ pub(crate) async fn resolve_environment(
fork_strategy,
dependency_metadata,
config_setting,
+ config_settings_package,
no_build_isolation,
no_build_isolation_package,
exclude_newer,
@@ -1948,6 +1951,7 @@ pub(crate) async fn resolve_environment(
state.clone().into_inner(),
*index_strategy,
config_setting,
+ config_settings_package,
build_isolation,
*link_mode,
build_options,
@@ -2013,6 +2017,7 @@ pub(crate) async fn sync_environment(
keyring_provider,
dependency_metadata,
config_setting,
+ config_settings_package,
no_build_isolation,
no_build_isolation_package,
exclude_newer,
@@ -2084,6 +2089,7 @@ pub(crate) async fn sync_environment(
state.clone().into_inner(),
index_strategy,
config_setting,
+ config_settings_package,
build_isolation,
link_mode,
build_options,
@@ -2106,6 +2112,7 @@ pub(crate) async fn sync_environment(
compile_bytecode,
index_locations,
config_setting,
+ config_settings_package,
&hasher,
tags,
&client,
@@ -2169,6 +2176,7 @@ pub(crate) async fn update_environment(
ResolverSettings {
build_options,
config_setting,
+ config_settings_package,
dependency_metadata,
exclude_newer,
fork_strategy,
@@ -2305,6 +2313,7 @@ pub(crate) async fn update_environment(
state.clone(),
*index_strategy,
config_setting,
+ config_settings_package,
build_isolation,
*link_mode,
build_options,
@@ -2362,6 +2371,7 @@ pub(crate) async fn update_environment(
*compile_bytecode,
index_locations,
config_setting,
+ config_settings_package,
&hasher,
tags,
&client,
diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs
index 8d2dd9629..adf3b61f2 100644
--- a/crates/uv/src/commands/project/sync.rs
+++ b/crates/uv/src/commands/project/sync.rs
@@ -573,6 +573,7 @@ pub(super) async fn do_sync(
keyring_provider,
dependency_metadata,
config_setting,
+ config_settings_package,
no_build_isolation,
no_build_isolation_package,
exclude_newer,
@@ -709,6 +710,7 @@ pub(super) async fn do_sync(
state.clone().into_inner(),
index_strategy,
config_setting,
+ config_settings_package,
build_isolation,
link_mode,
build_options,
@@ -733,6 +735,7 @@ pub(super) async fn do_sync(
compile_bytecode,
index_locations,
config_setting,
+ config_settings_package,
&hasher,
&tags,
&client,
diff --git a/crates/uv/src/commands/project/tree.rs b/crates/uv/src/commands/project/tree.rs
index cd1339d3e..756820dc7 100644
--- a/crates/uv/src/commands/project/tree.rs
+++ b/crates/uv/src/commands/project/tree.rs
@@ -200,6 +200,7 @@ pub(crate) async fn tree(
fork_strategy: _,
dependency_metadata: _,
config_setting: _,
+ config_settings_package: _,
no_build_isolation: _,
no_build_isolation_package: _,
exclude_newer: _,
diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs
index 92eb1ead7..9d3b87fe1 100644
--- a/crates/uv/src/commands/venv.rs
+++ b/crates/uv/src/commands/venv.rs
@@ -12,7 +12,7 @@ use uv_cache::Cache;
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, Constraints, DependencyGroups, IndexStrategy,
- KeyringProviderType, NoBinary, NoBuild, PreviewMode, SourceStrategy,
+ KeyringProviderType, NoBinary, NoBuild, PackageConfigSettings, PreviewMode, SourceStrategy,
};
use uv_dispatch::{BuildDispatch, SharedState};
use uv_distribution_types::Requirement;
@@ -269,6 +269,7 @@ pub(crate) async fn venv(
let build_constraints = Constraints::default();
let build_hasher = HashStrategy::default();
let config_settings = ConfigSettings::default();
+ let config_settings_package = PackageConfigSettings::default();
let sources = SourceStrategy::Disabled;
// Do not allow builds
@@ -286,6 +287,7 @@ pub(crate) async fn venv(
state.clone(),
index_strategy,
&config_settings,
+ &config_settings_package,
BuildIsolation::Isolated,
link_mode,
&build_options,
diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs
index 9c9b41065..0f6c9465f 100644
--- a/crates/uv/src/lib.rs
+++ b/crates/uv/src/lib.rs
@@ -524,6 +524,7 @@ async fn run(mut cli: Cli) -> Result {
args.settings.keyring_provider,
&globals.network_settings,
args.settings.config_setting,
+ args.settings.config_settings_package,
args.settings.no_build_isolation,
args.settings.no_build_isolation_package,
args.settings.build_options,
@@ -594,6 +595,7 @@ async fn run(mut cli: Cli) -> Result {
args.settings.allow_empty_requirements,
globals.installer_metadata,
&args.settings.config_setting,
+ &args.settings.config_settings_package,
args.settings.no_build_isolation,
args.settings.no_build_isolation_package,
args.settings.build_options,
@@ -745,6 +747,7 @@ async fn run(mut cli: Cli) -> Result {
args.settings.hash_checking,
globals.installer_metadata,
&args.settings.config_setting,
+ &args.settings.config_settings_package,
args.settings.no_build_isolation,
args.settings.no_build_isolation_package,
args.settings.build_options,
diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs
index 1ebeecba8..aa105cf97 100644
--- a/crates/uv/src/settings.rs
+++ b/crates/uv/src/settings.rs
@@ -23,9 +23,9 @@ use uv_client::Connectivity;
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, DependencyGroups, DryRun, EditableMode,
ExportFormat, ExtrasSpecification, HashCheckingMode, IndexStrategy, InstallOptions,
- KeyringProviderType, NoBinary, NoBuild, PreviewMode, ProjectBuildBackend, Reinstall,
- RequiredVersion, SourceStrategy, TargetTriple, TrustedHost, TrustedPublishing, Upgrade,
- VersionControlSystem,
+ KeyringProviderType, NoBinary, NoBuild, PackageConfigSettings, PreviewMode,
+ ProjectBuildBackend, Reinstall, RequiredVersion, SourceStrategy, TargetTriple, TrustedHost,
+ TrustedPublishing, Upgrade, VersionControlSystem,
};
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, IndexUrl, Requirement};
use uv_install_wheel::LinkMode;
@@ -712,6 +712,7 @@ impl ToolUpgradeSettings {
pre,
fork_strategy,
config_setting,
+ config_setting_package: config_settings_package,
no_build_isolation,
no_build_isolation_package,
build_isolation,
@@ -746,6 +747,7 @@ impl ToolUpgradeSettings {
pre,
fork_strategy,
config_setting,
+ config_settings_package,
no_build_isolation,
no_build_isolation_package,
build_isolation,
@@ -2694,6 +2696,7 @@ pub(crate) struct InstallerSettingsRef<'a> {
pub(crate) keyring_provider: KeyringProviderType,
pub(crate) dependency_metadata: &'a DependencyMetadata,
pub(crate) config_setting: &'a ConfigSettings,
+ pub(crate) config_settings_package: &'a PackageConfigSettings,
pub(crate) no_build_isolation: bool,
pub(crate) no_build_isolation_package: &'a [PackageName],
pub(crate) exclude_newer: Option,
@@ -2712,6 +2715,7 @@ pub(crate) struct InstallerSettingsRef<'a> {
pub(crate) struct ResolverSettings {
pub(crate) build_options: BuildOptions,
pub(crate) config_setting: ConfigSettings,
+ pub(crate) config_settings_package: PackageConfigSettings,
pub(crate) dependency_metadata: DependencyMetadata,
pub(crate) exclude_newer: Option,
pub(crate) fork_strategy: ForkStrategy,
@@ -2770,6 +2774,7 @@ impl From for ResolverSettings {
index_strategy: value.index_strategy.unwrap_or_default(),
keyring_provider: value.keyring_provider.unwrap_or_default(),
config_setting: value.config_settings.unwrap_or_default(),
+ config_settings_package: value.config_settings_package.unwrap_or_default(),
no_build_isolation: value.no_build_isolation.unwrap_or_default(),
no_build_isolation_package: value.no_build_isolation_package.unwrap_or_default(),
exclude_newer: value.exclude_newer,
@@ -2849,6 +2854,7 @@ impl From for ResolverInstallerSettings {
NoBuild::from_args(value.no_build, value.no_build_package.unwrap_or_default()),
),
config_setting: value.config_settings.unwrap_or_default(),
+ config_settings_package: value.config_settings_package.unwrap_or_default(),
dependency_metadata: DependencyMetadata::from_entries(
value.dependency_metadata.into_iter().flatten(),
),
@@ -2918,6 +2924,7 @@ pub(crate) struct PipSettings {
pub(crate) custom_compile_command: Option,
pub(crate) generate_hashes: bool,
pub(crate) config_setting: ConfigSettings,
+ pub(crate) config_settings_package: PackageConfigSettings,
pub(crate) python_version: Option,
pub(crate) python_platform: Option,
pub(crate) universal: bool,
@@ -2987,6 +2994,7 @@ impl PipSettings {
custom_compile_command,
generate_hashes,
config_settings,
+ config_settings_package,
python_version,
python_platform,
universal,
@@ -3022,6 +3030,7 @@ impl PipSettings {
fork_strategy: top_level_fork_strategy,
dependency_metadata: top_level_dependency_metadata,
config_settings: top_level_config_settings,
+ config_settings_package: top_level_config_settings_package,
no_build_isolation: top_level_no_build_isolation,
no_build_isolation_package: top_level_no_build_isolation_package,
exclude_newer: top_level_exclude_newer,
@@ -3054,6 +3063,8 @@ impl PipSettings {
let fork_strategy = fork_strategy.combine(top_level_fork_strategy);
let dependency_metadata = dependency_metadata.combine(top_level_dependency_metadata);
let config_settings = config_settings.combine(top_level_config_settings);
+ let config_settings_package =
+ config_settings_package.combine(top_level_config_settings_package);
let no_build_isolation = no_build_isolation.combine(top_level_no_build_isolation);
let no_build_isolation_package =
no_build_isolation_package.combine(top_level_no_build_isolation_package);
@@ -3156,6 +3167,10 @@ impl PipSettings {
.config_settings
.combine(config_settings)
.unwrap_or_default(),
+ config_settings_package: args
+ .config_settings_package
+ .combine(config_settings_package)
+ .unwrap_or_default(),
torch_backend: args.torch_backend.combine(torch_backend),
python_version: args.python_version.combine(python_version),
python_platform: args.python_platform.combine(python_platform),
@@ -3249,6 +3264,7 @@ impl<'a> From<&'a ResolverInstallerSettings> for InstallerSettingsRef<'a> {
keyring_provider: settings.resolver.keyring_provider,
dependency_metadata: &settings.resolver.dependency_metadata,
config_setting: &settings.resolver.config_setting,
+ config_settings_package: &settings.resolver.config_settings_package,
no_build_isolation: settings.resolver.no_build_isolation,
no_build_isolation_package: &settings.resolver.no_build_isolation_package,
exclude_newer: settings.resolver.exclude_newer,
diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs
index 2a7b0f404..a977ac813 100644
--- a/crates/uv/tests/it/pip_install.rs
+++ b/crates/uv/tests/it/pip_install.rs
@@ -4054,13 +4054,13 @@ fn config_settings_path() -> Result<()> {
"###
);
- // When installed without `--editable_mode=compat`, the `finder.py` file should be present.
+ // When installed without `editable_mode=compat`, the `finder.py` file should be present.
let finder = context
.site_packages()
.join("__editable___setuptools_editable_0_1_0_finder.py");
assert!(finder.exists());
- // Reinstalling with `--editable_mode=compat` should be a no-op; changes in build configuration
+ // Reinstalling with `editable_mode=compat` should be a no-op; changes in build configuration
// don't invalidate the environment.
uv_snapshot!(context.filters(), context.pip_install()
.arg("-r")
@@ -4089,7 +4089,7 @@ fn config_settings_path() -> Result<()> {
- setuptools-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/setuptools_editable)
"###);
- // Install the editable package with `--editable_mode=compat`. We should ignore the cached
+ // Install the editable package with `editable_mode=compat`. We should ignore the cached
// build configuration and rebuild.
uv_snapshot!(context.filters(), context.pip_install()
.arg("-r")
@@ -4109,7 +4109,7 @@ fn config_settings_path() -> Result<()> {
"###
);
- // When installed without `--editable_mode=compat`, the `finder.py` file should _not_ be present.
+ // When installed without `editable_mode=compat`, the `finder.py` file should _not_ be present.
let finder = context
.site_packages()
.join("__editable___setuptools_editable_0_1_0_finder.py");
@@ -11739,3 +11739,114 @@ fn install_python_preference() {
Audited 1 package in [TIME]
");
}
+
+#[test]
+fn config_settings_package() -> Result<()> {
+ let context = TestContext::new("3.12");
+
+ let requirements_txt = context.temp_dir.child("requirements.txt");
+ requirements_txt.write_str(&format!(
+ "-e {}",
+ context
+ .workspace_root
+ .join("scripts/packages/setuptools_editable")
+ .display()
+ ))?;
+
+ // Install the editable package.
+ uv_snapshot!(context.filters(), context.pip_install()
+ .arg("-r")
+ .arg("requirements.txt"), @r###"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+
+ ----- stderr -----
+ Resolved 2 packages in [TIME]
+ Prepared 2 packages in [TIME]
+ Installed 2 packages in [TIME]
+ + iniconfig==2.0.0
+ + setuptools-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/setuptools_editable)
+ "###
+ );
+
+ // When installed without `editable_mode=compat`, the `finder.py` file should be present.
+ let finder = context
+ .site_packages()
+ .join("__editable___setuptools_editable_0_1_0_finder.py");
+ assert!(finder.exists());
+
+ // Uninstall the package.
+ uv_snapshot!(context.filters(), context.pip_uninstall()
+ .arg("setuptools-editable"), @r###"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+
+ ----- stderr -----
+ Uninstalled 1 package in [TIME]
+ - setuptools-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/setuptools_editable)
+ "###);
+
+ // Install the editable package with `editable_mode=compat`, scoped to the package.
+ uv_snapshot!(context.filters(), context.pip_install()
+ .arg("-r")
+ .arg("requirements.txt")
+ .arg("--config-settings-package")
+ .arg("setuptools-editable:editable_mode=compat"), @r"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+
+ ----- stderr -----
+ Resolved 2 packages in [TIME]
+ Prepared 1 package in [TIME]
+ Installed 1 package in [TIME]
+ + setuptools-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/setuptools_editable)
+ "
+ );
+
+ // When installed with `editable_mode=compat`, the `finder.py` file should _not_ be present.
+ let finder = context
+ .site_packages()
+ .join("__editable___setuptools_editable_0_1_0_finder.py");
+ assert!(!finder.exists());
+
+ // Uninstall the package.
+ uv_snapshot!(context.filters(), context.pip_uninstall()
+ .arg("setuptools-editable"), @r###"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+
+ ----- stderr -----
+ Uninstalled 1 package in [TIME]
+ - setuptools-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/setuptools_editable)
+ "###);
+
+ // Install the editable package with `editable_mode=compat`, by scoped to a different package.
+ uv_snapshot!(context.filters(), context.pip_install()
+ .arg("-r")
+ .arg("requirements.txt")
+ .arg("--config-settings-package")
+ .arg("setuptools:editable_mode=compat")
+ , @r"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+
+ ----- stderr -----
+ Resolved 2 packages in [TIME]
+ Installed 1 package in [TIME]
+ + setuptools-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/setuptools_editable)
+ "
+ );
+
+ // When installed without `editable_mode=compat`, the `finder.py` file should be present.
+ let finder = context
+ .site_packages()
+ .join("__editable___setuptools_editable_0_1_0_finder.py");
+ assert!(finder.exists());
+
+ Ok(())
+}
diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs
index 2637af8ac..500e78965 100644
--- a/crates/uv/tests/it/show_settings.rs
+++ b/crates/uv/tests/it/show_settings.rs
@@ -203,6 +203,9 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -385,6 +388,9 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -568,6 +574,9 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -783,6 +792,9 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -933,6 +945,9 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -1127,6 +1142,9 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: Some(
X8664UnknownLinuxGnu,
@@ -1369,6 +1387,9 @@ fn resolve_index_url() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -1621,6 +1642,9 @@ fn resolve_index_url() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -1828,6 +1852,9 @@ fn resolve_find_links() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -2000,6 +2027,9 @@ fn resolve_top_level() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -2232,6 +2262,9 @@ fn resolve_top_level() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -2447,6 +2480,9 @@ fn resolve_top_level() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -2618,6 +2654,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -2773,6 +2812,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -2928,6 +2970,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -3085,6 +3130,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -3208,6 +3256,7 @@ fn resolve_tool() -> anyhow::Result<()> {
fork_strategy: None,
dependency_metadata: None,
config_settings: None,
+ config_settings_package: None,
no_build_isolation: None,
no_build_isolation_package: None,
exclude_newer: None,
@@ -3234,6 +3283,9 @@ fn resolve_tool() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
dependency_metadata: DependencyMetadata(
{},
),
@@ -3426,6 +3478,9 @@ fn resolve_poetry_toml() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -3643,6 +3698,9 @@ fn resolve_both() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -3950,6 +4008,9 @@ fn resolve_config_file() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -4004,7 +4065,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`, `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`, `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`, `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`, `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`
"
);
@@ -4199,6 +4260,9 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -4357,6 +4421,9 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -4534,6 +4601,9 @@ fn allow_insecure_host() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -4772,6 +4842,9 @@ fn index_priority() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -4989,6 +5062,9 @@ fn index_priority() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -5212,6 +5288,9 @@ fn index_priority() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -5430,6 +5509,9 @@ fn index_priority() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -5655,6 +5737,9 @@ fn index_priority() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -5873,6 +5958,9 @@ fn index_priority() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -6035,6 +6123,9 @@ fn verify_hashes() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -6183,6 +6274,9 @@ fn verify_hashes() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -6329,6 +6423,9 @@ fn verify_hashes() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -6477,6 +6574,9 @@ fn verify_hashes() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -6623,6 +6723,9 @@ fn verify_hashes() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
@@ -6770,6 +6873,9 @@ fn verify_hashes() -> anyhow::Result<()> {
config_setting: ConfigSettings(
{},
),
+ config_settings_package: PackageConfigSettings(
+ {},
+ ),
python_version: None,
python_platform: None,
universal: false,
diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs
index 5a8d79447..16c4d673a 100644
--- a/crates/uv/tests/it/sync.rs
+++ b/crates/uv/tests/it/sync.rs
@@ -11126,3 +11126,146 @@ fn sync_python_preference() -> Result<()> {
Ok(())
}
+
+#[test]
+fn sync_config_settings_package() -> Result<()> {
+ let context = TestContext::new("3.12").with_exclude_newer("2025-07-25T00:00:00Z");
+
+ // Create a child project that uses `setuptools`.
+ let dependency = context.temp_dir.child("dependency");
+ dependency.child("pyproject.toml").write_str(
+ r#"
+ [project]
+ name = "dependency"
+ version = "0.1.0"
+ requires-python = ">=3.12"
+ dependencies = []
+ [build-system]
+ requires = ["setuptools>=42"]
+ build-backend = "setuptools.build_meta"
+ "#,
+ )?;
+ dependency
+ .child("dependency")
+ .child("__init__.py")
+ .touch()?;
+
+ // Install the `dependency` without `editable_mode=compat`.
+ 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 = ["dependency"]
+
+ [tool.uv.sources]
+ dependency = { path = "dependency", editable = true }
+ "#,
+ )?;
+
+ // Lock the project
+ context.lock().assert().success();
+
+ uv_snapshot!(context.filters(), context.sync(), @r"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+
+ ----- stderr -----
+ Resolved 2 packages in [TIME]
+ Prepared 1 package in [TIME]
+ Installed 1 package in [TIME]
+ + dependency==0.1.0 (from file://[TEMP_DIR]/dependency)
+ ");
+
+ // When installed without `editable_mode=compat`, the `finder.py` file should be present.
+ let finder = context
+ .site_packages()
+ .join("__editable___dependency_0_1_0_finder.py");
+ assert!(finder.exists());
+
+ // Remove the virtual environment.
+ fs_err::remove_dir_all(&context.venv)?;
+
+ // Install the `dependency` with `editable_mode=compat` scoped to the package.
+ 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 = ["dependency"]
+
+ [tool.uv.sources]
+ dependency = { path = "dependency", editable = true }
+
+ [tool.uv.config-settings-package]
+ dependency = { editable_mode = "compat" }
+ "#,
+ )?;
+
+ uv_snapshot!(context.filters(), context.sync(), @r"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+
+ ----- stderr -----
+ Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
+ Creating virtual environment at: .venv
+ Resolved 2 packages in [TIME]
+ Prepared 1 package in [TIME]
+ Installed 1 package in [TIME]
+ + dependency==0.1.0 (from file://[TEMP_DIR]/dependency)
+ ");
+
+ // When installed with `editable_mode=compat`, the `finder.py` file should _not_ be present.
+ let finder = context
+ .site_packages()
+ .join("__editable___dependency_0_1_0_finder.py");
+ assert!(!finder.exists());
+
+ // Remove the virtual environment.
+ fs_err::remove_dir_all(&context.venv)?;
+
+ // Install the `dependency` with `editable_mode=compat` scoped to another package.
+ 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 = ["dependency"]
+
+ [tool.uv.sources]
+ dependency = { path = "dependency", editable = true }
+
+ [tool.uv.config-settings-package]
+ setuptools = { editable_mode = "compat" }
+ "#,
+ )?;
+
+ uv_snapshot!(context.filters(), context.sync(), @r"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+
+ ----- stderr -----
+ Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
+ Creating virtual environment at: .venv
+ Resolved 2 packages in [TIME]
+ Installed 1 package in [TIME]
+ + dependency==0.1.0 (from file://[TEMP_DIR]/dependency)
+ ");
+
+ // When installed without `editable_mode=compat`, the `finder.py` file should be present.
+ let finder = context
+ .site_packages()
+ .join("__editable___dependency_0_1_0_finder.py");
+ assert!(finder.exists());
+
+ Ok(())
+}
diff --git a/docs/reference/cli.md b/docs/reference/cli.md
index 4fc832cdb..2ca95dce0 100644
--- a/docs/reference/cli.md
+++ b/docs/reference/cli.md
@@ -84,6 +84,7 @@ uv run [OPTIONS] [COMMAND]
May also be set with the UV_COMPILE_BYTECODE environment variable.
--config-file config-fileThe path to a uv.toml file to use for configuration.
While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.
May also be set with the UV_CONFIG_FILE environment variable.
--config-setting, --config-settings, -C config-settingSettings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs
+--config-settings-package, --config-settings-package config-settings-packageSettings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs
--default-index default-indexThe URL of the default package index (by default: https://pypi.org/simple).
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
The index given by this flag is given lower priority than all other indexes specified via the --index flag.
@@ -442,6 +443,7 @@ uv add [OPTIONS] >
May also be set with the UV_COMPILE_BYTECODE environment variable.
--config-file config-fileThe path to a uv.toml file to use for configuration.
While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.
May also be set with the UV_CONFIG_FILE environment variable.
--config-setting, --config-settings, -C config-settingSettings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs
+--config-settings-package, --config-settings-package config-settings-packageSettings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs
--constraints, --constraint, -c constraintsConstrain versions using the given requirements files.
Constraints files are requirements.txt-like files that only control the version of a requirement that's installed. The constraints will not be added to the project's pyproject.toml file, but will be respected during dependency resolution.
This is equivalent to pip's --constraint option.
@@ -639,6 +641,7 @@ uv remove [OPTIONS] ...
May also be set with the UV_COMPILE_BYTECODE environment variable.
--config-file config-fileThe path to a uv.toml file to use for configuration.
While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.
May also be set with the UV_CONFIG_FILE environment variable.
--config-setting, --config-settings, -C config-settingSettings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs
+--config-settings-package, --config-settings-package config-settings-packageSettings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs
--default-index default-indexThe URL of the default package index (by default: https://pypi.org/simple).
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
The index given by this flag is given lower priority than all other indexes specified via the --index flag.
@@ -817,6 +820,7 @@ uv version [OPTIONS] [VALUE]
May also be set with the UV_COMPILE_BYTECODE environment variable.
--config-file config-fileThe path to a uv.toml file to use for configuration.
While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.
May also be set with the UV_CONFIG_FILE environment variable.
--config-setting, --config-settings, -C config-settingSettings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs
+--config-settings-package, --config-settings-package config-settings-packageSettings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs
--default-index default-indexThe URL of the default package index (by default: https://pypi.org/simple).
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
The index given by this flag is given lower priority than all other indexes specified via the --index flag.
@@ -1001,6 +1005,7 @@ uv sync [OPTIONS]
May also be set with the UV_COMPILE_BYTECODE environment variable.
--config-file config-fileThe path to a uv.toml file to use for configuration.
While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.
May also be set with the UV_CONFIG_FILE environment variable.
--config-setting, --config-settings, -C config-settingSettings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs
+--config-settings-package, --config-settings-package config-settings-packageSettings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs
--default-index default-indexThe URL of the default package index (by default: https://pypi.org/simple).
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
The index given by this flag is given lower priority than all other indexes specified via the --index flag.
@@ -1248,6 +1253,7 @@ uv lock [OPTIONS]
--config-file config-fileThe path to a uv.toml file to use for configuration.
While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.
May also be set with the UV_CONFIG_FILE environment variable.
--config-setting, --config-settings, -C config-settingSettings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs
+--config-settings-package, --config-settings-package config-settings-packageSettings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs
--default-index default-indexThe URL of the default package index (by default: https://pypi.org/simple).
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
The index given by this flag is given lower priority than all other indexes specified via the --index flag.
@@ -1411,6 +1417,7 @@ uv export [OPTIONS]
--config-file config-fileThe path to a uv.toml file to use for configuration.
While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.
May also be set with the UV_CONFIG_FILE environment variable.
--config-setting, --config-settings, -C config-settingSettings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs
+--config-settings-package, --config-settings-package config-settings-packageSettings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs
--default-index default-indexThe URL of the default package index (by default: https://pypi.org/simple).
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
The index given by this flag is given lower priority than all other indexes specified via the --index flag.
@@ -1605,6 +1612,7 @@ uv tree [OPTIONS]
--config-file config-fileThe path to a uv.toml file to use for configuration.
While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.
May also be set with the UV_CONFIG_FILE environment variable.
--config-setting, --config-settings, -C config-settingSettings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs
+--config-settings-package, --config-settings-package config-settings-packageSettings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs
--default-index default-indexThe URL of the default package index (by default: https://pypi.org/simple).
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
The index given by this flag is given lower priority than all other indexes specified via the --index flag.
@@ -1863,6 +1871,7 @@ uv tool run [OPTIONS] [COMMAND]
May also be set with the UV_COMPILE_BYTECODE environment variable.
--config-file config-fileThe path to a uv.toml file to use for configuration.
While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.
May also be set with the UV_CONFIG_FILE environment variable.
--config-setting, --config-settings, -C config-settingSettings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs
+--config-settings-package, --config-settings-package config-settings-packageSettings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs
--constraints, --constraint, -c constraintsConstrain versions using the given requirements files.
Constraints files are requirements.txt-like files that only control the version of a requirement that's installed. However, including a package in a constraints file will not trigger the installation of that package.
This is equivalent to pip's --constraint option.
@@ -2035,6 +2044,7 @@ uv tool install [OPTIONS]
May also be set with the UV_COMPILE_BYTECODE environment variable.
--config-file config-fileThe path to a uv.toml file to use for configuration.
While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.
May also be set with the UV_CONFIG_FILE environment variable.
--config-setting, --config-settings, -C config-settingSettings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs
+--config-settings-package, --config-settings-package config-settings-packageSettings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs
--constraints, --constraint, -c constraintsConstrain versions using the given requirements files.
Constraints files are requirements.txt-like files that only control the version of a requirement that's installed. However, including a package in a constraints file will not trigger the installation of that package.
This is equivalent to pip's --constraint option.
@@ -2202,6 +2212,7 @@ uv tool upgrade [OPTIONS] ...
May also be set with the UV_COMPILE_BYTECODE environment variable.
--config-file config-fileThe path to a uv.toml file to use for configuration.
While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.
May also be set with the UV_CONFIG_FILE environment variable.
--config-setting, --config-settings, -C config-settingSettings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs
+--config-setting-package, --config-settings-package config-setting-packageSettings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs
--default-index default-indexThe URL of the default package index (by default: https://pypi.org/simple).
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
The index given by this flag is given lower priority than all other indexes specified via the --index flag.
@@ -3345,6 +3356,7 @@ uv pip compile [OPTIONS] >
--config-file config-fileThe path to a uv.toml file to use for configuration.
While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.
May also be set with the UV_CONFIG_FILE environment variable.
--config-setting, --config-settings, -C config-settingSettings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs
+--config-settings-package, --config-settings-package config-settings-packageSettings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs
--constraints, --constraint, -c constraintsConstrain versions using the given requirements files.
Constraints files are requirements.txt-like files that only control the version of a requirement that's installed. However, including a package in a constraints file will not trigger the installation of that package.
This is equivalent to pip's --constraint option.
@@ -3650,6 +3662,7 @@ uv pip sync [OPTIONS] ...
May also be set with the UV_COMPILE_BYTECODE environment variable.
--config-file config-fileThe path to a uv.toml file to use for configuration.
While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.
May also be set with the UV_CONFIG_FILE environment variable.
--config-setting, --config-settings, -C config-settingSettings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs
+--config-settings-package, --config-settings-package config-settings-packageSettings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs
--constraints, --constraint, -c constraintsConstrain versions using the given requirements files.
Constraints files are requirements.txt-like files that only control the version of a requirement that's installed. However, including a package in a constraints file will not trigger the installation of that package.
This is equivalent to pip's --constraint option.
@@ -3900,6 +3913,7 @@ uv pip install [OPTIONS] |--editable May also be set with the UV_COMPILE_BYTECODE environment variable.
--config-file config-fileThe path to a uv.toml file to use for configuration.
While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.
May also be set with the UV_CONFIG_FILE environment variable.
--config-setting, --config-settings, -C config-settingSettings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs
+--config-settings-package, --config-settings-package config-settings-packageSettings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs
--constraints, --constraint, -c constraintsConstrain versions using the given requirements files.
Constraints files are requirements.txt-like files that only control the version of a requirement that's installed. However, including a package in a constraints file will not trigger the installation of that package.
This is equivalent to pip's --constraint option.
@@ -4845,6 +4859,7 @@ uv build [OPTIONS] [SRC]
--config-file config-fileThe path to a uv.toml file to use for configuration.
While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.
May also be set with the UV_CONFIG_FILE environment variable.
--config-setting, --config-settings, -C config-settingSettings to pass to the PEP 517 build backend, specified as KEY=VALUE pairs
+--config-settings-package, --config-settings-package config-settings-packageSettings to pass to the PEP 517 build backend for a specific package, specified as PACKAGE:KEY=VALUE pairs
--default-index default-indexThe URL of the default package index (by default: https://pypi.org/simple).
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
The index given by this flag is given lower priority than all other indexes specified via the --index flag.
diff --git a/docs/reference/settings.md b/docs/reference/settings.md
index bdee1e4a1..55d3f8ae4 100644
--- a/docs/reference/settings.md
+++ b/docs/reference/settings.md
@@ -1006,6 +1006,33 @@ specified as `KEY=VALUE` pairs.
---
+### [`config-settings-package`](#config-settings-package) {: #config-settings-package }
+
+Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend for specific packages,
+specified as `KEY=VALUE` pairs.
+
+Accepts a map from package names to string key-value pairs.
+
+**Default value**: `{}`
+
+**Type**: `dict`
+
+**Example usage**:
+
+=== "pyproject.toml"
+
+ ```toml
+ [tool.uv]
+ config-settings-package = { numpy = { editable_mode = "compat" } }
+ ```
+=== "uv.toml"
+
+ ```toml
+ config-settings-package = { numpy = { editable_mode = "compat" } }
+ ```
+
+---
+
### [`dependency-metadata`](#dependency-metadata) {: #dependency-metadata }
Pre-defined static metadata for dependencies of the project (direct or transitive). When
@@ -2244,6 +2271,33 @@ specified as `KEY=VALUE` pairs.
---
+#### [`config-settings-package`](#pip_config-settings-package) {: #pip_config-settings-package }
+
+
+Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend for specific packages,
+specified as `KEY=VALUE` pairs.
+
+**Default value**: `{}`
+
+**Type**: `dict`
+
+**Example usage**:
+
+=== "pyproject.toml"
+
+ ```toml
+ [tool.uv.pip]
+ config-settings-package = { numpy = { editable_mode = "compat" } }
+ ```
+=== "uv.toml"
+
+ ```toml
+ [pip]
+ config-settings-package = { numpy = { editable_mode = "compat" } }
+ ```
+
+---
+
#### [`custom-compile-command`](#pip_custom-compile-command) {: #pip_custom-compile-command }
diff --git a/uv.schema.json b/uv.schema.json
index ba89f65f4..22b30cd06 100644
--- a/uv.schema.json
+++ b/uv.schema.json
@@ -119,6 +119,17 @@
}
]
},
+ "config-settings-package": {
+ "description": "Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend for specific packages,\nspecified as `KEY=VALUE` pairs.\n\nAccepts a map from package names to string key-value pairs.",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/PackageConfigSettings"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
"conflicts": {
"description": "A list of sets of conflicting groups or extras.",
"anyOf": [
@@ -1104,6 +1115,13 @@
}
]
},
+ "PackageConfigSettings": {
+ "description": "Settings to pass to PEP 517 build backends on a per-package basis.",
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/definitions/ConfigSettings"
+ }
+ },
"PackageName": {
"description": "The normalized name of a package.\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: ",
"type": "string"
@@ -1185,6 +1203,17 @@
}
]
},
+ "config-settings-package": {
+ "description": "Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend for specific packages,\nspecified as `KEY=VALUE` pairs.",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/PackageConfigSettings"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
"custom-compile-command": {
"description": "The header comment to include at the top of the output file generated by `uv pip compile`.\n\nUsed to reflect custom build scripts and commands that wrap `uv pip compile`.",
"type": [