mirror of
https://github.com/astral-sh/uv.git
synced 2025-09-26 20:19:08 +00:00
Add support for config_settings
in PEP 517 hooks (#1833)
## Summary Adds `--config-setting` / `-C` (with a `--config-settings` alias for convenience) to the CLI. Closes https://github.com/astral-sh/uv/issues/1460.
This commit is contained in:
parent
1103298e6c
commit
aa73a4f0ea
19 changed files with 392 additions and 35 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -4600,9 +4600,12 @@ name = "uv-traits"
|
|||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"distribution-types",
|
||||
"once-map",
|
||||
"pep508_rs",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"uv-cache",
|
||||
"uv-interpreter",
|
||||
|
|
|
@ -21,7 +21,7 @@ platform-host = { path = "../platform-host" }
|
|||
uv-extract = { path = "../uv-extract" }
|
||||
uv-fs = { path = "../uv-fs" }
|
||||
uv-interpreter = { path = "../uv-interpreter" }
|
||||
uv-traits = { path = "../uv-traits" }
|
||||
uv-traits = { path = "../uv-traits", features = ["serde"] }
|
||||
pypi-types = { path = "../pypi-types" }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
|
|
|
@ -29,7 +29,7 @@ use distribution_types::Resolution;
|
|||
use pep508_rs::Requirement;
|
||||
use uv_fs::Normalized;
|
||||
use uv_interpreter::{Interpreter, Virtualenv};
|
||||
use uv_traits::{BuildContext, BuildKind, SetupPyStrategy, SourceBuildTrait};
|
||||
use uv_traits::{BuildContext, BuildKind, ConfigSettings, SetupPyStrategy, SourceBuildTrait};
|
||||
|
||||
/// e.g. `pygraphviz/graphviz_wrap.c:3020:10: fatal error: graphviz/cgraph.h: No such file or directory`
|
||||
static MISSING_HEADER_RE: Lazy<Regex> = Lazy::new(|| {
|
||||
|
@ -247,6 +247,7 @@ pub struct SourceBuildContext {
|
|||
pub struct SourceBuild {
|
||||
temp_dir: TempDir,
|
||||
source_tree: PathBuf,
|
||||
config_settings: ConfigSettings,
|
||||
/// If performing a PEP 517 build, the backend to use.
|
||||
pep517_backend: Option<Pep517Backend>,
|
||||
/// The virtual environment in which to build the source distribution.
|
||||
|
@ -281,6 +282,7 @@ impl SourceBuild {
|
|||
source_build_context: SourceBuildContext,
|
||||
package_id: String,
|
||||
setup_py: SetupPyStrategy,
|
||||
config_settings: ConfigSettings,
|
||||
build_kind: BuildKind,
|
||||
) -> Result<SourceBuild, Error> {
|
||||
let temp_dir = tempdir_in(build_context.cache().root())?;
|
||||
|
@ -354,6 +356,7 @@ impl SourceBuild {
|
|||
build_context,
|
||||
&package_id,
|
||||
build_kind,
|
||||
&config_settings,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
@ -364,6 +367,7 @@ impl SourceBuild {
|
|||
pep517_backend,
|
||||
venv,
|
||||
build_kind,
|
||||
config_settings,
|
||||
metadata_directory: None,
|
||||
package_id,
|
||||
})
|
||||
|
@ -492,10 +496,13 @@ impl SourceBuild {
|
|||
|
||||
prepare_metadata_for_build_wheel = getattr(backend, "prepare_metadata_for_build_wheel", None)
|
||||
if prepare_metadata_for_build_wheel:
|
||||
print(prepare_metadata_for_build_wheel("{}"))
|
||||
print(prepare_metadata_for_build_wheel("{}", config_settings={}))
|
||||
else:
|
||||
print()
|
||||
"#, pep517_backend.backend_import(), escape_path_for_python(&metadata_directory)
|
||||
"#,
|
||||
pep517_backend.backend_import(),
|
||||
escape_path_for_python(&metadata_directory),
|
||||
self.config_settings.escape_for_python(),
|
||||
};
|
||||
let span = info_span!(
|
||||
"run_python_script",
|
||||
|
@ -619,8 +626,13 @@ impl SourceBuild {
|
|||
let escaped_wheel_dir = escape_path_for_python(wheel_dir);
|
||||
let script = formatdoc! {
|
||||
r#"{}
|
||||
print(backend.build_{}("{}", metadata_directory={}))
|
||||
"#, pep517_backend.backend_import(), self.build_kind, escaped_wheel_dir, metadata_directory
|
||||
print(backend.build_{}("{}", metadata_directory={}, config_settings={}))
|
||||
"#,
|
||||
pep517_backend.backend_import(),
|
||||
self.build_kind,
|
||||
escaped_wheel_dir,
|
||||
metadata_directory,
|
||||
self.config_settings.escape_for_python()
|
||||
};
|
||||
let span = info_span!(
|
||||
"run_python_script",
|
||||
|
@ -682,6 +694,7 @@ async fn create_pep517_build_environment(
|
|||
build_context: &impl BuildContext,
|
||||
package_id: &str,
|
||||
build_kind: BuildKind,
|
||||
config_settings: &ConfigSettings,
|
||||
) -> Result<(), Error> {
|
||||
debug!(
|
||||
"Calling `{}.get_requires_for_build_{}()`",
|
||||
|
@ -694,11 +707,11 @@ async fn create_pep517_build_environment(
|
|||
|
||||
get_requires_for_build = getattr(backend, "get_requires_for_build_{}", None)
|
||||
if get_requires_for_build:
|
||||
requires = get_requires_for_build()
|
||||
requires = get_requires_for_build(config_settings={})
|
||||
else:
|
||||
requires = []
|
||||
print(json.dumps(requires))
|
||||
"#, pep517_backend.backend_import(), build_kind
|
||||
"#, pep517_backend.backend_import(), build_kind, config_settings.escape_for_python()
|
||||
};
|
||||
let span = info_span!(
|
||||
"run_python_script",
|
||||
|
|
|
@ -14,7 +14,7 @@ use uv_dispatch::BuildDispatch;
|
|||
use uv_installer::NoBinary;
|
||||
use uv_interpreter::Virtualenv;
|
||||
use uv_resolver::InMemoryIndex;
|
||||
use uv_traits::{BuildContext, BuildKind, InFlight, NoBuild, SetupPyStrategy};
|
||||
use uv_traits::{BuildContext, BuildKind, ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
|
||||
#[derive(Parser)]
|
||||
pub(crate) struct BuildArgs {
|
||||
|
@ -61,6 +61,7 @@ pub(crate) async fn build(args: BuildArgs) -> Result<PathBuf> {
|
|||
let index = InMemoryIndex::default();
|
||||
let setup_py = SetupPyStrategy::default();
|
||||
let in_flight = InFlight::default();
|
||||
let config_settings = ConfigSettings::default();
|
||||
|
||||
let build_dispatch = BuildDispatch::new(
|
||||
&client,
|
||||
|
@ -72,6 +73,7 @@ pub(crate) async fn build(args: BuildArgs) -> Result<PathBuf> {
|
|||
&in_flight,
|
||||
venv.python_executable(),
|
||||
setup_py,
|
||||
&config_settings,
|
||||
&NoBuild::None,
|
||||
&NoBinary::None,
|
||||
);
|
||||
|
@ -84,6 +86,7 @@ pub(crate) async fn build(args: BuildArgs) -> Result<PathBuf> {
|
|||
SourceBuildContext::default(),
|
||||
args.sdist.display().to_string(),
|
||||
setup_py,
|
||||
config_settings.clone(),
|
||||
build_kind,
|
||||
)
|
||||
.await?;
|
||||
|
|
|
@ -25,7 +25,7 @@ use uv_installer::{Downloader, NoBinary};
|
|||
use uv_interpreter::Virtualenv;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_resolver::{DistFinder, InMemoryIndex};
|
||||
use uv_traits::{BuildContext, InFlight, NoBuild, SetupPyStrategy};
|
||||
use uv_traits::{BuildContext, ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
|
||||
#[derive(Parser)]
|
||||
pub(crate) struct InstallManyArgs {
|
||||
|
@ -65,12 +65,12 @@ pub(crate) async fn install_many(args: InstallManyArgs) -> Result<()> {
|
|||
let setup_py = SetupPyStrategy::default();
|
||||
let in_flight = InFlight::default();
|
||||
let tags = venv.interpreter().tags()?;
|
||||
|
||||
let no_build = if args.no_build {
|
||||
NoBuild::All
|
||||
} else {
|
||||
NoBuild::None
|
||||
};
|
||||
let config_settings = ConfigSettings::default();
|
||||
|
||||
let build_dispatch = BuildDispatch::new(
|
||||
&client,
|
||||
|
@ -82,6 +82,7 @@ pub(crate) async fn install_many(args: InstallManyArgs) -> Result<()> {
|
|||
&in_flight,
|
||||
venv.python_executable(),
|
||||
setup_py,
|
||||
&config_settings,
|
||||
&no_build,
|
||||
&NoBinary::None,
|
||||
);
|
||||
|
|
|
@ -18,7 +18,7 @@ use uv_dispatch::BuildDispatch;
|
|||
use uv_installer::NoBinary;
|
||||
use uv_interpreter::Virtualenv;
|
||||
use uv_resolver::{InMemoryIndex, Manifest, Options, Resolver};
|
||||
use uv_traits::{InFlight, NoBuild, SetupPyStrategy};
|
||||
use uv_traits::{ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
|
||||
#[derive(ValueEnum, Default, Clone)]
|
||||
pub(crate) enum ResolveCliFormat {
|
||||
|
@ -72,12 +72,12 @@ pub(crate) async fn resolve_cli(args: ResolveCliArgs) -> Result<()> {
|
|||
};
|
||||
let index = InMemoryIndex::default();
|
||||
let in_flight = InFlight::default();
|
||||
|
||||
let no_build = if args.no_build {
|
||||
NoBuild::All
|
||||
} else {
|
||||
NoBuild::None
|
||||
};
|
||||
let config_settings = ConfigSettings::default();
|
||||
|
||||
let build_dispatch = BuildDispatch::new(
|
||||
&client,
|
||||
|
@ -89,6 +89,7 @@ pub(crate) async fn resolve_cli(args: ResolveCliArgs) -> Result<()> {
|
|||
&in_flight,
|
||||
venv.python_executable(),
|
||||
SetupPyStrategy::default(),
|
||||
&config_settings,
|
||||
&no_build,
|
||||
&NoBinary::None,
|
||||
);
|
||||
|
|
|
@ -21,7 +21,7 @@ use uv_installer::NoBinary;
|
|||
use uv_interpreter::Virtualenv;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_resolver::InMemoryIndex;
|
||||
use uv_traits::{BuildContext, InFlight, NoBuild, SetupPyStrategy};
|
||||
use uv_traits::{BuildContext, ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
|
||||
#[derive(Parser)]
|
||||
pub(crate) struct ResolveManyArgs {
|
||||
|
@ -96,6 +96,7 @@ pub(crate) async fn resolve_many(args: ResolveManyArgs) -> Result<()> {
|
|||
let index_locations = IndexLocations::default();
|
||||
let setup_py = SetupPyStrategy::default();
|
||||
let flat_index = FlatIndex::default();
|
||||
let config_settings = ConfigSettings::default();
|
||||
|
||||
// Create a `BuildDispatch` for each requirement.
|
||||
let build_dispatch = BuildDispatch::new(
|
||||
|
@ -108,6 +109,7 @@ pub(crate) async fn resolve_many(args: ResolveManyArgs) -> Result<()> {
|
|||
&in_flight,
|
||||
venv.python_executable(),
|
||||
setup_py,
|
||||
&config_settings,
|
||||
&no_build,
|
||||
&NoBinary::None,
|
||||
);
|
||||
|
|
|
@ -18,7 +18,7 @@ use uv_client::{FlatIndex, RegistryClient};
|
|||
use uv_installer::{Downloader, Installer, NoBinary, Plan, Planner, Reinstall, SitePackages};
|
||||
use uv_interpreter::{Interpreter, Virtualenv};
|
||||
use uv_resolver::{InMemoryIndex, Manifest, Options, Resolver};
|
||||
use uv_traits::{BuildContext, BuildKind, InFlight, NoBuild, SetupPyStrategy};
|
||||
use uv_traits::{BuildContext, BuildKind, ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
|
||||
/// The main implementation of [`BuildContext`], used by the CLI, see [`BuildContext`]
|
||||
/// documentation.
|
||||
|
@ -34,6 +34,7 @@ pub struct BuildDispatch<'a> {
|
|||
setup_py: SetupPyStrategy,
|
||||
no_build: &'a NoBuild,
|
||||
no_binary: &'a NoBinary,
|
||||
config_settings: &'a ConfigSettings,
|
||||
source_build_context: SourceBuildContext,
|
||||
options: Options,
|
||||
}
|
||||
|
@ -50,6 +51,7 @@ impl<'a> BuildDispatch<'a> {
|
|||
in_flight: &'a InFlight,
|
||||
base_python: PathBuf,
|
||||
setup_py: SetupPyStrategy,
|
||||
config_settings: &'a ConfigSettings,
|
||||
no_build: &'a NoBuild,
|
||||
no_binary: &'a NoBinary,
|
||||
) -> Self {
|
||||
|
@ -63,6 +65,7 @@ impl<'a> BuildDispatch<'a> {
|
|||
in_flight,
|
||||
base_python,
|
||||
setup_py,
|
||||
config_settings,
|
||||
no_build,
|
||||
no_binary,
|
||||
source_build_context: SourceBuildContext::default(),
|
||||
|
@ -279,6 +282,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
|
|||
self.source_build_context.clone(),
|
||||
package_id.to_string(),
|
||||
self.setup_py,
|
||||
self.config_settings.clone(),
|
||||
build_kind,
|
||||
)
|
||||
.boxed()
|
||||
|
|
|
@ -13,6 +13,7 @@ license = { workspace = true }
|
|||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
clap = { workspace = true, optional = true }
|
||||
distribution-types = { path = "../distribution-types" }
|
||||
once-map = { path = "../once-map" }
|
||||
pep508_rs = { path = "../pep508-rs" }
|
||||
|
@ -21,4 +22,10 @@ uv-interpreter = { path = "../uv-interpreter" }
|
|||
uv-normalize = { path = "../uv-normalize" }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
tokio = { workspace = true, features = ["sync"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
serde = ["dep:serde", "dep:serde_json"]
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
//! Avoid cyclic crate dependencies between resolver, installer and builder.
|
||||
|
||||
use std::collections::btree_map::Entry;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::future::Future;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::ser::SerializeMap;
|
||||
|
||||
use distribution_types::{CachedDist, DistributionId, IndexLocations, Resolution, SourceDist};
|
||||
use once_map::OnceMap;
|
||||
|
@ -288,6 +291,94 @@ impl NoBuild {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConfigSettingEntry {
|
||||
/// The key of the setting. For example, given `key=value`, this would be `key`.
|
||||
key: String,
|
||||
/// The value of the setting. For example, given `key=value`, this would be `value`.
|
||||
value: String,
|
||||
}
|
||||
|
||||
impl FromStr for ConfigSettingEntry {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let Some((key, value)) = s.split_once('=') else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid config setting: {s} (expected `KEY=VALUE`)"
|
||||
));
|
||||
};
|
||||
Ok(Self {
|
||||
key: key.trim().to_string(),
|
||||
value: value.trim().to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum ConfigSettingValue {
|
||||
/// The value consists of a single string.
|
||||
String(String),
|
||||
/// The value consists of a list of strings.
|
||||
List(Vec<String>),
|
||||
}
|
||||
|
||||
/// Settings to pass to a PEP 517 build backend, structured as a map from (string) key to string or
|
||||
/// list of strings.
|
||||
///
|
||||
/// See: <https://peps.python.org/pep-0517/#config-settings>
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct ConfigSettings(BTreeMap<String, ConfigSettingValue>);
|
||||
|
||||
impl FromIterator<ConfigSettingEntry> for ConfigSettings {
|
||||
fn from_iter<T: IntoIterator<Item = ConfigSettingEntry>>(iter: T) -> Self {
|
||||
let mut config = BTreeMap::default();
|
||||
for entry in iter {
|
||||
match config.entry(entry.key) {
|
||||
Entry::Vacant(vacant) => {
|
||||
vacant.insert(ConfigSettingValue::String(entry.value));
|
||||
}
|
||||
Entry::Occupied(mut occupied) => match occupied.get_mut() {
|
||||
ConfigSettingValue::String(existing) => {
|
||||
let existing = existing.clone();
|
||||
occupied.insert(ConfigSettingValue::List(vec![existing, entry.value]));
|
||||
}
|
||||
ConfigSettingValue::List(existing) => {
|
||||
existing.push(entry.value);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Self(config)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl ConfigSettings {
|
||||
/// Convert the settings to a string that can be passed directly to a PEP 517 build backend.
|
||||
pub fn escape_for_python(&self) -> String {
|
||||
serde_json::to_string(self).expect("Failed to serialize config settings")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for ConfigSettings {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let mut map = serializer.serialize_map(Some(self.0.len()))?;
|
||||
for (key, value) in &self.0 {
|
||||
match value {
|
||||
ConfigSettingValue::String(value) => {
|
||||
map.serialize_entry(&key, &value)?;
|
||||
}
|
||||
ConfigSettingValue::List(values) => {
|
||||
map.serialize_entry(&key, &values)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Error;
|
||||
|
@ -349,4 +440,81 @@ mod tests {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_config_settings() {
|
||||
let settings: ConfigSettings = vec![
|
||||
ConfigSettingEntry {
|
||||
key: "key".to_string(),
|
||||
value: "value".to_string(),
|
||||
},
|
||||
ConfigSettingEntry {
|
||||
key: "key".to_string(),
|
||||
value: "value2".to_string(),
|
||||
},
|
||||
ConfigSettingEntry {
|
||||
key: "list".to_string(),
|
||||
value: "value3".to_string(),
|
||||
},
|
||||
ConfigSettingEntry {
|
||||
key: "list".to_string(),
|
||||
value: "value4".to_string(),
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
assert_eq!(
|
||||
settings.0.get("key"),
|
||||
Some(&ConfigSettingValue::List(vec![
|
||||
"value".to_string(),
|
||||
"value2".to_string()
|
||||
]))
|
||||
);
|
||||
assert_eq!(
|
||||
settings.0.get("list"),
|
||||
Some(&ConfigSettingValue::List(vec![
|
||||
"value3".to_string(),
|
||||
"value4".to_string()
|
||||
]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "serde")]
|
||||
fn escape_for_python() {
|
||||
let mut settings = ConfigSettings::default();
|
||||
settings.0.insert(
|
||||
"key".to_string(),
|
||||
ConfigSettingValue::String("value".to_string()),
|
||||
);
|
||||
settings.0.insert(
|
||||
"list".to_string(),
|
||||
ConfigSettingValue::List(vec!["value1".to_string(), "value2".to_string()]),
|
||||
);
|
||||
assert_eq!(
|
||||
settings.escape_for_python(),
|
||||
r#"{"key":"value","list":["value1","value2"]}"#
|
||||
);
|
||||
|
||||
let mut settings = ConfigSettings::default();
|
||||
settings.0.insert(
|
||||
"key".to_string(),
|
||||
ConfigSettingValue::String("Hello, \"world!\"".to_string()),
|
||||
);
|
||||
settings.0.insert(
|
||||
"list".to_string(),
|
||||
ConfigSettingValue::List(vec!["'value1'".to_string()]),
|
||||
);
|
||||
assert_eq!(
|
||||
settings.escape_for_python(),
|
||||
r#"{"key":"Hello, \"world!\"","list":["'value1'"]}"#
|
||||
);
|
||||
|
||||
let mut settings = ConfigSettings::default();
|
||||
settings.0.insert(
|
||||
"key".to_string(),
|
||||
ConfigSettingValue::String("val\\1 {}ue".to_string()),
|
||||
);
|
||||
assert_eq!(settings.escape_for_python(), r#"{"key":"val\\1 {}ue"}"#);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ use uv_resolver::{
|
|||
AnnotationStyle, DependencyMode, DisplayResolutionGraph, InMemoryIndex, Manifest,
|
||||
OptionsBuilder, PreReleaseMode, ResolutionMode, Resolver,
|
||||
};
|
||||
use uv_traits::{InFlight, NoBuild, SetupPyStrategy};
|
||||
use uv_traits::{ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
use uv_warnings::warn_user;
|
||||
|
||||
use crate::commands::reporters::{DownloadReporter, ResolverReporter};
|
||||
|
@ -58,6 +58,7 @@ pub(crate) async fn pip_compile(
|
|||
include_find_links: bool,
|
||||
index_locations: IndexLocations,
|
||||
setup_py: SetupPyStrategy,
|
||||
config_settings: ConfigSettings,
|
||||
connectivity: Connectivity,
|
||||
no_build: &NoBuild,
|
||||
python_version: Option<PythonVersion>,
|
||||
|
@ -219,6 +220,7 @@ pub(crate) async fn pip_compile(
|
|||
&in_flight,
|
||||
interpreter.sys_executable().to_path_buf(),
|
||||
setup_py,
|
||||
&config_settings,
|
||||
no_build,
|
||||
&NoBinary::None,
|
||||
)
|
||||
|
|
|
@ -33,7 +33,7 @@ use uv_resolver::{
|
|||
DependencyMode, InMemoryIndex, Manifest, Options, OptionsBuilder, PreReleaseMode,
|
||||
ResolutionGraph, ResolutionMode, Resolver,
|
||||
};
|
||||
use uv_traits::{InFlight, NoBuild, SetupPyStrategy};
|
||||
use uv_traits::{ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
|
||||
use crate::commands::reporters::{DownloadReporter, InstallReporter, ResolverReporter};
|
||||
use crate::commands::{elapsed, ChangeEvent, ChangeEventKind, ExitStatus};
|
||||
|
@ -58,6 +58,7 @@ pub(crate) async fn pip_install(
|
|||
link_mode: LinkMode,
|
||||
setup_py: SetupPyStrategy,
|
||||
connectivity: Connectivity,
|
||||
config_settings: &ConfigSettings,
|
||||
no_build: &NoBuild,
|
||||
no_binary: &NoBinary,
|
||||
strict: bool,
|
||||
|
@ -173,6 +174,7 @@ pub(crate) async fn pip_install(
|
|||
&in_flight,
|
||||
venv.python_executable(),
|
||||
setup_py,
|
||||
config_settings,
|
||||
no_build,
|
||||
no_binary,
|
||||
)
|
||||
|
@ -255,6 +257,7 @@ pub(crate) async fn pip_install(
|
|||
&in_flight,
|
||||
venv.python_executable(),
|
||||
setup_py,
|
||||
config_settings,
|
||||
no_build,
|
||||
no_binary,
|
||||
)
|
||||
|
|
|
@ -20,7 +20,7 @@ use uv_installer::{
|
|||
};
|
||||
use uv_interpreter::Virtualenv;
|
||||
use uv_resolver::InMemoryIndex;
|
||||
use uv_traits::{InFlight, NoBuild, SetupPyStrategy};
|
||||
use uv_traits::{ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
|
||||
use crate::commands::reporters::{DownloadReporter, FinderReporter, InstallReporter};
|
||||
use crate::commands::{elapsed, ChangeEvent, ChangeEventKind, ExitStatus};
|
||||
|
@ -36,6 +36,7 @@ pub(crate) async fn pip_sync(
|
|||
index_locations: IndexLocations,
|
||||
setup_py: SetupPyStrategy,
|
||||
connectivity: Connectivity,
|
||||
config_settings: &ConfigSettings,
|
||||
no_build: &NoBuild,
|
||||
no_binary: &NoBinary,
|
||||
strict: bool,
|
||||
|
@ -112,6 +113,7 @@ pub(crate) async fn pip_sync(
|
|||
&in_flight,
|
||||
venv.python_executable(),
|
||||
setup_py,
|
||||
config_settings,
|
||||
no_build,
|
||||
no_binary,
|
||||
);
|
||||
|
|
|
@ -22,7 +22,7 @@ use uv_fs::Normalized;
|
|||
use uv_installer::NoBinary;
|
||||
use uv_interpreter::{find_default_python, find_requested_python, Error};
|
||||
use uv_resolver::{InMemoryIndex, OptionsBuilder};
|
||||
use uv_traits::{BuildContext, InFlight, NoBuild, SetupPyStrategy};
|
||||
use uv_traits::{BuildContext, ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
|
||||
use crate::commands::ExitStatus;
|
||||
use crate::printer::Printer;
|
||||
|
@ -154,6 +154,9 @@ async fn venv_impl(
|
|||
// Track in-flight downloads, builds, etc., across resolutions.
|
||||
let in_flight = InFlight::default();
|
||||
|
||||
// For seed packages, assume the default settings are sufficient.
|
||||
let config_settings = ConfigSettings::default();
|
||||
|
||||
// Prep the build context.
|
||||
let build_dispatch = BuildDispatch::new(
|
||||
&client,
|
||||
|
@ -165,6 +168,7 @@ async fn venv_impl(
|
|||
&in_flight,
|
||||
venv.python_executable(),
|
||||
SetupPyStrategy::default(),
|
||||
&config_settings,
|
||||
&NoBuild::All,
|
||||
&NoBinary::None,
|
||||
)
|
||||
|
|
|
@ -20,7 +20,9 @@ use uv_installer::{NoBinary, Reinstall};
|
|||
use uv_interpreter::PythonVersion;
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
use uv_resolver::{AnnotationStyle, DependencyMode, PreReleaseMode, ResolutionMode};
|
||||
use uv_traits::{NoBuild, PackageNameSpecifier, SetupPyStrategy};
|
||||
use uv_traits::{
|
||||
ConfigSettingEntry, ConfigSettings, NoBuild, PackageNameSpecifier, SetupPyStrategy,
|
||||
};
|
||||
|
||||
use crate::commands::{extra_name_with_clap_error, ExitStatus, Upgrade};
|
||||
use crate::compat::CompatArgs;
|
||||
|
@ -331,6 +333,10 @@ struct PipCompileArgs {
|
|||
#[clap(long, conflicts_with = "no_build")]
|
||||
only_binary: Vec<PackageNameSpecifier>,
|
||||
|
||||
/// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs.
|
||||
#[clap(long, short = 'C', alias = "config-settings")]
|
||||
config_setting: Vec<ConfigSettingEntry>,
|
||||
|
||||
/// The minimum Python version that should be supported by the compiled requirements (e.g.,
|
||||
/// `3.7` or `3.7.9`).
|
||||
///
|
||||
|
@ -456,6 +462,10 @@ struct PipSyncArgs {
|
|||
#[clap(long, conflicts_with = "no_build")]
|
||||
only_binary: Vec<PackageNameSpecifier>,
|
||||
|
||||
/// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs.
|
||||
#[clap(long, short = 'C', alias = "config-settings")]
|
||||
config_setting: Vec<ConfigSettingEntry>,
|
||||
|
||||
/// Validate the virtual environment after completing the installation, to detect packages with
|
||||
/// missing dependencies or other issues.
|
||||
#[clap(long)]
|
||||
|
@ -621,6 +631,10 @@ struct PipInstallArgs {
|
|||
#[clap(long, conflicts_with = "no_build")]
|
||||
only_binary: Vec<PackageNameSpecifier>,
|
||||
|
||||
/// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs.
|
||||
#[clap(long, short = 'C', alias = "config-settings")]
|
||||
config_setting: Vec<ConfigSettingEntry>,
|
||||
|
||||
/// Validate the virtual environment after completing the installation, to detect packages with
|
||||
/// missing dependencies or other issues.
|
||||
#[clap(long)]
|
||||
|
@ -873,6 +887,12 @@ async fn run() -> Result<ExitStatus> {
|
|||
} else {
|
||||
DependencyMode::Transitive
|
||||
};
|
||||
let setup_py = if args.legacy_setup_py {
|
||||
SetupPyStrategy::Setuptools
|
||||
} else {
|
||||
SetupPyStrategy::Pep517
|
||||
};
|
||||
let config_settings = args.config_setting.into_iter().collect::<ConfigSettings>();
|
||||
commands::pip_compile(
|
||||
&requirements,
|
||||
&constraints,
|
||||
|
@ -889,11 +909,8 @@ async fn run() -> Result<ExitStatus> {
|
|||
args.emit_index_url,
|
||||
args.emit_find_links,
|
||||
index_urls,
|
||||
if args.legacy_setup_py {
|
||||
SetupPyStrategy::Setuptools
|
||||
} else {
|
||||
SetupPyStrategy::Pep517
|
||||
},
|
||||
setup_py,
|
||||
config_settings,
|
||||
if args.offline {
|
||||
Connectivity::Offline
|
||||
} else {
|
||||
|
@ -928,21 +945,25 @@ async fn run() -> Result<ExitStatus> {
|
|||
let reinstall = Reinstall::from_args(args.reinstall, args.reinstall_package);
|
||||
let no_binary = NoBinary::from_args(args.no_binary);
|
||||
let no_build = NoBuild::from_args(args.only_binary, args.no_build);
|
||||
let setup_py = if args.legacy_setup_py {
|
||||
SetupPyStrategy::Setuptools
|
||||
} else {
|
||||
SetupPyStrategy::Pep517
|
||||
};
|
||||
let config_settings = args.config_setting.into_iter().collect::<ConfigSettings>();
|
||||
|
||||
commands::pip_sync(
|
||||
&sources,
|
||||
&reinstall,
|
||||
args.link_mode,
|
||||
index_urls,
|
||||
if args.legacy_setup_py {
|
||||
SetupPyStrategy::Setuptools
|
||||
} else {
|
||||
SetupPyStrategy::Pep517
|
||||
},
|
||||
setup_py,
|
||||
if args.offline {
|
||||
Connectivity::Offline
|
||||
} else {
|
||||
Connectivity::Online
|
||||
},
|
||||
&config_settings,
|
||||
&no_build,
|
||||
&no_binary,
|
||||
args.strict,
|
||||
|
@ -998,6 +1019,13 @@ async fn run() -> Result<ExitStatus> {
|
|||
} else {
|
||||
DependencyMode::Transitive
|
||||
};
|
||||
let setup_py = if args.legacy_setup_py {
|
||||
SetupPyStrategy::Setuptools
|
||||
} else {
|
||||
SetupPyStrategy::Pep517
|
||||
};
|
||||
let config_settings = args.config_setting.into_iter().collect::<ConfigSettings>();
|
||||
|
||||
commands::pip_install(
|
||||
&requirements,
|
||||
&constraints,
|
||||
|
@ -1010,16 +1038,13 @@ async fn run() -> Result<ExitStatus> {
|
|||
index_urls,
|
||||
&reinstall,
|
||||
args.link_mode,
|
||||
if args.legacy_setup_py {
|
||||
SetupPyStrategy::Setuptools
|
||||
} else {
|
||||
SetupPyStrategy::Pep517
|
||||
},
|
||||
setup_py,
|
||||
if args.offline {
|
||||
Connectivity::Offline
|
||||
} else {
|
||||
Connectivity::Online
|
||||
},
|
||||
&config_settings,
|
||||
&no_build,
|
||||
&no_binary,
|
||||
args.strict,
|
||||
|
|
|
@ -1598,3 +1598,105 @@ fn launcher_with_symlink() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn config_settings() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let current_dir = std::env::current_dir()?;
|
||||
let workspace_dir = regex::escape(
|
||||
Url::from_directory_path(current_dir.join("..").join("..").canonicalize()?)
|
||||
.unwrap()
|
||||
.as_str(),
|
||||
);
|
||||
|
||||
let filters = [(workspace_dir.as_str(), "file://[WORKSPACE_DIR]/")]
|
||||
.into_iter()
|
||||
.chain(INSTA_FILTERS.to_vec())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Install the editable package.
|
||||
uv_snapshot!(filters, Command::new(get_bin())
|
||||
.arg("pip")
|
||||
.arg("install")
|
||||
.arg("-e")
|
||||
.arg("../../scripts/editable-installs/setuptools_editable")
|
||||
.arg("--cache-dir")
|
||||
.arg(context.cache_dir.path())
|
||||
.arg("--exclude-newer")
|
||||
.arg(EXCLUDE_NEWER)
|
||||
.env("VIRTUAL_ENV", context.venv.as_os_str())
|
||||
.env("CARGO_TARGET_DIR", "../../../target/target_install_editable"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Built 1 editable in [TIME]
|
||||
Resolved 2 packages in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
+ setuptools-editable==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/setuptools_editable)
|
||||
"###
|
||||
);
|
||||
|
||||
// When installed without `--editable_mode=compat`, the `finder.py` file should be present.
|
||||
let finder = context
|
||||
.venv
|
||||
.join("lib/python3.12/site-packages")
|
||||
.join("__editable___setuptools_editable_0_1_0_finder.py");
|
||||
assert!(finder.exists());
|
||||
|
||||
// Install the editable package with `--editable_mode=compat`.
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let current_dir = std::env::current_dir()?;
|
||||
let workspace_dir = regex::escape(
|
||||
Url::from_directory_path(current_dir.join("..").join("..").canonicalize()?)
|
||||
.unwrap()
|
||||
.as_str(),
|
||||
);
|
||||
|
||||
let filters = [(workspace_dir.as_str(), "file://[WORKSPACE_DIR]/")]
|
||||
.into_iter()
|
||||
.chain(INSTA_FILTERS.to_vec())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
uv_snapshot!(filters, Command::new(get_bin())
|
||||
.arg("pip")
|
||||
.arg("install")
|
||||
.arg("-e")
|
||||
.arg("../../scripts/editable-installs/setuptools_editable")
|
||||
.arg("-C")
|
||||
.arg("editable_mode=compat")
|
||||
.arg("--cache-dir")
|
||||
.arg(context.cache_dir.path())
|
||||
.arg("--exclude-newer")
|
||||
.arg(EXCLUDE_NEWER)
|
||||
.env("VIRTUAL_ENV", context.venv.as_os_str())
|
||||
.env("CARGO_TARGET_DIR", "../../../target/target_install_editable"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Built 1 editable in [TIME]
|
||||
Resolved 2 packages in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
+ setuptools-editable==0.1.0 (from file://[WORKSPACE_DIR]/scripts/editable-installs/setuptools_editable)
|
||||
"###
|
||||
);
|
||||
|
||||
// When installed without `--editable_mode=compat`, the `finder.py` file should _not_ be present.
|
||||
let finder = context
|
||||
.venv
|
||||
.join("lib/python3.12/site-packages")
|
||||
.join("__editable___setuptools_editable_0_1_0_finder.py");
|
||||
assert!(!finder.exists());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
2
scripts/editable-installs/setuptools_editable/.gitignore
vendored
Normal file
2
scripts/editable-installs/setuptools_editable/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Artifacts from the build process.
|
||||
*.egg-info/
|
13
scripts/editable-installs/setuptools_editable/pyproject.toml
Normal file
13
scripts/editable-installs/setuptools_editable/pyproject.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[project]
|
||||
name = "setuptools_editable"
|
||||
version = "0.1.0"
|
||||
description = "Default template for a setuptools project"
|
||||
authors = [
|
||||
{name = "konstin", email = "konstin@mailbox.org"},
|
||||
]
|
||||
dependencies = ["iniconfig"]
|
||||
requires-python = ">=3.11,<3.13"
|
||||
license = {text = "MIT"}
|
||||
|
||||
[project.optional-dependencies]
|
||||
anyio = ["anyio>=3.3.0"]
|
|
@ -0,0 +1,2 @@
|
|||
def a():
|
||||
pass
|
Loading…
Add table
Add a link
Reference in a new issue