boot2

Playing with the boostrap
git clone https://git.ryansepassi.com/git/boot2.git
Log | Files | Refs | README

Makefile (15844B)


      1 # boot2 — Make-driven bootstrap pipeline.
      2 #
      3 # The bootN.sh scripts under boot/ are the canonical builders. This
      4 # Makefile is dependency tracking: every target is a real output path,
      5 # and the rule body invokes the right bootN.sh (or prep-src.sh /
      6 # prep-musl.sh) script with the appropriate ARCH and DRIVER.
      7 #
      8 # Output layout: build/<arch>/<driver>/boot{0..7}/<artifacts>.
      9 #   <arch>   ∈ {aarch64, amd64, riscv64, riscv32}
     10 #   <driver> ∈ {podman, seed}
     11 #
     12 # Path-based builds:
     13 #   make build/aarch64/podman/boot6/Image
     14 #   make build/amd64/seed/boot6/kernel.elf
     15 #   make build/riscv64/podman/boot1/M1pp        # only prep-src + boot0 + boot1
     16 #
     17 # Convenience entrypoints (default ARCH=aarch64, DRIVER=podman):
     18 #   make all                  build boot6 kernel + boot7 toolchain
     19 #   make test SUITE=<name>    run a test suite (see SUITE list at bottom)
     20 #   make image                build the per-arch container image used by tests
     21 #   make cloc                 line counts for the bootstrap sources
     22 #   make clean                rm -rf build/
     23 #
     24 # DRIVER=seed bootN stages depend on the podman-built boot6 kernel
     25 # (build/<arch>/podman/boot6/<kernel-name>) since the seed driver runs
     26 # scheme1 inside seed-kernel under QEMU. The seed-side dependency on
     27 # the podman side is encoded in the rules.
     28 
     29 # ── Config ───────────────────────────────────────────────────────────────
     30 
     31 ARCH    ?= aarch64
     32 DRIVER  ?= podman
     33 
     34 ALL_ARCHES  := aarch64 amd64 riscv64 riscv32
     35 TCC_ARCHES  := aarch64 amd64 riscv64
     36 ALL_DRIVERS := podman seed
     37 
     38 ifeq ($(filter $(ARCH),$(ALL_ARCHES)),)
     39   $(error ARCH '$(ARCH)' not supported — use one of $(ALL_ARCHES))
     40 endif
     41 ifeq ($(filter $(DRIVER),$(ALL_DRIVERS)),)
     42   $(error DRIVER '$(DRIVER)' not supported — use one of $(ALL_DRIVERS))
     43 endif
     44 ifeq ($(ARCH):$(DRIVER),riscv32:seed)
     45   $(error DRIVER=seed for riscv32 requires the intentionally unimplemented TCC/kernel stages; use DRIVER=podman)
     46 endif
     47 
     48 # Per-arch metadata mirrored from boot/lib-arch.sh.
     49 PLATFORM_aarch64    := linux/arm64
     50 PLATFORM_amd64      := linux/amd64
     51 PLATFORM_riscv64    := linux/riscv64
     52 # RV32 controller containers use the Podman server's native platform;
     53 # tests/Makefile and tests/run.sh resolve it only when they invoke Podman.
     54 PLATFORM_riscv32    := native
     55 
     56 KERNEL_NAME_aarch64 := Image
     57 KERNEL_NAME_amd64   := kernel.elf
     58 KERNEL_NAME_riscv64 := kernel.elf
     59 KERNEL_NAME_riscv32 := tcc-target-unimplemented
     60 
     61 MUSL_ARCH_aarch64   := aarch64
     62 MUSL_ARCH_amd64     := x86_64
     63 MUSL_ARCH_riscv64   := riscv64
     64 
     65 TCC_PKG := tcc-0.9.26-1147-gee75a10c
     66 
     67 OUT_DIR := build/$(ARCH)/$(DRIVER)
     68 
     69 .SUFFIXES:
     70 
     71 .PHONY: all help clean cloc src package release kit0
     72 
     73 # ── Top-level targets ────────────────────────────────────────────────────
     74 
     75 ifeq ($(filter $(ARCH),$(TCC_ARCHES)),)
     76 all: build/$(ARCH)/$(DRIVER)/boot2/catm \
     77      build/$(ARCH)/$(DRIVER)/boot2/scheme1
     78 else
     79 all: build/$(ARCH)/$(DRIVER)/boot6/$(KERNEL_NAME_$(ARCH)) \
     80      build/$(ARCH)/$(DRIVER)/boot7/toolchain/MANIFEST.sha256
     81 endif
     82 
     83 # Prepare the canonical source tree (prep-src + prep-musl) for ARCH.
     84 ifeq ($(filter $(ARCH),$(TCC_ARCHES)),)
     85 src: build/$(ARCH)/src/.stamp
     86 else
     87 src: build/$(ARCH)/src/musl/.stamp
     88 endif
     89 
     90 help:
     91 	@echo 'Targets (default ARCH=$(ARCH) DRIVER=$(DRIVER)):'
     92 	@echo '  make all                                 build implemented chain (RV32: through boot2)'
     93 	@echo '  make src                                 prep canonical src/ tree (RV32 omits TCC/musl)'
     94 	@echo '  make package                             quick: package boot2-<arch>.tar.gz from current build'
     95 	@echo '  make release                             validated: build + input/tar/output proofs, mint to dist/'
     96 	@echo '  make build/<arch>/<driver>/boot6/<kn>    kernel branch (kn = Image | kernel.elf)'
     97 	@echo '  make build/<arch>/<driver>/boot7/toolchain/MANIFEST.sha256'
     98 	@echo '                                           assemble final static toolchain'
     99 	@echo '  make build/<arch>/<driver>/bootN/<file>  any single artifact'
    100 	@echo '  make test SUITE=<suite>                  test suite (NAMES=<filter> optional)'
    101 	@echo '  make kit0 KIT0_PREP=<kit-prep-dir>       cc.scm + P1 build of Kit0 for ARCH'
    102 	@echo '  make image                               per-arch tests container image'
    103 	@echo '  make cloc                                line counts for bootstrap sources'
    104 	@echo '  make clean                               rm -rf build/'
    105 
    106 KIT0_ARCHES := aarch64 amd64 riscv64
    107 KIT0_PROFILE_aarch64 := aa64
    108 KIT0_PROFILE_amd64   := x64
    109 KIT0_PROFILE_riscv64 := rv64
    110 KIT0_PROFILE ?= $(KIT0_PROFILE_$(ARCH))
    111 KIT_DIR ?= ../../kit
    112 KIT0_PREP ?= $(KIT_DIR)/build/bootstrap-$(KIT0_PROFILE)/kit0-prep
    113 
    114 ifeq ($(filter $(ARCH),$(KIT0_ARCHES)),)
    115 kit0:
    116 	@echo "kit0 supports ARCH in {$(KIT0_ARCHES)} (got '$(ARCH)')" >&2; exit 2
    117 else
    118 kit0: build/$(ARCH)/$(DRIVER)/boot2/.stamp build/$(ARCH)/src/.stamp
    119 	KIT0_PREP='$(KIT0_PREP)' DRIVER='$(DRIVER)' boot/kit0.sh $(ARCH)
    120 endif
    121 
    122 clean:
    123 	rm -rf build/
    124 
    125 # `package`: package a per-arch tarball from the current build tree.
    126 # Depends on both terminal branches: the boot6 kernel and boot7's installed
    127 # toolchain (which pulls in boot5 musl), so every manifest entry exists.
    128 # Lands at build/<arch>/release/boot2-<arch>.tar.gz. Fast: no
    129 # reproducibility or verify check.
    130 ifeq ($(filter $(ARCH),$(TCC_ARCHES)),)
    131 package:
    132 	@echo "package is unavailable for $(ARCH): TCC support is intentionally unimplemented; the RV32 chain ends at boot2" >&2; exit 2
    133 else
    134 package: build/$(ARCH)/$(DRIVER)/boot6/$(KERNEL_NAME_$(ARCH)) \
    135          build/$(ARCH)/$(DRIVER)/boot7/toolchain/MANIFEST.sha256
    136 	DRIVER=$(DRIVER) tools/mkrelease.sh $(ARCH)
    137 endif
    138 
    139 # `release`: the validated path. Builds + packages once, regenerates
    140 # and compares canonical inputs, repackages the sealed payload to prove
    141 # stable tar bytes, then rebuilds from the archive and compares every
    142 # output hash. This is the only way a tarball lands in dist/.
    143 ifeq ($(filter $(ARCH),$(TCC_ARCHES)),)
    144 release:
    145 	@echo "release is unavailable for $(ARCH): TCC support is intentionally unimplemented; the RV32 chain ends at boot2" >&2; exit 2
    146 else
    147 release:
    148 	DRIVER=$(DRIVER) tools/release.sh $(ARCH)
    149 endif
    150 
    151 # ── prep-src + boot0..boot7 chain (rules per arch × driver) ──────────────
    152 #
    153 # The .stamp files are the make-rule pegs. Each rule lists its real
    154 # outputs as additional targets so single-binary builds work — `make
    155 # build/aarch64/podman/boot1/M1pp` resolves to the boot1 grouped target
    156 # and only walks back through prep-src + boot0.
    157 #
    158 # Per-arch source deps for prep-src.sh. The file list mirrors what
    159 # prep-src.sh actually copies, so a vendor/seed change re-triggers the
    160 # canonical-tree build.
    161 
    162 PREP_SRC_COMMON_SRCS := \
    163     bootprep/prep-src.sh boot/lib-arch.sh \
    164     bootprep/stage1-flatten.sh bootprep/libc-flatten.sh \
    165     bootprep/boot4-gen-runscm.sh \
    166     bootprep/boot5-enumerate.sh bootprep/boot5-gen-runscm.sh \
    167     bootprep/boot6-gen-runscm.sh \
    168     bootprep/assets/boot3-run.scm bootprep/assets/boot-hello.c \
    169     bootprep/assets/toolchain-hello.c \
    170     M1pp/M1pp.P1 hex2pp/hex2pp.P1 \
    171     P1/P1.M1pp P1/P1pp.P1pp \
    172     P1/entry-libc.P1pp P1/entry-plain.P1pp P1/elf-end.P1pp \
    173     catm/catm.P1pp \
    174     scheme1/scheme1.P1pp scheme1/prelude.scm \
    175     cc/cc.scm cc/main.scm \
    176     tcc/cc/mem.c \
    177     seed-kernel/kernel.c \
    178     vendor/musl/1.2.5.tar.gz \
    179     vendor/musl/deletes.txt \
    180     $(wildcard vendor/tcc/patches-lb/*.before) \
    181     $(wildcard vendor/tcc/patches-lb/*.after) \
    182     $(wildcard vendor/tcc/patches/*.before) \
    183     $(wildcard vendor/tcc/patches/*.after) \
    184     $(wildcard vendor/tcc/patches/files/*)
    185 
    186 prep_src_arch_srcs = \
    187     vendor/seed/$1/hex0-seed \
    188     vendor/seed/$1/ELF.hex2 \
    189     vendor/seed/$1/hex0.hex0 vendor/seed/$1/hex1.hex0 vendor/seed/$1/hex2.hex1 \
    190     vendor/seed/$1/catm.hex2 vendor/seed/$1/M0.hex2 \
    191     P1/P1-$1.M1 P1/P1-$1.M1pp \
    192     $(if $(filter $1,$(TCC_ARCHES)), \
    193         tcc/libc/$1/start.S tcc/libc/$1/sys_stubs.S \
    194         $(wildcard seed-kernel/arch/$1/*) \
    195         $(wildcard seed-kernel/user/*) \
    196         vendor/musl/generated/$(MUSL_ARCH_$1)/alltypes.h \
    197         vendor/musl/generated/$(MUSL_ARCH_$1)/syscall.h \
    198         $(wildcard vendor/musl/skip-$1.txt) \
    199         $(shell find vendor/musl/overrides -type f 2>/dev/null) \
    200         $(shell find vendor/mes-libc -type f \( -name '*.c' -o -name '*.h' \) 2>/dev/null) \
    201         $(wildcard vendor/mes-libc/patches/*.before) \
    202         $(wildcard vendor/mes-libc/patches/*.after) \
    203         $(wildcard vendor/tcc/0.9.26.tar.gz))
    204 
    205 # DRIVER=seed bootN stages run scheme1 under QEMU using the podman-built
    206 # boot6 kernel. Add that as a make dep so a clean-tree seed build pulls
    207 # in the podman side automatically. Empty for DRIVER=podman.
    208 seed_kernel_dep = $(if $(filter seed,$2),build/$1/podman/boot6/$(KERNEL_NAME_$1))
    209 
    210 # Per-arch prep-src rule. Driver-independent.
    211 #
    212 # prep-src.sh produces the full canonical tree, including the filtered
    213 # musl/ subtree (unpack + overrides + deletes + per-arch skip list,
    214 # plus the generated boot5 enumerate / run.scm). musl/.stamp is a
    215 # secondary peg that boot5 depends on — it's just touched after
    216 # src/.stamp, since the musl tree is populated by the same recipe.
    217 define PREP_RULES
    218 build/$1/src/.stamp: $$(PREP_SRC_COMMON_SRCS) $$(call prep_src_arch_srcs,$1)
    219 	bootprep/prep-src.sh $1
    220 	@touch $$@
    221 
    222 build/$1/src/musl/.stamp: build/$1/src/.stamp
    223 	@mkdir -p $$(@D) && touch $$@
    224 endef
    225 
    226 $(foreach a,$(ALL_ARCHES),$(eval $(call PREP_RULES,$a)))
    227 
    228 # Per-(arch, driver) boot0..boot7 rules.
    229 #
    230 # Each stage's .stamp is the recipe peg; each real artifact is declared
    231 # as a target depending on the .stamp with an empty recipe (`;`). We
    232 # avoid grouped targets (`&:`) — GNU make 4.4 does not propagate "must
    233 # remake of prereq" through grouped targets across multi-level chains,
    234 # so a path-based request like `make .../boot6/Image` would skip the
    235 # boot6 rebuild after a deeper change. Single-target rules with stamp-
    236 # anchored declarations propagate correctly.
    237 define BOOT_CHAIN_RULES
    238 # boot0: hex0-seed -> hex2 / M0 / catm
    239 build/$1/$2/boot0/.stamp: \
    240         build/$1/src/.stamp \
    241         boot/boot0.sh boot/lib-arch.sh boot/lib-pipeline.sh \
    242         $$(call seed_kernel_dep,$1,$2)
    243 	DRIVER=$2 boot/boot0.sh $1
    244 	@touch $$@
    245 
    246 build/$1/$2/boot0/hex2 build/$1/$2/boot0/M0 build/$1/$2/boot0/catm: \
    247     build/$1/$2/boot0/.stamp ;
    248 
    249 # boot1: M1pp.P1 + hex2pp.P1 -> M1pp + hex2pp
    250 build/$1/$2/boot1/.stamp: \
    251         build/$1/$2/boot0/.stamp \
    252         boot/boot1.sh boot/lib-arch.sh boot/lib-pipeline.sh \
    253         $$(call seed_kernel_dep,$1,$2)
    254 	DRIVER=$2 boot/boot1.sh $1
    255 	@touch $$@
    256 
    257 build/$1/$2/boot1/M1pp build/$1/$2/boot1/hex2pp: build/$1/$2/boot1/.stamp ;
    258 
    259 # boot2: catm.P1pp + scheme1.P1pp -> catm + scheme1
    260 build/$1/$2/boot2/.stamp: \
    261         build/$1/$2/boot1/.stamp build/$1/$2/boot0/.stamp \
    262         boot/boot2.sh boot/lib-arch.sh boot/lib-pipeline.sh \
    263         $$(call seed_kernel_dep,$1,$2)
    264 	DRIVER=$2 boot/boot2.sh $1
    265 	@touch $$@
    266 
    267 build/$1/$2/boot2/catm build/$1/$2/boot2/scheme1: build/$1/$2/boot2/.stamp ;
    268 
    269 # boot3: cc.scm-built bootstrap tcc (tcc0)
    270 build/$1/$2/boot3/.stamp: \
    271         build/$1/$2/boot2/.stamp build/$1/$2/boot1/.stamp \
    272         boot/boot3.sh boot/lib-arch.sh boot/lib-runscm.sh \
    273         $$(call seed_kernel_dep,$1,$2)
    274 	DRIVER=$2 boot/boot3.sh $1
    275 	@touch $$@
    276 
    277 build/$1/$2/boot3/tcc0 build/$1/$2/boot3/libc.P1pp \
    278 build/$1/$2/boot3/tcc.flat.P1pp: build/$1/$2/boot3/.stamp ;
    279 
    280 # boot4: tcc0 -> tcc1 -> tcc2 self-host chain (+ libc.a, libtcc1.a, hello)
    281 build/$1/$2/boot4/.stamp: \
    282         build/$1/$2/boot3/.stamp build/$1/$2/boot2/.stamp \
    283         boot/boot4.sh boot/lib-arch.sh boot/lib-runscm.sh \
    284         $$(call seed_kernel_dep,$1,$2)
    285 	DRIVER=$2 boot/boot4.sh $1
    286 	@touch $$@
    287 
    288 build/$1/$2/boot4/tcc1 build/$1/$2/boot4/tcc2 \
    289 build/$1/$2/boot4/hello \
    290 build/$1/$2/boot4/crt1.o build/$1/$2/boot4/libc.a \
    291 build/$1/$2/boot4/libtcc1.a: build/$1/$2/boot4/.stamp ;
    292 
    293 # boot5: musl libc + hello (consumes prep-musl output)
    294 build/$1/$2/boot5/.stamp: \
    295         build/$1/$2/boot4/.stamp build/$1/$2/boot2/.stamp \
    296         build/$1/src/musl/.stamp \
    297         boot/boot5.sh boot/lib-arch.sh boot/lib-runscm.sh \
    298         $$(call seed_kernel_dep,$1,$2)
    299 	DRIVER=$2 boot/boot5.sh $1
    300 	@touch $$@
    301 
    302 build/$1/$2/boot5/libc.a build/$1/$2/boot5/crt1.o \
    303 build/$1/$2/boot5/crti.o build/$1/$2/boot5/crtn.o \
    304 build/$1/$2/boot5/hello: build/$1/$2/boot5/.stamp ;
    305 
    306 # boot6: seed-kernel ELF/Image, built with boot4's tcc2
    307 build/$1/$2/boot6/.stamp: \
    308         build/$1/$2/boot4/.stamp build/$1/$2/boot2/.stamp \
    309         boot/boot6.sh boot/lib-arch.sh boot/lib-runscm.sh \
    310         $$(call seed_kernel_dep,$1,$2)
    311 	DRIVER=$2 boot/boot6.sh $1
    312 	@touch $$@
    313 
    314 build/$1/$2/boot6/$$(KERNEL_NAME_$1): build/$1/$2/boot6/.stamp ;
    315 
    316 # boot7: install the final compiler + musl static sysroot in one tree.
    317 # This is a deterministic host-side assembly stage for either DRIVER; all
    318 # copied binaries were already produced through that driver's boot stages.
    319 build/$1/$2/boot7/.stamp: \
    320         build/$1/$2/boot4/.stamp build/$1/$2/boot5/.stamp \
    321         build/$1/src/.stamp \
    322         boot/boot7.sh boot/lib-arch.sh
    323 	DRIVER=$2 boot/boot7.sh $1
    324 	@touch $$@
    325 
    326 build/$1/$2/boot7/toolchain/bin/tcc \
    327 build/$1/$2/boot7/toolchain/lib/libc.a \
    328 build/$1/$2/boot7/toolchain/lib/tcc/libtcc1.a \
    329 build/$1/$2/boot7/toolchain/lib/crt1.o \
    330 build/$1/$2/boot7/toolchain/lib/crti.o \
    331 build/$1/$2/boot7/toolchain/lib/crtn.o \
    332 build/$1/$2/boot7/toolchain/MANIFEST.sha256: build/$1/$2/boot7/.stamp ;
    333 endef
    334 
    335 BOOT_DRIVERS_aarch64 := $(ALL_DRIVERS)
    336 BOOT_DRIVERS_amd64   := $(ALL_DRIVERS)
    337 BOOT_DRIVERS_riscv64 := $(ALL_DRIVERS)
    338 BOOT_DRIVERS_riscv32 := podman
    339 
    340 $(foreach a,$(ALL_ARCHES),$(foreach d,$(BOOT_DRIVERS_$(a)),$(eval $(call BOOT_CHAIN_RULES,$a,$d))))
    341 
    342 # ── Top-level catm'd cc bundle (arch-independent) ────────────────────────
    343 #
    344 # scheme1 reads cc as one file: prelude + cc + main concatenated. The
    345 # output is arch-independent, so we keep a single repo-level copy that
    346 # every test invocation reuses regardless of arch. Plain `cat` is byte-
    347 # equivalent to running the bootN catm on these inputs.
    348 
    349 build/cc.scm: scheme1/prelude.scm cc/cc.scm cc/main.scm
    350 	mkdir -p $(@D)
    351 	cat $^ > $@
    352 
    353 # ── cloc ─────────────────────────────────────────────────────────────────
    354 #
    355 # Default covers every arch; `make cloc ARCH=<a>` narrows the per-arch
    356 # pieces (P1-<a>.M1pp/.M1, vendor/seed/<a>/*) to one arch.
    357 
    358 CLOC_SEED_BASES := hex0.hex0 hex1.hex0 hex2.hex1 catm.hex2 M0.hex2 ELF.hex2
    359 
    360 ifeq ($(origin ARCH),file)
    361   CLOC_ARCHES := $(ALL_ARCHES)
    362 else
    363   CLOC_ARCHES := $(ARCH)
    364 endif
    365 
    366 CLOC_FILES := \
    367     $(foreach a,$(CLOC_ARCHES),$(foreach f,$(CLOC_SEED_BASES),vendor/seed/$(a)/$(f))) \
    368     $(foreach a,$(CLOC_ARCHES),P1/P1-$(a).M1) \
    369     M1pp/M1pp.P1 \
    370     hex2pp/hex2pp.P1 \
    371     $(foreach a,$(CLOC_ARCHES),vendor/seed/$(a)/ELF.hex2) \
    372     $(foreach a,$(CLOC_ARCHES),P1/P1-$(a).M1pp) \
    373     P1/P1.M1pp \
    374     P1/P1pp.P1pp \
    375     scheme1/scheme1.P1pp \
    376     scheme1/prelude.scm \
    377     cc/cc.scm
    378 
    379 cloc:
    380 	@sh tools/count-lines.sh $(CLOC_FILES)
    381 
    382 # ── Test infrastructure (suites + their build deps) ──────────────────────
    383 # tests/Makefile owns the test-build rules and the `test` / `image` /
    384 # `tables` targets. ARCH/DRIVER and per-arch metadata above are visible
    385 # to the include. REPO_ROOT signals to tests/Makefile that it is being
    386 # included from the top-level (rather than invoked standalone via
    387 # `make -C tests`).
    388 REPO_ROOT := $(CURDIR)
    389 include tests/Makefile