//! Integration test del path autotools: //! - Repo git con `configure.ac` + `Makefile.am` + `hello.c` (sin `configure` generado). //! - Receta SIN `[build.phases]` → la heurística debe detectar AutoconfRaw y emitir //! `autoreconf -fi && ./configure --prefix=/usr` + `make` + `make install DESTDIR=/out`. //! - El binario instalado en el store debe correr en el sandbox. //! //! Skip transparente si el rootfs Alpine o zig no están bootstrappeados. use std::path::{Path, PathBuf}; use std::process::Command; use takana_build::{build, BuildConfig}; use takana_core::{Recipe, Store}; const HELLO_C: &str = r#" #include int main(void) { puts("hammer-autotools OK"); return 0; } "#; const CONFIGURE_AC: &str = r#" AC_INIT([helloat], [1.0]) AM_INIT_AUTOMAKE([foreign -Wall]) AC_PROG_CC AC_CONFIG_FILES([Makefile]) AC_OUTPUT "#; const MAKEFILE_AM: &str = r#" bin_PROGRAMS = helloat helloat_SOURCES = hello.c "#; const RECIPE_TOML: &str = r#" name = "helloat" version = "1.0" [source] repo = "PLACEHOLDER_REPO_URL" commit = "PLACEHOLDER_COMMIT" [build] compiler = "zig-cc" target = "x86_64-linux-musl" link = "static" "#; fn project_root() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) .parent() .unwrap() .parent() .unwrap() .to_path_buf() } fn skip_if_no_layout(cfg: &BuildConfig) -> bool { if !cfg.rootfs.join("bin/busybox").is_file() { eprintln!("SKIP: rootfs Alpine no encontrado en {}.", cfg.rootfs.display()); return true; } // Las tools de autotools se metieron con `apk add make autoconf automake m4 ...` // — comprobamos uno para no engañarnos si el rootfs es el minirootfs pelado. if !cfg.rootfs.join("usr/bin/autoreconf").is_file() { eprintln!( "SKIP: rootfs sin autotools. Corre: bwrap --bind {} / --share-net /sbin/apk add make autoconf automake m4 patch coreutils libtool pkgconf bash", cfg.rootfs.display() ); return true; } if !cfg.zig_dir.join("zig").is_file() { eprintln!("SKIP: zig no encontrado en {}.", cfg.zig_dir.display()); return true; } false } fn git(args: &[&str], cwd: &Path) { let st = Command::new("git").args(args).current_dir(cwd).status().expect("git"); assert!(st.success(), "git {args:?} falló"); } fn init_repo(upstream: &Path) -> String { std::fs::create_dir_all(upstream).unwrap(); std::fs::write(upstream.join("configure.ac"), CONFIGURE_AC).unwrap(); std::fs::write(upstream.join("Makefile.am"), MAKEFILE_AM).unwrap(); std::fs::write(upstream.join("hello.c"), HELLO_C).unwrap(); git(&["init", "-q", "-b", "main"], upstream); git(&["config", "user.email", "test@hammer"], upstream); git(&["config", "user.name", "hammer-test"], upstream); git(&["add", "."], upstream); git(&["commit", "-q", "-m", "init"], upstream); let out = Command::new("git") .args(["rev-parse", "HEAD"]) .current_dir(upstream) .output() .unwrap(); String::from_utf8(out.stdout).unwrap().trim().to_string() } #[test] fn build_autotools_hello_end_to_end() { let _ = tracing_subscriber::fmt().with_test_writer().try_init(); let project = project_root(); let cfg = BuildConfig { rootfs: project.join(".dev-fs/alpine"), zig_dir: project.join(".dev-fs/tools/zig"), work_root: tempfile::tempdir().unwrap().keep(), cache_root: Some(project.join(".dev-fs/cache")), }; if skip_if_no_layout(&cfg) { return; } let tmp = tempfile::tempdir().unwrap(); let store_root = tmp.path().join("store"); std::fs::create_dir_all(&store_root).unwrap(); let upstream = tmp.path().join("upstream"); let commit = init_repo(&upstream); let repo_url = format!("file://{}", upstream.display()); let recipe_dir = tmp.path().join("recipe"); std::fs::create_dir_all(&recipe_dir).unwrap(); let toml = RECIPE_TOML .replace("PLACEHOLDER_REPO_URL", &repo_url) .replace("PLACEHOLDER_COMMIT", &commit); let recipe_path = recipe_dir.join("helloat.toml"); std::fs::write(&recipe_path, toml).unwrap(); let recipe = Recipe::load_from_path(&recipe_path).expect("load recipe"); let store = Store::open(&store_root).unwrap(); let h = build(&recipe, &cfg, &store).expect("build helloat"); let sealed = store.path_of(&h, &recipe.name); let bin = sealed.join("usr/bin/helloat"); assert!(bin.is_file(), "esperaba {}", bin.display()); let header = std::fs::read(&bin).unwrap(); assert_eq!(&header[..4], b"\x7fELF", "es ELF"); let out = Command::new("bwrap") .args([ "--overlay-src", cfg.rootfs.to_str().unwrap(), "--tmp-overlay", "/", "--proc", "/proc", "--dev", "/dev", "--ro-bind", sealed.to_str().unwrap(), "/art", "--unshare-all", "/art/usr/bin/helloat", ]) .output() .expect("spawn bwrap"); assert!(out.status.success(), "helloat falló: {:?}", out); assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "hammer-autotools OK"); }