feat: Implement build state tracking and interactive package specification creation
- Added StateTracker to manage build steps and allow resuming interrupted builds. - Updated post_extract function to utilize StateTracker for patch and command execution. - Introduced makefile.rs to handle building and installing packages via Makefile. - Created interactive.rs for an interactive package specification creator with SHA256 computation for source URLs. - Enhanced checksum verification to support multiple algorithms (SHA256, SHA512, MD5). - Added example configuration files in contrib for system-wide and user-level Depot configurations. - Updated tests to cover new functionality and ensure correctness of state tracking and interactive creation.
This commit is contained in:
+142
-73
@@ -57,6 +57,14 @@ pub fn build(
|
||||
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()));
|
||||
@@ -80,89 +88,123 @@ pub fn build(
|
||||
env_vars.push(("LDFLAGS", ldflags));
|
||||
}
|
||||
|
||||
use crate::builder::state::{BuildStep, StateTracker};
|
||||
let mut state = StateTracker::new(&actual_src)?;
|
||||
|
||||
// Run configure
|
||||
println!("Running configure...");
|
||||
let mut configure_cmd = Command::new("./configure");
|
||||
configure_cmd.current_dir(&actual_src);
|
||||
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());
|
||||
bdir
|
||||
} else {
|
||||
actual_src.clone()
|
||||
};
|
||||
|
||||
crate::builder::prepare_command(&mut configure_cmd, &env_vars);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
// 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() {
|
||||
configure_cmd.arg(format!("--build={}", build));
|
||||
}
|
||||
}
|
||||
|
||||
for arg in &flags.configure {
|
||||
configure_cmd.arg(arg);
|
||||
}
|
||||
|
||||
let status = configure_cmd
|
||||
.status()
|
||||
.with_context(|| format!("Failed to run configure in {}", actual_src.display()))?;
|
||||
|
||||
if !status.success() {
|
||||
anyhow::bail!("configure failed with status: {}", status);
|
||||
}
|
||||
|
||||
// Run make
|
||||
println!("Running make...");
|
||||
let mut make_cmd = Command::new("make");
|
||||
make_cmd.current_dir(&actual_src);
|
||||
make_cmd.arg("-j").arg(num_cpus().to_string());
|
||||
|
||||
crate::builder::prepare_command(&mut make_cmd, &env_vars);
|
||||
|
||||
let status = make_cmd
|
||||
.status()
|
||||
.with_context(|| format!("Failed to run make in {}", actual_src.display()))?;
|
||||
|
||||
if !status.success() {
|
||||
anyhow::bail!("make failed with status: {}", status);
|
||||
}
|
||||
|
||||
// Run post-compile hooks (after make, before make install)
|
||||
hooks::run_post_compile_commands(spec, &actual_src, destdir)?;
|
||||
|
||||
// Run make install with fakeroot if not root
|
||||
println!(
|
||||
"Running make install{}...",
|
||||
if fakeroot::is_root() {
|
||||
""
|
||||
if !state.is_done(BuildStep::Configured) {
|
||||
println!("Running configure...");
|
||||
let configure_path = if flags.build_dir.is_some() {
|
||||
"../configure"
|
||||
} else {
|
||||
" (with internal fakeroot for build)"
|
||||
"./configure"
|
||||
};
|
||||
println!(" Configure path: {}", 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);
|
||||
|
||||
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));
|
||||
}
|
||||
);
|
||||
|
||||
let mut install_cmd = fakeroot::wrap_install_command("make", destdir);
|
||||
install_cmd.current_dir(&actual_src);
|
||||
install_cmd.arg("install");
|
||||
// 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() {
|
||||
configure_cmd.arg(format!("--build={}", build));
|
||||
}
|
||||
}
|
||||
|
||||
let mut install_env = env_vars.clone();
|
||||
install_env.push(("DESTDIR", destdir.to_string_lossy().into_owned()));
|
||||
crate::builder::prepare_command(&mut install_cmd, &install_env);
|
||||
for arg in &flags.configure {
|
||||
configure_cmd.arg(arg);
|
||||
}
|
||||
|
||||
let status = install_cmd
|
||||
.status()
|
||||
.with_context(|| format!("Failed to run make install for {}", spec.package.name))?;
|
||||
let status = configure_cmd
|
||||
.status()
|
||||
.with_context(|| format!("Failed to run configure in {}", build_dir.display()))?;
|
||||
|
||||
if !status.success() {
|
||||
anyhow::bail!("make install failed with status: {}", status);
|
||||
if !status.success() {
|
||||
anyhow::bail!("configure failed with status: {}", status);
|
||||
}
|
||||
state.mark_done(BuildStep::Configured)?;
|
||||
} else {
|
||||
println!("Skipping configure (already done)");
|
||||
}
|
||||
|
||||
// Run post-install hooks (after make install)
|
||||
hooks::run_post_install_commands(spec, &actual_src, destdir)?;
|
||||
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());
|
||||
|
||||
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()))?;
|
||||
|
||||
if !status.success() {
|
||||
anyhow::bail!("make failed with status: {}", status);
|
||||
}
|
||||
|
||||
// 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)");
|
||||
}
|
||||
|
||||
if !state.is_done(BuildStep::PostInstallDone) {
|
||||
// Run make install with fakeroot if not root
|
||||
println!(
|
||||
"Running make install{}...",
|
||||
if fakeroot::is_root() {
|
||||
""
|
||||
} else {
|
||||
" (with internal fakeroot for build)"
|
||||
}
|
||||
);
|
||||
|
||||
let mut install_cmd = fakeroot::wrap_install_command("make", destdir);
|
||||
install_cmd.current_dir(&build_dir);
|
||||
install_cmd.arg("install");
|
||||
|
||||
let mut install_env = env_vars.clone();
|
||||
install_env.push(("DESTDIR", 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))?;
|
||||
|
||||
if !status.success() {
|
||||
anyhow::bail!("make install failed with status: {}", status);
|
||||
}
|
||||
|
||||
// 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)");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -207,3 +249,30 @@ fn expand_shell_commands(input: &str, cc: &str) -> Result<String> {
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_expand_shell_commands_simple() -> Result<()> {
|
||||
let out = expand_shell_commands("x $(echo foo) y", "gcc")?;
|
||||
assert_eq!(out, "x foo y");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expand_shell_commands_replace_cc() -> Result<()> {
|
||||
// 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("") );
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_num_cpus_at_least_one() {
|
||||
let n = num_cpus();
|
||||
assert!(n >= 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,3 +40,71 @@ pub fn build(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
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;
|
||||
|
||||
fn mk_spec(name: &str, version: &str) -> PackageSpec {
|
||||
PackageSpec {
|
||||
package: PackageInfo {
|
||||
name: name.into(),
|
||||
version: version.into(),
|
||||
revision: 1,
|
||||
description: "d".into(),
|
||||
homepage: "h".into(),
|
||||
license: "MIT".into(),
|
||||
},
|
||||
packages: Vec::new(),
|
||||
alternatives: Default::default(),
|
||||
manual_sources: Vec::new(),
|
||||
source: vec![crate::package::Source {
|
||||
url: "h".into(),
|
||||
sha256: "s".into(),
|
||||
extract_dir: "e".into(),
|
||||
patches: Vec::new(),
|
||||
post_extract: Vec::new(),
|
||||
}],
|
||||
build: Build {
|
||||
build_type: BuildType::Bin,
|
||||
flags: BuildFlags::default(),
|
||||
},
|
||||
dependencies: Dependencies::default(),
|
||||
spec_dir: std::path::PathBuf::from("."),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bin_build_copies_files_and_symlinks() -> Result<()> {
|
||||
let tmp_src = tempdir()?;
|
||||
let tmp_dest = tempdir()?;
|
||||
let src = tmp_src.path();
|
||||
let dest = tmp_dest.path();
|
||||
|
||||
// Create a directory and files
|
||||
fs::create_dir_all(src.join("usr/bin"))?;
|
||||
fs::write(src.join("usr/bin/hello"), b"hi")?;
|
||||
|
||||
// Create a symlink
|
||||
let target = src.join("usr/lib/libdummy.so");
|
||||
fs::create_dir_all(target.parent().unwrap())?;
|
||||
fs::write(&target, b"lib")?;
|
||||
unix_fs::symlink(&target, src.join("usr/lib/libdummy.so.link"))?;
|
||||
|
||||
let spec = mk_spec("bin-test", "1.0");
|
||||
build(&spec, src, dest, None)?;
|
||||
|
||||
// Check copied file
|
||||
assert!(dest.join("usr/bin/hello").exists());
|
||||
|
||||
// Check symlink target exists at dest
|
||||
let link_path = dest.join("usr/lib/libdummy.so.link");
|
||||
assert!(link_path.exists());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
+112
-59
@@ -70,6 +70,14 @@ pub fn build(
|
||||
}
|
||||
env_vars.push(("CC", cc));
|
||||
|
||||
// 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()));
|
||||
@@ -91,71 +99,95 @@ pub fn build(
|
||||
None
|
||||
};
|
||||
|
||||
use crate::builder::state::{BuildStep, StateTracker};
|
||||
let mut state = StateTracker::new(&actual_src)?;
|
||||
|
||||
// Run cmake configure
|
||||
println!("Running cmake configure...");
|
||||
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");
|
||||
if !state.is_done(BuildStep::Configured) {
|
||||
println!("Running cmake configure...");
|
||||
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");
|
||||
|
||||
// Add toolchain file for cross-compilation
|
||||
if let Some(ref tf) = toolchain_file {
|
||||
cmake_cmd.arg(format!("-DCMAKE_TOOLCHAIN_FILE={}", tf.display()));
|
||||
}
|
||||
|
||||
// Add custom configure flags from spec (supports cross-compilation overrides)
|
||||
for flag in &flags.configure {
|
||||
// Expand environment variables in the flag
|
||||
let expanded = expand_env_vars(flag);
|
||||
cmake_cmd.arg(&expanded);
|
||||
}
|
||||
|
||||
crate::builder::prepare_command(&mut cmake_cmd, &env_vars);
|
||||
|
||||
let status = cmake_cmd.status().context("Failed to run cmake")?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("cmake configure failed");
|
||||
}
|
||||
|
||||
// Run cmake build
|
||||
println!("Running cmake build...");
|
||||
let mut build_cmd = Command::new("cmake");
|
||||
build_cmd.arg("--build").arg(&build_dir);
|
||||
build_cmd.arg("-j").arg(num_cpus().to_string());
|
||||
|
||||
crate::builder::prepare_command(&mut build_cmd, &env_vars);
|
||||
|
||||
let status = build_cmd
|
||||
.status()
|
||||
.with_context(|| format!("Failed to run cmake build for {}", spec.package.name))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("cmake build failed");
|
||||
}
|
||||
|
||||
// Run cmake install with fakeroot if not root
|
||||
println!(
|
||||
"Running cmake install{}...",
|
||||
if fakeroot::is_root() {
|
||||
""
|
||||
} else {
|
||||
" (with fakeroot)"
|
||||
// Add toolchain file for cross-compilation
|
||||
if let Some(ref tf) = toolchain_file {
|
||||
cmake_cmd.arg(format!("-DCMAKE_TOOLCHAIN_FILE={}", tf.display()));
|
||||
}
|
||||
);
|
||||
|
||||
let mut install_cmd = fakeroot::wrap_install_command("cmake", destdir);
|
||||
install_cmd.arg("--install").arg(&build_dir);
|
||||
// Add custom configure flags from spec (supports cross-compilation overrides)
|
||||
for flag in &flags.configure {
|
||||
// Expand environment variables in the flag
|
||||
let expanded = expand_env_vars(flag);
|
||||
cmake_cmd.arg(&expanded);
|
||||
}
|
||||
|
||||
let mut install_env = env_vars.clone();
|
||||
install_env.push(("DESTDIR", destdir.to_string_lossy().into_owned()));
|
||||
crate::builder::prepare_command(&mut install_cmd, &install_env);
|
||||
crate::builder::prepare_command(&mut cmake_cmd, &env_vars);
|
||||
|
||||
let status = install_cmd
|
||||
.status()
|
||||
.with_context(|| format!("Failed to run cmake install for {}", spec.package.name))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("cmake install failed");
|
||||
let status = cmake_cmd.status().context("Failed to run cmake")?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("cmake configure failed");
|
||||
}
|
||||
state.mark_done(BuildStep::Configured)?;
|
||||
} else {
|
||||
println!("Skipping cmake configure (already done)");
|
||||
}
|
||||
|
||||
if !state.is_done(BuildStep::PostCompileDone) {
|
||||
// Run cmake build
|
||||
println!("Running cmake build...");
|
||||
let mut build_cmd = Command::new("cmake");
|
||||
build_cmd.arg("--build").arg(&build_dir);
|
||||
build_cmd.arg("-j").arg(num_cpus().to_string());
|
||||
|
||||
crate::builder::prepare_command(&mut build_cmd, &env_vars);
|
||||
|
||||
let status = build_cmd
|
||||
.status()
|
||||
.with_context(|| format!("Failed to run cmake build for {}", spec.package.name))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("cmake build failed");
|
||||
}
|
||||
|
||||
// Note: CMake doesn't have a direct "after make, before install" hook as easy as autotools,
|
||||
// but we can run it here.
|
||||
crate::source::hooks::run_post_compile_commands(spec, &actual_src, destdir)?;
|
||||
state.mark_done(BuildStep::PostCompileDone)?;
|
||||
} else {
|
||||
println!("Skipping cmake build and post-compile hooks (already done)");
|
||||
}
|
||||
|
||||
if !state.is_done(BuildStep::PostInstallDone) {
|
||||
// Run cmake install with fakeroot if not root
|
||||
println!(
|
||||
"Running cmake install{}...",
|
||||
if fakeroot::is_root() {
|
||||
""
|
||||
} else {
|
||||
" (with fakeroot)"
|
||||
}
|
||||
);
|
||||
|
||||
let mut install_cmd = fakeroot::wrap_install_command("cmake", destdir);
|
||||
install_cmd.arg("--install").arg(&build_dir);
|
||||
|
||||
let mut install_env = env_vars.clone();
|
||||
install_env.push(("DESTDIR", 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 cmake install for {}", spec.package.name))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("cmake install failed");
|
||||
}
|
||||
|
||||
crate::source::hooks::run_post_install_commands(spec, &actual_src, destdir)?;
|
||||
state.mark_done(BuildStep::PostInstallDone)?;
|
||||
} else {
|
||||
println!("Skipping cmake install and post-install hooks (already done)");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -177,3 +209,24 @@ fn num_cpus() -> usize {
|
||||
.map(|n| n.get())
|
||||
.unwrap_or(1)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_expand_env_vars_replaces_vars() {
|
||||
// Set a test env var
|
||||
unsafe { std::env::set_var("DEPOT_TEST_FOO", "bar") };
|
||||
let input = "$DEPOT_TEST_FOO and ${DEPOT_TEST_FOO}";
|
||||
let out = expand_env_vars(input);
|
||||
assert!(out.contains("bar"));
|
||||
assert_eq!(out, "bar and bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_num_cpus_at_least_one() {
|
||||
let n = num_cpus();
|
||||
assert!(n >= 1);
|
||||
}
|
||||
}
|
||||
|
||||
+181
-59
@@ -23,6 +23,25 @@ pub fn build(
|
||||
// For custom builds, look for a build.sh script in the source directory
|
||||
let build_script = src_dir.join("build.sh");
|
||||
|
||||
// If the extracted source doesn't include build.sh but the spec directory does,
|
||||
// copy it into the source dir (this makes `depot install <local-spec>` behave
|
||||
// like the spec's build.sh being part of the package when appropriate).
|
||||
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()))?;
|
||||
// Ensure executable bit
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = fs::metadata(&build_script)?.permissions();
|
||||
perms.set_mode(0o755);
|
||||
fs::set_permissions(&build_script, perms)?;
|
||||
}
|
||||
println!("Using build.sh from spec dir: {}", spec_build.display());
|
||||
}
|
||||
|
||||
if !build_script.exists() {
|
||||
anyhow::bail!(
|
||||
"Custom build type requires build.sh in source directory: {}",
|
||||
@@ -30,74 +49,177 @@ pub fn build(
|
||||
);
|
||||
}
|
||||
|
||||
println!(
|
||||
"Running custom build script{}...",
|
||||
if fakeroot::is_root() {
|
||||
""
|
||||
} else {
|
||||
" (with fakeroot)"
|
||||
}
|
||||
);
|
||||
use crate::builder::state::{BuildStep, StateTracker};
|
||||
let mut state = StateTracker::new(&src_dir)?;
|
||||
|
||||
let mut cmd = fakeroot::wrap_install_command("bash", destdir);
|
||||
cmd.current_dir(src_dir);
|
||||
cmd.arg(&build_script);
|
||||
if !state.is_done(BuildStep::PostInstallDone) {
|
||||
println!(
|
||||
"Running custom build script{}...",
|
||||
if fakeroot::is_root() {
|
||||
""
|
||||
} else {
|
||||
" (with fakeroot)"
|
||||
}
|
||||
);
|
||||
|
||||
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(" ")
|
||||
let build_dir = if let Some(dir) = &flags.build_dir {
|
||||
let bdir = src_dir.join(dir);
|
||||
fs::create_dir_all(&bdir)?;
|
||||
bdir
|
||||
} else {
|
||||
format!(
|
||||
"{} -Wl,--dynamic-linker={}",
|
||||
flags.ldflags.join(" "),
|
||||
flags.libc
|
||||
)
|
||||
src_dir.to_path_buf()
|
||||
};
|
||||
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((
|
||||
"NYAPM_SPECDIR",
|
||||
spec.spec_dir.to_string_lossy().into_owned(),
|
||||
));
|
||||
let mut cmd = fakeroot::wrap_install_command("bash", destdir);
|
||||
cmd.current_dir(&build_dir);
|
||||
|
||||
if !flags.chost.is_empty() {
|
||||
env_vars.push(("CHOST", flags.chost.clone()));
|
||||
}
|
||||
if !flags.cbuild.is_empty() {
|
||||
env_vars.push(("CBUILD", flags.cbuild.clone()));
|
||||
}
|
||||
// Ensure build script path is absolute for when we are in a sub-build-dir
|
||||
let abs_build_script = if build_script.is_absolute() {
|
||||
build_script.clone()
|
||||
} else {
|
||||
std::env::current_dir()?.join(&build_script)
|
||||
};
|
||||
cmd.arg(&abs_build_script);
|
||||
|
||||
// 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)));
|
||||
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::prepare_command(&mut cmd, &env_vars);
|
||||
|
||||
let status = cmd
|
||||
.status()
|
||||
.with_context(|| format!("Failed to run build script: {}", build_script.display()))?;
|
||||
|
||||
if !status.success() {
|
||||
anyhow::bail!("Custom build script failed with status: {}", status);
|
||||
}
|
||||
state.mark_done(BuildStep::PostInstallDone)?;
|
||||
} else {
|
||||
env_vars.push(("CC", flags.cc.clone()));
|
||||
env_vars.push(("AR", flags.ar.clone()));
|
||||
}
|
||||
|
||||
crate::builder::prepare_command(&mut cmd, &env_vars);
|
||||
|
||||
let status = cmd
|
||||
.status()
|
||||
.with_context(|| format!("Failed to run build script: {}", build_script.display()))?;
|
||||
|
||||
if !status.success() {
|
||||
anyhow::bail!("Custom build script failed with status: {}", status);
|
||||
println!("Skipping custom build script (already done)");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::package::{Build, BuildFlags, BuildType, Dependencies, PackageInfo};
|
||||
use tempfile::tempdir;
|
||||
|
||||
fn mk_spec(name: &str, version: &str) -> PackageSpec {
|
||||
PackageSpec {
|
||||
package: PackageInfo {
|
||||
name: name.into(),
|
||||
version: version.into(),
|
||||
revision: 1,
|
||||
description: "d".into(),
|
||||
homepage: "h".into(),
|
||||
license: "MIT".into(),
|
||||
},
|
||||
packages: Vec::new(),
|
||||
alternatives: Default::default(),
|
||||
manual_sources: Vec::new(),
|
||||
source: vec![crate::package::Source {
|
||||
url: "h".into(),
|
||||
sha256: "s".into(),
|
||||
extract_dir: "e".into(),
|
||||
patches: Vec::new(),
|
||||
post_extract: Vec::new(),
|
||||
}],
|
||||
build: Build {
|
||||
build_type: BuildType::Custom,
|
||||
flags: BuildFlags::default(),
|
||||
},
|
||||
dependencies: Dependencies::default(),
|
||||
spec_dir: std::path::PathBuf::from("."),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_errors_without_build_sh() -> Result<()> {
|
||||
let tmp_src = tempdir()?;
|
||||
let tmp_dest = tempdir()?;
|
||||
|
||||
let spec = mk_spec("custom-no-build", "1.0");
|
||||
|
||||
let res = build(&spec, tmp_src.path(), tmp_dest.path(), None);
|
||||
assert!(res.is_err());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_uses_build_sh_from_spec_dir() -> Result<()> {
|
||||
let tmp_src = tempdir()?;
|
||||
let tmp_dest = tempdir()?;
|
||||
let spec_dir = tempdir()?;
|
||||
|
||||
// write a no-op build.sh into spec_dir
|
||||
let build_sh = spec_dir.path().join("build.sh");
|
||||
std::fs::write(&build_sh, "#!/bin/sh\nexit 0\n")?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = std::fs::metadata(&build_sh)?.permissions();
|
||||
perms.set_mode(0o755);
|
||||
std::fs::set_permissions(&build_sh, perms)?;
|
||||
}
|
||||
|
||||
let mut spec = mk_spec("custom-from-spec", "1.0");
|
||||
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)?;
|
||||
// If we reached here, build() succeeded and build.sh was copied into src
|
||||
assert!(tmp_src.path().join("build.sh").exists());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
use crate::builder::state::{BuildStep, StateTracker};
|
||||
use crate::cross::CrossConfig;
|
||||
use crate::package::PackageSpec;
|
||||
use anyhow::{Context, Result};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
pub fn build(
|
||||
spec: &PackageSpec,
|
||||
src_dir: &Path,
|
||||
destdir: &Path,
|
||||
cross: Option<&CrossConfig>,
|
||||
) -> 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: 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
|
||||
if !flags.rootfs.is_empty() && flags.rootfs != "/" {
|
||||
env_vars.push(("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...");
|
||||
|
||||
for cmd_str in &spec.build.flags.makefile_commands {
|
||||
let cmd_str = spec.expand_vars(cmd_str);
|
||||
println!(" Executing: {}", cmd_str);
|
||||
|
||||
let mut cmd = Command::new("sh");
|
||||
cmd.arg("-c").arg(&cmd_str);
|
||||
cmd.current_dir(src_dir);
|
||||
crate::builder::prepare_command(&mut cmd, &env_vars);
|
||||
|
||||
let status = cmd
|
||||
.status()
|
||||
.with_context(|| format!("Failed to run build command: {}", cmd_str))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("Build command failed: {}", cmd_str);
|
||||
}
|
||||
}
|
||||
|
||||
crate::source::hooks::run_post_compile_commands(spec, src_dir, destdir)?;
|
||||
state.mark_done(BuildStep::PostCompileDone)?;
|
||||
} else {
|
||||
println!("Skipping makefile build commands (already done)");
|
||||
}
|
||||
|
||||
if !state.is_done(BuildStep::PostInstallDone) {
|
||||
// Run install commands with fakeroot
|
||||
println!(
|
||||
"Running makefile install commands{}...",
|
||||
if crate::fakeroot::is_root() {
|
||||
""
|
||||
} else {
|
||||
" (with fakeroot)"
|
||||
}
|
||||
);
|
||||
|
||||
for cmd_str in &spec.build.flags.makefile_install_commands {
|
||||
let cmd_str = spec.expand_vars(cmd_str);
|
||||
println!(" Executing: {}", cmd_str);
|
||||
|
||||
// We need to run each command under fakeroot
|
||||
let mut cmd = crate::fakeroot::wrap_install_command("sh", destdir);
|
||||
cmd.arg("-c").arg(&cmd_str);
|
||||
cmd.current_dir(src_dir);
|
||||
|
||||
let mut install_env = env_vars.clone();
|
||||
install_env.push(("DESTDIR", destdir.to_string_lossy().into_owned()));
|
||||
crate::builder::prepare_command(&mut cmd, &install_env);
|
||||
|
||||
let status = cmd
|
||||
.status()
|
||||
.with_context(|| format!("Failed to run install command: {}", cmd_str))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("Install command failed: {}", cmd_str);
|
||||
}
|
||||
}
|
||||
|
||||
crate::source::hooks::run_post_install_commands(spec, src_dir, destdir)?;
|
||||
state.mark_done(BuildStep::PostInstallDone)?;
|
||||
} else {
|
||||
println!("Skipping makefile install commands (already done)");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::package::{Build, BuildFlags, BuildType, Dependencies, PackageInfo};
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
|
||||
fn mk_spec(name: &str, version: &str) -> PackageSpec {
|
||||
PackageSpec {
|
||||
package: PackageInfo {
|
||||
name: name.into(),
|
||||
version: version.into(),
|
||||
revision: 1,
|
||||
description: "d".into(),
|
||||
homepage: "h".into(),
|
||||
license: "MIT".into(),
|
||||
},
|
||||
packages: Vec::new(),
|
||||
alternatives: Default::default(),
|
||||
manual_sources: Vec::new(),
|
||||
source: vec![crate::package::Source {
|
||||
url: "h".into(),
|
||||
sha256: "s".into(),
|
||||
extract_dir: "e".into(),
|
||||
patches: Vec::new(),
|
||||
post_extract: Vec::new(),
|
||||
}],
|
||||
build: Build {
|
||||
build_type: BuildType::Makefile,
|
||||
flags: BuildFlags::default(),
|
||||
},
|
||||
dependencies: Dependencies::default(),
|
||||
spec_dir: std::path::PathBuf::from("."),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_makefile_build_runs_commands() -> Result<()> {
|
||||
let tmp_src = tempdir()?;
|
||||
let tmp_dest = tempdir()?;
|
||||
let src_path = tmp_src.path();
|
||||
let dest_path = tmp_dest.path();
|
||||
|
||||
let mut spec = mk_spec("test-make", "1.0");
|
||||
spec.build.flags.makefile_commands = vec![
|
||||
"echo 'building' > built.txt".into(),
|
||||
"echo 'more build' >> built.txt".into(),
|
||||
];
|
||||
spec.build.flags.makefile_install_commands = vec![
|
||||
"mkdir -p $DESTDIR/usr/bin".into(),
|
||||
"cp built.txt $DESTDIR/usr/bin/installed.txt".into(),
|
||||
];
|
||||
|
||||
build(&spec, src_path, dest_path, None)?;
|
||||
|
||||
// Verify build step
|
||||
let built_file = src_path.join("built.txt");
|
||||
assert!(built_file.exists());
|
||||
let content = fs::read_to_string(&built_file)?;
|
||||
assert!(content.contains("building"));
|
||||
assert!(content.contains("more build"));
|
||||
|
||||
// Verify install step
|
||||
let installed_file = dest_path.join("usr/bin/installed.txt");
|
||||
assert!(installed_file.exists());
|
||||
let installed_content = fs::read_to_string(&installed_file)?;
|
||||
assert_eq!(content, installed_content);
|
||||
|
||||
// Verify state persistence
|
||||
let state_tracker = StateTracker::new(src_path)?;
|
||||
assert!(state_tracker.is_done(BuildStep::PostCompileDone));
|
||||
assert!(state_tracker.is_done(BuildStep::PostInstallDone));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+94
-53
@@ -54,12 +54,20 @@ pub fn build(
|
||||
}
|
||||
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()));
|
||||
}
|
||||
|
||||
// Extract prefix from configure flags
|
||||
let prefix = flags
|
||||
.configure
|
||||
@@ -75,65 +83,87 @@ pub fn build(
|
||||
None
|
||||
};
|
||||
|
||||
use crate::builder::state::{BuildStep, StateTracker};
|
||||
let mut state = StateTracker::new(src_dir)?;
|
||||
|
||||
// Run meson setup
|
||||
println!("Running meson setup...");
|
||||
let mut meson_cmd = Command::new("meson");
|
||||
meson_cmd.current_dir(src_dir);
|
||||
meson_cmd.arg("setup");
|
||||
meson_cmd.arg(&build_dir);
|
||||
meson_cmd.arg(format!("--prefix={}", prefix));
|
||||
meson_cmd.arg("--buildtype=release");
|
||||
if !state.is_done(BuildStep::Configured) {
|
||||
println!("Running meson setup...");
|
||||
let mut meson_cmd = Command::new("meson");
|
||||
meson_cmd.current_dir(src_dir);
|
||||
meson_cmd.arg("setup");
|
||||
meson_cmd.arg(&build_dir);
|
||||
meson_cmd.arg(format!("--prefix={}", prefix));
|
||||
meson_cmd.arg("--buildtype=release");
|
||||
|
||||
// Add cross file for cross-compilation
|
||||
if let Some(ref cf) = cross_file {
|
||||
meson_cmd.arg(format!("--cross-file={}", cf.display()));
|
||||
}
|
||||
|
||||
crate::builder::prepare_command(&mut meson_cmd, &env_vars);
|
||||
|
||||
let status = meson_cmd.status().context("Failed to run meson setup")?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("meson setup failed");
|
||||
}
|
||||
|
||||
// Run ninja build
|
||||
println!("Running ninja...");
|
||||
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_command(&mut ninja_cmd, &env_vars);
|
||||
|
||||
let status = ninja_cmd
|
||||
.status()
|
||||
.with_context(|| format!("Failed to run ninja for {}", spec.package.name))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("ninja build failed");
|
||||
}
|
||||
|
||||
// Run meson install with fakeroot if not root
|
||||
println!(
|
||||
"Running meson install{}...",
|
||||
if fakeroot::is_root() {
|
||||
""
|
||||
} else {
|
||||
" (with fakeroot)"
|
||||
// Add cross file for cross-compilation
|
||||
if let Some(ref cf) = cross_file {
|
||||
meson_cmd.arg(format!("--cross-file={}", cf.display()));
|
||||
}
|
||||
);
|
||||
|
||||
let mut install_cmd = fakeroot::wrap_install_command("meson", destdir);
|
||||
install_cmd.arg("install");
|
||||
install_cmd.arg("-C").arg(&build_dir);
|
||||
crate::builder::prepare_command(&mut meson_cmd, &env_vars);
|
||||
|
||||
let mut install_env = env_vars.clone();
|
||||
install_env.push(("DESTDIR", destdir.to_string_lossy().into_owned()));
|
||||
crate::builder::prepare_command(&mut install_cmd, &install_env);
|
||||
let status = meson_cmd.status().context("Failed to run meson setup")?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("meson setup failed");
|
||||
}
|
||||
state.mark_done(BuildStep::Configured)?;
|
||||
} else {
|
||||
println!("Skipping meson setup (already done)");
|
||||
}
|
||||
|
||||
let status = install_cmd
|
||||
.status()
|
||||
.with_context(|| format!("Failed to run meson install for {}", spec.package.name))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("meson install failed");
|
||||
if !state.is_done(BuildStep::PostCompileDone) {
|
||||
// Run ninja build
|
||||
println!("Running ninja...");
|
||||
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_command(&mut ninja_cmd, &env_vars);
|
||||
|
||||
let status = ninja_cmd
|
||||
.status()
|
||||
.with_context(|| format!("Failed to run ninja for {}", spec.package.name))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("ninja build failed");
|
||||
}
|
||||
|
||||
crate::source::hooks::run_post_compile_commands(spec, src_dir, destdir)?;
|
||||
state.mark_done(BuildStep::PostCompileDone)?;
|
||||
} else {
|
||||
println!("Skipping ninja build and post-compile hooks (already done)");
|
||||
}
|
||||
|
||||
if !state.is_done(BuildStep::PostInstallDone) {
|
||||
// Run meson install with fakeroot if not root
|
||||
println!(
|
||||
"Running meson install{}...",
|
||||
if fakeroot::is_root() {
|
||||
""
|
||||
} else {
|
||||
" (with fakeroot)"
|
||||
}
|
||||
);
|
||||
|
||||
let mut install_cmd = fakeroot::wrap_install_command("meson", destdir);
|
||||
install_cmd.arg("install");
|
||||
install_cmd.arg("-C").arg(&build_dir);
|
||||
|
||||
let mut install_env = env_vars.clone();
|
||||
install_env.push(("DESTDIR", 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 meson install for {}", spec.package.name))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("meson install failed");
|
||||
}
|
||||
|
||||
crate::source::hooks::run_post_install_commands(spec, src_dir, destdir)?;
|
||||
state.mark_done(BuildStep::PostInstallDone)?;
|
||||
} else {
|
||||
println!("Skipping meson install and post-install hooks (already done)");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -144,3 +174,14 @@ fn num_cpus() -> usize {
|
||||
.map(|n| n.get())
|
||||
.unwrap_or(1)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_num_cpus_at_least_one() {
|
||||
let n = num_cpus();
|
||||
assert!(n >= 1);
|
||||
}
|
||||
}
|
||||
|
||||
+10
-1
@@ -4,8 +4,10 @@ mod autotools;
|
||||
mod bin;
|
||||
mod cmake;
|
||||
mod custom;
|
||||
mod makefile;
|
||||
mod meson;
|
||||
mod rust;
|
||||
pub mod state;
|
||||
|
||||
use crate::cross::CrossConfig;
|
||||
use crate::package::{BuildType, PackageSpec};
|
||||
@@ -17,7 +19,7 @@ use std::process::Command;
|
||||
pub fn prepare_command(cmd: &mut Command, env_vars: &[(&str, String)]) {
|
||||
cmd.env_clear();
|
||||
// Preserve essential environment variables
|
||||
for var in &["PATH", "LANG", "HOME", "SHELL", "DESTDIR"] {
|
||||
for var in &["PATH", "LANG", "HOME", "SHELL", "DESTDIR", "DEPOT_ROOTFS"] {
|
||||
if let Ok(val) = std::env::var(var) {
|
||||
cmd.env(var, val);
|
||||
}
|
||||
@@ -56,6 +58,7 @@ pub fn build(
|
||||
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),
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
@@ -71,6 +74,7 @@ mod tests {
|
||||
unsafe {
|
||||
std::env::set_var("PATH", "/usr/bin");
|
||||
std::env::set_var("HOME", "/home/test");
|
||||
std::env::set_var("DEPOT_ROOTFS", "/my/rootfs");
|
||||
}
|
||||
|
||||
prepare_command(&mut cmd, &[("MYVAR", "myval".to_string())]);
|
||||
@@ -83,6 +87,11 @@ mod tests {
|
||||
envs.get(std::ffi::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")),
|
||||
Some(&Some(std::ffi::OsString::from("/my/rootfs").as_os_str()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -52,6 +52,11 @@ pub fn build(
|
||||
env_vars.push(("RUSTFLAGS", flags.rustflags.join(" ")));
|
||||
}
|
||||
|
||||
// CARCH support
|
||||
if !flags.carch.is_empty() {
|
||||
env_vars.push(("CARCH", flags.carch.clone()));
|
||||
}
|
||||
|
||||
// If cross-compiling, set linker via CARGO_TARGET_*_LINKER
|
||||
if let Some(cc_cfg) = cross {
|
||||
// Convert target triple to uppercase with underscores for env var
|
||||
@@ -71,6 +76,9 @@ pub fn build(
|
||||
env_vars.push(("RUSTUP_TOOLCHAIN", "stable".to_string()));
|
||||
}
|
||||
|
||||
// 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 ({})...",
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
//! Build state tracking to allow resuming interrupted builds.
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum BuildStep {
|
||||
PatchesApplied,
|
||||
PostExtractDone,
|
||||
Configured,
|
||||
PostCompileDone,
|
||||
PostInstallDone,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
struct State {
|
||||
completed_steps: HashSet<BuildStep>,
|
||||
}
|
||||
|
||||
pub struct StateTracker {
|
||||
state_file: PathBuf,
|
||||
state: State,
|
||||
}
|
||||
|
||||
impl StateTracker {
|
||||
pub fn new(source_dir: &Path) -> Result<Self> {
|
||||
let state_file = source_dir.join(".depot_state");
|
||||
let state = if state_file.exists() {
|
||||
let content = fs::read_to_string(&state_file)?;
|
||||
toml::from_str(&content).unwrap_or_default()
|
||||
} else {
|
||||
State::default()
|
||||
};
|
||||
|
||||
Ok(Self { state_file, state })
|
||||
}
|
||||
|
||||
pub fn is_done(&self, step: BuildStep) -> bool {
|
||||
self.state.completed_steps.contains(&step)
|
||||
}
|
||||
|
||||
pub fn mark_done(&mut self, step: BuildStep) -> Result<()> {
|
||||
self.state.completed_steps.insert(step);
|
||||
self.save()
|
||||
}
|
||||
|
||||
fn save(&self) -> Result<()> {
|
||||
let content = toml::to_string_pretty(&self.state)?;
|
||||
fs::write(&self.state_file, content)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn test_state_tracker_init() -> Result<()> {
|
||||
let dir = tempdir()?;
|
||||
let tracker = StateTracker::new(dir.path())?;
|
||||
assert!(!tracker.state_file.exists());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mark_done_and_persistence() -> Result<()> {
|
||||
let dir = tempdir()?;
|
||||
let mut tracker = StateTracker::new(dir.path())?;
|
||||
|
||||
assert!(!tracker.is_done(BuildStep::Configured));
|
||||
tracker.mark_done(BuildStep::Configured)?;
|
||||
assert!(tracker.is_done(BuildStep::Configured));
|
||||
|
||||
// Check file exists
|
||||
assert!(tracker.state_file.exists());
|
||||
|
||||
// Reload
|
||||
let tracker2 = StateTracker::new(dir.path())?;
|
||||
assert!(tracker2.is_done(BuildStep::Configured));
|
||||
assert!(!tracker2.is_done(BuildStep::PostCompileDone));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user