feat: add support for PEP 517 config settings in Python builds
This commit is contained in:
+93
-1
@@ -35,6 +35,7 @@ pub fn build(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let flags = &spec.build.flags;
|
let flags = &spec.build.flags;
|
||||||
let actual_src = resolve_actual_src(spec, src_dir)?;
|
let actual_src = resolve_actual_src(spec, src_dir)?;
|
||||||
|
let config_settings = normalize_pep517_config_settings(spec)?;
|
||||||
fs::create_dir_all(destdir)
|
fs::create_dir_all(destdir)
|
||||||
.with_context(|| format!("Failed to create DESTDIR: {}", destdir.display()))?;
|
.with_context(|| format!("Failed to create DESTDIR: {}", destdir.display()))?;
|
||||||
|
|
||||||
@@ -63,9 +64,14 @@ pub fn build(
|
|||||||
|
|
||||||
match detect_frontend(&actual_src)? {
|
match detect_frontend(&actual_src)? {
|
||||||
BuildFrontend::Pep517(cfg) => {
|
BuildFrontend::Pep517(cfg) => {
|
||||||
build_wheel_pep517(&actual_src, &dist_dir, &env_vars, &cfg)?
|
build_wheel_pep517(&actual_src, &dist_dir, &env_vars, &cfg, &config_settings)?
|
||||||
}
|
}
|
||||||
BuildFrontend::LegacySetupPy => {
|
BuildFrontend::LegacySetupPy => {
|
||||||
|
if !config_settings.is_empty() {
|
||||||
|
bail!(
|
||||||
|
"build.flags.config_setting is only supported for PEP 517 builds (pyproject.toml)"
|
||||||
|
);
|
||||||
|
}
|
||||||
build_wheel_setup_py(&actual_src, &dist_dir, &env_vars)?;
|
build_wheel_setup_py(&actual_src, &dist_dir, &env_vars)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,6 +99,39 @@ pub fn build(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn normalize_pep517_config_settings(spec: &PackageSpec) -> Result<Vec<String>> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for raw in &spec.build.flags.config_settings {
|
||||||
|
let expanded = spec.expand_vars(raw);
|
||||||
|
let trimmed = expanded.trim();
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if trimmed.contains('\n') || trimmed.contains('\r') {
|
||||||
|
bail!(
|
||||||
|
"Invalid build.flags.config_setting entry '{}': newlines are not allowed",
|
||||||
|
raw
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = trimmed.split_once('=').map(|(k, _)| k).unwrap_or(trimmed);
|
||||||
|
let key = key.trim();
|
||||||
|
if key.is_empty() || key.chars().any(char::is_whitespace) {
|
||||||
|
bail!(
|
||||||
|
"Invalid build.flags.config_setting entry '{}'; expected KEY=VALUE or KEY",
|
||||||
|
raw
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((_, value)) = trimmed.split_once('=') {
|
||||||
|
out.push(format!("{key}={value}"));
|
||||||
|
} else {
|
||||||
|
out.push(format!("{key}="));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_actual_src(spec: &PackageSpec, src_dir: &Path) -> Result<PathBuf> {
|
fn resolve_actual_src(spec: &PackageSpec, src_dir: &Path) -> Result<PathBuf> {
|
||||||
let source_subdir = spec.expand_vars(&spec.build.flags.source_subdir);
|
let source_subdir = spec.expand_vars(&spec.build.flags.source_subdir);
|
||||||
if source_subdir.is_empty() {
|
if source_subdir.is_empty() {
|
||||||
@@ -264,6 +303,7 @@ fn build_wheel_pep517(
|
|||||||
dist_dir: &Path,
|
dist_dir: &Path,
|
||||||
env_vars: &[(String, String)],
|
env_vars: &[(String, String)],
|
||||||
cfg: &Pep517Config,
|
cfg: &Pep517Config,
|
||||||
|
config_settings: &[String],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
crate::log_info!("Building wheel via PEP 517 backend {}...", cfg.backend);
|
crate::log_info!("Building wheel via PEP 517 backend {}...", cfg.backend);
|
||||||
|
|
||||||
@@ -284,6 +324,11 @@ fn build_wheel_pep517(
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n"),
|
.join("\n"),
|
||||||
);
|
);
|
||||||
|
crate::builder::set_env_var(
|
||||||
|
&mut build_env,
|
||||||
|
"DEPOT_PY_CONFIG_SETTINGS",
|
||||||
|
config_settings.join("\n"),
|
||||||
|
);
|
||||||
crate::builder::prepare_command(&mut cmd, &build_env);
|
crate::builder::prepare_command(&mut cmd, &build_env);
|
||||||
|
|
||||||
let output = cmd.output().with_context(|| {
|
let output = cmd.output().with_context(|| {
|
||||||
@@ -687,6 +732,20 @@ if object_path:
|
|||||||
backend = getattr(backend, attr)
|
backend = getattr(backend, attr)
|
||||||
|
|
||||||
config_settings = {}
|
config_settings = {}
|
||||||
|
for raw in [p for p in os.environ.get("DEPOT_PY_CONFIG_SETTINGS", "").splitlines() if p]:
|
||||||
|
key, _, value = raw.partition("=")
|
||||||
|
key = key.strip()
|
||||||
|
if not key:
|
||||||
|
raise RuntimeError(f"Invalid DEPOT_PY_CONFIG_SETTINGS entry: {raw!r}")
|
||||||
|
existing = config_settings.get(key)
|
||||||
|
if existing is None:
|
||||||
|
config_settings[key] = value
|
||||||
|
elif isinstance(existing, list):
|
||||||
|
existing.append(value)
|
||||||
|
else:
|
||||||
|
config_settings[key] = [existing, value]
|
||||||
|
if not config_settings:
|
||||||
|
config_settings = None
|
||||||
if not hasattr(backend, "build_wheel"):
|
if not hasattr(backend, "build_wheel"):
|
||||||
raise RuntimeError(f"Backend {backend_spec!r} does not provide build_wheel")
|
raise RuntimeError(f"Backend {backend_spec!r} does not provide build_wheel")
|
||||||
wheel_name = backend.build_wheel(str(dist_dir), config_settings, None)
|
wheel_name = backend.build_wheel(str(dist_dir), config_settings, None)
|
||||||
@@ -734,6 +793,39 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normalize_pep517_config_settings_accepts_key_value_and_key_only() -> Result<()> {
|
||||||
|
let mut spec = mk_spec();
|
||||||
|
spec.package.version = "1.2.3".into();
|
||||||
|
spec.build.flags.config_settings = vec![
|
||||||
|
"editable_mode=compat".into(),
|
||||||
|
"setup-args=--plat-name=x86_64".into(),
|
||||||
|
"builddir".into(),
|
||||||
|
"version=$version".into(),
|
||||||
|
];
|
||||||
|
|
||||||
|
let normalized = normalize_pep517_config_settings(&spec)?;
|
||||||
|
assert_eq!(
|
||||||
|
normalized,
|
||||||
|
vec![
|
||||||
|
"editable_mode=compat".to_string(),
|
||||||
|
"setup-args=--plat-name=x86_64".to_string(),
|
||||||
|
"builddir=".to_string(),
|
||||||
|
"version=1.2.3".to_string(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normalize_pep517_config_settings_rejects_invalid_key() {
|
||||||
|
let mut spec = mk_spec();
|
||||||
|
spec.build.flags.config_settings = vec!["bad key=value".into()];
|
||||||
|
let err = normalize_pep517_config_settings(&spec)
|
||||||
|
.expect_err("invalid config setting key should fail");
|
||||||
|
assert!(err.to_string().contains("expected KEY=VALUE or KEY"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_console_scripts_basic() -> Result<()> {
|
fn parse_console_scripts_basic() -> Result<()> {
|
||||||
let text = r#"
|
let text = r#"
|
||||||
|
|||||||
@@ -614,6 +614,13 @@ pub fn create_interactive() -> Result<PackageSpec> {
|
|||||||
.prompt()?;
|
.prompt()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if matches!(build_type, BuildType::Python) && show_advanced {
|
||||||
|
flags.config_settings = prompt_repeating_list(
|
||||||
|
"PEP 517 config-setting",
|
||||||
|
"Format: KEY=VALUE (repeat keys to pass multiple values)",
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
if matches!(build_type, BuildType::Bin) {
|
if matches!(build_type, BuildType::Bin) {
|
||||||
flags.binary_type = prompt_optional_text(
|
flags.binary_type = prompt_optional_text(
|
||||||
"Binary type (optional):",
|
"Binary type (optional):",
|
||||||
@@ -1222,6 +1229,19 @@ pub fn spec_to_minimal_toml(spec: &PackageSpec) -> anyhow::Result<String> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if !spec.build.flags.config_settings.is_empty() {
|
||||||
|
flags_tbl.insert(
|
||||||
|
"config_setting".into(),
|
||||||
|
Value::Array(
|
||||||
|
spec.build
|
||||||
|
.flags
|
||||||
|
.config_settings
|
||||||
|
.iter()
|
||||||
|
.map(|s| Value::String(s.clone()))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
if spec.build.flags.bindir != defaults.bindir {
|
if spec.build.flags.bindir != defaults.bindir {
|
||||||
flags_tbl.insert(
|
flags_tbl.insert(
|
||||||
"bindir".into(),
|
"bindir".into(),
|
||||||
@@ -1445,6 +1465,7 @@ mod tests {
|
|||||||
flags.makefile_commands = vec!["make".into()];
|
flags.makefile_commands = vec!["make".into()];
|
||||||
flags.makefile_install_commands = vec!["make DESTDIR=$DESTDIR install".into()];
|
flags.makefile_install_commands = vec!["make DESTDIR=$DESTDIR install".into()];
|
||||||
flags.cargs = vec!["--locked".into()];
|
flags.cargs = vec!["--locked".into()];
|
||||||
|
flags.config_settings = vec!["editable_mode=compat".into()];
|
||||||
flags.rustflags = vec!["-Ctarget-cpu=native".into()];
|
flags.rustflags = vec!["-Ctarget-cpu=native".into()];
|
||||||
flags.cxxflags = vec!["-O2".into(), "-fno-rtti".into()];
|
flags.cxxflags = vec!["-O2".into(), "-fno-rtti".into()];
|
||||||
flags.target = "x86_64-unknown-linux-gnu".into();
|
flags.target = "x86_64-unknown-linux-gnu".into();
|
||||||
@@ -1501,6 +1522,7 @@ mod tests {
|
|||||||
assert!(toml.contains("makefile_commands = ["));
|
assert!(toml.contains("makefile_commands = ["));
|
||||||
assert!(toml.contains("makefile_install_commands = ["));
|
assert!(toml.contains("makefile_install_commands = ["));
|
||||||
assert!(toml.contains("cargs = ["));
|
assert!(toml.contains("cargs = ["));
|
||||||
|
assert!(toml.contains("config_setting = ["));
|
||||||
assert!(toml.contains("rustflags = ["));
|
assert!(toml.contains("rustflags = ["));
|
||||||
assert!(toml.contains("cxxflags = ["));
|
assert!(toml.contains("cxxflags = ["));
|
||||||
assert!(toml.contains("target = \"x86_64-unknown-linux-gnu\""));
|
assert!(toml.contains("target = \"x86_64-unknown-linux-gnu\""));
|
||||||
|
|||||||
@@ -531,6 +531,17 @@ impl PackageSpec {
|
|||||||
self.build.flags.configure_lib32 = vec![s.to_string()];
|
self.build.flags.configure_lib32 = vec![s.to_string()];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"config_setting" | "config_settings" | "config-setting" | "config-settings" => {
|
||||||
|
if let Some(arr) = v.as_array() {
|
||||||
|
self.build.flags.config_settings = arr
|
||||||
|
.iter()
|
||||||
|
.filter_map(|x| x.as_str())
|
||||||
|
.map(String::from)
|
||||||
|
.collect();
|
||||||
|
} else if let Some(s) = v.as_str() {
|
||||||
|
self.build.flags.config_settings = vec![s.to_string()];
|
||||||
|
}
|
||||||
|
}
|
||||||
"configure_file" | "configure-file" => {
|
"configure_file" | "configure-file" => {
|
||||||
if let Some(s) = v.as_str() {
|
if let Some(s) = v.as_str() {
|
||||||
self.build.flags.configure_file = s.to_string();
|
self.build.flags.configure_file = s.to_string();
|
||||||
@@ -706,6 +717,18 @@ impl PackageSpec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"config_setting" | "config_settings" | "config-setting" | "config-settings" => {
|
||||||
|
for v in values {
|
||||||
|
if let Some(arr) = v.as_array() {
|
||||||
|
self.build
|
||||||
|
.flags
|
||||||
|
.config_settings
|
||||||
|
.extend(arr.iter().filter_map(|x| x.as_str()).map(String::from));
|
||||||
|
} else if let Some(s) = v.as_str() {
|
||||||
|
self.build.flags.config_settings.push(s.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
"configure_file" | "configure-file" => {
|
"configure_file" | "configure-file" => {
|
||||||
if let Some(s) = values.last().and_then(|v| v.as_str()) {
|
if let Some(s) = values.last().and_then(|v| v.as_str()) {
|
||||||
self.build.flags.configure_file = s.to_string();
|
self.build.flags.configure_file = s.to_string();
|
||||||
@@ -1358,6 +1381,45 @@ type = "python"
|
|||||||
assert!(matches!(spec.build.build_type, BuildType::Python));
|
assert!(matches!(spec.build.build_type, BuildType::Python));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_python_config_settings_from_spec() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
let path = tmp.path().join("pkg.toml");
|
||||||
|
|
||||||
|
std::fs::write(
|
||||||
|
&path,
|
||||||
|
r#"
|
||||||
|
[package]
|
||||||
|
name = "foo"
|
||||||
|
version = "1.0"
|
||||||
|
description = "d"
|
||||||
|
homepage = "h"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://example.com/foo.tar.gz"
|
||||||
|
sha256 = "skip"
|
||||||
|
extract_dir = "foo"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
type = "python"
|
||||||
|
|
||||||
|
[build.flags]
|
||||||
|
config-setting = ["editable_mode=compat", "setup-args=--plat-name=x86_64"]
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let spec = PackageSpec::from_file(&path).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
spec.build.flags.config_settings,
|
||||||
|
vec![
|
||||||
|
"editable_mode=compat".to_string(),
|
||||||
|
"setup-args=--plat-name=x86_64".to_string()
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_multiple_licenses() {
|
fn parse_multiple_licenses() {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
@@ -1601,6 +1663,7 @@ no_compress_man = true
|
|||||||
skip_tests = true
|
skip_tests = true
|
||||||
keep = ["etc/locale.gen"]
|
keep = ["etc/locale.gen"]
|
||||||
configure_file = "configure.gnu"
|
configure_file = "configure.gnu"
|
||||||
|
config-setting = ["editable_mode=compat"]
|
||||||
post_configure = ["echo configured"]
|
post_configure = ["echo configured"]
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
@@ -1666,6 +1729,12 @@ post_configure = ["echo configured"]
|
|||||||
"build.flags.configure_file".to_string(),
|
"build.flags.configure_file".to_string(),
|
||||||
vec![toml::Value::String("build-aux/configure".to_string())],
|
vec![toml::Value::String("build-aux/configure".to_string())],
|
||||||
);
|
);
|
||||||
|
config.appends.insert(
|
||||||
|
"build.flags.config-setting".to_string(),
|
||||||
|
vec![toml::Value::String(
|
||||||
|
"setup-args=--plat-name=x86_64".to_string(),
|
||||||
|
)],
|
||||||
|
);
|
||||||
config.appends.insert(
|
config.appends.insert(
|
||||||
"build.flags.post_configure".to_string(),
|
"build.flags.post_configure".to_string(),
|
||||||
vec![toml::Value::String("touch configured.stamp".to_string())],
|
vec![toml::Value::String("touch configured.stamp".to_string())],
|
||||||
@@ -1754,6 +1823,18 @@ post_configure = ["echo configured"]
|
|||||||
.contains(&"tools".to_string())
|
.contains(&"tools".to_string())
|
||||||
);
|
);
|
||||||
assert_eq!(spec.build.flags.configure_file, "build-aux/configure");
|
assert_eq!(spec.build.flags.configure_file, "build-aux/configure");
|
||||||
|
assert!(
|
||||||
|
spec.build
|
||||||
|
.flags
|
||||||
|
.config_settings
|
||||||
|
.contains(&"editable_mode=compat".to_string())
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
spec.build
|
||||||
|
.flags
|
||||||
|
.config_settings
|
||||||
|
.contains(&"setup-args=--plat-name=x86_64".to_string())
|
||||||
|
);
|
||||||
assert!(
|
assert!(
|
||||||
spec.build
|
spec.build
|
||||||
.flags
|
.flags
|
||||||
@@ -2689,6 +2770,16 @@ pub struct BuildFlags {
|
|||||||
pub build_32: bool,
|
pub build_32: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub configure: Vec<String>,
|
pub configure: Vec<String>,
|
||||||
|
/// PEP 517 config settings for Python builds (each entry is `KEY=VALUE` or `KEY`).
|
||||||
|
#[serde(
|
||||||
|
default,
|
||||||
|
alias = "config-setting",
|
||||||
|
alias = "config-settings",
|
||||||
|
alias = "config_setting",
|
||||||
|
alias = "config_settings",
|
||||||
|
deserialize_with = "deserialize_string_or_array_no_split"
|
||||||
|
)]
|
||||||
|
pub config_settings: Vec<String>,
|
||||||
/// Configure arguments used only for the lib32 build variant (replaces `configure` when set).
|
/// Configure arguments used only for the lib32 build variant (replaces `configure` when set).
|
||||||
#[serde(default, alias = "configure-lib32", alias = "configure_lib32")]
|
#[serde(default, alias = "configure-lib32", alias = "configure_lib32")]
|
||||||
pub configure_lib32: Vec<String>,
|
pub configure_lib32: Vec<String>,
|
||||||
@@ -2911,6 +3002,7 @@ impl Default for BuildFlags {
|
|||||||
skip_tests: false,
|
skip_tests: false,
|
||||||
build_32: false,
|
build_32: false,
|
||||||
configure: Vec::new(),
|
configure: Vec::new(),
|
||||||
|
config_settings: Vec::new(),
|
||||||
configure_lib32: Vec::new(),
|
configure_lib32: Vec::new(),
|
||||||
configure_file: String::new(),
|
configure_file: String::new(),
|
||||||
cc: default_cc(),
|
cc: default_cc(),
|
||||||
@@ -2977,6 +3069,26 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn deserialize_string_or_array_no_split<'de, D>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> std::result::Result<Vec<String>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum StringOrArray {
|
||||||
|
String(String),
|
||||||
|
Array(Vec<String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
match Option::<StringOrArray>::deserialize(deserializer)? {
|
||||||
|
Some(StringOrArray::String(s)) => Ok(vec![s]),
|
||||||
|
Some(StringOrArray::Array(a)) => Ok(a),
|
||||||
|
None => Ok(Vec::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn deserialize_boolish<'de, D>(deserializer: D) -> std::result::Result<bool, D::Error>
|
fn deserialize_boolish<'de, D>(deserializer: D) -> std::result::Result<bool, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
|
|||||||
Reference in New Issue
Block a user