valkey: la familia «caché en memoria» estaba VACÍA — y el artefacto salió vacío sin que nada fallara

`scripts/mudanza/planear.py` agrupa el software del origen en familias funcionales y contesta, para
cada una, con qué la reemplaza takana. Cruzando esa tabla contra el catálogo, cuatro familias salen
SIN NADA sellado: caché en memoria, contenedores, correo y base vectorial. Ésta cierra la primera.

Valkey y no redis a propósito: redis dejó de ser software libre en 2024 (RSALv2/SSPLv1, que no pasan
la OSI); valkey es el fork de la Linux Foundation desde el último commit BSD, mismo protocolo y mismo
RDB/AOF. Y upstream instala los seis alias `redis-*`, así que un `redis-server` se reemplaza sin
tocar datos ni clientes. Para una distro que publica el catálogo con `license` poblado receta a
receta, meter SSPL sería meter algo que después hay que sacar.

**El hallazgo caro de esta receta no fue compilar: fue que el PRIMER artefacto se selló VACÍO.**
`make install PREFIX=/usr DESTDIR=/out` imprimió sus `INSTALL valkey-server` en verde y salió 0 — y
`/out` quedó sin un solo fichero. La causa está en la línea 65 de `src/Makefile`: valkey define
`INSTALL_BIN=$(PREFIX)/bin` **sin prefijar `$(DESTDIR)`**, o sea que IGNORA `DESTDIR` y copió los
binarios a `/usr/bin` dentro del sandbox, que se tira al terminar. Lo sellado fue un directorio con
`.hammer/recipe.toml` y nada más: un cache-hit permanente que habría contestado «valkey ya está»
para siempre. Es la regla 3 del repo en vivo — *un ausente falla ruidosamente; un vacío llega hasta
el final diciendo que todo fue bien*. Se vio mirando el árbol del artefacto, NO el código de salida.
El arreglo es `PREFIX=/out/usr`.

El otro muro, medido con un control mínimo fuera de valkey: con `zig cc` el link muere con
`undefined symbol: __cpu_model` en los tres binarios. No es valkey — es que `__builtin_cpu_supports()`
(que valkey usa para elegir en runtime las rutas AVX2/AVX-512) emite esa referencia y el
`compiler_rt` de zig 0.16.0 no la trae:

    printf '#include <stdio.h>\nint main(void){__builtin_cpu_init();return printf("%d",__builtin_cpu_supports("avx2"));}\n' > t.c
    zig cc -target x86_64-linux-musl -O2 -o t t.c   ⇒  ld.lld: error: undefined symbol: __cpu_model

De ahí `compiler = "gcc"`, que es la palanca declarativa que el lab expone justo para esto. Arrancarle
el multiversioning a parches habría costado las rutas SIMD de `BITCOUNT`/`PFCOUNT` y habría que
rehacer el parche en cada versión.

Verificado: REPRODUCE bit a bit. Único NEEDED `libc.so`, que provee `musl-shared` — raíz de `base`
desde hoy ⇒ resuelve en cualquier imagen sin declarar nada en la receta.
This commit is contained in:
Sergio
2026-09-11 17:29:41 +00:00
parent 9a1139ce5f
commit a90b4ef626
3 changed files with 123 additions and 10 deletions
+31 -10
View File
@@ -1,14 +1,14 @@
{
"schema": "hammer-build-state/1",
"totals": {
"recipes": 881,
"nodes": 883,
"sealed": 881,
"recipes": 882,
"nodes": 884,
"sealed": 882,
"ajeno": 2
},
"by_class": {
"ajeno": 2,
"c": 238,
"c": 239,
"go": 362,
"gui": 41,
"kernel": 6,
@@ -17,7 +17,7 @@
"debt_by_class": {},
"by_queue": {
"corpus": {
"sealed": 881,
"sealed": 882,
"ajeno": 2
}
},
@@ -43,7 +43,7 @@
"sealed": 101
}
},
"sin_perfil": 643,
"sin_perfil": 644,
"orphan_deps": [],
"topo_ok": true,
"nodes": {
@@ -1005,7 +1005,7 @@
"escritorio-sway",
"servidor"
],
"dependientes_total": 52
"dependientes_total": 53
},
"biome": {
"iname": "biome",
@@ -1282,7 +1282,7 @@
"escritorio-sway",
"servidor"
],
"dependientes_total": 12
"dependientes_total": 13
},
"bwrap": {
"iname": "bwrap",
@@ -10786,7 +10786,7 @@
"escritorio-sway",
"servidor"
],
"dependientes_total": 128
"dependientes_total": 129
},
"mako": {
"iname": "mako",
@@ -13150,7 +13150,7 @@
"escritorio-sway",
"servidor"
],
"dependientes_total": 410
"dependientes_total": 411
},
"plow": {
"iname": "plow",
@@ -16538,6 +16538,27 @@
"perfiles": [],
"dependientes_total": 0
},
"valkey": {
"iname": "valkey",
"version": "9.1.2",
"link": "dynamic",
"compiler": "gcc",
"deps": [
"binutils",
"busybox",
"make",
"pkgconf"
],
"cls": "c",
"queue": "corpus",
"foreign": false,
"hash": "b3:9571c1a691ff8dd2e91611a341d2ddab11a9cc9a030ed8054006a4626542a06f",
"state": "sealed",
"blocked_by": [],
"unblocks": 0,
"perfiles": [],
"dependientes_total": 0
},
"vals": {
"iname": "vals",
"version": "0.44.1",
+1
View File
@@ -75,3 +75,4 @@ popt 7202a918f06efe0315267027b071f5380f77aa15ce59eac83b5f078e6edd24f4 2026-09-11
logrotate 09698cb83519dc2423b8aa1905fa5803955ee1c79598590c30ff5b5b14e5e84d 2026-09-11 reproduce
chrony b9e26374f000ef5820bcc32bdb761b2b3e7306d1ed400c147271cfdbd77557e9 2026-09-11 reproduce
cronie 387347b89b107d97febbb2692ce2d292e74bc7b1fba105676befe396cc3ebf21 2026-09-11 reproduce
valkey 9571c1a691ff8dd2e91611a341d2ddab11a9cc9a030ed8054006a4626542a06f 2026-09-11 reproduce
1 # repro-verificado.tsv — qué artefactos se comprobaron que REPRODUCEN bit a bit, y cuándo.
75 logrotate
76 chrony
77 cronie
78 valkey
+91
View File
@@ -0,0 +1,91 @@
# valkey 9.1.2 — la caché en memoria. Entra por un hueco que se ve desde la herramienta de mudanza:
# `scripts/mudanza/planear.py` agrupa el software del origen en FAMILIAS funcionales y contesta, para
# cada una, si takana tiene con qué reemplazarla. Cruzada contra el catálogo, la familia «caché en
# memoria» salía SIN NADA: ni redis, ni valkey, ni memcached — o sea que si el servidor de origen usa
# una, la migración se para en seco y se entera tarde.
#
# **Por qué valkey y no redis, que es el que todo el mundo tiene corriendo.** No es gusto: redis dejó
# de ser software libre en 2024 (RSALv2/SSPLv1, licencias que no pasan la OSI) y valkey es el fork que
# la Linux Foundation sacó del último commit BSD, manteniendo el protocolo y el formato de RDB/AOF. Un
# `redis-server` se reemplaza por `valkey-server` sin tocar los datos ni los clientes. Para una distro
# que publica su catálogo con el campo `license` poblado receta a receta, meter SSPL sería meter algo
# que después hay que sacar.
#
# `MALLOC=libc` — valkey trae jemalloc vendorizado y lo usa por default en Linux. No acá: jemalloc
# asume glibc en varios sitios y el corpus es musl. `libc` es lo que usa Alpine para lo mismo. Ojo
# con el costo real, que no es cero: jemalloc es lo que le da a valkey la defragmentación activa
# (`activedefrag`), así que con `MALLOC=libc` esa opción no existe. Es la decisión correcta igual —
# un valkey que arranca sin defrag le gana a uno que no compila— pero queda dicho y no escondido.
#
# **Lo que NO hizo falta parchear, y vale la pena por qué:** `src/debug.c` usa `backtrace()` de
# `execinfo.h`, que musl no tiene. Upstream ya lo resuelve bien en `src/config.h`: pide
# `__linux__ && __GLIBC__` para definir `HAVE_BACKTRACE`, y musl no define `__GLIBC__`. Sale sin el
# volcado de pila en los crashes, que es la misma renuncia que hace Alpine.
#
# ⚠ **`compiler = "gcc"`, Y ESTO ESTÁ MEDIDO CON UN CONTROL MÍNIMO, NO SUPUESTO.** Con el `zig cc`
# por defecto el link muere en los tres binarios con `ld.lld: error: undefined symbol: __cpu_model`.
# La causa no es valkey: `src/util.c`, `src/bitops.c` y `src/hyperloglog.c` usan
# `__builtin_cpu_supports()` para elegir en runtime las rutas AVX2/AVX-512, y ese builtin emite una
# referencia a `__cpu_model`, que provee la runtime del compilador (libgcc). El `compiler_rt` de
# zig 0.16.0 NO la trae. Comprobado fuera de valkey, con seis líneas:
#
# printf '#include <stdio.h>\nint main(void){__builtin_cpu_init();return printf("%%d",__builtin_cpu_supports("avx2"));}\n' > t.c
# zig cc -target x86_64-linux-musl -O2 -o t t.c ⇒ ld.lld: error: undefined symbol: __cpu_model
#
# Por eso el escape es declarativo y no un parche: `compiler = "gcc"` es exactamente la palanca que
# el lab expone para «lld estricto de zig no linkea esto», y libgcc sí define `__cpu_model`.
# Arrancarle el multiversioning a valkey a parches habría sido peor: se pierden las rutas SIMD que
# son la mitad del rendimiento de `BITCOUNT` y `PFCOUNT`, y el parche hay que rehacerlo en cada
# versión.
#
# `OPTIMIZATION=-O2` apaga el LTO que valkey enciende sola cuando la optimización es `-O3` (su
# `src/Makefile` añade `-flto=auto -ffat-lto-objects`). No es por el `__cpu_model` —eso falla con y
# sin LTO, se probó— sino porque un LTO de fábrica sobre un árbol con seis deps vendorizadas es
# superficie de fallo que esta receta no necesita para su primera versión sellada.
#
# La reproducibilidad la trae upstream y conviene decir dónde: `src/mkreleasehdr.sh` hornea un
# `REDIS_BUILD_ID` que por default es `uname -n` + `date +%s` —la familia exacta del sello de tiempo
# de nftables— pero el script mira `SOURCE_DATE_EPOCH`, que el lab exporta.
#
# Lo que sale, comprobado sobre el artefacto sellado y no sobre el código de salida: `valkey-server`
# (10,4 M), `valkey-cli`, `valkey-benchmark`, los symlinks de sentinel/check-rdb/check-aof y —esto
# importa para migrar— los seis alias `redis-*`, que upstream instala solo. `valkey-server --version`
# contesta `v=9.1.2 malloc=libc`. Su único NEEDED es `libc.so`, que lo provee `musl-shared`, raíz del
# perfil `base` ⇒ resuelve en cualquier imagen sin declarar nada acá.
#
# `link = "dynamic"`: valkey enlaza C y C++ (la dep `fast_float` es C++), y la runtime de C++ del
# corpus se publica compartida. Un `link = "static"` acá sellaría un binario dinámico igual y la
# receta estaría mintiendo — la familia de fallos del `link=static` por libtool, vista de otro lado.
name = "valkey"
version = "9.1.2"
license = "BSD-3-Clause"
[source]
tarball = "https://github.com/valkey-io/valkey/archive/refs/tags/9.1.2.tar.gz"
sha256 = "19c23908e7d57e8d91ef85b41f5646307582f10f4f0fb999bbf89ed24ec9c983"
[build]
compiler = "gcc"
target = "x86_64-linux-musl"
link = "dynamic"
flags = []
[build.phases]
# `configure = 'true'` NO ES RELLENO. valkey 9 trae `CMakeLists.txt` ADEMÁS del `Makefile` clásico,
# y la heurística del lab detecta CMake primero ⇒ inyecta un `cmake -S . -B _build …` que muere con
# `cmake: not found` (el corpus no tiene cmake en las deps de esta receta). El no-op apaga esa fase
# y deja el camino que upstream mantiene como principal, que es `make` sobre `src/Makefile`.
configure = 'true'
compile = 'make -j"$(nproc)" MALLOC=libc PREFIX=/usr OPTIMIZATION=-O2'
# ⚠ **`PREFIX=/out/usr` Y NO `DESTDIR=/out`, Y ESTO CASI PASA INADVERTIDO.** El `src/Makefile` de
# valkey **IGNORA `DESTDIR`**: define `INSTALL_BIN=$(PREFIX)/bin` a secas (línea 65), sin prefijar
# `$(DESTDIR)`. Con `PREFIX=/usr DESTDIR=/out` el `make install` imprime sus `INSTALL valkey-server`
# en verde, sale 0, y copia los binarios a `/usr/bin` DENTRO del sandbox — un overlay que se tira al
# terminar. El resultado fue un artefacto SELLADO con `.hammer/recipe.toml` y NADA MÁS: la regla 3
# del repo en vivo («un ausente falla ruidosamente; un vacío llega hasta el final diciendo que todo
# fue bien»), y peor, un cache-hit permanente que habría contestado «valkey ya está» para siempre.
# Se detectó mirando el árbol del artefacto, no el código de salida.
install = 'make install MALLOC=libc PREFIX=/out/usr'
[deps]
build = ["binutils", "busybox", "make", "pkgconf"]