Allow --config-settings-package to apply configuration settings at the package level (#14573)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / build binary | linux libc (push) Blocked by required conditions
CI / build binary | linux aarch64 (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / build binary | msrv (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | linux (push) Blocked by required conditions
CI / smoke test | linux aarch64 (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | aarch64 windows implicit (push) Blocked by required conditions
CI / integration test | aarch64 windows explicit (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | pyodide on ubuntu (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded python on github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | registries (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | graalpy on ubuntu (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | aarch64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.8 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks | walltime aarch64 linux (push) Blocked by required conditions
CI / benchmarks | instrumented (push) Blocked by required conditions

## Summary

Closes https://github.com/astral-sh/uv/issues/14564.

Closes https://github.com/astral-sh/uv/issues/10940.
This commit is contained in:
Charlie Marsh 2025-07-17 21:27:54 -04:00 committed by GitHub
parent a6a5e65e0c
commit e724ddc63f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 927 additions and 58 deletions

View file

@ -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,

View file

@ -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<Vec<ConfigSettingEntry>>,
/// 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<Vec<ConfigSettingPackageEntry>>,
/// 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<Vec<ConfigSettingEntry>>,
/// 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<Vec<ConfigSettingPackageEntry>>,
/// 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<Vec<ConfigSettingEntry>>,
/// 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<Vec<ConfigSettingPackageEntry>>,
/// 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<Vec<ConfigSettingEntry>>,
/// 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<Vec<ConfigSettingPackageEntry>>,
/// Disable isolation when building source distributions.
///
/// Assumes that build dependencies specified by PEP 518 are already installed.

View file

@ -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<ResolverArgs> 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<ResolverArgs> for PipOptions {
},
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
config_settings_package: config_settings_package.map(|config_settings| {
config_settings
.into_iter()
.collect::<PackageConfigSettings>()
}),
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<InstallerArgs> for PipOptions {
index_strategy,
keyring_provider,
config_setting,
config_settings_package,
no_build_isolation,
build_isolation,
exclude_newer,
@ -120,6 +127,11 @@ impl From<InstallerArgs> for PipOptions {
keyring_provider,
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
config_settings_package: config_settings_package.map(|config_settings| {
config_settings
.into_iter()
.collect::<PackageConfigSettings>()
}),
no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
exclude_newer,
link_mode,
@ -147,6 +159,7 @@ impl From<ResolverInstallerArgs> 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<ResolverInstallerArgs> for PipOptions {
fork_strategy,
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
config_settings_package: config_settings_package.map(|config_settings| {
config_settings
.into_iter()
.collect::<PackageConfigSettings>()
}),
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::<ConfigSettings>()),
config_settings_package: config_settings_package.map(|config_settings| {
config_settings
.into_iter()
.collect::<PackageConfigSettings>()
}),
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::<ConfigSettings>()),
config_settings_package: config_settings_package.map(|config_settings| {
config_settings
.into_iter()
.collect::<PackageConfigSettings>()
}),
no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
no_build_isolation_package: if no_build_isolation_package.is_empty() {
None

View file

@ -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<Self, Self::Err> {
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<PackageName, ConfigSettings>);
impl FromIterator<ConfigSettingPackageEntry> for PackageConfigSettings {
fn from_iter<T: IntoIterator<Item = ConfigSettingPackageEntry>>(iter: T) -> Self {
let mut package_configs: BTreeMap<PackageName, Vec<ConfigSettingEntry>> = 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<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
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<A: serde::de::MapAccess<'de>>(
self,
mut map: A,
) -> Result<Self::Value, A::Error> {
let mut config = BTreeMap::default();
while let Some((key, value)) = map.next_entry::<String, ConfigSettings>()? {
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()))
);
}
}

View file

@ -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<ExcludeNewer>,
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,

View file

@ -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)
}
}
}

View file

@ -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.

View file

@ -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![];

View file

@ -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<ConfigSettings> {
}
}
impl Combine for Option<PackageConfigSettings> {
/// Combine two maps by merging the map in `self` with the map in `other`, if they're both
/// `Some`.
fn combine(self, other: Option<PackageConfigSettings>) -> Option<PackageConfigSettings> {
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

View file

@ -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<ForkStrategy>,
pub dependency_metadata: Option<Vec<StaticMetadata>>,
pub config_settings: Option<ConfigSettings>,
pub config_settings_package: Option<PackageConfigSettings>,
pub exclude_newer: Option<ExcludeNewer>,
pub link_mode: Option<LinkMode>,
pub upgrade: Option<bool>,
@ -587,6 +588,18 @@ pub struct ResolverInstallerOptions {
"#
)]
pub config_settings: Option<ConfigSettings>,
/// 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<PackageConfigSettings>,
/// 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<ConfigSettings>,
/// 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<PackageConfigSettings>,
/// 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<ResolverInstallerOptions> 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<ForkStrategy>,
pub dependency_metadata: Option<Vec<StaticMetadata>>,
pub config_settings: Option<ConfigSettings>,
pub config_settings_package: Option<PackageConfigSettings>,
pub no_build_isolation: Option<bool>,
pub no_build_isolation_package: Option<Vec<PackageName>>,
pub exclude_newer: Option<ExcludeNewer>,
@ -1741,6 +1766,7 @@ impl From<ResolverInstallerOptions> 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<ToolOptions> 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<ForkStrategy>,
dependency_metadata: Option<Vec<StaticMetadata>>,
config_settings: Option<ConfigSettings>,
config_settings_package: Option<PackageConfigSettings>,
no_build_isolation: Option<bool>,
no_build_isolation_package: Option<Vec<PackageName>>,
exclude_newer: Option<ExcludeNewer>,
@ -1911,6 +1939,7 @@ impl From<OptionsWire> 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<OptionsWire> for Options {
fork_strategy,
dependency_metadata,
config_settings,
config_settings_package,
no_build_isolation,
no_build_isolation_package,
exclude_newer,

View file

@ -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;

View file

@ -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<Vec<BuildMessage>, 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,

View file

@ -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<PackageName>,
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,

View file

@ -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<HashCheckingMode>,
installer_metadata: bool,
config_settings: &ConfigSettings,
config_settings_package: &PackageConfigSettings,
no_build_isolation: bool,
no_build_isolation_package: Vec<PackageName>,
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,

View file

@ -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,

View file

@ -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<PackageName>,
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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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: _,

View file

@ -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,

View file

@ -524,6 +524,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
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<ExitStatus> {
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<ExitStatus> {
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,

View file

@ -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<ExcludeNewer>,
@ -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<ExcludeNewer>,
pub(crate) fork_strategy: ForkStrategy,
@ -2770,6 +2774,7 @@ impl From<ResolverOptions> 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<ResolverInstallerOptions> 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<String>,
pub(crate) generate_hashes: bool,
pub(crate) config_setting: ConfigSettings,
pub(crate) config_settings_package: PackageConfigSettings,
pub(crate) python_version: Option<PythonVersion>,
pub(crate) python_platform: Option<TargetTriple>,
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,

View file

@ -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(())
}

View file

@ -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,

View file

@ -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(())
}