feat: improve depot UX and packaging workflow
- add shared terminal UI helpers for colored info/warn/success logs and yes/no prompts - add Python build backend (PEP 517/setup.py wheel build + install) and expose python in interactive spec creation - expand spec/build flags (post_configure, make phase targets/dirs, configure_file, no_strip, no_compress_man) - support split-output staging with internal .depot output dirs, shell helpers (haul/subdestdir), and per-output deps/provides overrides - improve staging with ELF auto-strip and zstd manpage compression (with opt-out flags) - enforce runtime deps before install and support legacy lifecycle hook script names - improve manual source handling with files/urls lists and stricter validation
This commit is contained in:
+536
-79
@@ -6,7 +6,7 @@ use crate::package::PackageSpec;
|
||||
use crate::source::hooks;
|
||||
use anyhow::{Context, Result};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
pub fn build(
|
||||
@@ -17,6 +17,7 @@ pub fn build(
|
||||
export_compiler_flags: bool,
|
||||
) -> Result<()> {
|
||||
let flags = &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(spec, src_dir)?;
|
||||
|
||||
@@ -45,22 +46,18 @@ pub fn build(
|
||||
let build_dir = if let Some(dir) = &flags.build_dir {
|
||||
let bdir = actual_src.join(dir);
|
||||
fs::create_dir_all(&bdir)?;
|
||||
println!(" Build directory: {}", bdir.display());
|
||||
crate::log_info!(" Build directory: {}", bdir.display());
|
||||
bdir
|
||||
} else {
|
||||
actual_src.clone()
|
||||
};
|
||||
|
||||
if !state.is_done(BuildStep::Configured) {
|
||||
println!("Running configure...");
|
||||
let configure_path = if flags.build_dir.is_some() {
|
||||
"../configure"
|
||||
} else {
|
||||
"./configure"
|
||||
};
|
||||
println!(" Configure path: {}", configure_path);
|
||||
crate::log_info!("Running configure...");
|
||||
let configure_path = resolve_configure_path(spec, &actual_src);
|
||||
crate::log_info!(" Configure path: {}", configure_path.display());
|
||||
|
||||
let mut configure_cmd = Command::new(configure_path);
|
||||
let mut configure_cmd = Command::new(&configure_path);
|
||||
configure_cmd.current_dir(&build_dir);
|
||||
|
||||
crate::builder::prepare_command(&mut configure_cmd, &env_vars);
|
||||
@@ -69,15 +66,11 @@ pub fn build(
|
||||
|
||||
// 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 help_text = configure_help_text(&configure_path, &build_dir, &env_vars);
|
||||
let supports_host =
|
||||
configure_supports_option(help_text.as_deref(), "--host", &flags.configure_file);
|
||||
let supports_build =
|
||||
configure_supports_option(help_text.as_deref(), "--build", &flags.configure_file);
|
||||
|
||||
let requested_host = if let Some(cc_cfg) = cross {
|
||||
Some(cc_cfg.host_triple().to_string())
|
||||
@@ -99,7 +92,7 @@ pub fn build(
|
||||
if supports_host {
|
||||
configure_cmd.arg(format!("--host={}", host));
|
||||
} else {
|
||||
println!(" configure does not support --host; skipping {}", host);
|
||||
crate::log_info!(" configure does not support --host; skipping {}", host);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,12 +100,13 @@ pub fn build(
|
||||
if supports_build {
|
||||
configure_cmd.arg(format!("--build={}", build));
|
||||
} else {
|
||||
println!(" configure does not support --build; skipping {}", build);
|
||||
crate::log_info!(" configure does not support --build; skipping {}", build);
|
||||
}
|
||||
}
|
||||
|
||||
for arg in &flags.configure {
|
||||
configure_cmd.arg(arg);
|
||||
let expanded = expand_configure_arg(spec, arg, &env_vars);
|
||||
configure_cmd.arg(expanded);
|
||||
}
|
||||
|
||||
let status = configure_cmd
|
||||
@@ -122,64 +116,155 @@ pub fn build(
|
||||
if !status.success() {
|
||||
anyhow::bail!("configure failed with status: {}", status);
|
||||
}
|
||||
|
||||
// Run post-configure hooks (after configure, before make)
|
||||
hooks::run_post_configure_commands(spec, &actual_src, destdir)?;
|
||||
state.mark_done(BuildStep::Configured)?;
|
||||
} else {
|
||||
println!("Skipping configure (already done)");
|
||||
crate::log_info!("Skipping configure (already done)");
|
||||
}
|
||||
|
||||
if !state.is_done(BuildStep::PostCompileDone) {
|
||||
// Run make
|
||||
println!("Running make...");
|
||||
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")?;
|
||||
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 {
|
||||
crate::log_info!("Running {} in {}...", make_exec, make_dir.display());
|
||||
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_command(&mut make_cmd, &env_vars);
|
||||
crate::builder::prepare_command(&mut make_cmd, &env_vars);
|
||||
|
||||
let status = make_cmd
|
||||
.status()
|
||||
.with_context(|| format!("Failed to run make in {}", build_dir.display()))?;
|
||||
let status = make_cmd.status().with_context(|| {
|
||||
format!("Failed to run {} in {}", make_exec, make_dir.display())
|
||||
})?;
|
||||
|
||||
if !status.success() {
|
||||
anyhow::bail!("make failed with status: {}", status);
|
||||
if !status.success() {
|
||||
anyhow::bail!(
|
||||
"{} failed with status: {} (dir: {})",
|
||||
make_exec,
|
||||
status,
|
||||
make_dir.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
if flags.skip_tests {
|
||||
crate::log_info!("Skipping tests: disabled by build.flags.skip_tests");
|
||||
} else {
|
||||
println!("Skipping tests: no 'check' or 'test' target in Makefile");
|
||||
let test_dirs = resolve_make_dirs(
|
||||
&build_dir,
|
||||
&flags.make_test_dirs,
|
||||
"build.flags.make_test_dirs",
|
||||
)?;
|
||||
let mut ran_any_tests = false;
|
||||
let configured_test_targets =
|
||||
phase_targets(&flags.make_test_target, &flags.make_test_targets, None);
|
||||
for test_dir in test_dirs {
|
||||
let test_targets = if !configured_test_targets.is_empty() {
|
||||
configured_test_targets.clone()
|
||||
} else if make_exec_supports_make_assignments(make_exec) {
|
||||
maybe_find_autotools_test_target(&test_dir, false)?
|
||||
.map(|t| vec![t.to_string()])
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
if !test_targets.is_empty() {
|
||||
crate::log_info!("Running {} in {}...", make_exec, test_dir.display());
|
||||
let mut test_cmd = Command::new(make_exec);
|
||||
test_cmd.current_dir(&test_dir);
|
||||
add_make_variable_overrides_if_supported(
|
||||
&mut test_cmd,
|
||||
make_exec,
|
||||
&flags.make_test_vars,
|
||||
"test",
|
||||
)?;
|
||||
for test_target in &test_targets {
|
||||
test_cmd.arg(test_target);
|
||||
}
|
||||
crate::builder::prepare_command(&mut test_cmd, &env_vars);
|
||||
|
||||
let test_targets_display = test_targets.join(" ");
|
||||
let status = test_cmd.status().with_context(|| {
|
||||
format!(
|
||||
"Failed to run {} {} in {}",
|
||||
make_exec,
|
||||
test_targets_display,
|
||||
test_dir.display()
|
||||
)
|
||||
})?;
|
||||
if !status.success() {
|
||||
anyhow::bail!(
|
||||
"{} {} failed with status: {} (dir: {})",
|
||||
make_exec,
|
||||
test_targets_display,
|
||||
status,
|
||||
test_dir.display()
|
||||
);
|
||||
}
|
||||
ran_any_tests = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !ran_any_tests {
|
||||
if flags.make_test_dirs.is_empty() {
|
||||
if !configured_test_targets.is_empty() {
|
||||
crate::log_info!("Skipping tests: no test directories to run");
|
||||
} else if make_exec_supports_make_assignments(make_exec) {
|
||||
crate::log_info!("Skipping tests: no 'check' or 'test' target in Makefile");
|
||||
} else {
|
||||
crate::log_info!(
|
||||
"Skipping tests: set build.flags.make_test_target when using build.flags.make_exec='{}'",
|
||||
make_exec
|
||||
);
|
||||
}
|
||||
} else if !configured_test_targets.is_empty() {
|
||||
crate::log_info!(
|
||||
"Skipping tests: no test targets ran in build.flags.make_test_dirs"
|
||||
);
|
||||
} else if make_exec_supports_make_assignments(make_exec) {
|
||||
crate::log_info!(
|
||||
"Skipping tests: no 'check' or 'test' target in build.flags.make_test_dirs"
|
||||
);
|
||||
} else {
|
||||
crate::log_info!(
|
||||
"Skipping tests: set build.flags.make_test_target when using build.flags.make_exec='{}'",
|
||||
make_exec
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run post-compile hooks (after make, before make install)
|
||||
hooks::run_post_compile_commands(spec, &actual_src, destdir)?;
|
||||
state.mark_done(BuildStep::PostCompileDone)?;
|
||||
} else {
|
||||
println!("Skipping make and post-compile hooks (already done)");
|
||||
crate::log_info!("Skipping make and post-compile hooks (already done)");
|
||||
}
|
||||
|
||||
if !state.is_done(BuildStep::PostInstallDone) {
|
||||
// Run make install with fakeroot if not root
|
||||
println!(
|
||||
"Running make install{}...",
|
||||
crate::log_info!(
|
||||
"Running {} {}{}...",
|
||||
make_exec,
|
||||
phase_targets(
|
||||
&flags.make_install_target,
|
||||
&flags.make_install_targets,
|
||||
Some("install")
|
||||
)
|
||||
.join(" "),
|
||||
if fakeroot::is_root() {
|
||||
""
|
||||
} else {
|
||||
@@ -187,34 +272,67 @@ 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 install_dirs = resolve_make_dirs(
|
||||
&build_dir,
|
||||
&flags.make_install_dirs,
|
||||
"build.flags.make_install_dirs",
|
||||
)?;
|
||||
let install_targets = phase_targets(
|
||||
&flags.make_install_target,
|
||||
&flags.make_install_targets,
|
||||
Some("install"),
|
||||
);
|
||||
for install_dir in install_dirs {
|
||||
let mut install_cmd = fakeroot::wrap_install_command(make_exec, destdir);
|
||||
install_cmd.current_dir(&install_dir);
|
||||
if make_exec_supports_make_assignments(make_exec)
|
||||
&& !has_make_variable_override(&flags.make_install_vars, "DESTDIR")
|
||||
{
|
||||
install_cmd.arg(format!("DESTDIR={}", destdir.to_string_lossy()));
|
||||
}
|
||||
add_make_variable_overrides_if_supported(
|
||||
&mut install_cmd,
|
||||
make_exec,
|
||||
&flags.make_install_vars,
|
||||
"install",
|
||||
)?;
|
||||
for install_target in &install_targets {
|
||||
install_cmd.arg(install_target);
|
||||
}
|
||||
|
||||
let mut install_env = env_vars.clone();
|
||||
install_env.push((
|
||||
"DESTDIR".to_string(),
|
||||
destdir.to_string_lossy().into_owned(),
|
||||
));
|
||||
crate::builder::prepare_command(&mut install_cmd, &install_env);
|
||||
let mut install_env = env_vars.clone();
|
||||
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
|
||||
.status()
|
||||
.with_context(|| format!("Failed to run make install for {}", spec.package.name))?;
|
||||
let status = install_cmd.status().with_context(|| {
|
||||
format!(
|
||||
"Failed to run {} {} for {} in {}",
|
||||
make_exec,
|
||||
install_targets.join(" "),
|
||||
spec.package.name,
|
||||
install_dir.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
if !status.success() {
|
||||
anyhow::bail!("make install failed with status: {}", status);
|
||||
if !status.success() {
|
||||
anyhow::bail!(
|
||||
"{} {} failed with status: {} (dir: {})",
|
||||
make_exec,
|
||||
install_targets.join(" "),
|
||||
status,
|
||||
install_dir.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Run post-install hooks (after make install)
|
||||
hooks::run_post_install_commands(spec, &actual_src, destdir)?;
|
||||
state.mark_done(BuildStep::PostInstallDone)?;
|
||||
} else {
|
||||
println!("Skipping make install and post-install hooks (already done)");
|
||||
crate::log_info!("Skipping make install and post-install hooks (already done)");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -248,8 +366,23 @@ fn resolve_actual_src(spec: &PackageSpec, src_dir: &Path) -> Result<std::path::P
|
||||
Ok(actual_src)
|
||||
}
|
||||
|
||||
fn resolve_configure_path(spec: &PackageSpec, actual_src: &Path) -> PathBuf {
|
||||
let configured = spec.expand_vars(&spec.build.flags.configure_file);
|
||||
let trimmed = configured.trim();
|
||||
if trimmed.is_empty() {
|
||||
return actual_src.join("configure");
|
||||
}
|
||||
|
||||
let path = Path::new(trimmed);
|
||||
if path.is_absolute() {
|
||||
path.to_path_buf()
|
||||
} else {
|
||||
actual_src.join(path)
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_help_text(
|
||||
configure_path: &str,
|
||||
configure_path: &Path,
|
||||
build_dir: &Path,
|
||||
env_vars: &crate::builder::EnvVars,
|
||||
) -> Option<String> {
|
||||
@@ -273,6 +406,12 @@ fn configure_help_supports_option(help_text: &str, option: &str) -> bool {
|
||||
help_text.contains(&with_eq) || help_text.contains(&with_space) || help_text.contains(option)
|
||||
}
|
||||
|
||||
fn configure_supports_option(help_text: Option<&str>, option: &str, configure_file: &str) -> bool {
|
||||
help_text
|
||||
.map(|text| configure_help_supports_option(text, option))
|
||||
.unwrap_or(configure_file.trim().is_empty())
|
||||
}
|
||||
|
||||
fn find_autotools_test_target(build_dir: &Path) -> Result<Option<&'static str>> {
|
||||
for target in ["check", "test"] {
|
||||
if makefile_has_target(build_dir, target)? {
|
||||
@@ -292,6 +431,48 @@ fn maybe_find_autotools_test_target(
|
||||
find_autotools_test_target(build_dir)
|
||||
}
|
||||
|
||||
fn resolve_make_dirs(build_dir: &Path, dirs: &[String], field_name: &str) -> Result<Vec<PathBuf>> {
|
||||
if dirs.is_empty() {
|
||||
return Ok(vec![build_dir.to_path_buf()]);
|
||||
}
|
||||
|
||||
let canonical_build = fs::canonicalize(build_dir)
|
||||
.with_context(|| format!("Failed to resolve build directory {}", build_dir.display()))?;
|
||||
let mut out = Vec::with_capacity(dirs.len());
|
||||
for raw in dirs {
|
||||
let trimmed = raw.trim();
|
||||
if trimmed.is_empty() {
|
||||
anyhow::bail!("{} contains an empty directory entry", field_name);
|
||||
}
|
||||
let rel = Path::new(trimmed);
|
||||
if rel.is_absolute() {
|
||||
anyhow::bail!("{} entry '{}' must be a relative path", field_name, trimmed);
|
||||
}
|
||||
|
||||
let candidate = build_dir.join(rel);
|
||||
let canonical_candidate = fs::canonicalize(&candidate).with_context(|| {
|
||||
format!(
|
||||
"{} entry '{}' does not exist in {}",
|
||||
field_name,
|
||||
trimmed,
|
||||
build_dir.display()
|
||||
)
|
||||
})?;
|
||||
if !canonical_candidate.starts_with(&canonical_build) {
|
||||
anyhow::bail!(
|
||||
"{} entry '{}' resolves outside build directory",
|
||||
field_name,
|
||||
trimmed
|
||||
);
|
||||
}
|
||||
if !canonical_candidate.is_dir() {
|
||||
anyhow::bail!("{} entry '{}' is not a directory", field_name, trimmed);
|
||||
}
|
||||
out.push(canonical_candidate);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn makefile_has_target(build_dir: &Path, target: &str) -> Result<bool> {
|
||||
for name in ["GNUmakefile", "Makefile", "makefile"] {
|
||||
let path = build_dir.join(name);
|
||||
@@ -339,6 +520,75 @@ fn makefile_content_has_target(content: &str, target: &str) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn nonempty_trimmed(value: &str) -> Option<&str> {
|
||||
let trimmed = value.trim();
|
||||
(!trimmed.is_empty()).then_some(trimmed)
|
||||
}
|
||||
|
||||
fn resolve_make_exec(configured: &str) -> &str {
|
||||
nonempty_trimmed(configured).unwrap_or("make")
|
||||
}
|
||||
|
||||
fn phase_targets(single: &str, many: &[String], default: Option<&str>) -> Vec<String> {
|
||||
let mut targets = Vec::new();
|
||||
if let Some(target) = nonempty_trimmed(single) {
|
||||
targets.push(target.to_string());
|
||||
}
|
||||
for target in many {
|
||||
if let Some(target) = nonempty_trimmed(target) {
|
||||
targets.push(target.to_string());
|
||||
}
|
||||
}
|
||||
if targets.is_empty()
|
||||
&& let Some(default_target) = default
|
||||
{
|
||||
targets.push(default_target.to_string());
|
||||
}
|
||||
targets
|
||||
}
|
||||
|
||||
fn make_exec_supports_make_assignments(make_exec: &str) -> bool {
|
||||
let Some(tool) = Path::new(make_exec)
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.map(|name| name.to_ascii_lowercase())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
tool == "make"
|
||||
|| tool == "gmake"
|
||||
|| tool == "bmake"
|
||||
|| tool == "nmake"
|
||||
|| tool.ends_with("-make")
|
||||
|| tool.ends_with("make.exe")
|
||||
}
|
||||
|
||||
fn add_make_variable_overrides_if_supported(
|
||||
cmd: &mut Command,
|
||||
make_exec: &str,
|
||||
vars: &[String],
|
||||
phase: &str,
|
||||
) -> Result<()> {
|
||||
if make_exec_supports_make_assignments(make_exec) {
|
||||
return add_make_variable_overrides(cmd, vars, phase);
|
||||
}
|
||||
if !vars.is_empty() {
|
||||
let field_name = match phase {
|
||||
"build" => "build.flags.make_vars",
|
||||
"test" => "build.flags.make_test_vars",
|
||||
"install" => "build.flags.make_install_vars",
|
||||
_ => "build.flags.make_*_vars",
|
||||
};
|
||||
anyhow::bail!(
|
||||
"{} is only supported with make-like executables; build.flags.make_exec='{}'",
|
||||
field_name,
|
||||
make_exec
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_make_variable_overrides(cmd: &mut Command, vars: &[String], phase: &str) -> Result<()> {
|
||||
for raw in vars {
|
||||
let var = raw.trim();
|
||||
@@ -396,7 +646,7 @@ fn expand_shell_commands(input: &str, cc: &str) -> Result<String> {
|
||||
}
|
||||
_ => {
|
||||
// Silently skip failed commands (e.g., gcc doesn't support -print-resource-dir)
|
||||
eprintln!("Warning: shell command '{}' failed, skipping", cmd);
|
||||
crate::log_warn!("Shell command '{}' failed, skipping", cmd);
|
||||
String::new()
|
||||
}
|
||||
};
|
||||
@@ -410,6 +660,29 @@ fn expand_shell_commands(input: &str, cc: &str) -> Result<String> {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn expand_env_vars(input: &str) -> String {
|
||||
let mut result = input.to_string();
|
||||
for (key, value) in std::env::vars() {
|
||||
result = result.replace(&format!("${}", key), &value);
|
||||
result = result.replace(&format!("${{{}}}", key), &value);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
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);
|
||||
result = result.replace(&format!("${{{}}}", k), v);
|
||||
}
|
||||
expand_env_vars(&result)
|
||||
}
|
||||
|
||||
fn expand_configure_arg(spec: &PackageSpec, arg: &str, envs: &[(String, String)]) -> String {
|
||||
let with_spec_vars = spec.expand_vars(arg);
|
||||
expand_with_envs(&with_spec_vars, envs)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -433,6 +706,48 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expand_with_envs_prefers_provided_envs() {
|
||||
let envs = vec![
|
||||
("CARCH".to_string(), "x86_64".to_string()),
|
||||
("CHOST".to_string(), "x86_64-sfg-linux-gnu".to_string()),
|
||||
];
|
||||
let out = expand_with_envs("--with-gcc-arch=$CARCH --host=${CHOST}", &envs);
|
||||
assert!(out.contains("--with-gcc-arch=x86_64"));
|
||||
assert!(out.contains("--host=x86_64-sfg-linux-gnu"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expand_configure_arg_expands_spec_and_env_vars() {
|
||||
let spec = PackageSpec {
|
||||
package: PackageInfo {
|
||||
name: "foo".into(),
|
||||
version: "1.2.3".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::default(),
|
||||
},
|
||||
dependencies: Dependencies::default(),
|
||||
package_alternatives: Default::default(),
|
||||
package_dependencies: Default::default(),
|
||||
spec_dir: PathBuf::from("."),
|
||||
};
|
||||
|
||||
let envs = vec![("CARCH".to_string(), "aarch64".to_string())];
|
||||
let expanded =
|
||||
expand_configure_arg(&spec, "--program-prefix=$name-$version-$CARCH-", &envs);
|
||||
assert_eq!(expanded, "--program-prefix=foo-1.2.3-aarch64-");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_num_cpus_at_least_one() {
|
||||
let n = num_cpus();
|
||||
@@ -447,6 +762,16 @@ mod tests {
|
||||
assert!(!configure_help_supports_option(help, "--target"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_configure_supports_option_defaults_by_configure_file_usage() {
|
||||
assert!(configure_supports_option(None, "--host", ""));
|
||||
assert!(!configure_supports_option(
|
||||
None,
|
||||
"--host",
|
||||
"build-aux/Configure"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_makefile_content_has_target_detects_check_and_test() {
|
||||
let content = r#"
|
||||
@@ -486,6 +811,31 @@ foo: bar
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_make_dirs_defaults_to_build_dir() -> Result<()> {
|
||||
let tmp = tempdir().unwrap();
|
||||
let dirs = resolve_make_dirs(tmp.path(), &[], "build.flags.make_dirs")?;
|
||||
assert_eq!(dirs, vec![tmp.path().to_path_buf()]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_make_dirs_resolves_multiple_relative_dirs() -> Result<()> {
|
||||
let tmp = tempdir().unwrap();
|
||||
std::fs::create_dir_all(tmp.path().join("lib"))?;
|
||||
std::fs::create_dir_all(tmp.path().join("libelf"))?;
|
||||
let dirs = resolve_make_dirs(
|
||||
tmp.path(),
|
||||
&["lib".to_string(), "libelf".to_string()],
|
||||
"build.flags.make_dirs",
|
||||
)?;
|
||||
assert_eq!(
|
||||
dirs,
|
||||
vec![tmp.path().join("lib"), tmp.path().join("libelf")]
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_make_variable_overrides_accepts_valid_assignments() -> Result<()> {
|
||||
let mut cmd = Command::new("make");
|
||||
@@ -530,6 +880,49 @@ foo: bar
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_make_exec_defaults_and_trims() {
|
||||
assert_eq!(resolve_make_exec(""), "make");
|
||||
assert_eq!(resolve_make_exec(" "), "make");
|
||||
assert_eq!(resolve_make_exec(" ninja "), "ninja");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_make_exec_supports_make_assignments_detects_make_variants() {
|
||||
assert!(make_exec_supports_make_assignments("make"));
|
||||
assert!(make_exec_supports_make_assignments("/usr/bin/gmake"));
|
||||
assert!(!make_exec_supports_make_assignments("ninja"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_phase_targets_merges_singular_plural_and_default() {
|
||||
assert_eq!(
|
||||
phase_targets("bootstrap", &["stage1".into(), "stage2".into()], None),
|
||||
vec![
|
||||
"bootstrap".to_string(),
|
||||
"stage1".to_string(),
|
||||
"stage2".to_string()
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
phase_targets("", &[], Some("install")),
|
||||
vec!["install".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_make_variable_overrides_if_supported_rejects_ninja_vars() {
|
||||
let mut cmd = Command::new("ninja");
|
||||
let err = add_make_variable_overrides_if_supported(
|
||||
&mut cmd,
|
||||
"ninja",
|
||||
&["V=1".to_string()],
|
||||
"build",
|
||||
)
|
||||
.expect_err("ninja should reject make variable override syntax");
|
||||
assert!(err.to_string().contains("build.flags.make_vars"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_actual_src_expands_source_subdir_vars() {
|
||||
let tmp = tempdir().unwrap();
|
||||
@@ -558,10 +951,74 @@ foo: bar
|
||||
},
|
||||
},
|
||||
dependencies: Dependencies::default(),
|
||||
package_alternatives: Default::default(),
|
||||
package_dependencies: Default::default(),
|
||||
spec_dir: PathBuf::from("."),
|
||||
};
|
||||
|
||||
let resolved = resolve_actual_src(&spec, &src_root).unwrap();
|
||||
assert_eq!(resolved, expanded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_configure_path_defaults_to_source_configure() {
|
||||
let spec = PackageSpec {
|
||||
package: PackageInfo {
|
||||
name: "foo".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::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 actual_src = PathBuf::from("/tmp/src");
|
||||
let configure = resolve_configure_path(&spec, &actual_src);
|
||||
assert_eq!(configure, actual_src.join("configure"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_configure_path_uses_configure_file_and_expands_vars() {
|
||||
let mut flags = BuildFlags::default();
|
||||
flags.configure_file = "build-aux/$name-configure".into();
|
||||
let spec = PackageSpec {
|
||||
package: PackageInfo {
|
||||
name: "foo".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::new(),
|
||||
build: Build {
|
||||
build_type: BuildType::Autotools,
|
||||
flags,
|
||||
},
|
||||
dependencies: Dependencies::default(),
|
||||
package_alternatives: Default::default(),
|
||||
package_dependencies: Default::default(),
|
||||
spec_dir: PathBuf::from("."),
|
||||
};
|
||||
|
||||
let actual_src = PathBuf::from("/tmp/src");
|
||||
let configure = resolve_configure_path(&spec, &actual_src);
|
||||
assert_eq!(configure, actual_src.join("build-aux/foo-configure"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user