From 4ddf121dbfe0a31faf61b7c589b8db59c6ceec86 Mon Sep 17 00:00:00 2001 From: SFG545 Date: Thu, 26 Feb 2026 16:58:20 -0600 Subject: [PATCH] Add lib32 companion build pipeline --- src/builder/autotools.rs | 35 +++++- src/builder/cmake.rs | 5 +- src/builder/custom.rs | 5 +- src/builder/makefile.rs | 5 +- src/builder/meson.rs | 5 +- src/builder/python.rs | 5 +- src/builder/state.rs | 29 ++++- src/cross.rs | 5 +- src/main.rs | 231 ++++++++++++++++++++++++++++++++++++++- src/package/spec.rs | 231 ++++++++++++++++++++++++++++++++++++++- 10 files changed, 539 insertions(+), 17 deletions(-) diff --git a/src/builder/autotools.rs b/src/builder/autotools.rs index 2ed49c3..de15f49 100755 --- a/src/builder/autotools.rs +++ b/src/builder/autotools.rs @@ -40,7 +40,10 @@ pub fn build( } use crate::builder::state::{BuildStep, StateTracker}; - let mut state = StateTracker::new(&actual_src)?; + let mut state = StateTracker::new_with_namespace( + &actual_src, + spec.build.flags.lib32_variant.then_some("lib32"), + )?; // Run configure let build_dir = if let Some(dir) = &flags.build_dir { @@ -78,7 +81,14 @@ pub fn build( Some(flags.chost.clone()) } else { None - }; + } + .map(|host| { + if flags.lib32_variant { + lib32_host_triple(&host) + } else { + host + } + }); let requested_build = if cross.is_some() { CrossConfig::build_triple().ok() @@ -104,6 +114,15 @@ pub fn build( } } + if flags.lib32_variant { + if !has_configure_option_prefix(&flags.configure, "--libdir") { + configure_cmd.arg("--libdir=/usr/lib32"); + } + if !has_configure_option_prefix(&flags.configure, "--libexecdir") { + configure_cmd.arg("--libexecdir=/usr/lib32"); + } + } + for arg in &flags.configure { let expanded = expand_configure_arg(spec, arg, &env_vars); configure_cmd.arg(expanded); @@ -412,6 +431,18 @@ fn configure_supports_option(help_text: Option<&str>, option: &str, configure_fi .unwrap_or(configure_file.trim().is_empty()) } +fn has_configure_option_prefix(args: &[String], option: &str) -> bool { + let with_eq = format!("{option}="); + args.iter().any(|arg| { + let trimmed = arg.trim(); + trimmed == option || trimmed.starts_with(&with_eq) + }) +} + +fn lib32_host_triple(host: &str) -> String { + host.replace("x86_64", "i686") +} + fn find_autotools_test_target(build_dir: &Path) -> Result> { for target in ["check", "test"] { if makefile_has_target(build_dir, target)? { diff --git a/src/builder/cmake.rs b/src/builder/cmake.rs index 0ad30f1..bda1865 100755 --- a/src/builder/cmake.rs +++ b/src/builder/cmake.rs @@ -46,7 +46,10 @@ pub fn build( }; use crate::builder::state::{BuildStep, StateTracker}; - let mut state = StateTracker::new(&actual_src)?; + let mut state = StateTracker::new_with_namespace( + &actual_src, + spec.build.flags.lib32_variant.then_some("lib32"), + )?; // Run cmake configure if !state.is_done(BuildStep::Configured) { diff --git a/src/builder/custom.rs b/src/builder/custom.rs index 0ecad1c..67a3ce4 100755 --- a/src/builder/custom.rs +++ b/src/builder/custom.rs @@ -59,7 +59,10 @@ pub fn build( } use crate::builder::state::{BuildStep, StateTracker}; - let mut state = StateTracker::new(src_dir)?; + let mut state = StateTracker::new_with_namespace( + src_dir, + spec.build.flags.lib32_variant.then_some("lib32"), + )?; if !state.is_done(BuildStep::Configured) { hooks::run_post_configure_commands(spec, src_dir, destdir)?; diff --git a/src/builder/makefile.rs b/src/builder/makefile.rs index d1fed7e..8340897 100644 --- a/src/builder/makefile.rs +++ b/src/builder/makefile.rs @@ -12,7 +12,10 @@ pub fn build( cross: Option<&CrossConfig>, export_compiler_flags: bool, ) -> Result<()> { - let mut state = StateTracker::new(src_dir)?; + let mut state = StateTracker::new_with_namespace( + src_dir, + spec.build.flags.lib32_variant.then_some("lib32"), + )?; let flags = &spec.build.flags; let mut env_vars = crate::builder::standard_build_env(spec, cross, true, export_compiler_flags); diff --git a/src/builder/meson.rs b/src/builder/meson.rs index 59c2ebe..893509c 100755 --- a/src/builder/meson.rs +++ b/src/builder/meson.rs @@ -37,7 +37,10 @@ pub fn build( }; use crate::builder::state::{BuildStep, StateTracker}; - let mut state = StateTracker::new(&actual_src)?; + let mut state = StateTracker::new_with_namespace( + &actual_src, + spec.build.flags.lib32_variant.then_some("lib32"), + )?; // Run meson setup if !state.is_done(BuildStep::Configured) { diff --git a/src/builder/python.rs b/src/builder/python.rs index 63c2cec..6d4fc01 100644 --- a/src/builder/python.rs +++ b/src/builder/python.rs @@ -43,7 +43,10 @@ pub fn build( crate::builder::set_env_var(&mut env_vars, "PYTHONDONTWRITEBYTECODE", "1"); crate::builder::set_env_var(&mut env_vars, "SETUPTOOLS_USE_DISTUTILS", "local"); - let mut state = StateTracker::new(&actual_src)?; + let mut state = StateTracker::new_with_namespace( + &actual_src, + spec.build.flags.lib32_variant.then_some("lib32"), + )?; if !state.is_done(BuildStep::Configured) { hooks::run_post_configure_commands(spec, &actual_src, destdir)?; state.mark_done(BuildStep::Configured)?; diff --git a/src/builder/state.rs b/src/builder/state.rs index 342eabb..8bf98c4 100644 --- a/src/builder/state.rs +++ b/src/builder/state.rs @@ -27,7 +27,15 @@ pub struct StateTracker { impl StateTracker { pub fn new(source_dir: &Path) -> Result { - let state_file = source_dir.join(".depot_state"); + Self::new_with_namespace(source_dir, None) + } + + pub fn new_with_namespace(source_dir: &Path, namespace: Option<&str>) -> Result { + let state_file = if let Some(ns) = namespace.and_then(normalize_state_namespace) { + source_dir.join(format!(".depot_state_{}", ns)) + } else { + source_dir.join(".depot_state") + }; let state = if state_file.exists() { let content = fs::read_to_string(&state_file)?; toml::from_str(&content).unwrap_or_default() @@ -54,6 +62,25 @@ impl StateTracker { } } +fn normalize_state_namespace(namespace: &str) -> Option { + let normalized: String = namespace + .chars() + .map(|ch| { + if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' { + ch + } else { + '_' + } + }) + .collect(); + let trimmed = normalized.trim_matches('_'); + if trimmed.is_empty() { + None + } else { + Some(trimmed.to_string()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/cross.rs b/src/cross.rs index 2d464ef..4a7fd94 100755 --- a/src/cross.rs +++ b/src/cross.rs @@ -86,10 +86,11 @@ impl CrossConfig { /// Get the build machine triple (native compiler) pub fn build_triple() -> Result { - let output = Command::new("gcc") + let output = Command::new("cc") .arg("-dumpmachine") .output() - .or_else(|_| Command::new("cc").arg("-dumpmachine").output()) + .or_else(|_| Command::new("clang").arg("-dumpmachine").output()) + .or_else(|_| Command::new("gcc").arg("-dumpmachine").output()) .context("Failed to determine build machine triple")?; Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) diff --git a/src/main.rs b/src/main.rs index 9f97b63..a367d95 100755 --- a/src/main.rs +++ b/src/main.rs @@ -69,6 +69,173 @@ fn output_destdir_for(base_destdir: &Path, primary_pkg: &str, output_pkg: &str) } } +fn lib32_package_name(name: &str) -> String { + format!("lib32-{name}") +} + +fn lib32_arch_name(arch: &str) -> String { + arch.replace("x86_64", "i686") +} + +fn make_lib32_build_spec(base: &package::PackageSpec) -> package::PackageSpec { + let mut spec = base.clone(); + let flags = &mut spec.build.flags; + flags.lib32_variant = true; + + if !flags.cflags_lib32.is_empty() { + flags.cflags.extend(flags.cflags_lib32.clone()); + } + if !flags.cxxflags_lib32.is_empty() { + flags.cxxflags.extend(flags.cxxflags_lib32.clone()); + } + if !flags.configure_lib32.is_empty() { + flags.configure = flags.configure_lib32.clone(); + } + if !flags.post_install_lib32.is_empty() { + flags.post_install = flags.post_install_lib32.clone(); + } + + flags.cc = format!("{} -m32", flags.cc.trim()); + flags.cxx = format!("{} -m32", flags.cxx.trim()); + + spec +} + +fn make_lib32_package_spec(base: &package::PackageSpec) -> package::PackageSpec { + let mut spec = base.clone(); + spec.package.name = lib32_package_name(&base.package.name); + // The lib32 pass currently emits a single companion package from /usr/lib32. + spec.packages.clear(); + spec +} + +fn copy_tree_preserving_links(src: &Path, dst: &Path) -> Result<()> { + use walkdir::WalkDir; + + fs::create_dir_all(dst) + .with_context(|| format!("Failed to create destination dir: {}", dst.display()))?; + + for entry in WalkDir::new(src) { + let entry = entry?; + let rel = entry + .path() + .strip_prefix(src) + .with_context(|| format!("Failed to strip prefix: {}", src.display()))?; + let target = dst.join(rel); + + if entry.file_type().is_dir() { + fs::create_dir_all(&target) + .with_context(|| format!("Failed to create dir: {}", target.display()))?; + continue; + } + + if let Some(parent) = target.parent() { + fs::create_dir_all(parent) + .with_context(|| format!("Failed to create dir: {}", parent.display()))?; + } + + if entry.file_type().is_symlink() { + let link_target = fs::read_link(entry.path()) + .with_context(|| format!("Failed to read symlink: {}", entry.path().display()))?; + #[cfg(unix)] + { + use std::os::unix::fs as unix_fs; + unix_fs::symlink(&link_target, &target).with_context(|| { + format!( + "Failed to create symlink {} -> {}", + target.display(), + link_target.display() + ) + })?; + } + #[cfg(not(unix))] + { + anyhow::bail!( + "Symlink-preserving lib32 staging copy is only supported on unix hosts" + ); + } + } else { + fs::copy(entry.path(), &target).with_context(|| { + format!( + "Failed to copy {} to {}", + entry.path().display(), + target.display() + ) + })?; + } + } + + Ok(()) +} + +fn build_lib32_companion_package( + pkg_spec: &package::PackageSpec, + src_dir: &Path, + config: &config::Config, + cross_config: Option<&cross::CrossConfig>, + export_compiler_flags: bool, +) -> Result> { + if !pkg_spec.build.flags.build_32 { + return Ok(None); + } + if pkg_spec.is_metapackage() { + crate::log_warn!( + "Ignoring build.flags.build-32 for metapackage {}", + pkg_spec.package.name + ); + return Ok(None); + } + + crate::log_info!("Running separate lib32 build pass..."); + let lib32_build_spec = make_lib32_build_spec(pkg_spec); + let lib32_install_destdir = config + .build_dir + .join("destdir") + .join(".lib32-build") + .join(&pkg_spec.package.name) + .join("lib32-dest"); + + builder::build( + &lib32_build_spec, + src_dir, + &lib32_install_destdir, + cross_config, + export_compiler_flags, + )?; + + let lib32_src = lib32_install_destdir.join("usr/lib32"); + if !lib32_src.exists() { + anyhow::bail!( + "lib32 build completed but did not install usr/lib32 into {}", + lib32_install_destdir.display() + ); + } + + let lib32_pkg_spec = make_lib32_package_spec(pkg_spec); + let lib32_destdir = config + .build_dir + .join("destdir") + .join(&lib32_pkg_spec.package.name); + if lib32_destdir.exists() { + fs::remove_dir_all(&lib32_destdir).with_context(|| { + format!("Failed to clean lib32 destdir: {}", lib32_destdir.display()) + })?; + } + fs::create_dir_all(&lib32_destdir).with_context(|| { + format!( + "Failed to create lib32 destdir: {}", + lib32_destdir.display() + ) + })?; + + copy_tree_preserving_links(&lib32_src, &lib32_destdir.join("usr/lib32"))?; + staging::add_licenses(src_dir, &lib32_destdir, &lib32_pkg_spec.package.name)?; + install::scripts::stage_scripts_from_spec_dir(&lib32_pkg_spec, &lib32_destdir)?; + staging::process(&lib32_destdir, &lib32_pkg_spec)?; + + Ok(Some((lib32_pkg_spec, lib32_destdir))) +} + fn install_staged_to_rootfs( pkg_spec: &package::PackageSpec, destdir: &Path, @@ -1166,11 +1333,19 @@ fn main() -> Result<()> { } } + let cross_config = cli + .cross_prefix + .as_ref() + .map(|p| cross::CrossConfig::from_prefix(p)) + .transpose()?; + let mut built_src_dir: Option = None; + let destdir = if let Some(dir) = &staging_dir { dir.path().to_path_buf() } else { // 1-2. Fetch + extract sources (supports archives and git URL#rev) let src_dir = source::prepare(&pkg_spec, &config.cache_dir, &config.build_dir)?; + built_src_dir = Some(src_dir.clone()); // 3. Build let destdir = config @@ -1178,12 +1353,6 @@ fn main() -> Result<()> { .join("destdir") .join(&pkg_spec.package.name); - // Build with optional cross-compilation - let cross_config = cli - .cross_prefix - .as_ref() - .map(|p| cross::CrossConfig::from_prefix(p)) - .transpose()?; builder::build( &pkg_spec, &src_dir, @@ -1220,6 +1389,22 @@ fn main() -> Result<()> { // TODO(snapper): create post-install snapshot after install commit succeeds. } + if let Some(src_dir) = built_src_dir.as_deref() + && let Some((lib32_spec, lib32_destdir)) = build_lib32_companion_package( + &pkg_spec, + src_dir, + &config, + cross_config.as_ref(), + !cli.no_flags, + )? + { + install_staged_to_rootfs(&lib32_spec, &lib32_destdir, &cli.rootfs, &config)?; + ui::success(format!( + "Successfully installed {} v{}", + lib32_spec.package.name, lib32_spec.package.version + )); + } + if cli.clean { clean_build_workspace(&config)?; } @@ -1408,6 +1593,33 @@ fn main() -> Result<()> { created_files.push(pkg_file); } + let mut lib32_install_bundle: Option<(package::PackageSpec, PathBuf)> = None; + if let Some((lib32_spec, lib32_destdir)) = build_lib32_companion_package( + &pkg_spec, + &src_dir, + &config, + cross_config.as_ref(), + !cli.no_flags, + )? { + let lib32_arch = lib32_arch_name(arch); + let packager = package::Packager::new( + lib32_spec.clone(), + lib32_destdir.clone(), + config.clone(), + ); + let pkg_file = packager.create_package(Path::new("."), &lib32_arch)?; + if let Some(sig_path) = + signing::auto_sign_zst_file_detached(&cli.rootfs, &pkg_file)? + { + ui::success(format!( + "Created detached signature: {}", + sig_path.display() + )); + } + created_files.push(pkg_file); + lib32_install_bundle = Some((lib32_spec, lib32_destdir)); + } + for f in &created_files { ui::success(format!("Build complete. Package created: {}", f.display())); } @@ -1432,6 +1644,13 @@ fn main() -> Result<()> { )); // TODO(snapper): create post-install snapshot after --install commit succeeds. } + if let Some((lib32_spec, lib32_destdir)) = &lib32_install_bundle { + install_staged_to_rootfs(lib32_spec, lib32_destdir, &cli.rootfs, &config)?; + ui::success(format!( + "Successfully installed {} v{}", + lib32_spec.package.name, lib32_spec.package.version + )); + } } if cli.clean { diff --git a/src/package/spec.rs b/src/package/spec.rs index 1b7a74a..68c50d3 100755 --- a/src/package/spec.rs +++ b/src/package/spec.rs @@ -240,6 +240,17 @@ impl PackageSpec { self.build.flags.cflags = vec![s.to_string()]; } } + "cflags-lib32" | "cflags_lib32" => { + if let Some(arr) = v.as_array() { + self.build.flags.cflags_lib32 = arr + .iter() + .filter_map(|x| x.as_str()) + .map(String::from) + .collect(); + } else if let Some(s) = v.as_str() { + self.build.flags.cflags_lib32 = vec![s.to_string()]; + } + } "cxxflags" => { if let Some(arr) = v.as_array() { self.build.flags.cxxflags = arr @@ -251,6 +262,17 @@ impl PackageSpec { self.build.flags.cxxflags = vec![s.to_string()]; } } + "cxxflags-lib32" | "cxxflags_lib32" => { + if let Some(arr) = v.as_array() { + self.build.flags.cxxflags_lib32 = arr + .iter() + .filter_map(|x| x.as_str()) + .map(String::from) + .collect(); + } else if let Some(s) = v.as_str() { + self.build.flags.cxxflags_lib32 = vec![s.to_string()]; + } + } "ldflags" => { if let Some(arr) = v.as_array() { self.build.flags.ldflags = arr @@ -472,6 +494,22 @@ impl PackageSpec { self.build.flags.skip_tests = b; } } + "build_32" | "build-32" => { + if let Some(b) = toml_value_as_boolish(v) { + self.build.flags.build_32 = b; + } + } + "configure_lib32" | "configure-lib32" => { + if let Some(arr) = v.as_array() { + self.build.flags.configure_lib32 = arr + .iter() + .filter_map(|x| x.as_str()) + .map(String::from) + .collect(); + } else if let Some(s) = v.as_str() { + self.build.flags.configure_lib32 = vec![s.to_string()]; + } + } "configure_file" | "configure-file" => { if let Some(s) = v.as_str() { self.build.flags.configure_file = s.to_string(); @@ -510,6 +548,17 @@ impl PackageSpec { self.build.flags.post_install = vec![s.to_string()]; } } + "post_install_lib32" | "post_install-lib32" | "post-install-lib32" => { + if let Some(arr) = v.as_array() { + self.build.flags.post_install_lib32 = arr + .iter() + .filter_map(|x| x.as_str()) + .map(String::from) + .collect(); + } else if let Some(s) = v.as_str() { + self.build.flags.post_install_lib32 = vec![s.to_string()]; + } + } // Add more fields as needed _ => {} } @@ -530,6 +579,18 @@ impl PackageSpec { } } } + "cflags-lib32" | "cflags_lib32" => { + for v in values { + if let Some(arr) = v.as_array() { + self.build + .flags + .cflags_lib32 + .extend(arr.iter().filter_map(|x| x.as_str()).map(String::from)); + } else if let Some(s) = v.as_str() { + self.build.flags.cflags_lib32.push(s.to_string()); + } + } + } "cxxflags" => { for v in values { if let Some(arr) = v.as_array() { @@ -542,6 +603,18 @@ impl PackageSpec { } } } + "cxxflags-lib32" | "cxxflags_lib32" => { + for v in values { + if let Some(arr) = v.as_array() { + self.build + .flags + .cxxflags_lib32 + .extend(arr.iter().filter_map(|x| x.as_str()).map(String::from)); + } else if let Some(s) = v.as_str() { + self.build.flags.cxxflags_lib32.push(s.to_string()); + } + } + } "ldflags" => { for v in values { if let Some(arr) = v.as_array() { @@ -578,6 +651,18 @@ impl PackageSpec { } } } + "configure_lib32" | "configure-lib32" => { + for v in values { + if let Some(arr) = v.as_array() { + self.build + .flags + .configure_lib32 + .extend(arr.iter().filter_map(|x| x.as_str()).map(String::from)); + } else if let Some(s) = v.as_str() { + self.build.flags.configure_lib32.push(s.to_string()); + } + } + } "configure_file" | "configure-file" => { if let Some(s) = values.last().and_then(|v| v.as_str()) { self.build.flags.configure_file = s.to_string(); @@ -619,6 +704,18 @@ impl PackageSpec { } } } + "post_install_lib32" | "post_install-lib32" | "post-install-lib32" => { + for v in values { + if let Some(arr) = v.as_array() { + self.build + .flags + .post_install_lib32 + .extend(arr.iter().filter_map(|x| x.as_str()).map(String::from)); + } else if let Some(s) = v.as_str() { + self.build.flags.post_install_lib32.push(s.to_string()); + } + } + } "cargs" => { for v in values { if let Some(arr) = v.as_array() { @@ -863,10 +960,15 @@ impl PackageSpec { } } "skip_tests" | "skip-tests" => { - if let Some(b) = values.last().and_then(|v| v.as_bool()) { + if let Some(b) = values.last().and_then(toml_value_as_boolish) { self.build.flags.skip_tests = b; } } + "build_32" | "build-32" => { + if let Some(b) = values.last().and_then(toml_value_as_boolish) { + self.build.flags.build_32 = b; + } + } _ => {} } } @@ -1753,6 +1855,47 @@ configure_file = "build-aux/configure" assert_eq!(spec.build.flags.configure_file, "build-aux/configure"); } + #[test] + fn parse_lib32_build_flags_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 = "autotools" + +[build.flags] +"build-32" = "true" +"CFLAGS-lib32" = ["-mstackrealign"] +"CXXFLAGS-lib32" = ["-fno-rtti"] +"configure-lib32" = ["--disable-static"] +"post_install-lib32" = ["echo lib32"] +"#, + ) + .unwrap(); + + let spec = PackageSpec::from_file(&path).unwrap(); + assert!(spec.build.flags.build_32); + assert_eq!(spec.build.flags.cflags_lib32, vec!["-mstackrealign"]); + assert_eq!(spec.build.flags.cxxflags_lib32, vec!["-fno-rtti"]); + assert_eq!(spec.build.flags.configure_lib32, vec!["--disable-static"]); + assert_eq!(spec.build.flags.post_install_lib32, vec!["echo lib32"]); + } + #[test] fn parse_post_configure_from_spec() { let tmp = tempfile::tempdir().unwrap(); @@ -2343,9 +2486,29 @@ pub enum BuildType { pub struct BuildFlags { #[serde(default, deserialize_with = "deserialize_string_or_array")] pub cflags: Vec, + /// Extra flags exported to `CFLAGS` only for the lib32 build variant. + #[serde( + default, + alias = "cflags-lib32", + alias = "cflags_lib32", + alias = "CFLAGS-lib32", + alias = "CFLAGS_lib32", + deserialize_with = "deserialize_string_or_array" + )] + pub cflags_lib32: Vec, /// Extra flags exported to `CXXFLAGS`. #[serde(default, deserialize_with = "deserialize_string_or_array")] pub cxxflags: Vec, + /// Extra flags exported to `CXXFLAGS` only for the lib32 build variant. + #[serde( + default, + alias = "cxxflags-lib32", + alias = "cxxflags_lib32", + alias = "CXXFLAGS-lib32", + alias = "CXXFLAGS_lib32", + deserialize_with = "deserialize_string_or_array" + )] + pub cxxflags_lib32: Vec, #[serde(default, deserialize_with = "deserialize_string_or_array")] pub ldflags: Vec, /// Keep existing files and install package-provided replacement as `.depotnew`. @@ -2368,8 +2531,19 @@ pub struct BuildFlags { /// Skip automatic build-system test execution (e.g. Autotools `make check`/`make test`). #[serde(default, alias = "skip-tests")] pub skip_tests: bool, + /// Run an additional lib32 build pass and emit a `lib32-*` package. + #[serde( + default, + alias = "build-32", + alias = "build_32", + deserialize_with = "deserialize_boolish" + )] + pub build_32: bool, #[serde(default)] pub configure: Vec, + /// Configure arguments used only for the lib32 build variant (replaces `configure` when set). + #[serde(default, alias = "configure-lib32", alias = "configure_lib32")] + pub configure_lib32: Vec, /// Autotools configure script path, relative to source root or absolute. #[serde(default, alias = "configure-file")] pub configure_file: String, @@ -2398,6 +2572,14 @@ pub struct BuildFlags { /// Commands to run after install (after make install) #[serde(default, alias = "post-install")] pub post_install: Vec, + /// Commands to run after the lib32 install step (replaces `post_install` when set). + #[serde( + default, + alias = "post-install-lib32", + alias = "post_install-lib32", + alias = "post_install_lib32" + )] + pub post_install_lib32: Vec, /// Specific commands for 'makefile' build type #[serde(default)] @@ -2544,20 +2726,27 @@ pub struct BuildFlags { /// Binary package type when using BuildType::Bin (e.g. "deb") #[serde(default)] pub binary_type: String, + /// Internal runtime marker used to adjust builder behavior for the lib32 variant. + #[serde(skip)] + pub lib32_variant: bool, } impl Default for BuildFlags { fn default() -> Self { BuildFlags { cflags: Vec::new(), + cflags_lib32: Vec::new(), cxxflags: Vec::new(), + cxxflags_lib32: Vec::new(), ldflags: Vec::new(), keep: Vec::new(), no_flags: false, no_strip: false, no_compress_man: false, skip_tests: false, + build_32: false, configure: Vec::new(), + configure_lib32: Vec::new(), configure_file: String::new(), cc: default_cc(), cxx: default_cxx(), @@ -2567,6 +2756,7 @@ impl Default for BuildFlags { post_configure: Vec::new(), post_compile: Vec::new(), post_install: Vec::new(), + post_install_lib32: Vec::new(), makefile_commands: Vec::new(), makefile_install_commands: Vec::new(), prefix: default_prefix(), @@ -2595,6 +2785,7 @@ impl Default for BuildFlags { source_subdir: String::new(), build_dir: None, binary_type: String::new(), + lib32_variant: false, } } } @@ -2619,6 +2810,44 @@ where } } +fn deserialize_boolish<'de, D>(deserializer: D) -> std::result::Result +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum Boolish { + Bool(bool), + String(String), + } + + match Option::::deserialize(deserializer)? { + Some(Boolish::Bool(v)) => Ok(v), + Some(Boolish::String(s)) => match s.trim().to_ascii_lowercase().as_str() { + "true" | "1" | "yes" | "on" => Ok(true), + "false" | "0" | "no" | "off" => Ok(false), + other => Err(serde::de::Error::custom(format!( + "expected boolean string for lib32 flag, got '{}'", + other + ))), + }, + None => Ok(false), + } +} + +fn toml_value_as_boolish(value: &toml::Value) -> Option { + if let Some(b) = value.as_bool() { + return Some(b); + } + value + .as_str() + .and_then(|s| match s.trim().to_ascii_lowercase().as_str() { + "true" | "1" | "yes" | "on" => Some(true), + "false" | "0" | "no" | "off" => Some(false), + _ => None, + }) +} + fn default_cc() -> String { // Prefer clang if available (supports -print-resource-dir and other useful flags) if std::process::Command::new("which")