Desbloquea integrar el workspace tawasuyu (y cualquier crate Rust) en el lab: - detección por Cargo.toml (gana sobre Makefile huérfano, cede ante CMake de un políglota C+Rust). - traducción de triple zig→rustc (x86_64-linux-musl → x86_64-unknown-linux-musl). - compile: cargo build --release --locked --offline --target <triple>; link por wrapper zig cc (CARGO_TARGET_<T>_LINKER) que provee el libgcc_s que el link dinámico musl exige (cierra el 'cannot find -lgcc_s' que destapó M2). link=dynamic añade -C target-feature=-crt-static para dlopen (apps gráficas: Vulkan/Wayland). - install: copia los ejecutables de target/<triple>/release a /out/usr/bin (busybox-safe). - doc 02-build-lab §Cargo (vendoreo de deps para build offline) + receta de ejemplo recipes/llimphi-counter.toml (caso gráfico dinámico). - 5 tests nuevos (34/34 verdes en hammer-build). Coordina con tawasuyu/03_ukupacha/arje/PLAN-ATESTACION-Y-HAMMER §C (milestone: falta BuildSys::Cargo en el lab).
154 lines
7.2 KiB
Markdown
154 lines
7.2 KiB
Markdown
# SDD 02 — El laboratorio de build
|
|
|
|
El laboratorio es la fábrica de artefactos: toma una **receta** (qué compilar y cómo) y
|
|
produce un **árbol de archivos** depositado en el store, direccionado por su hash. Es la única
|
|
parte del sistema que se permite ser funcional/determinista, porque es donde paga.
|
|
|
|
## 1. Receta (`Recipe`)
|
|
|
|
Una receta es una descripción declarativa y pura de *un* build. Es el átomo del grafo.
|
|
|
|
```toml
|
|
# ejemplo: recipes/grep.toml
|
|
name = "grep"
|
|
version = "3.11"
|
|
|
|
[source]
|
|
repo = "git://git.savannah.gnu.org/grep.git"
|
|
commit = "a1b2c3d4e5f6..." # FIJADO. Nunca "HEAD". Ver ADR 0006.
|
|
patches = ["patches/grep-perl-default.patch"]
|
|
|
|
[build]
|
|
compiler = "zig-cc" # por defecto; escotilla: "clang" | "gcc"
|
|
target = "x86_64-linux-musl"
|
|
link = "static" # "static" | "dynamic"
|
|
flags = ["--enable-perl-regexp"]
|
|
# fases override opcionales; si se omiten, se usan las heurísticas estándar
|
|
# configure = "..." compile = "..." install = "..."
|
|
|
|
[deps]
|
|
build = ["pcre2", "musl"] # dependencias de compilación (otras recetas)
|
|
runtime = [] # vacío si link = static
|
|
```
|
|
|
|
El **compilador es por receta** (no global): `zig-cc` por defecto por hermeticidad y musl
|
|
incluido, con escotilla a `clang`/`gcc` para paquetes con gcc-ismos. Ver
|
|
[ADR 0003](adr/0003-zig-cc-builder.md).
|
|
|
|
## 2. El identificador de contenido (CAS)
|
|
|
|
Cada artefacto se direcciona por un hash que captura **todo** lo que influye en su salida:
|
|
|
|
```
|
|
artifact_hash = BLAKE3(
|
|
source_commit // commit git fijado
|
|
⧺ canonical(patches) // contenido de los parches, no su ruta
|
|
⧺ canonical(build_flags) // flags + compilador + target + link
|
|
⧺ Σ dep.artifact_hash // hashes de las dependencias de build (recursivo)
|
|
)
|
|
```
|
|
|
|
Consecuencia: **entrada idéntica ⇒ hash idéntico ⇒ salida idéntica**. Esto es lo que permite:
|
|
|
|
- **Caché:** ¿el hash ya existe en el store? Se reusa, no se recompila.
|
|
- **Avance ordenado del grafo:** si un upstream avanza su commit fijado, sólo cambian los
|
|
hashes de ese nodo y sus dependientes; el resto del grafo se reusa.
|
|
- **Verificación de `.swm`:** dos máquinas con la misma receta producen el mismo hash; así se
|
|
verifica un cambio compartido sin confiar en binarios (ver [SDD 09](09-trust-model.md)).
|
|
|
|
> BLAKE3 por velocidad y paralelismo; el hash no es un secreto, es un identificador.
|
|
|
|
## 3. El sandbox de build
|
|
|
|
Cada build corre en su propio entorno aislado, montado con **bubblewrap** (`bwrap`), que usa
|
|
namespaces de Linux (mount, pid, net, user) sin requerir root real cuando el kernel lo permite.
|
|
|
|
Receta del aislamiento:
|
|
|
|
1. **Raíz tmpfs.** Se monta un `/` temporal vacío en tmpfs. Nada del host es visible salvo lo
|
|
que montemos explícitamente.
|
|
2. **Compilador inyectado.** `zig` (o el compilador de la receta) se monta read-only en el
|
|
`PATH` del sandbox. En el bootstrap es el único compilador presente — así se rompe el
|
|
cordón umbilical con el host.
|
|
3. **Fuentes read-only.** El árbol del repo (en el commit fijado) se monta read-only.
|
|
4. **Dependencias read-only.** Los artefactos de las deps de build, ya en el store, se montan
|
|
read-only en rutas conocidas.
|
|
5. **Sin red.** El namespace de red se aísla: el `fetch` ocurre *fuera* del sandbox (ver §4),
|
|
de modo que el build en sí no puede descargar nada no declarado → hermeticidad real.
|
|
6. **Salida vía `DESTDIR`.** El build instala en `DESTDIR=/out`; ese árbol es el artefacto que
|
|
se extrae y se sella en el store.
|
|
|
|
## 4. Fases del build
|
|
|
|
Inspiradas en BitBake (`do_fetch`/`do_unpack`/`do_configure`/`do_compile`/`do_install`) pero
|
|
mínimas. Sólo `fetch` toca la red, y ocurre **antes** de entrar al sandbox:
|
|
|
|
| Fase | ¿En sandbox? | Qué hace |
|
|
|---|---|---|
|
|
| `fetch` | No (red permitida) | Clona/actualiza el repo al commit fijado; verifica hash |
|
|
| `patch` | Sí | Aplica `source.patches` sobre el árbol read-only (en copia tmpfs) |
|
|
| `configure` | Sí | Detecta sistema de build (autotools/cmake/meson/cargo/make) y configura |
|
|
| `compile` | Sí | Compila con el compilador de la receta hacia `target`/`link` |
|
|
| `install` | Sí | `make install DESTDIR=/out` (o equivalente) |
|
|
| `seal` | No | Calcula el hash final del árbol `/out`, lo deposita en el store |
|
|
|
|
La detección de sistema de build en `configure` es heurística (presencia de `configure.ac`,
|
|
`CMakeLists.txt`, `meson.build`, `Cargo.toml`, `Makefile`) y se puede sobreescribir por receta.
|
|
|
|
### Cargo (crates Rust)
|
|
|
|
Un árbol con `Cargo.toml` se construye con `cargo` (Cargo gana sobre un `Makefile` huérfano,
|
|
pero cede ante un `CMakeLists.txt` de un políglota C+Rust). Particularidades:
|
|
|
|
- **Triple traducido.** El `target` estilo zig de la receta (`x86_64-linux-musl`) se traduce al
|
|
de rustc (`x86_64-unknown-linux-musl`).
|
|
- **Link con `zig cc`.** `compile` instala un wrapper `zig cc` como
|
|
`CARGO_TARGET_<triple>_LINKER` — cargo no acepta un linker de dos palabras, y zig provee el
|
|
`libgcc_s` que el link **dinámico** musl exige (sin él, `cannot find -lgcc_s`). `link =
|
|
"dynamic"` añade `RUSTFLAGS=-C target-feature=-crt-static` (necesario para `dlopen`, p. ej.
|
|
apps gráficas que cargan Vulkan/Wayland en runtime).
|
|
- **Hermético ⇒ deps vendoreadas.** `compile` corre `cargo build --release --locked --offline`:
|
|
el sandbox no tiene red, así que la fuente debe traer `vendor/` + `.cargo/config.toml`
|
|
(`cargo vendor`) o las deps pre-pobladas. `--locked` exige `Cargo.lock` fijo (coherente con
|
|
ADR 0006).
|
|
- **Install.** Copia los ejecutables del top de `target/<triple>/release/` a `/out/usr/bin`.
|
|
|
|
## 5. El grafo de dependencias
|
|
|
|
Las recetas forman un DAG por sus `deps.build`. El algoritmo de build:
|
|
|
|
```
|
|
build(recipe):
|
|
h = artifact_hash(recipe) # requiere resolver deps primero (recursivo)
|
|
if store.has(h): return h # caché
|
|
for dep in recipe.deps.build:
|
|
build(dep) # post-orden: deps antes que el nodo
|
|
sandbox = make_sandbox(recipe, deps)
|
|
run_phases(sandbox) # patch → configure → compile → install
|
|
return store.seal(sandbox.out, h)
|
|
```
|
|
|
|
Builds de nodos independientes se paralelizan (sin dependencia mutua en el DAG). El cap de
|
|
concurrencia se ajusta a los cores disponibles.
|
|
|
|
## 6. Bootstrap (romper el cordón umbilical)
|
|
|
|
El mayor error es usar las herramientas del host para compilar el sistema final. `zig cc`
|
|
reduce drásticamente este problema: un solo binario provee compilador C/C++ + musl + headers,
|
|
hermético, cross-compilando al target. El Stage 0 tradicional (cross-compiler mínimo) queda
|
|
en gran parte resuelto por `zig`.
|
|
|
|
Para la fase Alpine no necesitamos bootstrap completo: Alpine ya provee musl y un toolchain;
|
|
usamos `zig cc` para los builds de hammer y validamos el flujo. El bootstrap from-scratch real
|
|
es trabajo del track de distro propia ([SDD 10](10-roadmap.md), [ADR 0003](adr/0003-zig-cc-builder.md)).
|
|
|
|
## 7. Interfaz (qué expone el crate)
|
|
|
|
```rust
|
|
// hammer-build
|
|
pub fn build(recipe: &Recipe, store: &Store) -> Result<ArtifactHash>;
|
|
pub fn artifact_hash(recipe: &Recipe, store: &Store) -> Result<ArtifactHash>;
|
|
```
|
|
|
|
CLI: `hammer build <recipe.toml>` → imprime el hash del artefacto sellado.
|