commit 34d1a969385623fc738ff411b2c83b926bcfdef6
parent 4732be263077c6e3cf4b956bd9e963948c5ef797
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sat, 18 Jul 2026 09:51:29 -0700
boot7: assemble verified static toolchain
Diffstat:
13 files changed, 299 insertions(+), 53 deletions(-)
diff --git a/Makefile b/Makefile
@@ -5,7 +5,7 @@
# and the rule body invokes the right bootN.sh (or prep-src.sh /
# prep-musl.sh) script with the appropriate ARCH and DRIVER.
#
-# Output layout: build/<arch>/<driver>/boot{0..6}/<artifacts>.
+# Output layout: build/<arch>/<driver>/boot{0..7}/<artifacts>.
# <arch> ∈ {aarch64, amd64, riscv64}
# <driver> ∈ {podman, seed}
#
@@ -15,7 +15,7 @@
# make build/riscv64/podman/boot1/M1pp # only prep-src + boot0 + boot1
#
# Convenience entrypoints (default ARCH=aarch64, DRIVER=podman):
-# make all build boot6 kernel for ARCH × DRIVER
+# make all build boot6 kernel + boot7 toolchain
# make test SUITE=<name> run a test suite (see SUITE list at bottom)
# make image build the per-arch container image used by tests
# make cloc line counts for the bootstrap sources
@@ -64,18 +64,21 @@ OUT_DIR := build/$(ARCH)/$(DRIVER)
# ── Top-level targets ────────────────────────────────────────────────────
-all: build/$(ARCH)/$(DRIVER)/boot6/$(KERNEL_NAME_$(ARCH))
+all: build/$(ARCH)/$(DRIVER)/boot6/$(KERNEL_NAME_$(ARCH)) \
+ build/$(ARCH)/$(DRIVER)/boot7/toolchain/MANIFEST.sha256
# Prepare the canonical source tree (prep-src + prep-musl) for ARCH.
src: build/$(ARCH)/src/musl/.stamp
help:
@echo 'Targets (default ARCH=$(ARCH) DRIVER=$(DRIVER)):'
- @echo ' make all build boot6 kernel'
+ @echo ' make all build boot6 kernel + boot7 toolchain'
@echo ' make src prep canonical src/ tree (incl. musl)'
@echo ' make package quick: package boot2-<arch>.tar.gz from current build'
@echo ' make release validated: build + input/tar/output proofs, mint to dist/'
- @echo ' make build/<arch>/<driver>/boot6/<kn> full chain (kn = Image | kernel.elf)'
+ @echo ' make build/<arch>/<driver>/boot6/<kn> kernel branch (kn = Image | kernel.elf)'
+ @echo ' make build/<arch>/<driver>/boot7/toolchain/MANIFEST.sha256'
+ @echo ' assemble final static toolchain'
@echo ' make build/<arch>/<driver>/bootN/<file> any single artifact'
@echo ' make test SUITE=<suite> test suite (NAMES=<filter> optional)'
@echo ' make image per-arch tests container image'
@@ -86,13 +89,12 @@ clean:
rm -rf build/
# `package`: package a per-arch tarball from the current build tree.
-# Depends on the full chain (boot5 musl + boot6 kernel) so
-# OUTPUT_MANIFEST.txt has real artifacts to hash. boot5 is not on the
-# `all` dep path (boot6 doesn't link musl), so list it explicitly here.
+# Depends on both terminal branches: the boot6 kernel and boot7's installed
+# toolchain (which pulls in boot5 musl), so every manifest entry exists.
# Lands at build/<arch>/release/boot2-<arch>.tar.gz. Fast: no
# reproducibility or verify check.
-package: build/$(ARCH)/$(DRIVER)/boot5/libc.a \
- build/$(ARCH)/$(DRIVER)/boot6/$(KERNEL_NAME_$(ARCH))
+package: build/$(ARCH)/$(DRIVER)/boot6/$(KERNEL_NAME_$(ARCH)) \
+ build/$(ARCH)/$(DRIVER)/boot7/toolchain/MANIFEST.sha256
DRIVER=$(DRIVER) tools/mkrelease.sh $(ARCH)
# `release`: the validated path. Builds + packages once, regenerates
@@ -102,7 +104,7 @@ package: build/$(ARCH)/$(DRIVER)/boot5/libc.a \
release:
DRIVER=$(DRIVER) tools/release.sh $(ARCH)
-# ── prep-src + boot0..boot6 chain (rules per arch × driver) ──────────────
+# ── prep-src + boot0..boot7 chain (rules per arch × driver) ──────────────
#
# The .stamp files are the make-rule pegs. Each rule lists its real
# outputs as additional targets so single-binary builds work — `make
@@ -116,7 +118,9 @@ release:
PREP_SRC_COMMON_SRCS := \
bootprep/prep-src.sh boot/lib-arch.sh \
bootprep/stage1-flatten.sh bootprep/libc-flatten.sh \
- bootprep/boot4-gen-runscm.sh bootprep/boot6-gen-runscm.sh \
+ bootprep/boot4-gen-runscm.sh \
+ bootprep/boot5-enumerate.sh bootprep/boot5-gen-runscm.sh \
+ bootprep/boot6-gen-runscm.sh \
bootprep/assets/boot3-run.scm bootprep/assets/boot-hello.c \
M1pp/M1pp.P1 hex2pp/hex2pp.P1 \
P1/P1.M1pp P1/P1pp.P1pp \
@@ -170,7 +174,7 @@ endef
$(foreach a,$(ALL_ARCHES),$(eval $(call PREP_RULES,$a)))
-# Per-(arch, driver) boot0..boot6 rules.
+# Per-(arch, driver) boot0..boot7 rules.
#
# Each stage's .stamp is the recipe peg; each real artifact is declared
# as a target depending on the .stamp with an empty recipe (`;`). We
@@ -257,6 +261,24 @@ build/$1/$2/boot6/.stamp: \
@touch $$@
build/$1/$2/boot6/$$(KERNEL_NAME_$1): build/$1/$2/boot6/.stamp ;
+
+# boot7: install the final compiler + musl static sysroot in one tree.
+# This is a deterministic host-side assembly stage for either DRIVER; all
+# copied binaries were already produced through that driver's boot stages.
+build/$1/$2/boot7/.stamp: \
+ build/$1/$2/boot4/.stamp build/$1/$2/boot5/.stamp \
+ build/$1/src/.stamp \
+ boot/boot7.sh boot/lib-arch.sh
+ DRIVER=$2 boot/boot7.sh $1
+ @touch $$@
+
+build/$1/$2/boot7/toolchain/bin/tcc \
+build/$1/$2/boot7/toolchain/lib/libc.a \
+build/$1/$2/boot7/toolchain/lib/libtcc1.a \
+build/$1/$2/boot7/toolchain/lib/crt1.o \
+build/$1/$2/boot7/toolchain/lib/crti.o \
+build/$1/$2/boot7/toolchain/lib/crtn.o \
+build/$1/$2/boot7/toolchain/MANIFEST.sha256: build/$1/$2/boot7/.stamp ;
endef
$(foreach a,$(ALL_ARCHES),$(foreach d,$(ALL_DRIVERS),$(eval $(call BOOT_CHAIN_RULES,$a,$d))))
diff --git a/README.md b/README.md
@@ -65,6 +65,10 @@ A series walking through the chain, one rung at a time:
;; ── boot6.sh ── seed-kernel ───────────────────────────────────────────
;; tcc links the seed-kernel ELF (Image on aarch64). That kernel is the
;; runtime for DRIVER=seed re-runs, closing the bootstrap loop.
+
+;; ── boot7.sh ── installed static toolchain ──────────────────────────────────
+;; Assemble tcc2 + libtcc1.a + musl libc/crt + public headers under one
+;; relocatable toolchain/{bin,lib,include}/ tree and hash every file.
```
For a stage-by-stage walk-through of what is built, what becomes
@@ -134,7 +138,7 @@ files crossed by the chain, and the order in which to read them, is in
* **5 minutes** — the chain pseudocode above, plus
[docs/TOUR.md §0 "Map"](docs/TOUR.md).
* **An hour** — [docs/TOUR.md](docs/TOUR.md) end to end, then skim
- [boot/boot0.sh](boot/boot0.sh) … [boot/boot6.sh](boot/boot6.sh).
+ [boot/boot0.sh](boot/boot0.sh) … [boot/boot7.sh](boot/boot7.sh).
* **A day** — the component specs in dependency order:
[docs/P1.md](docs/P1.md), [docs/M1PP.md](docs/M1PP.md),
[docs/HEX2pp.md](docs/HEX2pp.md), [docs/LIBP1PP.md](docs/LIBP1PP.md),
@@ -147,7 +151,8 @@ files crossed by the chain, and the order in which to read them, is in
`DRIVER={podman,seed} × ARCH={aarch64,amd64,riscv64}`
-`DRIVER` selects the runtime that executes each `bootN` stage:
+`DRIVER` selects the runtime that executes the compiling `bootN` stages;
+boot7 is a host-side installation step over that driver's verified outputs:
* **podman** (default) — each stage runs in a container with access only to its
input binaries and sources.
@@ -174,24 +179,29 @@ Or via path-based Make targets — outputs are the targets, so deps walk
the chain back to source-prep:
```sh
-make build/aarch64/podman/boot6/Image # full chain
+make build/aarch64/podman/boot6/Image # kernel branch
make build/amd64/podman/boot6/kernel.elf
+make build/aarch64/podman/boot7/toolchain/MANIFEST.sha256
make build/riscv64/podman/boot1/M1pp # only prep-src + boot0 + boot1
-make all ARCH=aarch64 DRIVER=podman # convenience: boot6 kernel
+make all ARCH=aarch64 DRIVER=podman # kernel + installed toolchain
make help # target list
```
-Per-stage outputs land at `build/<arch>/<driver>/boot{0..6}/`; the
+Per-stage outputs land at `build/<arch>/<driver>/boot{0..7}/`; the
canonical generated source tree (used by every stage) is at
`build/<arch>/src/`.
+The final static toolchain is assembled at
+`build/<arch>/<driver>/boot7/toolchain/`, with `bin/`, `lib/`, and
+`include/` subdirectories plus a complete `MANIFEST.sha256`.
+
## Reproducible releases
The validated release path performs one clean build, regenerates the
canonical source tree and compares its input manifest, repackages the sealed
payload and asserts byte-identical tar output, then rebuilds boot0 through
-boot6 from the extracted archive and compares every key output against its
-SHA-256 manifest:
+boot7 from the extracted archive, compares every key stage output, and
+checks every installed toolchain file against its SHA-256 manifest:
```sh
ARCH=aarch64 # or amd64, riscv64
@@ -205,11 +215,12 @@ results are:
* `dist/boot2-$ARCH.tar.gz` — self-contained source and verification bundle
* `dist/boot2-$ARCH.tar.gz.sha256` — archive digest
* `dist/boot2-$ARCH.outputs.sha256` — expected hashes for all stage artifacts
+* `dist/boot2-$ARCH.toolchain.sha256` — every final toolchain file and hash
* `dist/boot2-$ARCH.tar.gz.provenance` — revision, driver, host, and timestamp
Artifact hashes are release-specific and are generated from the validated
-build; they are not hard-coded in this README. The final artifact is the
-`boot6/Image` row for aarch64 and `boot6/kernel.elf` for amd64/riscv64.
+build; they are not hard-coded in this README. The boot6 kernel closes the
+execution loop; boot7 is the consumable static C toolchain.
To verify and reproduce a minted release on macOS (use `sha256sum` instead of
`shasum -a 256` on Linux):
diff --git a/boot/boot.sh b/boot/boot.sh
@@ -1,12 +1,12 @@
#!/bin/sh
-## boot.sh — drive boot0 → boot6 end-to-end under one driver.
+## boot.sh — drive boot0 → boot7 end-to-end under one driver.
set -eu
case "${1:-}" in
-h|--help)
cat <<'EOF'
-boot.sh — drive boot0 → boot6 end-to-end under one driver.
+boot.sh — drive boot0 → boot7 end-to-end under one driver.
Usage:
boot/boot.sh <aarch64|amd64|riscv64>
@@ -52,3 +52,4 @@ rm -rf build/$ARCH/$DRIVER
./boot/boot4.sh $ARCH
./boot/boot5.sh $ARCH
./boot/boot6.sh $ARCH
+./boot/boot7.sh $ARCH
diff --git a/boot/boot7.sh b/boot/boot7.sh
@@ -0,0 +1,105 @@
+#!/bin/sh
+## boot7.sh — assemble the final static C toolchain sysroot.
+##
+## This stage performs no compilation. It installs the verified boot4/boot5
+## products and the canonical musl headers into one relocatable directory:
+##
+## build/$ARCH/$DRIVER/boot7/toolchain/
+## bin/tcc boot4's fixed-point compiler (tcc2)
+## lib/{libc.a,libtcc1.a} boot5 musl + boot4 compiler runtime
+## lib/crt{1,i,n}.o boot5 startup objects
+## include/ installed public musl headers
+## include/tcc/stdarg-bridge.h
+## MANIFEST.sha256 hash of every installed file except itself
+##
+## boot7 is deliberately host-side for both DRIVER values: it only copies
+## already-verified artifacts. Slash-containing paths work in both execution
+## transports, but assembling an installed tree does not gain anything from
+## hundreds of guest-side copy operations.
+##
+## Usage: boot/boot7.sh <arch>
+## <arch> ∈ {aarch64, amd64, riscv64} for either DRIVER (default podman).
+
+set -eu
+
+. boot/lib-arch.sh
+bootlib_init boot7 "${1:-}"
+require_src
+
+OUT=build/$ARCH/$DRIVER/boot7
+STAGE=build/$ARCH/$DRIVER/.boot7-stage
+export OUT STAGE
+
+BOOT4=build/$ARCH/$DRIVER/boot4
+BOOT5=build/$ARCH/$DRIVER/boot5
+MUSL=build/$ARCH/src/src/musl
+BRIDGE=build/$ARCH/src/src/tcc/stdarg-bridge.h
+
+for f in \
+ "$BOOT4/tcc2" "$BOOT4/libtcc1.a" \
+ "$BOOT5/libc.a" "$BOOT5/crt1.o" "$BOOT5/crti.o" "$BOOT5/crtn.o" \
+ "$MUSL/obj/include/bits/alltypes.h" \
+ "$MUSL/obj/include/bits/syscall.h" \
+ "$BRIDGE"
+do
+ require_file "$f"
+done
+
+rm -rf "$STAGE"
+mkdir -p "$STAGE/toolchain/bin" "$STAGE/toolchain/lib" \
+ "$STAGE/toolchain/include/tcc"
+TOOLCHAIN=$STAGE/toolchain
+
+# Final built binaries and libraries. boot4's mes-libc/crt1 are bootstrap
+# internals; the installed libc/crt objects are boot5's musl products.
+cp "$BOOT4/tcc2" "$TOOLCHAIN/bin/tcc"
+cp "$BOOT4/libtcc1.a" "$TOOLCHAIN/lib/libtcc1.a"
+cp "$BOOT5/libc.a" "$TOOLCHAIN/lib/libc.a"
+cp "$BOOT5/crt1.o" "$TOOLCHAIN/lib/crt1.o"
+cp "$BOOT5/crti.o" "$TOOLCHAIN/lib/crti.o"
+cp "$BOOT5/crtn.o" "$TOOLCHAIN/lib/crtn.o"
+
+# Install public musl headers with the same precedence as musl's
+# install-headers target: generic bits, arch bits, then generated bits.
+# The general include/ tree is disjoint from the installed bits/ overlay.
+copy_headers() {
+ _src=$1; _prefix=$2
+ ( cd "$_src" && find . -type f -name '*.h' | LC_ALL=C sort ) |
+ while IFS= read -r _rel; do
+ _rel=${_rel#./}
+ _dst=$TOOLCHAIN/include/$_prefix$_rel
+ mkdir -p "$(dirname "$_dst")"
+ cp "$_src/$_rel" "$_dst"
+ done
+}
+
+copy_headers "$MUSL/include" ""
+copy_headers "$MUSL/arch/generic/bits" "bits/"
+copy_headers "$MUSL/arch/$MUSL_ARCH/bits" "bits/"
+copy_headers "$MUSL/obj/include/bits" "bits/"
+cp "$BRIDGE" "$TOOLCHAIN/include/tcc/stdarg-bridge.h"
+
+chmod 0755 "$TOOLCHAIN/bin/tcc"
+find "$TOOLCHAIN/lib" "$TOOLCHAIN/include" -type f -exec chmod 0644 {} +
+
+if command -v sha256sum >/dev/null 2>&1; then
+ sha256() { sha256sum "$1" | awk '{print $1}'; }
+else
+ sha256() { shasum -a 256 "$1" | awk '{print $1}'; }
+fi
+
+: > "$TOOLCHAIN/MANIFEST.sha256"
+( cd "$TOOLCHAIN" && find . -type f ! -name MANIFEST.sha256 | LC_ALL=C sort ) |
+while IFS= read -r rel; do
+ rel=${rel#./}
+ printf '%s %s\n' "$(sha256 "$TOOLCHAIN/$rel")" "$rel" \
+ >> "$TOOLCHAIN/MANIFEST.sha256"
+done
+chmod 0644 "$TOOLCHAIN/MANIFEST.sha256"
+
+rm -rf "$OUT"
+mkdir -p "$OUT"
+mv "$TOOLCHAIN" "$OUT/toolchain"
+
+count=$(wc -l < "$OUT/toolchain/MANIFEST.sha256" | tr -d ' ')
+echo "[$BOOT_TAG] OK -> $OUT/toolchain ($count files)"
diff --git a/boot/lib-arch.sh b/boot/lib-arch.sh
@@ -1,5 +1,5 @@
# lib-arch.sh — single source for arch + driver setup shared by
-# boot/boot.sh, boot/boot{0..6}.sh, lib-pipeline.sh, lib-runscm.sh.
+# boot/boot.sh, boot/boot{0..7}.sh, lib-pipeline.sh, lib-runscm.sh.
#
# Public entry points (call in this order from a bootN.sh):
#
diff --git a/bootprep/boot5-enumerate.sh b/bootprep/boot5-enumerate.sh
@@ -15,6 +15,11 @@
## Usage: bootprep/boot5-enumerate.sh <arch>
set -eu
+# Source enumeration becomes archive member order, so it must not inherit
+# the host's locale collation (for example, whether '_' sorts before '.').
+LC_ALL=C
+export LC_ALL
+
[ "$#" -eq 1 ] || { echo "usage: $0 <arch>" >&2; exit 2; }
ARCH=$1
diff --git a/docs/TOUR.md b/docs/TOUR.md
@@ -10,7 +10,7 @@ together.
## §0. Map
-The chain is a sequence of seven shell scripts under [boot/](../boot).
+The chain is a sequence of eight shell scripts under [boot/](../boot).
Each `bootN.sh` produces one or two binaries from the binaries the
prior stages produced, plus source from the canonical
`build/<arch>/src/` tree (prepared once by `bootprep/prep-src.sh`).
@@ -24,10 +24,12 @@ prior stages produced, plus source from the canonical
| 4 | [boot4.sh](../boot/boot4.sh) | `tcc1`, `tcc2`, `libc.a`, `libtcc1.a` | tcc self-host, byte-identical fixed point `tcc1 == tcc2`, minimal libc |
| 5 | [boot5.sh](../boot/boot5.sh) | `libc.a`, `crt{1,i,n}.o` | musl-1.2.5 built by the self-hosted tcc |
| 6 | [boot6.sh](../boot/boot6.sh) | `Image` (aarch64) / `kernel.elf` | a minimal kernel that can host the chain (`DRIVER=seed`) |
+| 7 | [boot7.sh](../boot/boot7.sh) | `toolchain/{bin,lib,include}` | final compiler and static sysroot installed in one tree |
Drivers (`DRIVER=podman` default, `DRIVER=seed` for the loop pass) only
-change *where* each stage executes; the inputs, outputs, and shell
-scripts are identical.
+change where compiling stages execute; their inputs, outputs, and shell
+scripts are identical. Boot7 is the same host-side installation step over
+either driver's already-built artifacts.
## §1. boot0 — from a hex seed to a macro assembler
@@ -280,7 +282,29 @@ stage inside the kernel that boot6 just produced.
console / fs / trap dispatcher headers.
The full OS contract is [docs/OS.md](OS.md).
-## §8. The loop
+## §8. boot7 — install the final static toolchain
+
+**You arrive with**: fixed-point `tcc2` and `libtcc1.a` from boot4,
+musl `libc.a` and `crt{1,i,n}.o` from boot5, and the canonical generated
+musl headers.
+
+**boot7 builds**: no new machine code. It installs those verified files
+into a relocatable `toolchain/` tree with conventional `bin/`, `lib/`, and
+`include/` directories. The public header overlay follows musl's own
+`install-headers` precedence: generic `bits/`, architecture-specific
+`bits/`, then generated `alltypes.h` and `syscall.h`.
+
+`bin/tcc` is the unwrapped `tcc2` ELF. No shell wrapper or other unbuilt
+executable is added; callers pass the installed include, CRT, and library
+paths directly to TCC. A separate `MANIFEST.sha256` records every installed
+file.
+
+**Trust extension**: none. This is a deterministic assembly boundary that
+turns the proof-oriented stage outputs into the consumable product of the
+chain. It runs host-side for either driver because it only copies artifacts
+already produced and compared under Podman or the seed kernel.
+
+## §9. The loop
The chain has now produced a kernel. The kernel is enough to run any
of the earlier stages: under `DRIVER=seed`, each `bootN.sh` packs its
diff --git a/tests/README.md b/tests/README.md
@@ -106,5 +106,5 @@ tests/seed-accept.sh boot5 # boot5 byte-eq vs podman
ARCH is fixed to `aarch64` since that's the only seed-driver-complete
arch today. Prereq for every mode: `./boot/boot.sh aarch64` has run
-under the default `DRIVER=podman` so `build/aarch64/podman/boot{0..6}/`
+under the default `DRIVER=podman` so `build/aarch64/podman/boot{0..7}/`
is populated.
diff --git a/tests/seed-accept.sh b/tests/seed-accept.sh
@@ -19,7 +19,7 @@
## hello vs the podman reference.
##
## All three modes target ARCH=aarch64 (the only seed-driver-complete
-## arch today). Prereq for every mode: build/aarch64/podman/boot{0..6}/
+## arch today). Prereq for every mode: build/aarch64/podman/boot{0..7}/
## populated via `./boot/boot.sh aarch64` (default DRIVER=podman),
## including boot6/Image as the seed kernel.
##
diff --git a/tools/mkrelease.sh b/tools/mkrelease.sh
@@ -2,24 +2,25 @@
## mkrelease.sh — package a per-arch boot2 release tarball.
##
## A release tarball is a self-contained bundle that lets anyone
-## reproduce the full boot0..boot6 chain off the bundled inputs and
+## reproduce the full boot0..boot7 chain off the bundled inputs and
## byte-compare the outputs against a hash manifest. Layout:
##
## boot2-<arch>[-<rev>].tar.gz
## boot2-<arch>[-<rev>]/
## README.md extract + run instructions
-## verify.sh drives boot0..boot6 + diffs OUTPUT_MANIFEST
+## verify.sh drives boot0..boot7 + diffs manifests
## INPUT_MANIFEST.txt sha256 of every input under src/ + boot/
## OUTPUT_MANIFEST.txt sha256 of expected per-stage artifacts
-## (driver-agnostic; the project's seed-accept
-## harness verifies podman vs seed equivalence)
+## TOOLCHAIN_MANIFEST.txt sha256 of every installed boot7 file
+## Both output manifests are driver-agnostic; the project's
+## seed-accept harness verifies podman vs seed equivalence.
## src/ the sealed source tree (from
## build/<arch>/src/, produced by prep-src.sh)
-## boot/ boot0..boot6 stage drivers + libs +
+## boot/ boot0..boot7 stage drivers + libs +
## containers/Containerfile.*
##
## The output manifest is generated from the current build outputs in
-## build/<arch>/<driver>/boot{0..6}/. mkrelease.sh does NOT rebuild;
+## build/<arch>/<driver>/boot{0..7}/. mkrelease.sh does NOT rebuild;
## prereqs (`make all ARCH=<arch>`) must already have run.
##
## Usage: tools/mkrelease.sh <arch>
@@ -67,6 +68,7 @@ TARBALL=$REL_DIR/$NAME.tar.gz
[ -d "$SRC_TREE" ] || { echo "[mkrelease] missing $SRC_TREE — run bootprep/prep-src.sh $ARCH" >&2; exit 1; }
[ -f "$BUILD_TREE/boot6/$KERNEL_NAME" ] || { echo "[mkrelease] missing $BUILD_TREE/boot6/$KERNEL_NAME — run 'make all ARCH=$ARCH DRIVER=$DRIVER'" >&2; exit 1; }
+[ -f "$BUILD_TREE/boot7/toolchain/MANIFEST.sha256" ] || { echo "[mkrelease] missing $BUILD_TREE/boot7/toolchain/MANIFEST.sha256 — run 'make all ARCH=$ARCH DRIVER=$DRIVER'" >&2; exit 1; }
# Portable sha256. Use sha256sum if present; else shasum -a 256.
if command -v sha256sum >/dev/null 2>&1; then
@@ -133,6 +135,7 @@ boot5/crti.o
boot5/crtn.o
boot5/hello
boot6/$KERNEL_NAME
+boot7/toolchain/MANIFEST.sha256
EOF
}
@@ -160,6 +163,13 @@ fi
n_out=$(wc -l < "$OUTMAN" | tr -d ' ')
# ── (6) tarball — deterministic ──────────────────────────────────────
+# Keep detailed installed-tree hashes next to the concise per-stage
+# manifest. verify.sh compares this with boot7's regenerated manifest,
+# then checks every installed file named by it.
+TOOLMAN=$STAGING/TOOLCHAIN_MANIFEST.txt
+cp "$BUILD_TREE/boot7/toolchain/MANIFEST.sha256" "$TOOLMAN"
+n_tool=$(wc -l < "$TOOLMAN" | tr -d ' ')
+
echo "[mkrelease] tar -> $TARBALL"
TARBALL_ABS=$PWD/$TARBALL
tools/release/tar-payload.sh "$STAGING" "$TARBALL_ABS"
@@ -174,3 +184,4 @@ echo "[mkrelease] tarball : $TARBALL ($bytes bytes)"
echo "[mkrelease] sha256 : $TAR_SHA"
echo "[mkrelease] inputs : $n_in files"
echo "[mkrelease] outputs : $n_out artifacts"
+echo "[mkrelease] toolchain: $n_tool files"
diff --git a/tools/release.sh b/tools/release.sh
@@ -3,15 +3,16 @@
##
## Releases are critical, so the path to minting one is paranoid:
##
-## 1. Clean-build and package prep-src → boot0..boot6 once.
+## 1. Clean-build and package prep-src → boot0..boot7 once.
## 2. Extract the archive into a fresh directory under $HOME.
## 3. Clean-regenerate the checkout's canonical src/ tree and compare
## its full boot/ + src/ hash manifest with the bundled
## INPUT_MANIFEST.txt.
## 4. Repackage the extracted sealed payload and assert that its tarball
## is byte-identical to the original.
-## 5. Run the bundled verify.sh. It rebuilds boot0..boot6 from only the
-## sealed inputs and compares every artifact with OUTPUT_MANIFEST.txt,
+## 5. Run the bundled verify.sh. It rebuilds boot0..boot7 from only the
+## sealed inputs, compares stage artifacts with OUTPUT_MANIFEST.txt,
+## and checks the complete installed tree with TOOLCHAIN_MANIFEST.txt,
## proving both build determinism and archive completeness.
## 6. Promote the validated tarball to dist/<name>.tar.gz with a
## sha256 sidecar. dist/ is the only directory mkrelease.sh /
@@ -97,8 +98,10 @@ tar xzf "$ARCHIVE" -C "$VERIFY_BASE"
SEALED=$VERIFY_BASE/$NAME
INPUT_MANIFEST=$SEALED/INPUT_MANIFEST.txt
OUTPUT_MANIFEST=$SEALED/OUTPUT_MANIFEST.txt
+TOOLCHAIN_MANIFEST=$SEALED/TOOLCHAIN_MANIFEST.txt
[ -f "$INPUT_MANIFEST" ] || { log "FAIL: archive has no INPUT_MANIFEST.txt"; exit 1; }
[ -f "$OUTPUT_MANIFEST" ] || { log "FAIL: archive has no OUTPUT_MANIFEST.txt"; exit 1; }
+[ -f "$TOOLCHAIN_MANIFEST" ] || { log "FAIL: archive has no TOOLCHAIN_MANIFEST.txt"; exit 1; }
# Independently regenerate canonical inputs from the checkout. This is
# cheap compared with boot3 and proves prep-src produces the sealed tree.
@@ -143,6 +146,7 @@ if [ "$DRIVER" = seed ]; then
fi
OUTPUT_MANIFEST_SHA=$(sha256 "$OUTPUT_MANIFEST")
+TOOLCHAIN_MANIFEST_SHA=$(sha256 "$TOOLCHAIN_MANIFEST")
# Promote to dist/ — the only directory we treat as "publishable".
hr "mint"
@@ -150,6 +154,7 @@ mkdir -p "$DIST"
cp "$ARCHIVE" "$DIST/$NAME.tar.gz"
printf '%s %s\n' "$SHA_A" "$NAME.tar.gz" > "$DIST/$NAME.tar.gz.sha256"
cp "$OUTPUT_MANIFEST" "$DIST/$NAME.outputs.sha256"
+cp "$TOOLCHAIN_MANIFEST" "$DIST/$NAME.toolchain.sha256"
# Provenance sidecar — keeps git rev / build host / driver / timestamp
# outside the tarball so they don't perturb the content hash.
@@ -158,6 +163,8 @@ cp "$OUTPUT_MANIFEST" "$DIST/$NAME.outputs.sha256"
printf 'sha256: %s\n' "$SHA_A"
printf 'outputs: %s.outputs.sha256\n' "$NAME"
printf 'outputs_sha256: %s\n' "$OUTPUT_MANIFEST_SHA"
+ printf 'toolchain: %s.toolchain.sha256\n' "$NAME"
+ printf 'toolchain_sha256: %s\n' "$TOOLCHAIN_MANIFEST_SHA"
printf 'arch: %s\n' "$ARCH"
printf 'driver: %s\n' "$DRIVER"
printf 'git_rev: %s%s\n' "$REV" "$DIRTY"
@@ -174,3 +181,4 @@ log "MINTED in ${elapsed}s"
log " tarball : $DIST/$NAME.tar.gz ($bytes bytes)"
log " sha256 : $SHA_A"
log " outputs : $DIST/$NAME.outputs.sha256"
+log " toolchain: $DIST/$NAME.toolchain.sha256"
diff --git a/tools/release/README.md b/tools/release/README.md
@@ -2,15 +2,17 @@
This tarball is a self-contained input bundle for the boot2 bootstrap
chain on `@ARCH@`. Extract it, run `./verify.sh`, and the chain will be
-re-run from boot0 through boot6 using only the bundled inputs. The
-outputs are sha256-compared against `OUTPUT_MANIFEST.txt`.
+re-run from boot0 through boot7 using only the bundled inputs. Stage
+outputs are sha256-compared against `OUTPUT_MANIFEST.txt`; every file in
+the final installed toolchain is checked against `TOOLCHAIN_MANIFEST.txt`.
The tarball is content-addressed: its sha256 reflects only the bundled
inputs + manifests. Provenance (the git rev it was built from, build
date, build host arch) lives in the sidecar `<tarball>.provenance`
file in the same directory as the tarball, not inside it. Validated
releases also publish `<name>.outputs.sha256`, an external copy of the
-artifact hashes bundled as `OUTPUT_MANIFEST.txt`.
+artifact hashes bundled as `OUTPUT_MANIFEST.txt`, and
+`<name>.toolchain.sha256`, the detailed installed-tree hashes.
## Contents
@@ -20,13 +22,14 @@ boot2-@ARCH@/
├── verify.sh build + diff driver
├── INPUT_MANIFEST.txt sha256 of every file under src/ + boot/
├── OUTPUT_MANIFEST.txt sha256 of expected per-stage artifacts
+├── TOOLCHAIN_MANIFEST.txt sha256 of every file installed by boot7
├── src/ sealed source tree (canonical inputs)
│ ├── bin/hex0-seed vendored ELF seed (the only opaque input)
│ ├── src/ all textual sources: P1, M1pp, hex2pp,
│ │ scheme1, cc, tcc, mes-libc, musl, kernel
│ └── run/ run.scm files driving boot3..boot6
-└── boot/ boot0..boot6 stage drivers + libs
- ├── boot{0..6}.sh
+└── boot/ boot0..boot7 stage drivers + libs
+ ├── boot{0..7}.sh
├── lib-{arch,pipeline,runscm}.sh
└── containers/ Containerfile.{busybox,empty} (DRIVER=podman)
```
@@ -66,19 +69,30 @@ DRIVER=seed ./verify.sh
1. Stages `src/` into `build/@ARCH@/src/` (the layout the boot stage
scripts expect).
-2. Runs `boot/boot0.sh` … `boot/boot6.sh` in order under the selected
+2. Runs `boot/boot0.sh` … `boot/boot7.sh` in order under the selected
driver.
-3. Hashes each artifact listed in `OUTPUT_MANIFEST.txt` and prints
- `OK` / `DIFFER` / `MISSING` per row.
+3. Hashes each artifact listed in `OUTPUT_MANIFEST.txt`, then checks the
+ complete installed `boot7/toolchain/` tree against
+ `TOOLCHAIN_MANIFEST.txt`.
Exit status is 0 iff every artifact matches.
+After verification, the ready-to-use static toolchain is at:
+
+```text
+build/@ARCH@/<driver>/boot7/toolchain/
+├── bin/tcc
+├── lib/{libc.a,libtcc1.a,crt1.o,crti.o,crtn.o}
+├── include/
+└── MANIFEST.sha256
+```
+
## Drivers
| DRIVER | runtime | prereqs |
| ---------- | ----------------------------------------------- | ---------------------------------- |
| `podman` | each stage runs in a minimal container | `podman` (rootless ok), `qemu-user-static` for cross-arch |
-| `seed` | each stage runs inside `seed-kernel` under qemu | one prior `DRIVER=podman` pass to mint the boot6 kernel; `qemu-system-@ARCH@` |
+| `seed` | boot0..6 run in `seed-kernel`; boot7 installs their outputs | one prior `DRIVER=podman` pass to mint the boot6 kernel; `qemu-system-@ARCH@` |
The output manifest is **driver-agnostic**: the same artifacts must
hash identically under both drivers. The upstream repo's
diff --git a/tools/release/verify.sh b/tools/release/verify.sh
@@ -1,6 +1,6 @@
#!/bin/sh
-## verify.sh — drive boot0..boot6 off the bundled inputs and compare
-## the outputs against OUTPUT_MANIFEST.txt.
+## verify.sh — drive boot0..boot7 off the bundled inputs and compare
+## the outputs against OUTPUT_MANIFEST.txt and TOOLCHAIN_MANIFEST.txt.
##
## This script ships inside a boot2-@ARCH@ release tarball. It
## stages the sealed src/ tree at build/@ARCH@/src/ (the layout every
@@ -76,10 +76,10 @@ if [ ! -d "build/$ARCH/src" ]; then
cp -R src "build/$ARCH/src"
fi
-# ── (2) drive boot0..boot6 ────────────────────────────────────────────
+# ── (2) drive boot0..boot7 ────────────────────────────────────────────────
if [ "$CHECK_ONLY" = 0 ]; then
export DRIVER
- for s in 0 1 2 3 4 5 6; do
+ for s in 0 1 2 3 4 5 6 7; do
echo "[verify] boot$s"
./boot/boot$s.sh "$ARCH"
done
@@ -121,4 +121,49 @@ if [ "$fail" -gt 0 ]; then
echo "[verify] FAIL" >&2
exit 1
fi
+
+# ── (4) verify the installed toolchain tree ────────────────────────────────
+TOOLCHAIN=$BUILD_TREE/boot7/toolchain
+GENERATED_TOOLMAN=$TOOLCHAIN/MANIFEST.sha256
+if [ ! -f "$GENERATED_TOOLMAN" ]; then
+ echo "[verify] FAIL: missing $GENERATED_TOOLMAN" >&2
+ exit 1
+fi
+if ! cmp -s TOOLCHAIN_MANIFEST.txt "$GENERATED_TOOLMAN"; then
+ echo "[verify] FAIL: boot7 manifest differs from TOOLCHAIN_MANIFEST.txt" >&2
+ diff -u TOOLCHAIN_MANIFEST.txt "$GENERATED_TOOLMAN" || true
+ exit 1
+fi
+
+echo "[verify] checking installed boot7 toolchain files"
+tool_ok=0
+tool_fail=0
+while IFS= read -r line; do
+ case "$line" in
+ ''|\#*) continue ;;
+ esac
+ want=$(printf '%s' "$line" | awk '{print $1}')
+ rel=$(printf '%s' "$line" | awk '{print $2}')
+ f=$TOOLCHAIN/$rel
+ if [ ! -f "$f" ]; then
+ printf 'MISSING toolchain/%s\n' "$rel"
+ tool_fail=$((tool_fail + 1))
+ continue
+ fi
+ got=$(sha256 "$f")
+ if [ "$got" = "$want" ]; then
+ tool_ok=$((tool_ok + 1))
+ else
+ printf 'DIFFER toolchain/%s\n' "$rel"
+ printf ' want %s\n' "$want"
+ printf ' got %s\n' "$got"
+ tool_fail=$((tool_fail + 1))
+ fi
+done < TOOLCHAIN_MANIFEST.txt
+
+echo "[verify] toolchain ok=$tool_ok differ-or-missing=$tool_fail"
+if [ "$tool_fail" -gt 0 ]; then
+ echo "[verify] FAIL" >&2
+ exit 1
+fi
echo "[verify] PASS"