Etapa G: tanda C tier-2 construye+corre — 6 al corpus (45->51) + wrap abuild local
BUILD-YIELD C MEDIDO (no especulacion): 6/8 de la tanda construyen+corren como ELF
estatico musl. Promovidos: file 5.47, gawk 5.3.2, gzip 1.14, tar 1.35, tree 2.3.2,
which 2.23 (+ sus parches musl de Alpine).
Texture honesta del tier-2 C (la friccion que el tier-1 Rust no tiene):
- gawk/tar/tree/which: zig-cc directo, sin tocar nada.
- file: zig-cc MISCOMPILA -> el `file` recien hecho segfaultea generando magic.mgc
(mismo sintoma que binutils). Escape compiler="gcc" -> construye.
- gzip: (1) configure "C compiler cannot create executables" con zig-cc -> compiler="gcc";
(2) luego el install fallaba por `local i;` de la package() de Alpine.
- jq (oniguruma), sed (perl): NO build-friction sino dep faltante en el corpus
(completitud) -> quedan pendientes hasta importar esas libs.
Fix generico que destrabo gzip (y futuros): el importador Alpine ENVUELVE el cuerpo de
build()/package() en una funcion shell. abuild los corre COMO funciones (donde `local`
es valido); el lab corre la fase plana bajo sh -c, donde `local` fuera de funcion es
error. Envolver restaura el contexto de abuild sin tocar el sandbox ni las fases planas
del corpus (solo lo importado). test translate_wraps_body_in_function_for_local.
Confirmado: los 6 binarios corren (--version). file/gzip llevan compiler="gcc" en su
receta (escape declarativo, gueto conocido).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
89061cac03d517a89b3d5bd06de7f83146e7aa26 · ruwaq sergio · 2026-06-21T14:07:08Z (4 killa ñawpaq) · tayta db84ee664 · kay commitpi sach'ata qhaway
Semántico sayk'uy · 2
crates/hammer-cli/src/alpine_import.rs
diff --git a/crates/hammer-cli/src/alpine_import.rs b/crates/hammer-cli/src/alpine_import.rs
index f9e1ec2b..c29d83e7 100644
--- a/crates/hammer-cli/src/alpine_import.rs
+++ b/crates/hammer-cli/src/alpine_import.rs
@@ -260,13 +260,20 @@ fn func_body(text: &str, name: &str) -> Option<String> {
/// NO toca `$CBUILD/$CHOST/$srcdir/$builddir` ni `--shared`. `$CBUILD/$CHOST` los RESUELVE el lab en
/// runtime (env con el triple nativo saneado, Etapa G Fase 3 — `hammer-build`); `$srcdir/$builddir` y
/// `--shared` quedan para el humano. `make DESTDIR="$pkgdir"` → `make DESTDIR="/out"`.
+///
+/// Además ENVUELVE el cuerpo en una función shell: abuild ejecuta `build()/package()` COMO funciones,
+/// donde `local i; …` es válido; el lab corre la fase bajo `sh -c "<cuerpo>"` plano, donde `local`
+/// fuera de función es error (rompía gzip al instalar). Envolver restaura el contexto de abuild sin
+/// tocar el sandbox ni las fases planas escritas a mano del corpus (sólo se envuelve lo importado).
fn translate_abuild(body: &str, name: &str, version: &str) -> String {
- body.replace("${pkgdir}", "/out")
+ let subst = body
+ .replace("${pkgdir}", "/out")
.replace("$pkgdir", "/out")
.replace("${pkgname}", name)
.replace("$pkgname", name)
.replace("${pkgver}", version)
- .replace("$pkgver", version)
+ .replace("$pkgver", version);
+ format!("_abuild_phase() {{\n{subst}\n}}\n_abuild_phase")
}
/// Primer hash de un bloque `sha512sums="<hash> <fichero>\n..."`.
@@ -348,6 +355,16 @@ package() {
assert!(!t.contains("$pkg"));
}
+ #[test]
+ fn translate_wraps_body_in_function_for_local() {
+ // abuild package() usa `local i;` (válido en función); el lab corre la fase plana ⇒ hay que
+ // envolverla o `local` rompe (gzip). El cuerpo queda DENTRO de la función, invocada al final.
+ let t = translate_abuild("local i; for i in a b; do mv $i /out/; done", "p", "1");
+ assert!(t.starts_with("_abuild_phase() {"), "{t}");
+ assert!(t.trim_end().ends_with("_abuild_phase"), "se invoca al final: {t}");
+ assert!(t.contains("local i;"), "el `local` se preserva dentro de la función: {t}");
+ }
+
#[test]
fn no_patches_is_empty() {
let apk = "pkgname=zlib\npkgver=1.3\nsource=\"https://zlib.net/zlib-$pkgver.tar.gz\"\nsha512sums=\"x z\"\n";
diff --git a/recipes/0001-softmagic-compare-FILE_GUID-values-in-wire-format.patch b/recipes/0001-softmagic-compare-FILE_GUID-values-in-wire-format.patch
new file mode 100644
index 00000000..49deb6b4
--- /dev/null
+++ b/recipes/0001-softmagic-compare-FILE_GUID-values-in-wire-format.patch
@@ -0,0 +1,76 @@
+From 80297b6e252963d2c08f84f40bc3b5e517505ff4 Mon Sep 17 00:00:00 2001
+From: Natanael Copa <ncopa@alpinelinux.org>
+Date: Tue, 10 Mar 2026 11:54:48 +0100
+Subject: [PATCH] softmagic: compare FILE_GUID values in wire format
+
+Comparing GUIDs with memcmp() on native struct storage breaks on
+big-endian systems like s390x, since EFI GUIDs are stored in mixed-endian
+wire format. Convert the parsed magic GUID to on-disk byte order before
+comparing against file contents.
+
+Fixes efi-signature-list-sha256 test on s390x.
+---
+ src/file.h | 1 +
+ src/funcs.c | 20 ++++++++++++++++++++
+ src/softmagic.c | 2 +-
+ 3 files changed, 22 insertions(+), 1 deletion(-)
+
+diff --git a/src/file.h b/src/file.h
+index 06cf91d1..6f4087cd 100644
+--- a/src/file.h
++++ b/src/file.h
+@@ -559,6 +559,7 @@ file_protected int file_checkfmt(char *, size_t, const char *);
+ file_protected size_t file_printedlen(const struct magic_set *);
+ file_protected int file_print_guid(char *, size_t, const uint64_t *);
+ file_protected int file_parse_guid(const char *, uint64_t *);
++file_protected int file_compare_guid(const uint64_t *, const uint64_t *);
+ file_protected int file_replace(struct magic_set *, const char *, const char *);
+ file_protected int file_printf(struct magic_set *, const char *, ...)
+ __attribute__((__format__(__printf__, 2, 3)));
+diff --git a/src/funcs.c b/src/funcs.c
+index fa62b26d..c3cc0341 100644
+--- a/src/funcs.c
++++ b/src/funcs.c
+@@ -955,6 +955,26 @@ file_print_guid(char *str, size_t len, const uint64_t *guid)
+ #endif
+ }
+
++file_protected int
++file_compare_guid(const uint64_t *mguid, const uint64_t *fguid)
++{
++ const struct guid *m = CAST(const struct guid *, CAST(const void *, mguid));
++ const unsigned char *f = CAST(const unsigned char *, CAST(const void *, fguid));
++ unsigned char mbuf[16];
++
++ mbuf[0] = (unsigned char)(m->data1);
++ mbuf[1] = (unsigned char)(m->data1 >> 8);
++ mbuf[2] = (unsigned char)(m->data1 >> 16);
++ mbuf[3] = (unsigned char)(m->data1 >> 24);
++ mbuf[4] = (unsigned char)(m->data2);
++ mbuf[5] = (unsigned char)(m->data2 >> 8);
++ mbuf[6] = (unsigned char)(m->data3);
++ mbuf[7] = (unsigned char)(m->data3 >> 8);
++ memcpy(mbuf + 8, m->data4, 8);
++
++ return memcmp(mbuf, f, 16);
++}
++
+ file_protected int
+ file_pipe_closexec(int *fds)
+ {
+diff --git a/src/softmagic.c b/src/softmagic.c
+index 1a198005..ee85356d 100644
+--- a/src/softmagic.c
++++ b/src/softmagic.c
+@@ -2407,7 +2407,7 @@ magiccheck(struct magic_set *ms, struct magic *m, file_regex_t **m_cache)
+ return matched;
+ case FILE_GUID:
+ l = 0;
+- v = memcmp(m->value.guid, p->guid, sizeof(p->guid));
++ v = file_compare_guid(m->value.guid, p->guid);
+ break;
+ default:
+ file_magerror(ms, "invalid type %d in magiccheck()", m->type);
+--
+2.53.0
+
diff --git a/recipes/0002-inliniac-revert-previous-and-always-offset.patch b/recipes/0002-inliniac-revert-previous-and-always-offset.patch
new file mode 100644
index 00000000..177e2579
--- /dev/null
+++ b/recipes/0002-inliniac-revert-previous-and-always-offset.patch
@@ -0,0 +1,25 @@
+Patch-Source: https://github.com/file/file/commit/c54605718190ad8fe9c25cb475f1f32ca7cd54f7
+---
+From c54605718190ad8fe9c25cb475f1f32ca7cd54f7 Mon Sep 17 00:00:00 2001
+From: Christos Zoulas <christos@zoulas.com>
+Date: Sun, 12 Apr 2026 22:15:14 +0000
+Subject: [PATCH] PR/725: inliniac: Revert previous and always set offset.
+
+---
+ src/softmagic.c | 5 ++---
+ 1 file changed, 2 insertions(+), 3 deletions(-)
+
+diff --git a/src/softmagic.c b/src/softmagic.c
+index 9aaf8b3e5..7cf7c798f 100644
+--- a/src/softmagic.c
++++ b/src/softmagic.c
+@@ -1577,8 +1577,7 @@ msetoffset(struct magic_set *ms, struct magic *m, struct buffer *bb,
+ ms->offset = offset;
+ ms->eoffset = 0;
+ } else {
+- if (b->fd != -1)
+- ms->offset = ms->eoffset + offset;
++ ms->offset = ms->eoffset + offset;
+ }
+ }
+ if ((ms->flags & MAGIC_DEBUG) != 0) {
diff --git a/recipes/echild-strerror.patch b/recipes/echild-strerror.patch
new file mode 100644
index 00000000..5626b690
--- /dev/null
+++ b/recipes/echild-strerror.patch
@@ -0,0 +1,14 @@
+musl and glibc use different string values for the ECHILD errno.
+
+diff -upr gawk-5.1.0.orig/test/testext.ok gawk-5.1.0/test/testext.ok
+--- gawk-5.1.0.orig/test/testext.ok 2021-02-06 12:39:05.990158877 +0100
++++ gawk-5.1.0/test/testext.ok 2021-02-06 12:39:24.080236494 +0100
+@@ -21,7 +21,7 @@ var_test: sym_update of ARGC failed - co
+ var_test: sym_update("testvar") succeeded
+ var_test() returned 1, test_var = 42
+
+-test_errno() returned 1, ERRNO = No child processes
++test_errno() returned 1, ERRNO = No child process
+
+ fubar = 9
+ rumpus = -5
diff --git a/recipes/file.toml b/recipes/file.toml
new file mode 100644
index 00000000..34d280d9
--- /dev/null
+++ b/recipes/file.toml
@@ -0,0 +1,40 @@
+# Importada de Alpine aports por `hammer import-alpine` (Etapa G). PUNTO DE PARTIDA — pero
+# YA trae los parches de musl de Alpine (lo que un import de nix pierde). Pendiente: el
+# sha256 del tarball (el wrapper lo calcula), y adaptar build/install del shell de abuild.
+name = "file"
+version = "5.47"
+
+[source]
+tarball = "https://astron.com/pub/file/file-5.47.tar.gz"
+# FIXME sha256: el wrapper lo calcula (Alpine publica sha512). sha512 de Alpine:
+# sha512 = "a19c1f2b584bcfa70d4a02545667a90ff9e069523a5fc7b84d79b2b32cb7e59b73c555943784a13d56fa6d4618e18a254fd6102187def1a4a7d936b41ae7ce31"
+sha256 = "45672fec165cb4cc1358a2d76b5d57d22876dcb97ab169427ac385cbe1d5597a"
+patches = ["0001-softmagic-compare-FILE_GUID-values-in-wire-format.patch", "0002-inliniac-revert-previous-and-always-offset.patch"]
+
+[build]
+compiler = "gcc"
+target = "x86_64-linux-musl"
+link = "static"
+flags = []
+
+[build.phases]
+# de build() de Alpine (traducido; el lab provee $CBUILD/$CHOST — Etapa G Fase 3; revisá --shared para estático):
+compile = '''
+_abuild_phase() {
+./configure \
+ --build=$CBUILD \
+ --host=$CHOST \
+ --prefix=/usr \
+ --datadir=/usr/share \
+ --enable-static
+ make
+}
+_abuild_phase
+'''
+# de package() de Alpine (traducido $pkgdir→/out):
+install = '''
+_abuild_phase() {
+make DESTDIR="/out" install
+}
+_abuild_phase
+'''
diff --git a/recipes/gawk.toml b/recipes/gawk.toml
new file mode 100644
index 00000000..d436ea4a
--- /dev/null
+++ b/recipes/gawk.toml
@@ -0,0 +1,36 @@
+# Importada de Alpine aports por `hammer import-alpine` (Etapa G). PUNTO DE PARTIDA — pero
+# YA trae los parches de musl de Alpine (lo que un import de nix pierde). Pendiente: el
+# sha256 del tarball (el wrapper lo calcula), y adaptar build/install del shell de abuild.
+name = "gawk"
+version = "5.3.2"
+
+[source]
+tarball = "https://ftp.gnu.org/gnu/gawk/gawk-5.3.2.tar.xz"
+# FIXME sha256: el wrapper lo calcula (Alpine publica sha512). sha512 de Alpine:
+# sha512 = "2268150fa35ae049a6ff3d0d0fa110db10477014c25f50e2ab4e3ee5fd60133369d2a994f59db4eb718020a0af5c4003ae7278c63e7fffa72f431ff4a1429e48"
+sha256 = "f8c3486509de705192138b00ef2c00bbbdd0e84c30d5c07d23fc73a9dc4cc9cc"
+patches = ["echild-strerror.patch"]
+
+[build]
+compiler = "zig-cc"
+target = "x86_64-linux-musl"
+link = "static"
+flags = []
+
+[build.phases]
+# de build() de Alpine (traducido; el lab provee $CBUILD/$CHOST — Etapa G Fase 3; revisá --shared para estático):
+compile = '''
+CFLAGS="$CFLAGS -flto=auto" \
+ ./configure \
+ --build=$CBUILD \
+ --host=$CHOST \
+ --prefix=/usr \
+ --sysconfdir=/etc \
+ --mandir=/usr/share/man \
+ --infodir=/usr/share/info \
+ --disable-nls \
+ --disable-pma
+ make
+'''
+# de package() de Alpine (traducido $pkgdir→/out):
+install = "make DESTDIR=\"/out\" install"
diff --git a/recipes/gcc15.patch b/recipes/gcc15.patch
new file mode 100644
index 00000000..c9174df3
--- /dev/null
+++ b/recipes/gcc15.patch
@@ -0,0 +1,44 @@
+Patch-Source: https://lists.gnu.org/archive/html/which-bugs/2025-03/msg00000.html
+---
+From 16a1647fc26953fab659de5f55d4c0defdfb894f Mon Sep 17 00:00:00 2001
+From: Khem Raj <raj.khem@gmail.com>
+Date: Sat, 22 Mar 2025 17:56:19 -0700
+Subject: [PATCH] getopt: Fix signature of getenv function
+
+This happens on musl systems using GCC 15
+
+../which-2.21/getopt.h:106:12: error: conflicting types for 'getopt'; have
+'int(void)'
+ 106 | extern int getopt ();
+ | ^~~~~~
+---
+ getopt.c | 2 +-
+ getopt.h | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/getopt.c b/getopt.c
+index 9ac2ed6..7e14270 100644
+--- a/getopt.c
++++ b/getopt.c
+@@ -205,7 +205,7 @@ static char *posixly_correct;
+ /* Avoid depending on library functions or files
+ whose names are inconsistent. */
+
+-char *getenv();
++char *getenv(const char*);
+
+ static char *my_index(str, chr) const char *str;
+ int chr;
+diff --git a/getopt.h b/getopt.h
+index f080053..635fc46 100644
+--- a/getopt.h
++++ b/getopt.h
+@@ -102,7 +102,7 @@ struct option {
+ errors, only prototype getopt for the GNU C library. */
+ extern int getopt(int argc, char *const *argv, const char *shortopts);
+ #else /* not __GNU_LIBRARY__ */
+-extern int getopt();
++extern int getopt(int, char * const [], const char *);
+ #endif /* __GNU_LIBRARY__ */
+ extern int getopt_long(int argc, char *const *argv, const char *shortopts, const struct option *longopts, int *longind);
+ extern int getopt_long_only(int argc, char *const *argv, const char *shortopts, const struct option *longopts,
diff --git a/recipes/gzip.toml b/recipes/gzip.toml
new file mode 100644
index 00000000..ad76df79
--- /dev/null
+++ b/recipes/gzip.toml
@@ -0,0 +1,54 @@
+# Importada de Alpine aports por `hammer import-alpine` (Etapa G). PUNTO DE PARTIDA — pero
+# YA trae los parches de musl de Alpine (lo que un import de nix pierde). Pendiente: el
+# sha256 del tarball (el wrapper lo calcula), y adaptar build/install del shell de abuild.
+name = "gzip"
+version = "1.14"
+
+[source]
+tarball = "https://ftp.gnu.org/gnu/gzip/gzip-1.14.tar.xz"
+# FIXME sha256: el wrapper lo calcula (Alpine publica sha512). sha512 de Alpine:
+# sha512 = "82aef53188b3e69b51b7ddab5b8c44a11a5b73c0039b22a315a0c7d244694feab0146748add4265901eb1b4c0cee8a9eb69594995f098830d964091af97079c5"
+sha256 = "01a7b881bd220bfdf615f97b8718f80bdfd3f6add385b993dcf6efd14e8c0ac6"
+
+[build]
+compiler = "gcc"
+target = "x86_64-linux-musl"
+link = "static"
+flags = []
+
+[build.phases]
+# de build() de Alpine (traducido; el lab provee $CBUILD/$CHOST — Etapa G Fase 3; revisá --shared para estático):
+compile = '''
+_abuild_phase() {
+# avoid text relocation
+ export DEFS="NO_ASM"
+ ./configure \
+ --build=$CBUILD \
+ --host=$CHOST \
+ --prefix=/usr \
+ --mandir=/usr/share/man \
+ --infodir=/usr/share/info
+ make
+}
+_abuild_phase
+'''
+# de package() de Alpine (traducido $pkgdir→/out):
+install = '''
+_abuild_phase() {
+make DESTDIR=/out install
+
+ rm -rf "/out"/usr/lib/charset.alias
+ rmdir -p "/out"/usr/lib 2>/dev/null || true
+
+ mkdir -p "/out"/bin
+ local i; for i in gzip gunzip zcat; do
+ mv "/out"/usr/bin/$i "/out"/bin/
+ done
+
+ # http://bugs.alpinelinux.org/issues/4011
+ ln -sf /bin/gunzip "/out"/usr/bin/uncompress
+}
+_abuild_phase
+'''
+
+# depends de runtime de Alpine (NO build-deps): less
diff --git a/recipes/ignore-apk-tools-checksums.patch b/recipes/ignore-apk-tools-checksums.patch
new file mode 100644
index 00000000..cd6a1e1c
--- /dev/null
+++ b/recipes/ignore-apk-tools-checksums.patch
@@ -0,0 +1,17 @@
+Make GNU tar ignore the apk-tools header fields
+
+This should problably not be upstreamed
+
+diff --git a/src/xheader.c b/src/xheader.c
+index 1347ce1..89b3a44 100644
+--- a/src/xheader.c
++++ b/src/xheader.c
+@@ -1777,5 +1777,8 @@ struct xhdr_tab const xhdr_tab[] = {
+ they are restored *only once* during extraction later on. */
+ { "SCHILY.xattr", xattr_coder, xattr_decoder, 0, true },
+
++ /* Ignore apk-tools headers */
++ { "APK-TOOLS", dummy_coder, dummy_decoder, 0, true },
++
+ { NULL, NULL, NULL, 0, false }
+ };
diff --git a/recipes/tar.toml b/recipes/tar.toml
new file mode 100644
index 00000000..5fc9c995
--- /dev/null
+++ b/recipes/tar.toml
@@ -0,0 +1,41 @@
+# Importada de Alpine aports por `hammer import-alpine` (Etapa G). PUNTO DE PARTIDA — pero
+# YA trae los parches de musl de Alpine (lo que un import de nix pierde). Pendiente: el
+# sha256 del tarball (el wrapper lo calcula), y adaptar build/install del shell de abuild.
+name = "tar"
+version = "1.35"
+
+[source]
+tarball = "https://ftp.gnu.org/gnu/tar/tar-1.35.tar.xz"
+# FIXME sha256: el wrapper lo calcula (Alpine publica sha512). sha512 de Alpine:
+# sha512 = "8b84ed661e6c878fa33eb5c1808d20351e6f40551ac63f96014fb0d0b9c72d5d94d8865d39e36bcb184fd250f84778a3b271bbd8bd2ceb69eece0c3568577510"
+sha256 = "4d62ff37342ec7aed748535323930c7cf94acf71c3591882b26a7ea50f3edc16"
+patches = ["ignore-apk-tools-checksums.patch"]
+
+[build]
+compiler = "zig-cc"
+target = "x86_64-linux-musl"
+link = "static"
+flags = []
+
+[build.phases]
+# de build() de Alpine (traducido; el lab provee $CBUILD/$CHOST — Etapa G Fase 3; revisá --shared para estático):
+compile = '''
+export CFLAGS="$CFLAGS -flto=auto"
+ gl_cv_func_gettimeofday_clobber=no \
+ gl_cv_func_tzset_clobber=no \
+ ./configure \
+ --build=$CBUILD \
+ --host=$CHOST \
+ --prefix=/usr \
+ --sysconfdir=/etc \
+ --mandir=/usr/share/man \
+ --localstatedir=/var
+ make
+'''
+# de package() de Alpine (traducido $pkgdir→/out):
+install = '''
+make DESTDIR="/out" install
+
+ mkdir -p "/out"/bin
+ mv -v "/out"/usr/bin/tar "/out"/bin/
+'''
diff --git a/recipes/tree.toml b/recipes/tree.toml
new file mode 100644
index 00000000..eb67fe90
--- /dev/null
+++ b/recipes/tree.toml
@@ -0,0 +1,23 @@
+# Importada de Alpine aports por `hammer import-alpine` (Etapa G). PUNTO DE PARTIDA — pero
+# YA trae los parches de musl de Alpine (lo que un import de nix pierde). Pendiente: el
+# sha256 del tarball (el wrapper lo calcula), y adaptar build/install del shell de abuild.
+name = "tree"
+version = "2.3.2"
+
+[source]
+tarball = "https://gitlab.com/OldManProgrammer/unix-tree/-/archive/2.3.2/unix-tree-2.3.2.tar.gz"
+# FIXME sha256: el wrapper lo calcula (Alpine publica sha512). sha512 de Alpine:
+# sha512 = "c22dd6bd6074f521959d31c3c9adbccec5a039029ca818df8345c7572f5b29b4b7b813e9ce4336cc0d600095130a709f7cbf7f88765a7a1e6894b16701d1fcd9"
+sha256 = "513a53cbc42ca1f4ea06af2bab1f5283524a3848266b1d162416f8033afc4985"
+
+[build]
+compiler = "zig-cc"
+target = "x86_64-linux-musl"
+link = "static"
+flags = []
+
+[build.phases]
+# de build() de Alpine (traducido; el lab provee $CBUILD/$CHOST — Etapa G Fase 3; revisá --shared para estático):
+compile = "make LDFLAGS=\"$LDFLAGS\" CFLAGS=\"$CFLAGS\""
+# de package() de Alpine (traducido $pkgdir→/out):
+install = "make PREFIX=\"/out/usr\" MANDIR=\"/out/usr/share/man\" install"
diff --git a/recipes/which.toml b/recipes/which.toml
new file mode 100644
index 00000000..1ddb7294
--- /dev/null
+++ b/recipes/which.toml
@@ -0,0 +1,30 @@
+# Importada de Alpine aports por `hammer import-alpine` (Etapa G). PUNTO DE PARTIDA — pero
+# YA trae los parches de musl de Alpine (lo que un import de nix pierde). Pendiente: el
+# sha256 del tarball (el wrapper lo calcula), y adaptar build/install del shell de abuild.
+name = "which"
+version = "2.23"
+
+[source]
+tarball = "https://ftp.gnu.org/gnu/which/which-2.23.tar.gz"
+# FIXME sha256: el wrapper lo calcula (Alpine publica sha512). sha512 de Alpine:
+# sha512 = "738807f79e8cfc5967541a28ae7021247c04c4177279f09be2c19c069af450a7e3b19baf9079fe5569b25b4630bb400be242a123647e52c9fe54f0ad007317bf"
+sha256 = "a2c558226fc4d9e4ce331bd2fd3c3f17f955115d2c00e447618a4ef9978a2a73"
+patches = ["gcc15.patch"]
+
+[build]
+compiler = "zig-cc"
+target = "x86_64-linux-musl"
+link = "static"
+flags = []
+
+[build.phases]
+# de build() de Alpine (traducido; el lab provee $CBUILD/$CHOST — Etapa G Fase 3; revisá --shared para estático):
+compile = '''
+./configure \
+ --build=$CBUILD \
+ --host=$CHOST \
+ --prefix=/usr
+ make
+'''
+# de package() de Alpine (traducido $pkgdir→/out):
+install = "make DESTDIR=\"/out\" install"