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:
Charlie Marsh 2024-02-22 19:53:45 -05:00 committed by GitHub
parent 1103298e6c
commit aa73a4f0ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 392 additions and 35 deletions

3
Cargo.lock generated
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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"]

View file

@ -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"}"#);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,2 @@
# Artifacts from the build process.
*.egg-info/

View 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"]

View file

@ -0,0 +1,2 @@
def a():
pass