mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-17 22:07:47 +00:00
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
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:
parent
a6a5e65e0c
commit
e724ddc63f
30 changed files with 927 additions and 58 deletions
|
@ -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()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue