build: zig_version por-receta — 5 víctimas C dropean gcc (zig 0.13)

Mata gcc para 5 de las 7 recetas que lo forzaban, vía una escotilla nueva:

- hammer-core/hammer-build: campo `[build].zig_version` por receta. Cuando se
  fija, el lab resuelve ese zig (hermano del por defecto, `zig-x86_64-linux-<v>`)
  en vez del global, y entra al hash SÓLO si está presente (baseline 9adefb82
  intacto). `effective_zig_dir` lo aplica en ensure_layout + Sandbox.

- Causa: BISECT con oráculo flex (reproducido sólo vía lab: musl DINÁMICO) — el
  miscompile es una REGRESIÓN de zig 0.14; 0.13.0 compila limpio, 0.14/0.15/0.16
  fallan. Es C/musl-dinámico, NO afecta C++.

- Flip a zig_version="0.13.0" (quitando CC=gcc): flex, openssl, elfutils,
  binutils, python3. Verificados: `as` 2.45.1 corre, python3 3.12.10 corre
  (deepfreeze OK), libcrypto/libelf sellan. Todas son tools (no inputs del 4/4).

cmake queda en gcc: su segfault es C++ (libc++/musl), bug distinto que 0.13 NO
arregla (ni con -static). El kernel queda pendiente de verificar.

Tests: hammer-core/hammer-build verdes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 20:20:17 -04:00
co-authored by Claude Opus 4.8
parent 54ccc130dd
commit 7bf49fb960
7 changed files with 86 additions and 35 deletions
+26 -2
View File
@@ -117,6 +117,27 @@ fn materialize_build_deps(
Ok(dirs)
}
/// Resuelve el directorio de zig para una receta: el por defecto del lab (`cfg.zig_dir`), salvo que
/// la receta fije `build.zig_version`, en cuyo caso se usa el zig de esa versión, ubicado como
/// **hermano** del por defecto (`<…>/zig-x86_64-linux-<version>`, el layout de `.dev-fs/tools/`).
/// Escotilla por-receta para esquivar la regresión de zig 0.14 sin tocar el toolchain global.
fn effective_zig_dir(cfg: &BuildConfig, recipe: &Recipe) -> hammer_core::Result<PathBuf> {
let Some(version) = &recipe.build.zig_version else {
return Ok(cfg.zig_dir.clone());
};
let parent = cfg.zig_dir.parent().unwrap_or_else(|| Path::new("."));
let dir = parent.join(format!("zig-x86_64-linux-{version}"));
if !dir.join("zig").is_file() {
return Err(hammer_core::Error::Recipe(format!(
"zig_version=\"{version}\": no encuentro el ejecutable zig en {} \
(bajá ese zig a {}/)",
dir.display(),
parent.display(),
)));
}
Ok(dir)
}
/// Compila una receta y devuelve el hash del artefacto sellado en el store.
/// Si el hash ya existe en el store, devuelve sin recompilar (caché).
pub fn build(
@@ -129,7 +150,10 @@ pub fn build(
tracing::info!(hash = %h, name = %recipe.name, "caché: artefacto ya en el store");
return Ok(h);
}
sandbox::ensure_layout(&cfg.rootfs, &cfg.zig_dir)?;
// zig efectivo de ESTA receta: el por defecto del lab, salvo que la receta fije `zig_version`
// (escotilla para esquivar la regresión de zig 0.14 en ciertos binarios musl; ver SDD 11 §7.2b).
let zig_dir = effective_zig_dir(cfg, recipe)?;
sandbox::ensure_layout(&cfg.rootfs, &zig_dir)?;
// Materializa las deps de build (las construye si faltan) ANTES del fetch: sus árboles sellados
// se apilan como capas overlay del sandbox para que headers/libs/pkgconfig estén en /usr.
@@ -165,7 +189,7 @@ pub fn build(
}
let sb = Sandbox {
rootfs: cfg.rootfs.clone(),
zig_dir: cfg.zig_dir.clone(),
zig_dir: zig_dir.clone(),
src_dir: src_tree.clone(),
out_dir: out_dir.clone(),
cache_dir: cfg.cache_root.clone(),
+12
View File
@@ -96,6 +96,13 @@ pub struct Build {
pub link: LinkMode,
#[serde(default)]
pub flags: Vec<String>,
/// Versión de zig a usar para ESTA receta, distinta del zig por defecto del lab. Sirve de
/// escotilla por-receta para esquivar la regresión de zig 0.14 (que miscompila ciertos binarios
/// musl dinámicos: flex/cmake/python/binutils/…): `zig_version = "0.13.0"` los construye con el
/// zig bueno sin tocar el toolchain global ni el baseline de reproducibilidad (estas piezas son
/// herramientas, no inputs del of_tree del 4/4). `None` ⇒ el zig por defecto. Ver SDD 11 §7.2b.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub zig_version: Option<String>,
/// Overrides explícitos de las fases del build. Lo no especificado se decide por
/// heurística en el lab (ver `docs/02-build-lab.md` §4).
#[serde(default)]
@@ -209,6 +216,11 @@ impl Recipe {
self.build.target.as_bytes().to_vec(),
self.build.link.as_str().as_bytes().to_vec(),
];
// Sólo entra al hash si está fijado: una receta sin `zig_version` mantiene su hash de antes
// de existir el campo (compatibilidad hacia atrás; el baseline 9adefb82 no se mueve).
if let Some(zv) = &self.build.zig_version {
v.push(format!("zig:{zv}").into_bytes());
}
for p in &self.source.patches {
let resolved = self.base_dir.join(p);
let bytes = std::fs::read(&resolved).map_err(|e| {
+9 -5
View File
@@ -34,14 +34,18 @@ tarball = "https://ftp.gnu.org/gnu/binutils/binutils-2.45.1.tar.gz"
sha256 = "2b63ae0ad015eb2755294f127bfd179c1849979dd87b6e8e1a7376964a4323dc"
[build]
compiler = "zig-cc"
target = "x86_64-linux-musl"
link = "dynamic"
compiler = "zig-cc"
target = "x86_64-linux-musl"
link = "dynamic"
# zig 0.13.0 en vez del 0.16: con zig 0.14+ binutils miscompila (`as` SEGFAULTEA, "Internal error").
# Es la REGRESIÓN de zig 0.14 en C/musl (bisect con flex: 0.13.0 limpio, 0.14+ rotos). Antes se
# esquivaba con CC=gcc; ahora con el zig bueno por-receta gcc deja de hacer falta. SDD 11 §7.2b.
zig_version = "0.13.0"
[build.phases]
# --disable-nls: sin gettext (ausente en el toolchain hermético). --disable-werror: binutils promueve
# varios warnings a error por defecto. --disable-gprofng: el profiler de binutils 2.4x no es parte del
# toolchain ar/ld/as y arrastra chequeos de profiling (mcount) innecesarios.
configure = "CC=gcc CXX=g++ ./configure --prefix=/usr --disable-nls --disable-werror --disable-gprofng"
compile = "CC=gcc CXX=g++ make -j\"$(nproc)\""
configure = "./configure --prefix=/usr --disable-nls --disable-werror --disable-gprofng"
compile = "make -j\"$(nproc)\""
install = "make install DESTDIR=/out PREFIX=/usr"
+11 -9
View File
@@ -15,7 +15,8 @@
# libeu.a pero objtool NO referencia (sólo necesita el core de libelf) ⇒ basta que
# COMPILEN; las funcs argp quedan como undefined inertes en el .a.
# - rawmemchr: shim inline (lo usa libelf/elf_getarsym.c), force-include global.
# CC=gcc (NO zig): consistente con binutils/openssl; evita miscompilación.
# zig 0.13.0 (NO gcc): la regresión de zig 0.14 en C/musl (bisect con flex) miscompila; 0.13.0 la
# esquiva, consistente con binutils/openssl/flex. $CC (= zig cc del lab) para los stubs y las fases.
name = "elfutils"
version = "0.194"
@@ -25,9 +26,10 @@ tarball = "https://sourceware.org/elfutils/ftp/0.194/elfutils-0.194.tar.bz2"
sha256 = "09e2ff033d39baa8b388a2d7fbc5390bfde99ae3b7c67c7daaf7433fbcf0f01e"
[build]
compiler = "zig-cc"
target = "x86_64-linux-musl"
link = "dynamic"
compiler = "zig-cc"
target = "x86_64-linux-musl"
link = "dynamic"
zig_version = "0.13.0"
[build.phases]
configure = """
@@ -92,13 +94,13 @@ printf '%s\\n' \
'void argp_error(const struct argp_state*s,const char*f,...){}' \
'void argp_state_help(const struct argp_state*s,FILE*t,unsigned f){}' \
'void argp_usage(const struct argp_state*s){}' > compat/argp-stub.c && \
gcc -c compat/argp-stub.c -o compat/argp-stub.o && ar rcs compat/libargp.a compat/argp-stub.o && \
$CC -c compat/argp-stub.c -o compat/argp-stub.o && ar rcs compat/libargp.a compat/argp-stub.o && \
printf '%s\\n' \
'typedef struct{int x;}FTS;typedef struct{int y;}FTSENT;' \
'FTS*fts_open(char*const*p,int o,int(*c)(const FTSENT**,const FTSENT**)){return 0;}' \
'FTSENT*fts_read(FTS*f){return 0;}' 'FTSENT*fts_children(FTS*f,int o){return 0;}' \
'int fts_set(FTS*f,FTSENT*e,int o){return 0;}' 'int fts_close(FTS*f){return 0;}' > compat/fts-stub.c && \
gcc -c compat/fts-stub.c -o compat/fts-stub.o && ar rcs compat/libfts.a compat/fts-stub.o && \
$CC -c compat/fts-stub.c -o compat/fts-stub.o && ar rcs compat/libfts.a compat/fts-stub.o && \
printf '%s\\n' \
'struct obstack;void(*obstack_alloc_failed_handler)(void);int obstack_exit_failure;' \
'int _obstack_begin(struct obstack*o,int s,int a,void*(*c)(long),void(*f)(void*)){return 1;}' \
@@ -106,15 +108,15 @@ printf '%s\\n' \
'void _obstack_newchunk(struct obstack*o,int l){}' 'int _obstack_allocated_p(struct obstack*o,void*p){return 0;}' \
'void _obstack_free(struct obstack*o,void*p){}' 'int _obstack_memory_used(struct obstack*o){return 0;}' \
> compat/obstack-stub.c && \
gcc -c compat/obstack-stub.c -o compat/obstack-stub.o && ar rcs compat/libobstack.a compat/obstack-stub.o && \
CC=gcc CFLAGS="-O2 -g -I$PWD/compat" CPPFLAGS="-I$PWD/compat" LDFLAGS="-L$PWD/compat" \
$CC -c compat/obstack-stub.c -o compat/obstack-stub.o && ar rcs compat/libobstack.a compat/obstack-stub.o && \
CFLAGS="-O2 -g -I$PWD/compat" CPPFLAGS="-I$PWD/compat" LDFLAGS="-L$PWD/compat" \
./configure --prefix=/usr --libdir=/usr/lib --disable-nls --disable-demangler \
--disable-libdebuginfod --disable-debuginfod --program-prefix=eu-
"""
# El -include del shim de rawmemchr va SÓLO en compile (en configure rompe el test "undeclared builtins"
# de autoconf). CFLAGS aquí reemplaza el de configure (mismo valor + el -include); AM_CFLAGS (warnings/
# -Werror) se preserva.
compile = "CC=gcc make -C lib CFLAGS=\"-O2 -g -I$PWD/compat -include $PWD/compat/musl-extra.h\" && CC=gcc make -C libelf libelf.a libelf.so CFLAGS=\"-O2 -g -I$PWD/compat -include $PWD/compat/musl-extra.h\""
compile = "make -C lib CFLAGS=\"-O2 -g -I$PWD/compat -include $PWD/compat/musl-extra.h\" && make -C libelf libelf.a libelf.so CFLAGS=\"-O2 -g -I$PWD/compat -include $PWD/compat/musl-extra.h\""
install = """
mkdir -p /out/usr/lib/pkgconfig /out/usr/include && \
cp -a libelf/libelf.a libelf/libelf.so* /out/usr/lib/ && \
+11 -10
View File
@@ -14,16 +14,17 @@ tarball = "https://github.com/westes/flex/releases/download/v2.6.4/flex-2.6.4.ta
sha256 = "e87aae032bf07c26f85ac0ed3250998c37621d95f8bd748b31f15b33c45ee995"
[build]
compiler = "zig-cc"
target = "x86_64-linux-musl"
link = "dynamic"
compiler = "zig-cc"
target = "x86_64-linux-musl"
link = "dynamic"
# zig 0.13.0 en vez del 0.16 por defecto: flex se auto-construye (un `stage1flex` procesa su propio
# scan.l → stage1scan.c) y con zig 0.14+ ese stage1flex sale miscompilado → "unrecognized rule". El
# bug es una REGRESIÓN introducida en zig 0.14 (bisect: 0.13.0 compila flex LIMPIO, 0.14/0.15/0.16
# fallan), específica del path musl DINÁMICO. Antes esto se esquivaba con `CC=gcc`; ahora con el zig
# bueno por-receta ⇒ gcc deja de hacer falta (toolchain 100% zig). No es input del 4/4. SDD 11 §7.2b.
zig_version = "0.13.0"
# CC=gcc/CXX=g++ (NO zig), igual que binutils/python3/cmake: flex se auto-construye (un `stage1flex`
# procesa su propio scan.l → stage1scan.c). Con `zig cc` ese stage1flex sale miscompilado y falla con
# "unrecognized rule" al parsear scan.l (zig 0.16 miscompila binarios musl no-triviales). gcc nativo
# (como Alpine) lo produce funcional. link=dynamic: gcc enlaza el musl del toolchain (estático con
# libtool+stage-scanner es frágil; flex es herramienta de build-time, corre, no se enlaza en el 4/4).
[build.phases]
configure = "CC=gcc CXX=g++ ./configure --prefix=/usr --disable-nls --disable-shared"
compile = "CC=gcc CXX=g++ make -j\"$(nproc)\""
configure = "./configure --prefix=/usr --disable-nls --disable-shared"
compile = "make -j\"$(nproc)\""
install = "make install DESTDIR=/out PREFIX=/usr"
+9 -5
View File
@@ -22,11 +22,15 @@ tarball = "https://github.com/openssl/openssl/releases/download/openssl-3.5.4/op
sha256 = "967311f84955316969bdb1d8d4b983718ef42338639c621ec4c34fddef355e99"
[build]
compiler = "zig-cc"
target = "x86_64-linux-musl"
link = "static"
compiler = "zig-cc"
target = "x86_64-linux-musl"
link = "static"
# zig 0.13.0 en vez del 0.16: el gcc era precautorio por el riesgo de miscompilación de zig 0.16 en
# binarios grandes (regresión 0.14). 0.13.0 lo esquiva (mismo bisect que flex). openssl es C+asm GAS,
# que el assembler integrado de zig cc (clang) maneja. Con el zig bueno por-receta gcc no hace falta.
zig_version = "0.13.0"
[build.phases]
configure = "CC=gcc ./Configure linux-x86_64 --prefix=/usr --libdir=lib no-shared no-docs no-tests no-apps"
compile = "CC=gcc make -j\"$(nproc)\" build_libs"
configure = "./Configure linux-x86_64 --prefix=/usr --libdir=lib no-shared no-docs no-tests no-apps"
compile = "make -j\"$(nproc)\" build_libs"
install = "make DESTDIR=/out install_dev"
+8 -4
View File
@@ -27,14 +27,18 @@ tarball = "https://www.python.org/ftp/python/3.12.10/Python-3.12.10.tgz"
sha256 = "15d9c623abfd2165fe816ea1fb385d6ed8cf3c664661ab357f1782e3036a6dac"
[build]
compiler = "zig-cc"
target = "x86_64-linux-musl"
link = "dynamic"
compiler = "zig-cc"
target = "x86_64-linux-musl"
link = "dynamic"
# zig 0.13.0 (NO gcc): con zig 0.14+ el intérprete `_bootstrap_python` SEGFAULTEA en "freeze modules"
# (regresión 0.14 en C/musl, misma clase que flex). 0.13.0 la esquiva ⇒ gcc deja de hacer falta.
# CFLAGS=-Wno-error=date-time se conserva: clang (zig cc) NO silencia -Wdate-time vía SOURCE_DATE_EPOCH.
zig_version = "0.13.0"
[deps]
build = ["zlib"]
[build.phases]
configure = "CC=gcc CXX=g++ ./configure --prefix=/usr --without-ensurepip --disable-test-modules --with-ensurepip=no CFLAGS=\"-Wno-error=date-time\""
configure = "./configure --prefix=/usr --without-ensurepip --disable-test-modules --with-ensurepip=no CFLAGS=\"-Wno-error=date-time\""
compile = "make -j\"$(nproc)\""
install = "make install DESTDIR=/out"