Add lib32 companion build pipeline

This commit is contained in:
2026-02-26 16:58:20 -06:00
parent 59ea295b77
commit 4ddf121dbf
10 changed files with 539 additions and 17 deletions
+33 -2
View File
@@ -40,7 +40,10 @@ pub fn build(
} }
use crate::builder::state::{BuildStep, StateTracker}; 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 // Run configure
let build_dir = if let Some(dir) = &flags.build_dir { let build_dir = if let Some(dir) = &flags.build_dir {
@@ -78,7 +81,14 @@ pub fn build(
Some(flags.chost.clone()) Some(flags.chost.clone())
} else { } else {
None None
}; }
.map(|host| {
if flags.lib32_variant {
lib32_host_triple(&host)
} else {
host
}
});
let requested_build = if cross.is_some() { let requested_build = if cross.is_some() {
CrossConfig::build_triple().ok() 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 { for arg in &flags.configure {
let expanded = expand_configure_arg(spec, arg, &env_vars); let expanded = expand_configure_arg(spec, arg, &env_vars);
configure_cmd.arg(expanded); 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()) .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<Option<&'static str>> { fn find_autotools_test_target(build_dir: &Path) -> Result<Option<&'static str>> {
for target in ["check", "test"] { for target in ["check", "test"] {
if makefile_has_target(build_dir, target)? { if makefile_has_target(build_dir, target)? {
+4 -1
View File
@@ -46,7 +46,10 @@ pub fn build(
}; };
use crate::builder::state::{BuildStep, StateTracker}; 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 // Run cmake configure
if !state.is_done(BuildStep::Configured) { if !state.is_done(BuildStep::Configured) {
+4 -1
View File
@@ -59,7 +59,10 @@ pub fn build(
} }
use crate::builder::state::{BuildStep, StateTracker}; 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) { if !state.is_done(BuildStep::Configured) {
hooks::run_post_configure_commands(spec, src_dir, destdir)?; hooks::run_post_configure_commands(spec, src_dir, destdir)?;
+4 -1
View File
@@ -12,7 +12,10 @@ pub fn build(
cross: Option<&CrossConfig>, cross: Option<&CrossConfig>,
export_compiler_flags: bool, export_compiler_flags: bool,
) -> Result<()> { ) -> 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 flags = &spec.build.flags;
let mut env_vars = crate::builder::standard_build_env(spec, cross, true, export_compiler_flags); let mut env_vars = crate::builder::standard_build_env(spec, cross, true, export_compiler_flags);
+4 -1
View File
@@ -37,7 +37,10 @@ pub fn build(
}; };
use crate::builder::state::{BuildStep, StateTracker}; 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 // Run meson setup
if !state.is_done(BuildStep::Configured) { if !state.is_done(BuildStep::Configured) {
+4 -1
View File
@@ -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, "PYTHONDONTWRITEBYTECODE", "1");
crate::builder::set_env_var(&mut env_vars, "SETUPTOOLS_USE_DISTUTILS", "local"); 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) { if !state.is_done(BuildStep::Configured) {
hooks::run_post_configure_commands(spec, &actual_src, destdir)?; hooks::run_post_configure_commands(spec, &actual_src, destdir)?;
state.mark_done(BuildStep::Configured)?; state.mark_done(BuildStep::Configured)?;
+28 -1
View File
@@ -27,7 +27,15 @@ pub struct StateTracker {
impl StateTracker { impl StateTracker {
pub fn new(source_dir: &Path) -> Result<Self> { pub fn new(source_dir: &Path) -> Result<Self> {
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<Self> {
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 state = if state_file.exists() {
let content = fs::read_to_string(&state_file)?; let content = fs::read_to_string(&state_file)?;
toml::from_str(&content).unwrap_or_default() toml::from_str(&content).unwrap_or_default()
@@ -54,6 +62,25 @@ impl StateTracker {
} }
} }
fn normalize_state_namespace(namespace: &str) -> Option<String> {
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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
+3 -2
View File
@@ -86,10 +86,11 @@ impl CrossConfig {
/// Get the build machine triple (native compiler) /// Get the build machine triple (native compiler)
pub fn build_triple() -> Result<String> { pub fn build_triple() -> Result<String> {
let output = Command::new("gcc") let output = Command::new("cc")
.arg("-dumpmachine") .arg("-dumpmachine")
.output() .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")?; .context("Failed to determine build machine triple")?;
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
+225 -6
View File
@@ -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<Option<(package::PackageSpec, PathBuf)>> {
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( fn install_staged_to_rootfs(
pkg_spec: &package::PackageSpec, pkg_spec: &package::PackageSpec,
destdir: &Path, 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<PathBuf> = None;
let destdir = if let Some(dir) = &staging_dir { let destdir = if let Some(dir) = &staging_dir {
dir.path().to_path_buf() dir.path().to_path_buf()
} else { } else {
// 1-2. Fetch + extract sources (supports archives and git URL#rev) // 1-2. Fetch + extract sources (supports archives and git URL#rev)
let src_dir = source::prepare(&pkg_spec, &config.cache_dir, &config.build_dir)?; let src_dir = source::prepare(&pkg_spec, &config.cache_dir, &config.build_dir)?;
built_src_dir = Some(src_dir.clone());
// 3. Build // 3. Build
let destdir = config let destdir = config
@@ -1178,12 +1353,6 @@ fn main() -> Result<()> {
.join("destdir") .join("destdir")
.join(&pkg_spec.package.name); .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( builder::build(
&pkg_spec, &pkg_spec,
&src_dir, &src_dir,
@@ -1220,6 +1389,22 @@ fn main() -> Result<()> {
// TODO(snapper): create post-install snapshot after install commit succeeds. // 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 { if cli.clean {
clean_build_workspace(&config)?; clean_build_workspace(&config)?;
} }
@@ -1408,6 +1593,33 @@ fn main() -> Result<()> {
created_files.push(pkg_file); 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 { for f in &created_files {
ui::success(format!("Build complete. Package created: {}", f.display())); 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. // 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 { if cli.clean {
+230 -1
View File
@@ -240,6 +240,17 @@ impl PackageSpec {
self.build.flags.cflags = vec![s.to_string()]; 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" => { "cxxflags" => {
if let Some(arr) = v.as_array() { if let Some(arr) = v.as_array() {
self.build.flags.cxxflags = arr self.build.flags.cxxflags = arr
@@ -251,6 +262,17 @@ impl PackageSpec {
self.build.flags.cxxflags = vec![s.to_string()]; 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" => { "ldflags" => {
if let Some(arr) = v.as_array() { if let Some(arr) = v.as_array() {
self.build.flags.ldflags = arr self.build.flags.ldflags = arr
@@ -472,6 +494,22 @@ impl PackageSpec {
self.build.flags.skip_tests = b; 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" => { "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();
@@ -510,6 +548,17 @@ impl PackageSpec {
self.build.flags.post_install = vec![s.to_string()]; 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 // 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" => { "cxxflags" => {
for v in values { for v in values {
if let Some(arr) = v.as_array() { 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" => { "ldflags" => {
for v in values { for v in values {
if let Some(arr) = v.as_array() { 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" => { "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();
@@ -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" => { "cargs" => {
for v in values { for v in values {
if let Some(arr) = v.as_array() { if let Some(arr) = v.as_array() {
@@ -863,10 +960,15 @@ impl PackageSpec {
} }
} }
"skip_tests" | "skip-tests" => { "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; 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"); 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] #[test]
fn parse_post_configure_from_spec() { fn parse_post_configure_from_spec() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
@@ -2343,9 +2486,29 @@ pub enum BuildType {
pub struct BuildFlags { pub struct BuildFlags {
#[serde(default, deserialize_with = "deserialize_string_or_array")] #[serde(default, deserialize_with = "deserialize_string_or_array")]
pub cflags: Vec<String>, pub cflags: Vec<String>,
/// 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<String>,
/// Extra flags exported to `CXXFLAGS`. /// Extra flags exported to `CXXFLAGS`.
#[serde(default, deserialize_with = "deserialize_string_or_array")] #[serde(default, deserialize_with = "deserialize_string_or_array")]
pub cxxflags: Vec<String>, pub cxxflags: Vec<String>,
/// 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<String>,
#[serde(default, deserialize_with = "deserialize_string_or_array")] #[serde(default, deserialize_with = "deserialize_string_or_array")]
pub ldflags: Vec<String>, pub ldflags: Vec<String>,
/// Keep existing files and install package-provided replacement as `<path>.depotnew`. /// Keep existing files and install package-provided replacement as `<path>.depotnew`.
@@ -2368,8 +2531,19 @@ pub struct BuildFlags {
/// Skip automatic build-system test execution (e.g. Autotools `make check`/`make test`). /// Skip automatic build-system test execution (e.g. Autotools `make check`/`make test`).
#[serde(default, alias = "skip-tests")] #[serde(default, alias = "skip-tests")]
pub skip_tests: bool, 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)] #[serde(default)]
pub configure: Vec<String>, pub configure: Vec<String>,
/// 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<String>,
/// Autotools configure script path, relative to source root or absolute. /// Autotools configure script path, relative to source root or absolute.
#[serde(default, alias = "configure-file")] #[serde(default, alias = "configure-file")]
pub configure_file: String, pub configure_file: String,
@@ -2398,6 +2572,14 @@ pub struct BuildFlags {
/// Commands to run after install (after make install) /// Commands to run after install (after make install)
#[serde(default, alias = "post-install")] #[serde(default, alias = "post-install")]
pub post_install: Vec<String>, pub post_install: Vec<String>,
/// 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<String>,
/// Specific commands for 'makefile' build type /// Specific commands for 'makefile' build type
#[serde(default)] #[serde(default)]
@@ -2544,20 +2726,27 @@ pub struct BuildFlags {
/// Binary package type when using BuildType::Bin (e.g. "deb") /// Binary package type when using BuildType::Bin (e.g. "deb")
#[serde(default)] #[serde(default)]
pub binary_type: String, 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 { impl Default for BuildFlags {
fn default() -> Self { fn default() -> Self {
BuildFlags { BuildFlags {
cflags: Vec::new(), cflags: Vec::new(),
cflags_lib32: Vec::new(),
cxxflags: Vec::new(), cxxflags: Vec::new(),
cxxflags_lib32: Vec::new(),
ldflags: Vec::new(), ldflags: Vec::new(),
keep: Vec::new(), keep: Vec::new(),
no_flags: false, no_flags: false,
no_strip: false, no_strip: false,
no_compress_man: false, no_compress_man: false,
skip_tests: false, skip_tests: false,
build_32: false,
configure: Vec::new(), configure: Vec::new(),
configure_lib32: Vec::new(),
configure_file: String::new(), configure_file: String::new(),
cc: default_cc(), cc: default_cc(),
cxx: default_cxx(), cxx: default_cxx(),
@@ -2567,6 +2756,7 @@ impl Default for BuildFlags {
post_configure: Vec::new(), post_configure: Vec::new(),
post_compile: Vec::new(), post_compile: Vec::new(),
post_install: Vec::new(), post_install: Vec::new(),
post_install_lib32: Vec::new(),
makefile_commands: Vec::new(), makefile_commands: Vec::new(),
makefile_install_commands: Vec::new(), makefile_install_commands: Vec::new(),
prefix: default_prefix(), prefix: default_prefix(),
@@ -2595,6 +2785,7 @@ impl Default for BuildFlags {
source_subdir: String::new(), source_subdir: String::new(),
build_dir: None, build_dir: None,
binary_type: String::new(), binary_type: String::new(),
lib32_variant: false,
} }
} }
} }
@@ -2619,6 +2810,44 @@ where
} }
} }
fn deserialize_boolish<'de, D>(deserializer: D) -> std::result::Result<bool, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum Boolish {
Bool(bool),
String(String),
}
match Option::<Boolish>::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<bool> {
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 { fn default_cc() -> String {
// Prefer clang if available (supports -print-resource-dir and other useful flags) // Prefer clang if available (supports -print-resource-dir and other useful flags)
if std::process::Command::new("which") if std::process::Command::new("which")