feat: update depot version and enhance host build support

- Bump the version of the "depot" package from 0.26.1 to 0.27.0 in Cargo.toml and Cargo.lock.
- Modify meson.build, autotools.rs, cmake.rs, custom.rs, makefile.rs, and other builder files to include a new parameter for host build directory.
- Implement logic to handle host builds across various build systems (Autotools, CMake, Meson).
- Add tests to ensure that environment variables for host build directories are correctly expanded.
- Introduce new flags in PackageSpec to control host build behavior.
This commit is contained in:
2026-03-17 19:29:04 -05:00
parent a6c7ca6fa7
commit 3bc4d738c4
15 changed files with 662 additions and 36 deletions
Generated
+1 -1
View File
@@ -418,7 +418,7 @@ checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2"
[[package]]
name = "depot"
version = "0.26.1"
version = "0.27.0"
dependencies = [
"anyhow",
"ar",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "depot"
version = "0.26.1"
version = "0.27.0"
edition = "2024"
[lints.rust]
+1 -1
View File
@@ -1,6 +1,6 @@
project(
'depot',
version: '0.26.1',
version: '0.27.0',
meson_version: '>=0.60.0',
default_options: ['buildtype=release'],
)
+146 -1
View File
@@ -1,5 +1,6 @@
//! GNU Autotools build system (configure && make && make install)
use crate::builder::state::{BuildStep, StateTracker};
use crate::cross::CrossConfig;
use crate::fakeroot;
use crate::package::PackageSpec;
@@ -15,6 +16,7 @@ pub fn build(
destdir: &Path,
cross: Option<&CrossConfig>,
export_compiler_flags: bool,
host_build_dir: Option<&Path>,
) -> Result<()> {
let flags = &spec.build.flags;
let make_exec = resolve_make_exec(&flags.make_exec);
@@ -26,6 +28,13 @@ pub fn build(
// Build environment variables
let mut env_vars = crate::builder::standard_build_env(spec, cross, true, export_compiler_flags);
if let Some(host_dir) = host_build_dir {
crate::builder::set_env_var(
&mut env_vars,
crate::builder::DEPOT_BUILD_HOST_DIR_ENV,
host_dir.to_string_lossy().into_owned(),
);
}
let cc = if let Some(cc_cfg) = cross {
cc_cfg.cc.clone()
} else {
@@ -44,7 +53,6 @@ pub fn build(
crate::builder::set_env_var(&mut env_vars, "CFLAGS", expanded);
}
use crate::builder::state::{BuildStep, StateTracker};
let mut state = StateTracker::new_with_namespace(
&actual_src,
spec.build.flags.lib32_variant.then_some("lib32"),
@@ -385,6 +393,104 @@ pub fn build(
Ok(())
}
pub(crate) fn ensure_host_build(
spec: &PackageSpec,
src_dir: &Path,
export_compiler_flags: bool,
) -> Result<PathBuf> {
let host_spec = crate::builder::host_build_spec(spec);
let flags = &host_spec.build.flags;
let make_exec = resolve_make_exec(&flags.make_exec);
let export_compiler_flags = export_compiler_flags && !flags.no_flags;
let actual_src = resolve_actual_src(&host_spec, src_dir)?;
let build_dir = crate::builder::host_build_dir_for_source(&actual_src, flags);
fs::create_dir_all(&build_dir)?;
let mut env_vars =
crate::builder::standard_build_env(&host_spec, None, true, export_compiler_flags);
if export_compiler_flags
&& let Some(cflags_str) = env_vars
.iter()
.find(|(key, _)| key == "CFLAGS")
.map(|(_, value)| value.clone())
&& !cflags_str.trim().is_empty()
{
let expanded = expand_shell_commands(&cflags_str, &flags.cc)?;
crate::builder::set_env_var(&mut env_vars, "CFLAGS", expanded);
}
let mut state = StateTracker::new_with_namespace(&actual_src, Some("host"))?;
if !state.is_done(BuildStep::Configured) {
crate::log_info!(
"Running host-side configure build in {}...",
build_dir.display()
);
let configure_path = resolve_configure_path(&host_spec, &actual_src);
let mut configure_cmd = Command::new(&configure_path);
configure_cmd.current_dir(&build_dir);
crate::builder::prepare_tool_command(&mut configure_cmd, &env_vars);
let help_text = configure_help_text(&configure_path, &build_dir, &env_vars);
configure_cmd.arg(format!("--prefix={}", flags.prefix));
for default_dir_arg in default_configure_install_dirs(flags, help_text.as_deref()) {
configure_cmd.arg(default_dir_arg);
}
for arg in &flags.configure {
let expanded = expand_configure_arg(&host_spec, arg, &env_vars);
configure_cmd.arg(expanded);
}
let status = crate::interrupts::command_status(&mut configure_cmd)
.with_context(|| format!("Failed to run host configure in {}", build_dir.display()))?;
if !status.success() {
anyhow::bail!("host configure failed with status: {}", status);
}
state.mark_done(BuildStep::Configured)?;
}
if !state.is_done(BuildStep::PostCompileDone) {
let build_targets = phase_targets(&flags.make_target, &flags.make_targets, None);
let make_dirs = resolve_make_dirs(&build_dir, &flags.make_dirs, "build.flags.make_dirs")?;
for make_dir in make_dirs {
let mut make_cmd = Command::new(make_exec);
make_cmd.current_dir(&make_dir);
make_cmd.arg("-j").arg(num_cpus().to_string());
add_make_variable_overrides_if_supported(
&mut make_cmd,
make_exec,
&flags.make_vars,
"build",
)?;
for target in &build_targets {
make_cmd.arg(target);
}
crate::builder::prepare_tool_command(&mut make_cmd, &env_vars);
let status = crate::interrupts::command_status(&mut make_cmd).with_context(|| {
format!("Failed to run host {} in {}", make_exec, make_dir.display())
})?;
if !status.success() {
anyhow::bail!(
"host {} failed with status: {} (dir: {})",
make_exec,
status,
make_dir.display()
);
}
}
state.mark_done(BuildStep::PostCompileDone)?;
}
fs::canonicalize(&build_dir)
.with_context(|| format!("Failed to resolve host build dir: {}", build_dir.display()))
}
fn num_cpus() -> usize {
std::thread::available_parallelism()
.map(|n| n.get())
@@ -851,6 +957,45 @@ mod tests {
assert_eq!(expanded, "--program-prefix=foo-1.2.3-aarch64-");
}
#[test]
fn test_expand_configure_arg_expands_host_build_dir_env() {
let spec = PackageSpec {
package: PackageInfo {
name: "foo".into(),
real_name: None,
version: "1.2.3".into(),
revision: 1,
description: "d".into(),
homepage: "h".into(),
abi_breaking: false,
license: vec!["MIT".into()],
},
packages: Vec::new(),
alternatives: Default::default(),
manual_sources: Vec::new(),
source: Vec::new(),
build: Build {
build_type: BuildType::Autotools,
flags: BuildFlags::default(),
},
dependencies: Dependencies::default(),
package_alternatives: Default::default(),
package_dependencies: Default::default(),
spec_dir: PathBuf::from("."),
};
let envs = vec![(
crate::builder::DEPOT_BUILD_HOST_DIR_ENV.to_string(),
"/tmp/build-host".to_string(),
)];
let expanded = expand_configure_arg(
&spec,
"--with-build-tools=$DEPOT_BUILD_HOST_DIR/tools",
&envs,
);
assert_eq!(expanded, "--with-build-tools=/tmp/build-host/tools");
}
#[test]
fn test_num_cpus_at_least_one() {
let n = num_cpus();
+2 -1
View File
@@ -17,6 +17,7 @@ pub fn build(
destdir: &Path,
_cross: Option<&CrossConfig>,
_export_compiler_flags: bool,
_host_build_dir: Option<&Path>,
) -> Result<()> {
crate::log_info!(
"Binary install: copying files from {} to {} (pkg type={})",
@@ -114,7 +115,7 @@ mod tests {
unix_fs::symlink(&target, src.join("usr/lib/libdummy.so.link"))?;
let spec = mk_spec("bin-test", "1.0");
build(&spec, src, dest, None, true)?;
build(&spec, src, dest, None, true, None)?;
// Check copied file
assert!(dest.join("usr/bin/hello").exists());
+111 -3
View File
@@ -1,11 +1,12 @@
//! CMake build system
use crate::builder::state::{BuildStep, StateTracker};
use crate::cross::CrossConfig;
use crate::fakeroot;
use crate::package::PackageSpec;
use anyhow::{Context, Result};
use std::fs;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::process::Command;
pub fn build(
@@ -14,6 +15,7 @@ pub fn build(
destdir: &Path,
cross: Option<&CrossConfig>,
export_compiler_flags: bool,
host_build_dir: Option<&Path>,
) -> Result<()> {
let flags = &spec.build.flags;
let make_exec_override = flags.make_exec.trim();
@@ -32,7 +34,14 @@ pub fn build(
fs::create_dir_all(destdir)?;
// Environment variables
let 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);
if let Some(host_dir) = host_build_dir {
crate::builder::set_env_var(
&mut env_vars,
crate::builder::DEPOT_BUILD_HOST_DIR_ENV,
host_dir.to_string_lossy().into_owned(),
);
}
// Extract prefix from configure flags (cmake-style -DCMAKE_INSTALL_PREFIX=)
let prefix = cmake_cache_entry_value(&flags.configure, "CMAKE_INSTALL_PREFIX")
@@ -45,7 +54,6 @@ pub fn build(
None
};
use crate::builder::state::{BuildStep, StateTracker};
let mut state = StateTracker::new_with_namespace(
&actual_src,
spec.build.flags.lib32_variant.then_some("lib32"),
@@ -257,6 +265,96 @@ pub fn build(
Ok(())
}
pub(crate) fn ensure_host_build(
spec: &PackageSpec,
src_dir: &Path,
export_compiler_flags: bool,
) -> Result<PathBuf> {
let host_spec = crate::builder::host_build_spec(spec);
let flags = &host_spec.build.flags;
let actual_src = resolve_actual_src(&host_spec, src_dir)?;
let build_dir = crate::builder::host_build_dir_for_source(&actual_src, flags);
fs::create_dir_all(&build_dir)?;
let env_vars =
crate::builder::standard_build_env(&host_spec, None, true, export_compiler_flags);
let prefix = cmake_cache_entry_value(&flags.configure, "CMAKE_INSTALL_PREFIX")
.unwrap_or(flags.prefix.as_str());
let mut state = StateTracker::new_with_namespace(&actual_src, Some("host"))?;
if !state.is_done(BuildStep::Configured) {
crate::log_info!(
"Running host-side cmake configure in {}...",
build_dir.display()
);
let mut cmake_cmd = Command::new("cmake");
cmake_cmd.current_dir(&build_dir);
cmake_cmd.arg("-S").arg(&actual_src);
cmake_cmd.arg("-B").arg(&build_dir);
cmake_cmd.arg(format!("-DCMAKE_INSTALL_PREFIX={}", prefix));
cmake_cmd.arg("-DCMAKE_BUILD_TYPE=Release");
for arg in cmake_install_dir_args(flags) {
cmake_cmd.arg(arg);
}
let make_exec_override = flags.make_exec.trim();
if !make_exec_override.is_empty() {
if !cmake_configure_flags_specify_generator(&flags.configure)
&& let Some(generator) = cmake_generator_for_make_exec(make_exec_override)
{
cmake_cmd.arg("-G").arg(generator);
}
if !cmake_configure_flags_set_make_program(&flags.configure) {
cmake_cmd.arg(format!("-DCMAKE_MAKE_PROGRAM={make_exec_override}"));
}
}
for flag in &flags.configure {
let expanded = expand_with_envs(flag, &env_vars);
cmake_cmd.arg(&expanded);
}
crate::builder::prepare_tool_command(&mut cmake_cmd, &env_vars);
let status = crate::interrupts::command_status(&mut cmake_cmd)
.context("Failed to run host cmake")?;
if !status.success() {
anyhow::bail!("host cmake configure failed");
}
state.mark_done(BuildStep::Configured)?;
}
if !state.is_done(BuildStep::PostCompileDone) {
let build_targets = phase_targets(&flags.make_target, &flags.make_targets);
let mut build_cmd = Command::new("cmake");
build_cmd.arg("--build").arg(&build_dir);
build_cmd.arg("-j").arg(num_cpus().to_string());
if !build_targets.is_empty() {
build_cmd.arg("--target");
for target in &build_targets {
build_cmd.arg(target);
}
}
crate::builder::prepare_tool_command(&mut build_cmd, &env_vars);
let status = crate::interrupts::command_status(&mut build_cmd)
.with_context(|| format!("Failed to run host cmake build for {}", spec.package.name))?;
if !status.success() {
anyhow::bail!("host cmake build failed");
}
state.mark_done(BuildStep::PostCompileDone)?;
}
fs::canonicalize(&build_dir)
.with_context(|| format!("Failed to resolve host build dir: {}", build_dir.display()))
}
/// Expand environment variables in a string (e.g., $DEPOT_SYSROOT)
fn expand_env_vars(input: &str) -> String {
let mut result = input.to_string();
@@ -513,6 +611,16 @@ mod tests {
// $HOME should be expanded from process env (may be present)
}
#[test]
fn test_expand_with_envs_expands_host_build_dir() {
let envs = vec![(
crate::builder::DEPOT_BUILD_HOST_DIR_ENV.to_string(),
"/tmp/build-host".to_string(),
)];
let out = expand_with_envs("-DTOOLS_DIR=$DEPOT_BUILD_HOST_DIR/bin", &envs);
assert_eq!(out, "-DTOOLS_DIR=/tmp/build-host/bin");
}
#[test]
fn test_num_cpus_at_least_one() {
let n = num_cpus();
+7 -6
View File
@@ -15,6 +15,7 @@ pub fn build(
destdir: &Path,
cross: Option<&CrossConfig>,
export_compiler_flags: bool,
_host_build_dir: Option<&Path>,
) -> Result<()> {
let flags = &spec.build.flags;
let build_dir = if let Some(dir) = &flags.build_dir {
@@ -330,7 +331,7 @@ mod tests {
let spec = mk_spec("custom-no-build", "1.0");
let res = build(&spec, tmp_src.path(), tmp_dest.path(), None, true);
let res = build(&spec, tmp_src.path(), tmp_dest.path(), None, true, None);
assert!(res.is_err());
Ok(())
}
@@ -356,7 +357,7 @@ mod tests {
spec.spec_dir = spec_dir.path().to_path_buf();
// src_dir is empty; build() should copy build.sh from spec_dir and run it (no-op)
build(&spec, tmp_src.path(), tmp_dest.path(), None, true)?;
build(&spec, tmp_src.path(), tmp_dest.path(), None, true, None)?;
// If we reached here, build() succeeded and build.sh was copied into src
assert!(tmp_src.path().join("build.sh").exists());
Ok(())
@@ -404,7 +405,7 @@ depot_install_dev_pkg() {
license: vec!["MIT".into()],
});
build(&spec, tmp_src.path(), tmp_dest.path(), None, true)?;
build(&spec, tmp_src.path(), tmp_dest.path(), None, true, None)?;
assert!(tmp_dest.path().join("usr/share/primary.txt").exists());
assert!(
@@ -454,7 +455,7 @@ depot_install() {
license: vec!["MIT".into()],
});
let err = build(&spec, tmp_src.path(), tmp_dest.path(), None, true)
let err = build(&spec, tmp_src.path(), tmp_dest.path(), None, true, None)
.expect_err("missing per-output install handler should fail");
assert!(err.to_string().contains("Custom build script failed"));
Ok(())
@@ -482,7 +483,7 @@ exit 0
}
let spec = mk_spec("custom-fail-fast", "1.0");
let err = build(&spec, tmp_src.path(), tmp_dest.path(), None, true)
let err = build(&spec, tmp_src.path(), tmp_dest.path(), None, true, None)
.expect_err("non-function custom scripts should fail when a command fails");
assert!(err.to_string().contains("Custom build script failed"));
Ok(())
@@ -539,7 +540,7 @@ exec "$@"
let mut spec = mk_spec("custom-lib32", "1.0");
spec.build.flags.lib32_variant = true;
build(&spec, tmp_src.path(), tmp_dest.path(), None, true)?;
build(&spec, tmp_src.path(), tmp_dest.path(), None, true, None)?;
assert_eq!(
std::fs::read_to_string(tmp_dest.path().join("usr/lib32/libfoo.so.1"))?,
+3 -2
View File
@@ -12,6 +12,7 @@ pub fn build(
destdir: &Path,
cross: Option<&CrossConfig>,
export_compiler_flags: bool,
_host_build_dir: Option<&Path>,
) -> Result<()> {
let mut state = StateTracker::new_with_namespace(
src_dir,
@@ -217,7 +218,7 @@ exec "$@"
"cp built.txt $DESTDIR/usr/bin/installed.txt".into(),
];
build(&spec, src_path, dest_path, None, true)?;
build(&spec, src_path, dest_path, None, true, None)?;
// Verify build step
let built_file = src_path.join("built.txt");
@@ -271,7 +272,7 @@ exec "$@"
"printf 'bin' > \"$DESTDIR/usr/bin/foo\"".into(),
];
build(&spec, src_path, dest_path, None, true)?;
build(&spec, src_path, dest_path, None, true, None)?;
assert_eq!(
fs::read_to_string(dest_path.join("usr/lib32/libfoo.so.1"))?,
+88 -2
View File
@@ -1,5 +1,6 @@
//! Meson build system
use crate::builder::state::{BuildStep, StateTracker};
use crate::cross::CrossConfig;
use crate::fakeroot;
use crate::package::PackageSpec;
@@ -14,6 +15,7 @@ pub fn build(
destdir: &Path,
cross: Option<&CrossConfig>,
export_compiler_flags: bool,
host_build_dir: Option<&Path>,
) -> Result<()> {
let flags = &spec.build.flags;
@@ -27,7 +29,14 @@ pub fn build(
fs::create_dir_all(destdir)?;
// Environment variables
let 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);
if let Some(host_dir) = host_build_dir {
crate::builder::set_env_var(
&mut env_vars,
crate::builder::DEPOT_BUILD_HOST_DIR_ENV,
host_dir.to_string_lossy().into_owned(),
);
}
// Generate cross file if cross-compiling, or when the lib32 variant needs
// Meson to treat the build as x86 instead of the native x86_64 host.
@@ -39,7 +48,6 @@ pub fn build(
None
};
use crate::builder::state::{BuildStep, StateTracker};
let mut state = StateTracker::new_with_namespace(
&actual_src,
spec.build.flags.lib32_variant.then_some("lib32"),
@@ -188,6 +196,68 @@ pub fn build(
Ok(())
}
pub(crate) fn ensure_host_build(
spec: &PackageSpec,
src_dir: &Path,
export_compiler_flags: bool,
) -> Result<PathBuf> {
let host_spec = crate::builder::host_build_spec(spec);
let flags = &host_spec.build.flags;
let actual_src = resolve_actual_src(&host_spec, src_dir)?;
let build_dir = crate::builder::host_build_dir_for_source(&actual_src, flags);
fs::create_dir_all(&build_dir)?;
let env_vars =
crate::builder::standard_build_env(&host_spec, None, true, export_compiler_flags);
let mut state = StateTracker::new_with_namespace(&actual_src, Some("host"))?;
if !state.is_done(BuildStep::Configured) {
crate::log_info!(
"Running host-side meson setup in {}...",
build_dir.display()
);
let mut meson_cmd = Command::new("meson");
meson_cmd.current_dir(&actual_src);
meson_cmd.arg("setup");
meson_cmd.arg(&build_dir);
for arg in meson_setup_args(flags, None, &env_vars) {
meson_cmd.arg(arg);
}
crate::builder::prepare_tool_command(&mut meson_cmd, &env_vars);
let status = crate::interrupts::command_status(&mut meson_cmd)
.context("Failed to run host meson setup")?;
if !status.success() {
anyhow::bail!("host meson setup failed");
}
state.mark_done(BuildStep::Configured)?;
}
if !state.is_done(BuildStep::PostCompileDone) {
let mut ninja_cmd = Command::new("ninja");
ninja_cmd.current_dir(&build_dir);
ninja_cmd.arg("-j").arg(num_cpus().to_string());
crate::builder::prepare_tool_command(&mut ninja_cmd, &env_vars);
let status = crate::interrupts::command_status(&mut ninja_cmd)
.with_context(|| format!("Failed to run host ninja for {}", spec.package.name))?;
if !status.success() {
anyhow::bail!("host ninja build failed");
}
state.mark_done(BuildStep::PostCompileDone)?;
}
fs::canonicalize(&build_dir)
.with_context(|| format!("Failed to resolve host build dir: {}", build_dir.display()))
}
fn num_cpus() -> usize {
std::thread::available_parallelism()
.map(|n| n.get())
@@ -485,6 +555,22 @@ mod tests {
assert!(args.iter().any(|a| a == "--buildtype=release"));
}
#[test]
fn test_meson_setup_args_expand_host_build_dir() {
let mut flags = BuildFlags::default();
flags.configure = vec!["-Dtools_dir=$DEPOT_BUILD_HOST_DIR/bin".into()];
let args = meson_setup_args(
&flags,
None,
&[(
crate::builder::DEPOT_BUILD_HOST_DIR_ENV.to_string(),
"/tmp/build-host".to_string(),
)],
);
assert!(args.iter().any(|a| a == "-Dtools_dir=/tmp/build-host/bin"));
}
#[test]
fn test_meson_setup_args_include_install_dirs() {
let args = meson_setup_args(&BuildFlags::default(), None, &[]);
+244 -15
View File
@@ -21,6 +21,13 @@ use std::process::{Command, Stdio};
use walkdir::WalkDir;
pub type EnvVars = Vec<(String, String)>;
pub(crate) const DEPOT_BUILD_HOST_DIR_ENV: &str = "DEPOT_BUILD_HOST_DIR";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TargetBuildKind {
Primary,
Lib32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct InstallDirs {
@@ -55,6 +62,99 @@ fn default_libdir_for_variant(lib32_variant: bool) -> &'static str {
}
}
fn normalized_arch(arch: &str) -> &str {
match arch.trim() {
"amd64" => "x86_64",
"arm64" => "aarch64",
other => other,
}
}
fn lib32_arch_for(arch: &str) -> String {
match normalized_arch(arch) {
"x86_64" => "i686".to_string(),
other => other.to_string(),
}
}
pub(crate) fn host_arch() -> &'static str {
normalized_arch(std::env::consts::ARCH)
}
pub(crate) fn effective_target_arch(
flags: &crate::package::BuildFlags,
cross: Option<&CrossConfig>,
kind: TargetBuildKind,
) -> String {
match kind {
TargetBuildKind::Lib32 => {
if let Some(cc_cfg) = cross {
return crate::cross::target_arch_from_triple(&crate::cross::lib32_target_triple(
cc_cfg.host_triple(),
))
.to_string();
}
if !flags.chost.trim().is_empty() {
return crate::cross::target_arch_from_triple(&crate::cross::lib32_target_triple(
flags.chost.trim(),
))
.to_string();
}
let base = if flags.carch.trim().is_empty() {
host_arch()
} else {
flags.carch.trim()
};
lib32_arch_for(base)
}
TargetBuildKind::Primary => {
if let Some(cc_cfg) = cross {
return crate::cross::target_arch_from_triple(cc_cfg.host_triple()).to_string();
}
if !flags.chost.trim().is_empty() {
return crate::cross::target_arch_from_triple(flags.chost.trim()).to_string();
}
if !flags.carch.trim().is_empty() {
return flags.carch.trim().to_string();
}
host_arch().to_string()
}
}
}
fn target_arch_differs_from_host(
flags: &crate::package::BuildFlags,
cross: Option<&CrossConfig>,
kind: TargetBuildKind,
) -> bool {
normalized_arch(&effective_target_arch(flags, cross, kind)) != host_arch()
}
pub(crate) fn default_host_build_dir_name(flags: &crate::package::BuildFlags) -> String {
match flags.build_dir.as_deref().map(str::trim) {
Some(dir) if !dir.is_empty() => format!("{}-host", dir),
_ => "build-host".to_string(),
}
}
pub(crate) fn host_build_dir_for_source(
src_root: &Path,
flags: &crate::package::BuildFlags,
) -> PathBuf {
src_root.join(default_host_build_dir_name(flags))
}
pub(crate) fn host_build_spec(spec: &PackageSpec) -> PackageSpec {
let mut host_spec = spec.clone();
host_spec.build.flags.lib32_variant = false;
host_spec.build.flags.chost.clear();
host_spec.build.flags.cbuild.clear();
host_spec.build.flags.carch = host_arch().to_string();
host_spec.build.flags.host_build_dir = None;
host_spec.build.flags.build_dir = Some(default_host_build_dir_name(&spec.build.flags));
host_spec
}
fn configured_install_dir(value: &str, default: &str) -> String {
let trimmed = value.trim();
if trimmed.is_empty() {
@@ -357,8 +457,14 @@ pub fn standard_build_env(
if !flags.cbuild.is_empty() {
set_env_var(&mut env_vars, "CBUILD", flags.cbuild.clone());
}
if !flags.carch.is_empty() {
set_env_var(&mut env_vars, "CARCH", flags.carch.clone());
let target_kind = if flags.lib32_variant {
TargetBuildKind::Lib32
} else {
TargetBuildKind::Primary
};
let effective_carch = effective_target_arch(flags, cross, target_kind);
if !effective_carch.is_empty() {
set_env_var(&mut env_vars, "CARCH", effective_carch);
}
if !flags.prefix.is_empty() {
set_env_var(&mut env_vars, "PREFIX", flags.prefix.clone());
@@ -422,6 +528,34 @@ pub fn standard_build_env(
env_vars
}
pub fn ensure_host_build(
spec: &PackageSpec,
src_dir: &Path,
cross: Option<&CrossConfig>,
export_compiler_flags: bool,
kind: TargetBuildKind,
) -> Result<Option<PathBuf>> {
if !spec.build.flags.host_build
|| !target_arch_differs_from_host(&spec.build.flags, cross, kind)
{
return Ok(None);
}
let host_dir = match spec.build.build_type {
BuildType::Autotools => autotools::ensure_host_build(spec, src_dir, export_compiler_flags)?,
BuildType::CMake => cmake::ensure_host_build(spec, src_dir, export_compiler_flags)?,
BuildType::Meson => meson::ensure_host_build(spec, src_dir, export_compiler_flags)?,
other => {
anyhow::bail!(
"build.flags.host_build is currently supported only for autotools/cmake/meson (got {:?})",
other
);
}
};
Ok(Some(host_dir))
}
/// Prepare a Command with a hermetic environment and some essential variables preserved.
pub fn prepare_command(cmd: &mut Command, env_vars: &EnvVars) {
cmd.env_clear();
@@ -498,6 +632,7 @@ pub fn build(
destdir: &Path,
cross: Option<&CrossConfig>,
export_compiler_flags: bool,
host_build_dir: Option<&Path>,
) -> Result<()> {
if let Some(cc) = cross {
crate::log_info!(
@@ -515,25 +650,84 @@ pub fn build(
}
match spec.build.build_type {
BuildType::Autotools => {
autotools::build(spec, src_dir, destdir, cross, export_compiler_flags)
}
BuildType::CMake => cmake::build(spec, src_dir, destdir, cross, export_compiler_flags),
BuildType::Meson => meson::build(spec, src_dir, destdir, cross, export_compiler_flags),
BuildType::Perl => perl::build(spec, src_dir, destdir, cross, export_compiler_flags),
BuildType::Custom => custom::build(spec, src_dir, destdir, cross, export_compiler_flags),
BuildType::Python => python::build(spec, src_dir, destdir, cross, export_compiler_flags),
BuildType::Rust => rust::build(spec, src_dir, destdir, cross, export_compiler_flags),
BuildType::Bin => bin::build(spec, src_dir, destdir, cross, export_compiler_flags),
BuildType::Autotools => autotools::build(
spec,
src_dir,
destdir,
cross,
export_compiler_flags,
host_build_dir,
),
BuildType::CMake => cmake::build(
spec,
src_dir,
destdir,
cross,
export_compiler_flags,
host_build_dir,
),
BuildType::Meson => meson::build(
spec,
src_dir,
destdir,
cross,
export_compiler_flags,
host_build_dir,
),
BuildType::Perl => perl::build(
spec,
src_dir,
destdir,
cross,
export_compiler_flags,
host_build_dir,
),
BuildType::Custom => custom::build(
spec,
src_dir,
destdir,
cross,
export_compiler_flags,
host_build_dir,
),
BuildType::Python => python::build(
spec,
src_dir,
destdir,
cross,
export_compiler_flags,
host_build_dir,
),
BuildType::Rust => rust::build(
spec,
src_dir,
destdir,
cross,
export_compiler_flags,
host_build_dir,
),
BuildType::Bin => bin::build(
spec,
src_dir,
destdir,
cross,
export_compiler_flags,
host_build_dir,
),
BuildType::Meta => {
// Metapackages are metadata-only; create an empty staging root and let
// packaging/installation metadata carry dependencies.
std::fs::create_dir_all(destdir)?;
Ok(())
}
BuildType::Makefile => {
makefile::build(spec, src_dir, destdir, cross, export_compiler_flags)
}
BuildType::Makefile => makefile::build(
spec,
src_dir,
destdir,
cross,
export_compiler_flags,
host_build_dir,
),
}
}
#[cfg(test)]
@@ -675,6 +869,41 @@ mod tests {
assert!(env.iter().any(|(k, v)| k == "CPP" && v == "clang-cpp"));
}
#[test]
fn test_standard_build_env_exports_effective_carch_for_cross_and_lib32() {
let spec = mk_spec(Vec::new(), Vec::new());
let cross = CrossConfig {
prefix: "aarch64-linux-gnu".into(),
cc: "aarch64-linux-gnu-gcc".into(),
cxx: "aarch64-linux-gnu-g++".into(),
ar: "aarch64-linux-gnu-ar".into(),
ranlib: "aarch64-linux-gnu-ranlib".into(),
strip: "aarch64-linux-gnu-strip".into(),
ld: "aarch64-linux-gnu-ld".into(),
nm: "aarch64-linux-gnu-nm".into(),
objcopy: "aarch64-linux-gnu-objcopy".into(),
objdump: "aarch64-linux-gnu-objdump".into(),
readelf: "aarch64-linux-gnu-readelf".into(),
};
let cross_env = standard_build_env(&spec, Some(&cross), true, true);
assert!(
cross_env
.iter()
.any(|(k, v)| k == "CARCH" && v == "aarch64"),
"expected cross builds to export target CARCH"
);
let mut lib32_spec = spec.clone();
lib32_spec.build.flags.lib32_variant = true;
lib32_spec.build.flags.carch = "x86_64".into();
let lib32_env = standard_build_env(&lib32_spec, None, true, true);
assert!(
lib32_env.iter().any(|(k, v)| k == "CARCH" && v == "i686"),
"expected lib32 builds to export i686 CARCH"
);
}
#[test]
fn test_standard_build_env_respects_export_compiler_flags_toggle() {
let mut spec = mk_spec(vec!["-O2"], vec!["-Wl,--as-needed"]);
+3 -2
View File
@@ -17,6 +17,7 @@ pub fn build(
destdir: &Path,
cross: Option<&CrossConfig>,
export_compiler_flags: bool,
_host_build_dir: Option<&Path>,
) -> Result<()> {
let flags = &spec.build.flags;
if flags.build_dir.is_some() {
@@ -459,7 +460,7 @@ exec "$@"
spec.build.flags.make_exec = tools_path.join("make").to_string_lossy().into_owned();
spec.build.flags.configure = vec!["CCFLAGS=-fPIC".into()];
let build_result = build(&spec, src_path, dest_path, None, true);
let build_result = build(&spec, src_path, dest_path, None, true, None);
build_result?;
@@ -490,7 +491,7 @@ exec "$@"
let mut spec = mk_spec("perl-test", "1.0");
spec.build.flags.build_dir = Some("build".into());
let err = build(&spec, tmp_src.path(), tmp_dest.path(), None, true)
let err = build(&spec, tmp_src.path(), tmp_dest.path(), None, true, None)
.expect_err("perl build should reject build_dir");
assert!(
err.to_string()
+1
View File
@@ -32,6 +32,7 @@ pub fn build(
destdir: &Path,
cross: Option<&CrossConfig>,
export_compiler_flags: bool,
_host_build_dir: Option<&Path>,
) -> Result<()> {
let flags = &spec.build.flags;
let actual_src = resolve_actual_src(spec, src_dir)?;
+1
View File
@@ -14,6 +14,7 @@ pub fn build(
destdir: &Path,
cross: Option<&CrossConfig>,
export_compiler_flags: bool,
_host_build_dir: Option<&Path>,
) -> Result<()> {
let flags = &spec.build.flags;
+35 -1
View File
@@ -1182,9 +1182,19 @@ fn build_lib32_companion_package(
}
crate::log_info!("Running separate lib32 build pass...");
let host_build_dir = builder::ensure_host_build(
pkg_spec,
src_dir,
cross_config,
export_compiler_flags,
builder::TargetBuildKind::Lib32,
)?;
let mut lib32_input = pkg_spec.clone();
lib32_input.build.flags.build_32 = true;
let lib32_build_spec = make_lib32_build_spec(&lib32_input);
let mut lib32_build_spec = make_lib32_build_spec(&lib32_input);
if let Some(host_dir) = host_build_dir.as_ref() {
lib32_build_spec.build.flags.host_build_dir = Some(host_dir.to_string_lossy().into_owned());
}
let lib32_pkg_spec = make_lib32_package_spec(pkg_spec);
let lib32_destdir = config
.build_dir
@@ -1208,6 +1218,7 @@ fn build_lib32_companion_package(
&lib32_destdir,
cross_config,
export_compiler_flags,
host_build_dir.as_deref(),
)?;
let lib32_src = lib32_destdir.join("usr/lib32");
@@ -4545,6 +4556,16 @@ fn run_direct_install_request(
// 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());
let host_build_dir = builder::ensure_host_build(
&pkg_spec,
&src_dir,
cross_config.as_ref(),
!options.no_flags,
builder::TargetBuildKind::Primary,
)?;
if let Some(host_dir) = host_build_dir.as_ref() {
pkg_spec.build.flags.host_build_dir = Some(host_dir.to_string_lossy().into_owned());
}
// 3. Build
let destdir = config
@@ -4559,6 +4580,7 @@ fn run_direct_install_request(
&destdir,
cross_config.as_ref(),
!options.no_flags,
host_build_dir.as_deref(),
)?;
// 3.1 Copy license files into staged tree
@@ -5108,6 +5130,17 @@ pub fn run(cli: Cli) -> Result<()> {
.as_ref()
.map(|p| cross::CrossConfig::from_prefix(p))
.transpose()?;
let host_build_dir = builder::ensure_host_build(
&pkg_spec,
&src_dir,
cross_config.as_ref(),
!no_flags,
builder::TargetBuildKind::Primary,
)?;
if let Some(host_dir) = host_build_dir.as_ref() {
pkg_spec.build.flags.host_build_dir =
Some(host_dir.to_string_lossy().into_owned());
}
if !lib32_only {
builder::build(
&pkg_spec,
@@ -5115,6 +5148,7 @@ pub fn run(cli: Cli) -> Result<()> {
&destdir,
cross_config.as_ref(),
!no_flags,
host_build_dir.as_deref(),
)?;
if let Some(watcher) = interrupt_watcher.as_ref() {
watcher.check()?;
+18
View File
@@ -865,6 +865,11 @@ impl PackageSpec {
self.build.flags.lib32_only = b;
}
}
"host_build" | "host-build" => {
if let Some(b) = toml_value_as_boolish(v) {
self.build.flags.host_build = b;
}
}
"configure_lib32" | "configure-lib32" => {
if let Some(arr) = v.as_array() {
self.build.flags.configure_lib32 = arr
@@ -4126,6 +4131,14 @@ pub struct BuildFlags {
deserialize_with = "deserialize_boolish"
)]
pub lib32_only: bool,
/// Perform an additional native host-side helper build when the active target arch differs.
#[serde(
default,
alias = "host-build",
alias = "host_build",
deserialize_with = "deserialize_boolish"
)]
pub host_build: bool,
#[serde(default)]
pub configure: Vec<String>,
/// PEP 517 config settings for Python builds (each entry is `KEY=VALUE` or `KEY`).
@@ -4403,6 +4416,9 @@ pub struct BuildFlags {
/// Internal runtime marker used to adjust builder behavior for the lib32 variant.
#[serde(skip)]
pub lib32_variant: bool,
/// Internal runtime marker containing the absolute path to the native host helper build dir.
#[serde(skip)]
pub host_build_dir: Option<String>,
}
impl Default for BuildFlags {
@@ -4432,6 +4448,7 @@ impl Default for BuildFlags {
skip_tests: false,
build_32: false,
lib32_only: false,
host_build: false,
configure: Vec::new(),
config_settings: Vec::new(),
configure_lib32: Vec::new(),
@@ -4491,6 +4508,7 @@ impl Default for BuildFlags {
build_dir: None,
binary_type: String::new(),
lib32_variant: false,
host_build_dir: None,
}
}
}