Refactor and enhance source management and installation processes
- Improved error handling and context messages in `checkout` function in `git.rs`. - Updated license field in package specification to use a vector in `hooks.rs`. - Enhanced `copy_manual_sources` function in `mod.rs` to support both local file and remote URL sources, including checksum verification. - Added new utility functions for path normalization and skipping installation of specific files in `staging/mod.rs`. - Implemented logic to handle existing files during installation, allowing for `.depotnew` suffix for kept files. - Added tests for manual source copying, installation behavior, and path validation. - Introduced a new LICENSE file with MIT License terms.
This commit is contained in:
+359
-70
@@ -14,78 +14,28 @@ pub fn build(
|
||||
src_dir: &Path,
|
||||
destdir: &Path,
|
||||
cross: Option<&CrossConfig>,
|
||||
export_compiler_flags: bool,
|
||||
) -> Result<()> {
|
||||
let flags = &spec.build.flags;
|
||||
|
||||
// Determine actual source directory (support source_subdir)
|
||||
let actual_src = if !flags.source_subdir.is_empty() {
|
||||
src_dir.join(&flags.source_subdir)
|
||||
} else {
|
||||
src_dir.to_path_buf()
|
||||
};
|
||||
|
||||
if !actual_src.exists() {
|
||||
anyhow::bail!(
|
||||
"Source directory not found: {} (source_subdir: {})",
|
||||
actual_src.display(),
|
||||
flags.source_subdir
|
||||
);
|
||||
}
|
||||
let export_compiler_flags = export_compiler_flags && !flags.no_flags;
|
||||
let actual_src = resolve_actual_src(spec, src_dir)?;
|
||||
|
||||
// Create destdir
|
||||
fs::create_dir_all(destdir)?;
|
||||
|
||||
// Build environment variables
|
||||
let mut env_vars: Vec<(&str, String)> = vec![];
|
||||
|
||||
// Use cross-compilation tools if configured
|
||||
let (cc, ar) = if let Some(cc_cfg) = cross {
|
||||
(cc_cfg.cc.clone(), cc_cfg.ar.clone())
|
||||
let mut env_vars = crate::builder::standard_build_env(spec, cross, true, export_compiler_flags);
|
||||
let cc = if let Some(cc_cfg) = cross {
|
||||
cc_cfg.cc.clone()
|
||||
} else {
|
||||
(flags.cc.clone(), flags.ar.clone())
|
||||
flags.cc.clone()
|
||||
};
|
||||
|
||||
if !flags.cflags.is_empty() {
|
||||
if export_compiler_flags && !flags.cflags.is_empty() {
|
||||
// Expand shell command substitutions like $($CC -print-resource-dir)
|
||||
let cflags_str = flags.cflags.join(" ");
|
||||
let expanded = expand_shell_commands(&cflags_str, &cc)?;
|
||||
env_vars.push(("CFLAGS", expanded));
|
||||
}
|
||||
if !flags.ldflags.is_empty() {
|
||||
env_vars.push(("LDFLAGS", flags.ldflags.join(" ")));
|
||||
}
|
||||
env_vars.push(("CC", cc.clone()));
|
||||
env_vars.push(("AR", ar));
|
||||
|
||||
// CARCH support
|
||||
if !flags.carch.is_empty() {
|
||||
env_vars.push(("CARCH", flags.carch.clone()));
|
||||
}
|
||||
|
||||
// Export rootfs for build scripts
|
||||
env_vars.push(("DEPOT_ROOTFS", flags.rootfs.clone()));
|
||||
|
||||
// Add cross-compilation environment
|
||||
if let Some(cc_cfg) = cross {
|
||||
env_vars.push(("CXX", cc_cfg.cxx.clone()));
|
||||
env_vars.push(("RANLIB", cc_cfg.ranlib.clone()));
|
||||
env_vars.push(("STRIP", cc_cfg.strip.clone()));
|
||||
env_vars.push(("LD", cc_cfg.ld.clone()));
|
||||
env_vars.push(("NM", cc_cfg.nm.clone()));
|
||||
}
|
||||
|
||||
// Add dynamic loader flag if specified
|
||||
if !flags.libc.is_empty() {
|
||||
let ldflags = if flags.ldflags.is_empty() {
|
||||
format!("-Wl,--dynamic-linker={}", flags.libc)
|
||||
} else {
|
||||
format!(
|
||||
"{} -Wl,--dynamic-linker={}",
|
||||
flags.ldflags.join(" "),
|
||||
flags.libc
|
||||
)
|
||||
};
|
||||
env_vars.push(("LDFLAGS", ldflags));
|
||||
crate::builder::set_env_var(&mut env_vars, "CFLAGS", expanded);
|
||||
}
|
||||
|
||||
use crate::builder::state::{BuildStep, StateTracker};
|
||||
@@ -117,18 +67,47 @@ pub fn build(
|
||||
|
||||
configure_cmd.arg(format!("--prefix={}", flags.prefix));
|
||||
|
||||
if !flags.chost.is_empty() {
|
||||
configure_cmd.arg(format!("--host={}", flags.chost));
|
||||
}
|
||||
if !flags.cbuild.is_empty() {
|
||||
configure_cmd.arg(format!("--build={}", flags.cbuild));
|
||||
// Some projects use non-GNU configure scripts that reject --host/--build.
|
||||
// Probe support first and only add these options when advertised.
|
||||
let help_text = configure_help_text(configure_path, &build_dir, &env_vars);
|
||||
let supports_host = help_text
|
||||
.as_deref()
|
||||
.map(|s| configure_help_supports_option(s, "--host"))
|
||||
.unwrap_or(true);
|
||||
let supports_build = help_text
|
||||
.as_deref()
|
||||
.map(|s| configure_help_supports_option(s, "--build"))
|
||||
.unwrap_or(true);
|
||||
|
||||
let requested_host = if let Some(cc_cfg) = cross {
|
||||
Some(cc_cfg.host_triple().to_string())
|
||||
} else if !flags.chost.is_empty() {
|
||||
Some(flags.chost.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let requested_build = if cross.is_some() {
|
||||
CrossConfig::build_triple().ok()
|
||||
} else if !flags.cbuild.is_empty() {
|
||||
Some(flags.cbuild.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(host) = requested_host {
|
||||
if supports_host {
|
||||
configure_cmd.arg(format!("--host={}", host));
|
||||
} else {
|
||||
println!(" configure does not support --host; skipping {}", host);
|
||||
}
|
||||
}
|
||||
|
||||
// Add cross-compilation flags
|
||||
if let Some(cc_cfg) = cross {
|
||||
configure_cmd.arg(format!("--host={}", cc_cfg.host_triple()));
|
||||
if let Ok(build) = CrossConfig::build_triple() {
|
||||
if let Some(build) = requested_build {
|
||||
if supports_build {
|
||||
configure_cmd.arg(format!("--build={}", build));
|
||||
} else {
|
||||
println!(" configure does not support --build; skipping {}", build);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +133,7 @@ pub fn build(
|
||||
let mut make_cmd = Command::new("make");
|
||||
make_cmd.current_dir(&build_dir);
|
||||
make_cmd.arg("-j").arg(num_cpus().to_string());
|
||||
add_make_variable_overrides(&mut make_cmd, &flags.make_vars, "build")?;
|
||||
|
||||
crate::builder::prepare_command(&mut make_cmd, &env_vars);
|
||||
|
||||
@@ -165,6 +145,30 @@ pub fn build(
|
||||
anyhow::bail!("make failed with status: {}", status);
|
||||
}
|
||||
|
||||
if let Some(test_target) = maybe_find_autotools_test_target(&build_dir, flags.skip_tests)? {
|
||||
println!("Running make {}...", test_target);
|
||||
let mut test_cmd = Command::new("make");
|
||||
test_cmd.current_dir(&build_dir);
|
||||
add_make_variable_overrides(&mut test_cmd, &flags.make_test_vars, "test")?;
|
||||
test_cmd.arg(test_target);
|
||||
crate::builder::prepare_command(&mut test_cmd, &env_vars);
|
||||
|
||||
let status = test_cmd.status().with_context(|| {
|
||||
format!(
|
||||
"Failed to run make {} in {}",
|
||||
test_target,
|
||||
build_dir.display()
|
||||
)
|
||||
})?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("make {} failed with status: {}", test_target, status);
|
||||
}
|
||||
} else if flags.skip_tests {
|
||||
println!("Skipping tests: disabled by build.flags.skip_tests");
|
||||
} else {
|
||||
println!("Skipping tests: no 'check' or 'test' target in Makefile");
|
||||
}
|
||||
|
||||
// Run post-compile hooks (after make, before make install)
|
||||
hooks::run_post_compile_commands(spec, &actual_src, destdir)?;
|
||||
state.mark_done(BuildStep::PostCompileDone)?;
|
||||
@@ -185,10 +189,17 @@ pub fn build(
|
||||
|
||||
let mut install_cmd = fakeroot::wrap_install_command("make", destdir);
|
||||
install_cmd.current_dir(&build_dir);
|
||||
if !has_make_variable_override(&flags.make_install_vars, "DESTDIR") {
|
||||
install_cmd.arg(format!("DESTDIR={}", destdir.to_string_lossy()));
|
||||
}
|
||||
add_make_variable_overrides(&mut install_cmd, &flags.make_install_vars, "install")?;
|
||||
install_cmd.arg("install");
|
||||
|
||||
let mut install_env = env_vars.clone();
|
||||
install_env.push(("DESTDIR", destdir.to_string_lossy().into_owned()));
|
||||
install_env.push((
|
||||
"DESTDIR".to_string(),
|
||||
destdir.to_string_lossy().into_owned(),
|
||||
));
|
||||
crate::builder::prepare_command(&mut install_cmd, &install_env);
|
||||
|
||||
let status = install_cmd
|
||||
@@ -215,6 +226,155 @@ fn num_cpus() -> usize {
|
||||
.unwrap_or(1)
|
||||
}
|
||||
|
||||
fn resolve_actual_src(spec: &PackageSpec, src_dir: &Path) -> Result<std::path::PathBuf> {
|
||||
let flags = &spec.build.flags;
|
||||
let source_subdir = spec.expand_vars(&flags.source_subdir);
|
||||
|
||||
let actual_src = if source_subdir.is_empty() {
|
||||
src_dir.to_path_buf()
|
||||
} else {
|
||||
src_dir.join(&source_subdir)
|
||||
};
|
||||
|
||||
if !actual_src.exists() {
|
||||
anyhow::bail!(
|
||||
"Source directory not found: {} (source_subdir: {} -> {})",
|
||||
actual_src.display(),
|
||||
flags.source_subdir,
|
||||
source_subdir
|
||||
);
|
||||
}
|
||||
|
||||
Ok(actual_src)
|
||||
}
|
||||
|
||||
fn configure_help_text(
|
||||
configure_path: &str,
|
||||
build_dir: &Path,
|
||||
env_vars: &crate::builder::EnvVars,
|
||||
) -> Option<String> {
|
||||
let mut help_cmd = Command::new(configure_path);
|
||||
help_cmd.current_dir(build_dir);
|
||||
help_cmd.arg("--help");
|
||||
crate::builder::prepare_command(&mut help_cmd, env_vars);
|
||||
let output = help_cmd.output().ok()?;
|
||||
if !output.status.success() {
|
||||
return None;
|
||||
}
|
||||
let mut text = String::new();
|
||||
text.push_str(&String::from_utf8_lossy(&output.stdout));
|
||||
text.push_str(&String::from_utf8_lossy(&output.stderr));
|
||||
Some(text)
|
||||
}
|
||||
|
||||
fn configure_help_supports_option(help_text: &str, option: &str) -> bool {
|
||||
let with_eq = format!("{}=", option);
|
||||
let with_space = format!("{} ", option);
|
||||
help_text.contains(&with_eq) || help_text.contains(&with_space) || help_text.contains(option)
|
||||
}
|
||||
|
||||
fn find_autotools_test_target(build_dir: &Path) -> Result<Option<&'static str>> {
|
||||
for target in ["check", "test"] {
|
||||
if makefile_has_target(build_dir, target)? {
|
||||
return Ok(Some(target));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn maybe_find_autotools_test_target(
|
||||
build_dir: &Path,
|
||||
skip_tests: bool,
|
||||
) -> Result<Option<&'static str>> {
|
||||
if skip_tests {
|
||||
return Ok(None);
|
||||
}
|
||||
find_autotools_test_target(build_dir)
|
||||
}
|
||||
|
||||
fn makefile_has_target(build_dir: &Path, target: &str) -> Result<bool> {
|
||||
for name in ["GNUmakefile", "Makefile", "makefile"] {
|
||||
let path = build_dir.join(name);
|
||||
if !path.exists() {
|
||||
continue;
|
||||
}
|
||||
let content = fs::read_to_string(&path)
|
||||
.with_context(|| format!("Failed to read makefile: {}", path.display()))?;
|
||||
if makefile_content_has_target(&content, target) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn makefile_content_has_target(content: &str, target: &str) -> bool {
|
||||
for raw in content.lines() {
|
||||
let line = raw.trim_start();
|
||||
if line.is_empty() || line.starts_with('#') || line.starts_with('\t') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(rest) = line.strip_prefix(".PHONY:") {
|
||||
if rest.split_whitespace().any(|t| t == target) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(colon_pos) = line.find(':') else {
|
||||
continue;
|
||||
};
|
||||
let rhs = &line[colon_pos + 1..];
|
||||
if rhs.starts_with('=') {
|
||||
// Variable assignment (e.g. FOO:=bar), not a make target.
|
||||
continue;
|
||||
}
|
||||
|
||||
let lhs = &line[..colon_pos];
|
||||
if lhs.split_whitespace().any(|t| t == target) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn add_make_variable_overrides(cmd: &mut Command, vars: &[String], phase: &str) -> Result<()> {
|
||||
for raw in vars {
|
||||
let var = raw.trim();
|
||||
if var.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let Some((name, _value)) = var.split_once('=') else {
|
||||
anyhow::bail!(
|
||||
"Invalid make variable override '{}' for {} phase; expected NAME=VALUE",
|
||||
var,
|
||||
phase
|
||||
);
|
||||
};
|
||||
let name = name.trim();
|
||||
if name.is_empty() || name.contains(char::is_whitespace) {
|
||||
anyhow::bail!(
|
||||
"Invalid make variable override '{}' for {} phase; expected NAME=VALUE",
|
||||
var,
|
||||
phase
|
||||
);
|
||||
}
|
||||
cmd.arg(var);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn has_make_variable_override(vars: &[String], name: &str) -> bool {
|
||||
vars.iter().any(|raw| {
|
||||
let var = raw.trim();
|
||||
let Some((lhs, _rhs)) = var.split_once('=') else {
|
||||
return false;
|
||||
};
|
||||
lhs.trim() == name
|
||||
})
|
||||
}
|
||||
|
||||
/// Expand shell command substitutions like $($CC -print-resource-dir) in a string
|
||||
fn expand_shell_commands(input: &str, cc: &str) -> Result<String> {
|
||||
let mut result = input.to_string();
|
||||
@@ -253,6 +413,9 @@ fn expand_shell_commands(input: &str, cc: &str) -> Result<String> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::package::{Build, BuildFlags, BuildType, Dependencies, PackageInfo, PackageSpec};
|
||||
use std::path::PathBuf;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn test_expand_shell_commands_simple() -> Result<()> {
|
||||
@@ -266,7 +429,7 @@ mod tests {
|
||||
// The command contains $CC which should be replaced with provided cc
|
||||
let out = expand_shell_commands("start $($CC -v >/dev/null; echo OK) end", "mycc")?;
|
||||
// Since the inner command echoes OK, after replacing $CC it should run and include OK
|
||||
assert!(out.contains("OK") || out.contains("") );
|
||||
assert!(out.contains("OK") || out.contains(""));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -275,4 +438,130 @@ mod tests {
|
||||
let n = num_cpus();
|
||||
assert!(n >= 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_configure_help_supports_host_build() {
|
||||
let help = "Usage: configure [OPTION]...\n --host=HOST cross host\n --build=BUILD";
|
||||
assert!(configure_help_supports_option(help, "--host"));
|
||||
assert!(configure_help_supports_option(help, "--build"));
|
||||
assert!(!configure_help_supports_option(help, "--target"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_makefile_content_has_target_detects_check_and_test() {
|
||||
let content = r#"
|
||||
.PHONY: all check
|
||||
all:
|
||||
@echo all
|
||||
check:
|
||||
@echo check
|
||||
"#;
|
||||
assert!(makefile_content_has_target(content, "check"));
|
||||
assert!(!makefile_content_has_target(content, "test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_makefile_content_has_target_ignores_assignments() {
|
||||
let content = r#"
|
||||
TEST := value
|
||||
VAR:=$(shell echo hi)
|
||||
foo: bar
|
||||
@true
|
||||
"#;
|
||||
assert!(!makefile_content_has_target(content, "TEST"));
|
||||
assert!(!makefile_content_has_target(content, "VAR"));
|
||||
assert!(!makefile_content_has_target(content, "check"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_maybe_find_autotools_test_target_respects_skip_tests() -> Result<()> {
|
||||
let tmp = tempdir().unwrap();
|
||||
std::fs::write(tmp.path().join("Makefile"), "check:\n\t@true\n").unwrap();
|
||||
|
||||
let skipped = maybe_find_autotools_test_target(tmp.path(), true)?;
|
||||
assert_eq!(skipped, None);
|
||||
|
||||
let detected = maybe_find_autotools_test_target(tmp.path(), false)?;
|
||||
assert_eq!(detected, Some("check"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_make_variable_overrides_accepts_valid_assignments() -> Result<()> {
|
||||
let mut cmd = Command::new("make");
|
||||
add_make_variable_overrides(
|
||||
&mut cmd,
|
||||
&[
|
||||
"CC=clang".to_string(),
|
||||
"V=1".to_string(),
|
||||
" CFLAGS=-O2 -pipe ".to_string(),
|
||||
],
|
||||
"build",
|
||||
)?;
|
||||
let args: Vec<String> = cmd
|
||||
.get_args()
|
||||
.map(|a| a.to_string_lossy().to_string())
|
||||
.collect();
|
||||
assert_eq!(args, vec!["CC=clang", "V=1", "CFLAGS=-O2 -pipe"]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_make_variable_overrides_rejects_invalid_assignment() {
|
||||
let mut cmd = Command::new("make");
|
||||
let err = add_make_variable_overrides(&mut cmd, &["not-an-assignment".to_string()], "test")
|
||||
.expect_err("expected invalid assignment to fail");
|
||||
assert!(err.to_string().contains("expected NAME=VALUE"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_make_variable_override_detects_destdir() {
|
||||
assert!(has_make_variable_override(
|
||||
&["DESTDIR=/tmp/pkg".to_string()],
|
||||
"DESTDIR"
|
||||
));
|
||||
assert!(has_make_variable_override(
|
||||
&[" DESTDIR =/tmp/pkg ".to_string()],
|
||||
"DESTDIR"
|
||||
));
|
||||
assert!(!has_make_variable_override(
|
||||
&["V=1".to_string(), "PREFIX=/usr".to_string()],
|
||||
"DESTDIR"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_actual_src_expands_source_subdir_vars() {
|
||||
let tmp = tempdir().unwrap();
|
||||
let src_root = tmp.path().join("srcroot");
|
||||
let expanded = src_root.join("expect5.45.4").join("unix");
|
||||
std::fs::create_dir_all(&expanded).unwrap();
|
||||
|
||||
let spec = PackageSpec {
|
||||
package: PackageInfo {
|
||||
name: "expect".into(),
|
||||
version: "5.45.4".into(),
|
||||
revision: 1,
|
||||
description: "d".into(),
|
||||
homepage: "h".into(),
|
||||
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 {
|
||||
source_subdir: "$name$version/unix".into(),
|
||||
..BuildFlags::default()
|
||||
},
|
||||
},
|
||||
dependencies: Dependencies::default(),
|
||||
spec_dir: PathBuf::from("."),
|
||||
};
|
||||
|
||||
let resolved = resolve_actual_src(&spec, &src_root).unwrap();
|
||||
assert_eq!(resolved, expanded);
|
||||
}
|
||||
}
|
||||
|
||||
+23
-10
@@ -1,12 +1,12 @@
|
||||
//! Binary package "build" system — used when package supplies a prebuilt binary installer
|
||||
|
||||
use crate::cross::CrossConfig;
|
||||
use crate::package::PackageSpec;
|
||||
use anyhow::{Context, Result};
|
||||
use std::fs;
|
||||
use std::os::unix::fs as unix_fs;
|
||||
use std::path::Path;
|
||||
use walkdir::WalkDir;
|
||||
use crate::package::PackageSpec;
|
||||
use crate::cross::CrossConfig;
|
||||
|
||||
/// For binary packages we simply copy the extracted files into DESTDIR (preserving
|
||||
/// directory structure). This is useful for .deb packages where extract step
|
||||
@@ -16,9 +16,16 @@ pub fn build(
|
||||
src_dir: &Path,
|
||||
destdir: &Path,
|
||||
_cross: Option<&CrossConfig>,
|
||||
_export_compiler_flags: bool,
|
||||
) -> Result<()> {
|
||||
println!("Binary install: copying files from {} to {} (pkg type={})", src_dir.display(), destdir.display(), _spec.build.flags.binary_type);
|
||||
fs::create_dir_all(destdir).with_context(|| format!("Failed to create destdir: {}", destdir.display()))?;
|
||||
println!(
|
||||
"Binary install: copying files from {} to {} (pkg type={})",
|
||||
src_dir.display(),
|
||||
destdir.display(),
|
||||
_spec.build.flags.binary_type
|
||||
);
|
||||
fs::create_dir_all(destdir)
|
||||
.with_context(|| format!("Failed to create destdir: {}", destdir.display()))?;
|
||||
|
||||
for entry in WalkDir::new(src_dir) {
|
||||
let entry = entry?;
|
||||
@@ -28,12 +35,18 @@ pub fn build(
|
||||
fs::create_dir_all(&target)?;
|
||||
} else if entry.file_type().is_symlink() {
|
||||
let link_target = fs::read_link(entry.path())?;
|
||||
if let Some(parent) = target.parent() { fs::create_dir_all(parent)?; }
|
||||
if let Some(parent) = target.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
// overwrite existing links/files
|
||||
if target.exists() { let _ = fs::remove_file(&target); }
|
||||
if target.exists() {
|
||||
let _ = fs::remove_file(&target);
|
||||
}
|
||||
unix_fs::symlink(link_target, &target)?;
|
||||
} else {
|
||||
if let Some(parent) = target.parent() { fs::create_dir_all(parent)?; }
|
||||
if let Some(parent) = target.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
fs::copy(entry.path(), &target)?;
|
||||
}
|
||||
}
|
||||
@@ -45,9 +58,9 @@ pub fn build(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::package::{Build, BuildFlags, BuildType, Dependencies, PackageInfo};
|
||||
use tempfile::tempdir;
|
||||
use std::fs;
|
||||
use std::os::unix::fs as unix_fs;
|
||||
use tempfile::tempdir;
|
||||
|
||||
fn mk_spec(name: &str, version: &str) -> PackageSpec {
|
||||
PackageSpec {
|
||||
@@ -57,7 +70,7 @@ mod tests {
|
||||
revision: 1,
|
||||
description: "d".into(),
|
||||
homepage: "h".into(),
|
||||
license: "MIT".into(),
|
||||
license: vec!["MIT".into()],
|
||||
},
|
||||
packages: Vec::new(),
|
||||
alternatives: Default::default(),
|
||||
@@ -96,7 +109,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)?;
|
||||
build(&spec, src, dest, None, true)?;
|
||||
|
||||
// Check copied file
|
||||
assert!(dest.join("usr/bin/hello").exists());
|
||||
|
||||
+51
-62
@@ -13,13 +13,13 @@ pub fn build(
|
||||
src_dir: &Path,
|
||||
destdir: &Path,
|
||||
cross: Option<&CrossConfig>,
|
||||
export_compiler_flags: bool,
|
||||
) -> Result<()> {
|
||||
let flags = &spec.build.flags;
|
||||
|
||||
// Determine actual source directory (support source_subdir)
|
||||
let actual_src = resolve_actual_src(spec, src_dir)?;
|
||||
|
||||
|
||||
let build_dir = actual_src.join("build");
|
||||
|
||||
// Create directories
|
||||
@@ -27,55 +27,7 @@ pub fn build(
|
||||
fs::create_dir_all(destdir)?;
|
||||
|
||||
// Environment variables
|
||||
let mut env_vars: Vec<(&str, String)> = vec![];
|
||||
|
||||
// Use cross-compilation tools if configured
|
||||
let cc = if let Some(cc_cfg) = cross {
|
||||
cc_cfg.cc.clone()
|
||||
} else {
|
||||
flags.cc.clone()
|
||||
};
|
||||
|
||||
if !flags.cflags.is_empty() {
|
||||
env_vars.push(("CFLAGS", flags.cflags.join(" ")));
|
||||
}
|
||||
if !flags.chost.is_empty() {
|
||||
env_vars.push(("CHOST", flags.chost.clone()));
|
||||
}
|
||||
if !flags.cbuild.is_empty() {
|
||||
env_vars.push(("CBUILD", flags.cbuild.clone()));
|
||||
}
|
||||
if !flags.ldflags.is_empty() {
|
||||
let ldflags = if flags.libc.is_empty() {
|
||||
flags.ldflags.join(" ")
|
||||
} else {
|
||||
format!(
|
||||
"{} -Wl,--dynamic-linker={}",
|
||||
flags.ldflags.join(" "),
|
||||
flags.libc
|
||||
)
|
||||
};
|
||||
env_vars.push(("LDFLAGS", ldflags));
|
||||
}
|
||||
env_vars.push(("CC", cc));
|
||||
|
||||
// Ensure CXX is exported for configure flags that reference $CXX
|
||||
// prefer cross-config CXX when cross-compiling (handled earlier), otherwise use flags.cxx
|
||||
env_vars.push(("CXX", flags.cxx.clone()));
|
||||
|
||||
// Export rootfs for build scripts
|
||||
env_vars.push(("DEPOT_ROOTFS", flags.rootfs.clone()));
|
||||
|
||||
// CARCH support
|
||||
if !flags.carch.is_empty() {
|
||||
env_vars.push(("CARCH", flags.carch.clone()));
|
||||
}
|
||||
|
||||
// Add cross-compilation env
|
||||
if let Some(cc_cfg) = cross {
|
||||
env_vars.push(("CXX", cc_cfg.cxx.clone()));
|
||||
env_vars.push(("AR", cc_cfg.ar.clone()));
|
||||
}
|
||||
let env_vars = crate::builder::standard_build_env(spec, cross, true, export_compiler_flags);
|
||||
|
||||
// Extract prefix from configure flags (cmake-style -DCMAKE_INSTALL_PREFIX=)
|
||||
let prefix = flags
|
||||
@@ -168,7 +120,10 @@ pub fn build(
|
||||
install_cmd.arg("--install").arg(&build_dir);
|
||||
|
||||
let mut install_env = env_vars.clone();
|
||||
install_env.push(("DESTDIR", destdir.to_string_lossy().into_owned()));
|
||||
install_env.push((
|
||||
"DESTDIR".to_string(),
|
||||
destdir.to_string_lossy().into_owned(),
|
||||
));
|
||||
crate::builder::prepare_command(&mut install_cmd, &install_env);
|
||||
|
||||
let status = install_cmd
|
||||
@@ -199,7 +154,7 @@ fn expand_env_vars(input: &str) -> String {
|
||||
}
|
||||
|
||||
/// Expand using a provided set of env vars (used to expand flags before spawning child).
|
||||
fn expand_with_envs(input: &str, envs: &Vec<(&str, String)>) -> String {
|
||||
fn expand_with_envs(input: &str, envs: &[(String, String)]) -> String {
|
||||
let mut result = input.to_string();
|
||||
for (k, v) in envs {
|
||||
result = result.replace(&format!("${}", k), v);
|
||||
@@ -220,29 +175,38 @@ fn num_cpus() -> usize {
|
||||
/// - `src_dir/<sub>` -> use if exists
|
||||
/// - `spec.spec_dir/<sub>` -> use if exists (supports `../llvm`)
|
||||
/// - bare relative path (cwd)
|
||||
fn resolve_actual_src(spec: &crate::package::PackageSpec, src_dir: &Path) -> anyhow::Result<std::path::PathBuf> {
|
||||
fn resolve_actual_src(
|
||||
spec: &crate::package::PackageSpec,
|
||||
src_dir: &Path,
|
||||
) -> anyhow::Result<std::path::PathBuf> {
|
||||
let flags = &spec.build.flags;
|
||||
if flags.source_subdir.is_empty() {
|
||||
let source_subdir = spec.expand_vars(&flags.source_subdir);
|
||||
if source_subdir.is_empty() {
|
||||
return Ok(src_dir.to_path_buf());
|
||||
}
|
||||
|
||||
let candidate = std::path::Path::new(&flags.source_subdir);
|
||||
let candidate = std::path::Path::new(&source_subdir);
|
||||
// 1) absolute path -> use directly
|
||||
if candidate.is_absolute() {
|
||||
if candidate.exists() {
|
||||
return Ok(candidate.to_path_buf());
|
||||
}
|
||||
anyhow::bail!("Source directory not found: {} (source_subdir: {})", candidate.display(), flags.source_subdir);
|
||||
anyhow::bail!(
|
||||
"Source directory not found: {} (source_subdir: {} -> {})",
|
||||
candidate.display(),
|
||||
flags.source_subdir,
|
||||
source_subdir
|
||||
);
|
||||
}
|
||||
|
||||
// 2) src_dir/<source_subdir>
|
||||
let under_src = src_dir.join(&flags.source_subdir);
|
||||
let under_src = src_dir.join(&source_subdir);
|
||||
if under_src.exists() {
|
||||
return Ok(under_src);
|
||||
}
|
||||
|
||||
// 3) spec_dir/<source_subdir> (useful for ../llvm relative to spec)
|
||||
let spec_path = spec.spec_dir.join(&flags.source_subdir);
|
||||
let spec_path = spec.spec_dir.join(&source_subdir);
|
||||
if spec_path.exists() {
|
||||
return Ok(spec_path);
|
||||
}
|
||||
@@ -254,7 +218,8 @@ fn resolve_actual_src(spec: &crate::package::PackageSpec, src_dir: &Path) -> any
|
||||
|
||||
// fallback error
|
||||
anyhow::bail!(
|
||||
"Source directory not found: {} (tried src_dir, spec_dir, and absolute path)",
|
||||
"Source directory not found: {} (expanded from '{}'; tried src_dir, spec_dir, and absolute path)",
|
||||
source_subdir,
|
||||
flags.source_subdir
|
||||
);
|
||||
}
|
||||
@@ -277,7 +242,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_expand_with_envs_prefers_provided_envs() {
|
||||
let envs = vec![("CXX", "my-cxx".to_string()), ("CC", "my-cc".to_string())];
|
||||
let envs = vec![
|
||||
("CXX".to_string(), "my-cxx".to_string()),
|
||||
("CC".to_string(), "my-cc".to_string()),
|
||||
];
|
||||
let s = "-DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=${CXX} -DROOT=$HOME";
|
||||
let out = expand_with_envs(s, &envs);
|
||||
assert!(out.contains("my-cc"));
|
||||
@@ -297,18 +265,33 @@ mod tests {
|
||||
let src_root = tmp.path().join("srcroot");
|
||||
let spec_dir = tmp.path().join("specdir");
|
||||
let external = tmp.path().join("external");
|
||||
let expanded = src_root.join("x-1.0").join("sub");
|
||||
std::fs::create_dir_all(&src_root.join("sub")).unwrap();
|
||||
std::fs::create_dir_all(&expanded).unwrap();
|
||||
// create directories for candidates
|
||||
std::fs::create_dir_all(&spec_dir.join("../llvm")).unwrap();
|
||||
std::fs::create_dir_all(&external).unwrap();
|
||||
|
||||
let spec = PackageSpec {
|
||||
package: PackageInfo { name: "x".into(), version: "1.0".into(), revision: 1, description: "".into(), homepage: "".into(), license: "MIT".into() },
|
||||
package: PackageInfo {
|
||||
name: "x".into(),
|
||||
version: "1.0".into(),
|
||||
revision: 1,
|
||||
description: "".into(),
|
||||
homepage: "".into(),
|
||||
license: vec!["MIT".into()],
|
||||
},
|
||||
packages: Vec::new(),
|
||||
alternatives: Default::default(),
|
||||
manual_sources: Vec::new(),
|
||||
source: Vec::new(),
|
||||
build: Build { build_type: BuildType::CMake, flags: BuildFlags { source_subdir: "sub".into(), ..BuildFlags::default() } },
|
||||
build: Build {
|
||||
build_type: BuildType::CMake,
|
||||
flags: BuildFlags {
|
||||
source_subdir: "sub".into(),
|
||||
..BuildFlags::default()
|
||||
},
|
||||
},
|
||||
dependencies: Default::default(),
|
||||
spec_dir: spec_dir.clone(),
|
||||
};
|
||||
@@ -328,5 +311,11 @@ mod tests {
|
||||
spec3.build.flags.source_subdir = external.to_string_lossy().into_owned();
|
||||
let p3 = resolve_actual_src(&spec3, &src_root).unwrap();
|
||||
assert_eq!(p3, external);
|
||||
|
||||
// case: variable expansion in source_subdir
|
||||
let mut spec4 = spec.clone();
|
||||
spec4.build.flags.source_subdir = "$name-$version/sub".into();
|
||||
let p4 = resolve_actual_src(&spec4, &src_root).unwrap();
|
||||
assert_eq!(p4, expanded);
|
||||
}
|
||||
}
|
||||
|
||||
+27
-61
@@ -12,13 +12,14 @@ pub fn build(
|
||||
src_dir: &Path,
|
||||
destdir: &Path,
|
||||
cross: Option<&CrossConfig>,
|
||||
export_compiler_flags: bool,
|
||||
) -> Result<()> {
|
||||
let flags = &spec.build.flags;
|
||||
|
||||
// Create destdir
|
||||
fs::create_dir_all(destdir)?;
|
||||
|
||||
let mut env_vars: Vec<(&str, String)> = vec![];
|
||||
let mut env_vars = crate::builder::standard_build_env(spec, cross, true, export_compiler_flags);
|
||||
|
||||
// For custom builds, look for a build.sh script in the source directory
|
||||
let build_script = src_dir.join("build.sh");
|
||||
@@ -29,8 +30,12 @@ pub fn build(
|
||||
let spec_build = spec.spec_dir.join("build.sh");
|
||||
if !build_script.exists() && spec_build.exists() {
|
||||
fs::create_dir_all(src_dir)?;
|
||||
fs::copy(&spec_build, &build_script)
|
||||
.with_context(|| format!("Failed to copy build.sh from spec dir: {}", spec_build.display()))?;
|
||||
fs::copy(&spec_build, &build_script).with_context(|| {
|
||||
format!(
|
||||
"Failed to copy build.sh from spec dir: {}",
|
||||
spec_build.display()
|
||||
)
|
||||
})?;
|
||||
// Ensure executable bit
|
||||
#[cfg(unix)]
|
||||
{
|
||||
@@ -50,7 +55,7 @@ pub fn build(
|
||||
}
|
||||
|
||||
use crate::builder::state::{BuildStep, StateTracker};
|
||||
let mut state = StateTracker::new(&src_dir)?;
|
||||
let mut state = StateTracker::new(src_dir)?;
|
||||
|
||||
if !state.is_done(BuildStep::PostInstallDone) {
|
||||
println!(
|
||||
@@ -70,7 +75,8 @@ pub fn build(
|
||||
src_dir.to_path_buf()
|
||||
};
|
||||
|
||||
let mut cmd = fakeroot::wrap_install_command("bash", destdir);
|
||||
// Use POSIX `sh` (more likely to be available in minimal/chroot environments)
|
||||
let mut cmd = fakeroot::wrap_install_command("sh", destdir);
|
||||
cmd.current_dir(&build_dir);
|
||||
|
||||
// Ensure build script path is absolute for when we are in a sub-build-dir
|
||||
@@ -81,62 +87,22 @@ pub fn build(
|
||||
};
|
||||
cmd.arg(&abs_build_script);
|
||||
|
||||
if !flags.cflags.is_empty() {
|
||||
env_vars.push(("CFLAGS", flags.cflags.join(" ")));
|
||||
}
|
||||
if !flags.ldflags.is_empty() {
|
||||
let ldflags = if flags.libc.is_empty() {
|
||||
flags.ldflags.join(" ")
|
||||
} else {
|
||||
format!(
|
||||
"{} -Wl,--dynamic-linker={}",
|
||||
flags.ldflags.join(" "),
|
||||
flags.libc
|
||||
)
|
||||
};
|
||||
env_vars.push(("LDFLAGS", ldflags));
|
||||
}
|
||||
|
||||
env_vars.push(("DESTDIR", destdir.to_string_lossy().into_owned()));
|
||||
env_vars.push(("PREFIX", spec.build.flags.prefix.clone()));
|
||||
env_vars.push(("DEPOT_ROOTFS", spec.build.flags.rootfs.clone()));
|
||||
env_vars.push((
|
||||
"DEPOT_SPECDIR",
|
||||
spec.spec_dir.to_string_lossy().into_owned(),
|
||||
));
|
||||
|
||||
if !flags.chost.is_empty() {
|
||||
env_vars.push(("CHOST", flags.chost.clone()));
|
||||
}
|
||||
if !flags.cbuild.is_empty() {
|
||||
env_vars.push(("CBUILD", flags.cbuild.clone()));
|
||||
}
|
||||
|
||||
if !flags.carch.is_empty() {
|
||||
env_vars.push(("CARCH", flags.carch.clone()));
|
||||
}
|
||||
|
||||
// Use cross-compilation tools if configured
|
||||
if let Some(cc_cfg) = cross {
|
||||
env_vars.push(("CC", cc_cfg.cc.clone()));
|
||||
env_vars.push(("CXX", cc_cfg.cxx.clone()));
|
||||
env_vars.push(("AR", cc_cfg.ar.clone()));
|
||||
env_vars.push(("RANLIB", cc_cfg.ranlib.clone()));
|
||||
env_vars.push(("STRIP", cc_cfg.strip.clone()));
|
||||
env_vars.push(("LD", cc_cfg.ld.clone()));
|
||||
env_vars.push(("NM", cc_cfg.nm.clone()));
|
||||
env_vars.push(("CROSS_PREFIX", cc_cfg.prefix.clone()));
|
||||
env_vars.push(("CROSS_COMPILE", format!("{}-", cc_cfg.prefix)));
|
||||
} else {
|
||||
env_vars.push(("CC", flags.cc.clone()));
|
||||
env_vars.push(("AR", flags.ar.clone()));
|
||||
}
|
||||
crate::builder::set_env_var(
|
||||
&mut env_vars,
|
||||
"DESTDIR",
|
||||
destdir.to_string_lossy().into_owned(),
|
||||
);
|
||||
|
||||
crate::builder::prepare_command(&mut cmd, &env_vars);
|
||||
|
||||
let status = cmd
|
||||
.status()
|
||||
.with_context(|| format!("Failed to run build script: {}", build_script.display()))?;
|
||||
// Run the command and include the OS error on spawn failures for clearer diagnostics
|
||||
let status = cmd.status().map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"Failed to run build script {}: {}",
|
||||
build_script.display(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
|
||||
if !status.success() {
|
||||
anyhow::bail!("Custom build script failed with status: {}", status);
|
||||
@@ -163,7 +129,7 @@ mod tests {
|
||||
revision: 1,
|
||||
description: "d".into(),
|
||||
homepage: "h".into(),
|
||||
license: "MIT".into(),
|
||||
license: vec!["MIT".into()],
|
||||
},
|
||||
packages: Vec::new(),
|
||||
alternatives: Default::default(),
|
||||
@@ -191,7 +157,7 @@ mod tests {
|
||||
|
||||
let spec = mk_spec("custom-no-build", "1.0");
|
||||
|
||||
let res = build(&spec, tmp_src.path(), tmp_dest.path(), None);
|
||||
let res = build(&spec, tmp_src.path(), tmp_dest.path(), None, true);
|
||||
assert!(res.is_err());
|
||||
Ok(())
|
||||
}
|
||||
@@ -217,7 +183,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)
|
||||
let _ = build(&spec, tmp_src.path(), tmp_dest.path(), None)?;
|
||||
let _ = build(&spec, tmp_src.path(), tmp_dest.path(), None, true)?;
|
||||
// If we reached here, build() succeeded and build.sh was copied into src
|
||||
assert!(tmp_src.path().join("build.sh").exists());
|
||||
Ok(())
|
||||
|
||||
+14
-33
@@ -10,43 +10,21 @@ pub fn build(
|
||||
src_dir: &Path,
|
||||
destdir: &Path,
|
||||
cross: Option<&CrossConfig>,
|
||||
export_compiler_flags: bool,
|
||||
) -> Result<()> {
|
||||
let mut state = StateTracker::new(src_dir)?;
|
||||
let flags = &spec.build.flags;
|
||||
|
||||
// Use cross-compilation tools if configured, otherwise use flags from spec
|
||||
// Note: Makefile builds might rely on CC/CXX/AR being set, or might use make flags.
|
||||
// We set them in env vars.
|
||||
let (cc, ar) = if let Some(cc_cfg) = cross {
|
||||
(cc_cfg.cc.as_str(), cc_cfg.ar.as_str())
|
||||
} else {
|
||||
(flags.cc.as_str(), flags.ar.as_str())
|
||||
};
|
||||
let mut env_vars = crate::builder::standard_build_env(spec, cross, true, export_compiler_flags);
|
||||
|
||||
let mut env_vars: Vec<(&str, String)> = vec![("CC", cc.to_string()), ("AR", ar.to_string())];
|
||||
|
||||
let cflags;
|
||||
if !flags.cflags.is_empty() {
|
||||
cflags = flags.cflags.join(" ");
|
||||
env_vars.push(("CFLAGS", cflags));
|
||||
}
|
||||
|
||||
let ldflags;
|
||||
if !flags.ldflags.is_empty() {
|
||||
ldflags = flags.ldflags.join(" ");
|
||||
env_vars.push(("LDFLAGS", ldflags));
|
||||
}
|
||||
|
||||
// CARCH support
|
||||
if !flags.carch.is_empty() {
|
||||
env_vars.push(("CARCH", flags.carch.clone()));
|
||||
}
|
||||
|
||||
// Export PKG_CONFIG_SYSROOT_DIR for pkg-config and a general DEPOT_ROOTFS
|
||||
// Export PKG_CONFIG_SYSROOT_DIR for pkg-config
|
||||
if !flags.rootfs.is_empty() && flags.rootfs != "/" {
|
||||
env_vars.push(("PKG_CONFIG_SYSROOT_DIR", flags.rootfs.clone()));
|
||||
crate::builder::set_env_var(
|
||||
&mut env_vars,
|
||||
"PKG_CONFIG_SYSROOT_DIR",
|
||||
flags.rootfs.clone(),
|
||||
);
|
||||
}
|
||||
env_vars.push(("DEPOT_ROOTFS", flags.rootfs.clone()));
|
||||
|
||||
if !state.is_done(BuildStep::PostCompileDone) {
|
||||
println!("Running makefile build commands...");
|
||||
@@ -95,7 +73,10 @@ pub fn build(
|
||||
cmd.current_dir(src_dir);
|
||||
|
||||
let mut install_env = env_vars.clone();
|
||||
install_env.push(("DESTDIR", destdir.to_string_lossy().into_owned()));
|
||||
install_env.push((
|
||||
"DESTDIR".to_string(),
|
||||
destdir.to_string_lossy().into_owned(),
|
||||
));
|
||||
crate::builder::prepare_command(&mut cmd, &install_env);
|
||||
|
||||
let status = cmd
|
||||
@@ -130,7 +111,7 @@ mod tests {
|
||||
revision: 1,
|
||||
description: "d".into(),
|
||||
homepage: "h".into(),
|
||||
license: "MIT".into(),
|
||||
license: vec!["MIT".into()],
|
||||
},
|
||||
packages: Vec::new(),
|
||||
alternatives: Default::default(),
|
||||
@@ -168,7 +149,7 @@ mod tests {
|
||||
"cp built.txt $DESTDIR/usr/bin/installed.txt".into(),
|
||||
];
|
||||
|
||||
build(&spec, src_path, dest_path, None)?;
|
||||
build(&spec, src_path, dest_path, None, true)?;
|
||||
|
||||
// Verify build step
|
||||
let built_file = src_path.join("built.txt");
|
||||
|
||||
+6
-46
@@ -13,6 +13,7 @@ pub fn build(
|
||||
src_dir: &Path,
|
||||
destdir: &Path,
|
||||
cross: Option<&CrossConfig>,
|
||||
export_compiler_flags: bool,
|
||||
) -> Result<()> {
|
||||
let flags = &spec.build.flags;
|
||||
let build_dir = src_dir.join("builddir");
|
||||
@@ -22,51 +23,7 @@ pub fn build(
|
||||
fs::create_dir_all(destdir)?;
|
||||
|
||||
// Environment variables
|
||||
let mut env_vars: Vec<(&str, String)> = vec![];
|
||||
|
||||
// Use cross-compilation tools if configured
|
||||
let cc = if let Some(cc_cfg) = cross {
|
||||
cc_cfg.cc.clone()
|
||||
} else {
|
||||
flags.cc.clone()
|
||||
};
|
||||
|
||||
if !flags.cflags.is_empty() {
|
||||
env_vars.push(("CFLAGS", flags.cflags.join(" ")));
|
||||
}
|
||||
if !flags.chost.is_empty() {
|
||||
env_vars.push(("CHOST", flags.chost.clone()));
|
||||
}
|
||||
if !flags.cbuild.is_empty() {
|
||||
env_vars.push(("CBUILD", flags.cbuild.clone()));
|
||||
}
|
||||
if !flags.ldflags.is_empty() {
|
||||
let ldflags = if flags.libc.is_empty() {
|
||||
flags.ldflags.join(" ")
|
||||
} else {
|
||||
format!(
|
||||
"{} -Wl,--dynamic-linker={}",
|
||||
flags.ldflags.join(" "),
|
||||
flags.libc
|
||||
)
|
||||
};
|
||||
env_vars.push(("LDFLAGS", ldflags));
|
||||
}
|
||||
env_vars.push(("CC", cc));
|
||||
|
||||
// Export rootfs for build scripts
|
||||
env_vars.push(("DEPOT_ROOTFS", flags.rootfs.clone()));
|
||||
|
||||
// Add cross-compilation env
|
||||
if let Some(cc_cfg) = cross {
|
||||
env_vars.push(("CXX", cc_cfg.cxx.clone()));
|
||||
env_vars.push(("AR", cc_cfg.ar.clone()));
|
||||
}
|
||||
|
||||
// CARCH support
|
||||
if !flags.carch.is_empty() {
|
||||
env_vars.push(("CARCH", flags.carch.clone()));
|
||||
}
|
||||
let env_vars = crate::builder::standard_build_env(spec, cross, true, export_compiler_flags);
|
||||
|
||||
// Extract prefix from configure flags
|
||||
let prefix = flags
|
||||
@@ -150,7 +107,10 @@ pub fn build(
|
||||
install_cmd.arg("-C").arg(&build_dir);
|
||||
|
||||
let mut install_env = env_vars.clone();
|
||||
install_env.push(("DESTDIR", destdir.to_string_lossy().into_owned()));
|
||||
install_env.push((
|
||||
"DESTDIR".to_string(),
|
||||
destdir.to_string_lossy().into_owned(),
|
||||
));
|
||||
crate::builder::prepare_command(&mut install_cmd, &install_env);
|
||||
|
||||
let status = install_cmd
|
||||
|
||||
+240
-18
@@ -15,8 +15,112 @@ use anyhow::Result;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
pub type EnvVars = Vec<(String, String)>;
|
||||
|
||||
pub fn set_env_var(env_vars: &mut EnvVars, key: &str, value: impl Into<String>) {
|
||||
let value = value.into();
|
||||
if let Some((_, existing)) = env_vars.iter_mut().find(|(k, _)| k == key) {
|
||||
*existing = value;
|
||||
} else {
|
||||
env_vars.push((key.to_string(), value));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn standard_build_env(
|
||||
spec: &PackageSpec,
|
||||
cross: Option<&CrossConfig>,
|
||||
include_compiler_env: bool,
|
||||
export_compiler_flags: bool,
|
||||
) -> EnvVars {
|
||||
let flags = &spec.build.flags;
|
||||
let mut env_vars: EnvVars = Vec::new();
|
||||
let export_compiler_flags = export_compiler_flags && !flags.no_flags;
|
||||
|
||||
if include_compiler_env && export_compiler_flags {
|
||||
if !flags.cflags.is_empty() {
|
||||
set_env_var(&mut env_vars, "CFLAGS", flags.cflags.join(" "));
|
||||
}
|
||||
|
||||
let ldflags = if !flags.ldflags.is_empty() || !flags.libc.is_empty() {
|
||||
if flags.libc.is_empty() {
|
||||
flags.ldflags.join(" ")
|
||||
} else if flags.ldflags.is_empty() {
|
||||
format!("-Wl,--dynamic-linker={}", flags.libc)
|
||||
} else {
|
||||
format!(
|
||||
"{} -Wl,--dynamic-linker={}",
|
||||
flags.ldflags.join(" "),
|
||||
flags.libc
|
||||
)
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
if !ldflags.is_empty() {
|
||||
set_env_var(&mut env_vars, "LDFLAGS", ldflags);
|
||||
}
|
||||
}
|
||||
|
||||
if !flags.chost.is_empty() {
|
||||
set_env_var(&mut env_vars, "CHOST", flags.chost.clone());
|
||||
}
|
||||
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());
|
||||
}
|
||||
if !flags.prefix.is_empty() {
|
||||
set_env_var(&mut env_vars, "PREFIX", flags.prefix.clone());
|
||||
}
|
||||
|
||||
set_env_var(&mut env_vars, "DEPOT_ROOTFS", flags.rootfs.clone());
|
||||
set_env_var(
|
||||
&mut env_vars,
|
||||
"DEPOT_SPECDIR",
|
||||
spec.spec_dir.to_string_lossy().into_owned(),
|
||||
);
|
||||
|
||||
if include_compiler_env {
|
||||
if let Some(cc_cfg) = cross {
|
||||
set_env_var(&mut env_vars, "CC", cc_cfg.cc.clone());
|
||||
set_env_var(&mut env_vars, "CXX", cc_cfg.cxx.clone());
|
||||
set_env_var(&mut env_vars, "AR", cc_cfg.ar.clone());
|
||||
set_env_var(&mut env_vars, "RANLIB", cc_cfg.ranlib.clone());
|
||||
set_env_var(&mut env_vars, "STRIP", cc_cfg.strip.clone());
|
||||
set_env_var(&mut env_vars, "LD", cc_cfg.ld.clone());
|
||||
set_env_var(&mut env_vars, "NM", cc_cfg.nm.clone());
|
||||
set_env_var(&mut env_vars, "CROSS_PREFIX", cc_cfg.prefix.clone());
|
||||
set_env_var(
|
||||
&mut env_vars,
|
||||
"CROSS_COMPILE",
|
||||
format!("{}-", cc_cfg.prefix),
|
||||
);
|
||||
} else {
|
||||
set_env_var(&mut env_vars, "CC", flags.cc.clone());
|
||||
set_env_var(&mut env_vars, "CXX", flags.cxx.clone());
|
||||
set_env_var(&mut env_vars, "AR", flags.ar.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for key in &flags.passthrough_env {
|
||||
let key = key.trim();
|
||||
if key.is_empty() || key.contains('=') {
|
||||
continue;
|
||||
}
|
||||
if env_vars.iter().any(|(existing, _)| existing == key) {
|
||||
continue;
|
||||
}
|
||||
if let Ok(value) = std::env::var(key) {
|
||||
set_env_var(&mut env_vars, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
env_vars
|
||||
}
|
||||
|
||||
/// Prepare a Command with a hermetic environment and some essential variables preserved.
|
||||
pub fn prepare_command(cmd: &mut Command, env_vars: &[(&str, String)]) {
|
||||
pub fn prepare_command(cmd: &mut Command, env_vars: &EnvVars) {
|
||||
cmd.env_clear();
|
||||
// Preserve essential environment variables
|
||||
for var in &["PATH", "LANG", "HOME", "SHELL", "DESTDIR", "DEPOT_ROOTFS"] {
|
||||
@@ -36,6 +140,7 @@ pub fn build(
|
||||
src_dir: &Path,
|
||||
destdir: &Path,
|
||||
cross: Option<&CrossConfig>,
|
||||
export_compiler_flags: bool,
|
||||
) -> Result<()> {
|
||||
if let Some(cc) = cross {
|
||||
println!(
|
||||
@@ -52,18 +157,58 @@ pub fn build(
|
||||
}
|
||||
|
||||
match spec.build.build_type {
|
||||
BuildType::Autotools => autotools::build(spec, src_dir, destdir, cross),
|
||||
BuildType::CMake => cmake::build(spec, src_dir, destdir, cross),
|
||||
BuildType::Meson => meson::build(spec, src_dir, destdir, cross),
|
||||
BuildType::Custom => custom::build(spec, src_dir, destdir, cross),
|
||||
BuildType::Rust => rust::build(spec, src_dir, destdir, cross),
|
||||
BuildType::Bin => bin::build(spec, src_dir, destdir, cross),
|
||||
BuildType::Makefile => makefile::build(spec, src_dir, destdir, cross),
|
||||
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::Custom => custom::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::Makefile => {
|
||||
makefile::build(spec, src_dir, destdir, cross, export_compiler_flags)
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::package::{Build, BuildFlags, BuildType, Dependencies, PackageInfo, PackageSpec};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn mk_spec(cflags: Vec<&str>, ldflags: Vec<&str>) -> PackageSpec {
|
||||
let mut flags = BuildFlags::default();
|
||||
flags.cflags = cflags.into_iter().map(String::from).collect();
|
||||
flags.ldflags = ldflags.into_iter().map(String::from).collect();
|
||||
PackageSpec {
|
||||
package: PackageInfo {
|
||||
name: "env-test".into(),
|
||||
version: "1.0".into(),
|
||||
revision: 1,
|
||||
description: "d".into(),
|
||||
homepage: "h".into(),
|
||||
license: vec!["MIT".into()],
|
||||
},
|
||||
packages: Vec::new(),
|
||||
alternatives: Default::default(),
|
||||
manual_sources: Vec::new(),
|
||||
source: vec![crate::package::Source {
|
||||
url: "https://example.test/src.tar.gz".into(),
|
||||
sha256: "abc".into(),
|
||||
extract_dir: "src".into(),
|
||||
patches: Vec::new(),
|
||||
post_extract: Vec::new(),
|
||||
}],
|
||||
build: Build {
|
||||
build_type: BuildType::Custom,
|
||||
flags,
|
||||
},
|
||||
dependencies: Dependencies::default(),
|
||||
spec_dir: PathBuf::from("."),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prepare_command() {
|
||||
@@ -77,19 +222,19 @@ mod tests {
|
||||
std::env::set_var("DEPOT_ROOTFS", "/my/rootfs");
|
||||
}
|
||||
|
||||
prepare_command(&mut cmd, &[("MYVAR", "myval".to_string())]);
|
||||
prepare_command(&mut cmd, &vec![("MYVAR".to_string(), "myval".to_string())]);
|
||||
|
||||
let envs: std::collections::HashMap<_, _> = cmd.get_envs().collect();
|
||||
assert!(envs.get(std::ffi::OsStr::new("PATH")).is_some());
|
||||
assert!(envs.get(std::ffi::OsStr::new("HOME")).is_some());
|
||||
assert!(envs.get(std::ffi::OsStr::new("FORBIDDEN")).is_none());
|
||||
let envs: HashMap<_, _> = cmd.get_envs().collect();
|
||||
assert!(envs.get(OsStr::new("PATH")).is_some());
|
||||
assert!(envs.get(OsStr::new("HOME")).is_some());
|
||||
assert!(envs.get(OsStr::new("FORBIDDEN")).is_none());
|
||||
assert_eq!(
|
||||
envs.get(std::ffi::OsStr::new("MYVAR")),
|
||||
envs.get(OsStr::new("MYVAR")),
|
||||
Some(&Some(std::ffi::OsString::from("myval").as_os_str()))
|
||||
);
|
||||
// DEPOT_ROOTFS should be preserved from the parent environment
|
||||
assert_eq!(
|
||||
envs.get(std::ffi::OsStr::new("DEPOT_ROOTFS")),
|
||||
envs.get(OsStr::new("DEPOT_ROOTFS")),
|
||||
Some(&Some(std::ffi::OsString::from("/my/rootfs").as_os_str()))
|
||||
);
|
||||
}
|
||||
@@ -100,11 +245,88 @@ mod tests {
|
||||
unsafe {
|
||||
std::env::set_var("DESTDIR", "/tmp/dest");
|
||||
}
|
||||
prepare_command(&mut cmd, &[]);
|
||||
let envs: std::collections::HashMap<_, _> = cmd.get_envs().collect();
|
||||
prepare_command(&mut cmd, &Vec::new());
|
||||
let envs: HashMap<_, _> = cmd.get_envs().collect();
|
||||
assert_eq!(
|
||||
envs.get(std::ffi::OsStr::new("DESTDIR")),
|
||||
envs.get(OsStr::new("DESTDIR")),
|
||||
Some(&Some(std::ffi::OsString::from("/tmp/dest").as_os_str()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_standard_build_env_respects_export_compiler_flags_toggle() {
|
||||
let spec = mk_spec(vec!["-O2"], vec!["-Wl,--as-needed"]);
|
||||
|
||||
let enabled = standard_build_env(&spec, None, true, true);
|
||||
assert!(
|
||||
enabled.iter().any(|(k, v)| k == "CFLAGS" && v == "-O2"),
|
||||
"expected CFLAGS to be exported when enabled"
|
||||
);
|
||||
assert!(
|
||||
enabled
|
||||
.iter()
|
||||
.any(|(k, v)| k == "LDFLAGS" && v == "-Wl,--as-needed"),
|
||||
"expected LDFLAGS to be exported when enabled"
|
||||
);
|
||||
|
||||
let disabled = standard_build_env(&spec, None, true, false);
|
||||
assert!(
|
||||
!disabled.iter().any(|(k, _)| k == "CFLAGS"),
|
||||
"expected CFLAGS to be omitted when disabled"
|
||||
);
|
||||
assert!(
|
||||
!disabled.iter().any(|(k, _)| k == "CXXFLAGS"),
|
||||
"expected CXXFLAGS to be omitted when disabled"
|
||||
);
|
||||
assert!(
|
||||
!disabled.iter().any(|(k, _)| k == "LDFLAGS"),
|
||||
"expected LDFLAGS to be omitted when disabled"
|
||||
);
|
||||
|
||||
let mut disabled_by_spec = spec.clone();
|
||||
disabled_by_spec.build.flags.no_flags = true;
|
||||
let disabled_env = standard_build_env(&disabled_by_spec, None, true, true);
|
||||
assert!(
|
||||
!disabled_env.iter().any(|(k, _)| k == "CFLAGS"),
|
||||
"expected CFLAGS to be omitted when no_flags is set in spec"
|
||||
);
|
||||
assert!(
|
||||
!disabled_env.iter().any(|(k, _)| k == "LDFLAGS"),
|
||||
"expected LDFLAGS to be omitted when no_flags is set in spec"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_standard_build_env_exports_passthrough_env() {
|
||||
let mut spec = mk_spec(Vec::new(), Vec::new());
|
||||
spec.build.flags.passthrough_env = vec!["RUSTFLAGS".into()];
|
||||
|
||||
unsafe {
|
||||
std::env::set_var("RUSTFLAGS", "-C target-cpu=native");
|
||||
}
|
||||
|
||||
let env = standard_build_env(&spec, None, false, true);
|
||||
assert!(
|
||||
env.iter()
|
||||
.any(|(k, v)| k == "RUSTFLAGS" && v == "-C target-cpu=native"),
|
||||
"expected RUSTFLAGS to be copied from parent environment"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_standard_build_env_passthrough_does_not_override_default_vars() {
|
||||
let mut spec = mk_spec(Vec::new(), Vec::new());
|
||||
spec.build.flags.cc = "spec-cc".to_string();
|
||||
spec.build.flags.passthrough_env = vec!["CC".into()];
|
||||
|
||||
unsafe {
|
||||
std::env::set_var("CC", "host-cc");
|
||||
}
|
||||
|
||||
let env = standard_build_env(&spec, None, true, true);
|
||||
assert!(
|
||||
env.iter().any(|(k, v)| k == "CC" && v == "spec-cc"),
|
||||
"expected default CC to take precedence over passthrough CC"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+8
-14
@@ -13,6 +13,7 @@ pub fn build(
|
||||
src_dir: &Path,
|
||||
destdir: &Path,
|
||||
cross: Option<&CrossConfig>,
|
||||
export_compiler_flags: bool,
|
||||
) -> Result<()> {
|
||||
let flags = &spec.build.flags;
|
||||
|
||||
@@ -45,16 +46,12 @@ pub fn build(
|
||||
};
|
||||
|
||||
// Build environment
|
||||
let mut env_vars: Vec<(&str, String)> = vec![];
|
||||
let mut env_vars =
|
||||
crate::builder::standard_build_env(spec, cross, false, export_compiler_flags);
|
||||
|
||||
// RUSTFLAGS
|
||||
if !flags.rustflags.is_empty() {
|
||||
env_vars.push(("RUSTFLAGS", flags.rustflags.join(" ")));
|
||||
}
|
||||
|
||||
// CARCH support
|
||||
if !flags.carch.is_empty() {
|
||||
env_vars.push(("CARCH", flags.carch.clone()));
|
||||
crate::builder::set_env_var(&mut env_vars, "RUSTFLAGS", flags.rustflags.join(" "));
|
||||
}
|
||||
|
||||
// If cross-compiling, set linker via CARGO_TARGET_*_LINKER
|
||||
@@ -66,19 +63,16 @@ pub fn build(
|
||||
.to_uppercase()
|
||||
.replace('-', "_");
|
||||
let linker_var = format!("CARGO_TARGET_{}_LINKER", target_env);
|
||||
env_vars.push((linker_var.leak(), cc_cfg.cc.clone()));
|
||||
env_vars.push(("CC".to_string().leak(), cc_cfg.cc.clone()));
|
||||
env_vars.push(("AR".to_string().leak(), cc_cfg.ar.clone()));
|
||||
crate::builder::set_env_var(&mut env_vars, &linker_var, cc_cfg.cc.clone());
|
||||
crate::builder::set_env_var(&mut env_vars, "CC", cc_cfg.cc.clone());
|
||||
crate::builder::set_env_var(&mut env_vars, "AR", cc_cfg.ar.clone());
|
||||
}
|
||||
|
||||
// Set default rustup toolchain if not already set
|
||||
if std::env::var("RUSTUP_TOOLCHAIN").is_err() {
|
||||
env_vars.push(("RUSTUP_TOOLCHAIN", "stable".to_string()));
|
||||
crate::builder::set_env_var(&mut env_vars, "RUSTUP_TOOLCHAIN", "stable");
|
||||
}
|
||||
|
||||
// Export DEPOT_ROOTFS so build scripts can detect the target rootfs
|
||||
env_vars.push(("DEPOT_ROOTFS", flags.rootfs.clone()));
|
||||
|
||||
// Run cargo build
|
||||
println!(
|
||||
"Running cargo build ({})...",
|
||||
|
||||
Reference in New Issue
Block a user