hammer-{core,build,bootstrap,overlay,journal,mirror,upgrade,agent,recover,cli}
→ takana-*, con sus deps de workspace, sus identificadores en el fuente y las
referencias -p de los scripts.
VERIFICADO que no mueve nada del corpus: `takana hash recipes/zlib.toml`
devuelve b3:dc363f26… , idéntico a antes del renombre. Los nombres de crate no
entran en hash_inputs, pero eso se comprueba, no se supone. 600 tests en verde.
DOS BINARIOS SE CONGELAN, y no por prolijidad:
- `hammerd` — paquete Y binario. Es componente de Stage 1 de la distro (musl,
busybox, hammerd, arje-zero), lo supervisa arje-zero en el sistema arrancado,
`PRESEED=hammerd` lo nombra en selfhost-verify y sus bytes anclan el baseline
of_tree. El nombre del crate va en los símbolos ⇒ renombrarlo mueve los bytes.
- `hammer-recover` — el PAQUETE se renombra a takana-recover, el BINARIO no.
hammer-live-install.sh lo copia a /usr/sbin/hammer-recover en sistemas ya
instalados y hornea un hook de arranque que lo invoca por ese nombre:
renombrarlo rompe máquinas instaladas, no el repo.
Consecuencia que hay que anotar igual: al renombrar hammer-core, los bytes de
hammerd cambian de todos modos porque linkea contra un crate con otro nombre.
El baseline of_tree del selfhost hay que rehacerlo — es efecto de la etapa 4,
no de un cambio de hammerd.
Las referencias en comentarios de recetas y docs (rutas hammer-core/src/…)
quedan para la etapa 5: son texto, no mueven hash.
163 lines
5.1 KiB
Rust
163 lines
5.1 KiB
Rust
//! 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 <stdio.h>
|
|
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");
|
|
}
|