# Foundry OS -- the single entry point. `make help` lists the targets. # # Everything generated goes to out/ (git-ignored); `make clean` removes it. # Downloads (musl source etc.) go to cache/ (git-ignored, SHA256-checked). include config.mk OUT := $(CURDIR)/out IMAGE_V2 := $(OUT)/$(IMAGE_NAME) IMAGE_V1 := $(OUT)/$(IMAGE_V1_NAME) IMAGE := $(if $(filter v1,$(CONTRACT)),$(IMAGE_V1),$(IMAGE_V2)) STAGING_V1 := $(OUT)/staging-v1 STAGING_V2 := $(OUT)/staging-v2 RUST_OUT := $(OUT)/rust-target/x86_64-unknown-none/release FIXTURES := $(OUT)/fixtures KSRC := $(OUT)/kernel-src KTARGET := $(OUT)/kernel-target KBOOT := $(OUT)/kernel/bootimage.bin KBOOT_HL := $(OUT)/kernel/bootimage-headless.bin SYSROOT := $(OUT)/sysroot FCC := $(CURDIR)/toolchain/foundry-cc C_OUT := $(OUT)/c # Everything in /bin, in order: the kernel's list, then Foundry OS's own. ALL_BIN := $(BIN_ORDER) $(FOUNDRY_C) $(FOUNDRY_PORTS) PORTS_OUT := $(OUT)/ports # Contract v2: the kernel's ABI test programs live on B: (/mnt/disk0/bin), # in the kernel's order; everything else is in A:/bin. DATA_BIN := $(filter-out $(RUST_PROGRAMS),$(BIN_ORDER)) .DEFAULT_GOAL := image .PHONY: help image image-v1 image-v2 rust fixtures cprogs ports staging-v1 staging-v2 kernel kernel-src run test test-v1 test-v2 test-reference compare k1-check rbsh-test musl clean distclean help: @echo "Foundry OS (userland for the Rustbelt kernel $(KERNEL_VERSION) @ $(KERNEL_REV))" @echo "" @echo " make / make image build the disk image for contract $(CONTRACT): $(notdir $(IMAGE))" @echo " make image-v1 build out/$(IMAGE_V1_NAME) (contract v1: one FAT32 partition)" @echo " make image-v2 build out/$(IMAGE_NAME) (contract v2: ext2 root + FAT32 + exFAT share)" @echo " make rust cargo build the Rust programs in rust/" @echo " make fixtures build the kernel's ABI test programs from the pinned kernel" @echo " make cprogs build our C programs (c/) as static-PIEs with toolchain/foundry-cc" @echo " make ports fetch (cache/) + build the ports ($(FOUNDRY_PORTS)) the same way" @echo " make staging-v1/-v2 assemble the image trees in out/staging-v1, out/staging-v2" @echo " make kernel build the pinned kernel into out/kernel-target" @echo " make run boot the image in a QEMU window" @echo " make test make test-$(CONTRACT)" @echo " make test-v2 headless boot: init runs the v2 self-test; then host fsck + share checks" @echo " make test-v1 headless boot of the v1 image (the kernel runs the shell self-test)" @echo " make test-reference the v1 test, but booting the kernel's own disk.img (baseline)" @echo " make compare compare out/$(IMAGE_V1_NAME) with the kernel's own disk.img" @echo " make rbsh-test host unit tests for rbsh's parser and drive letters (no QEMU)" @echo " make k1-check build the kernel with RUSTBELT_DISK0=our image (K1) and check it boots ours" @echo " make musl fetch (cache/) + build musl $(MUSL_VERSION) into out/sysroot" @echo " make clean rm -rf out (distclean also removes cache/)" # ---- the pinned kernel ------------------------------------------------------- kernel-src: scripts/build-kernel.sh $(KERNEL_DIR) $(KERNEL_REV) $(OUT) --export-only kernel: scripts/build-kernel.sh $(KERNEL_DIR) $(KERNEL_REV) $(OUT) # ---- programs --------------------------------------------------------------- rust: cd rust && CARGO_TARGET_DIR=$(OUT)/rust-target cargo build --release fixtures: kernel-src scripts/build-fixtures.sh $(KSRC) $(FIXTURES) "$(FIXTURE_RUST)" "$(FIXTURE_C)" "$(FIXTURE_MUSL)" # Our own C programs: gcc + our musl, static-PIE (docs/porting.md). cprogs: $(SYSROOT)/lib/musl-pie.specs mkdir -p $(C_OUT) for p in $(FOUNDRY_C); do \ $(FCC) -O2 -Wall -Wextra -pthread -Wl,--build-id=none c/$$p.c -o $(C_OUT)/$$p || exit 1; \ done @echo "[cprogs] built $(FOUNDRY_C) into $(C_OUT)" # Ports: each one is fetched into cache/ (SHA256-checked) and built once; # delete out/ports/bin/NAME to rebuild it. ports: $(addprefix $(PORTS_OUT)/bin/,$(FOUNDRY_PORTS)) $(PORTS_OUT)/bin/%: $(SYSROOT)/lib/musl-pie.specs ports/%/build.sh ports/fetch.sh $* cache ports/$*/build.sh cache $(PORTS_OUT)/$*-build $@ # ---- the image --------------------------------------------------------------- image: image-$(CONTRACT) # Contract v1: rootfs/ goes to the top of the FAT32 volume, all programs to # /bin, rbsh as /bin/sh (the kernel runs /mnt/disk0/bin/sh). staging-v1: rust fixtures cprogs ports rm -rf $(STAGING_V1) && mkdir -p $(STAGING_V1)/bin cp -r rootfs/. $(STAGING_V1)/ for p in $(filter-out sh,$(RUST_PROGRAMS)); do cp $(RUST_OUT)/$$p $(STAGING_V1)/bin/; done cp $(RUST_OUT)/rbsh $(STAGING_V1)/bin/sh cp $(FIXTURES)/* $(STAGING_V1)/bin/ for p in $(FOUNDRY_C); do cp $(C_OUT)/$$p $(STAGING_V1)/bin/; done for p in $(FOUNDRY_PORTS); do cp $(PORTS_OUT)/bin/$$p $(STAGING_V1)/bin/; done image-v1: staging-v1 scripts/mkimage.sh $(STAGING_V1) $(IMAGE_V1) $(IMAGE_MIB) $(IMAGE_LABEL) "$(ALL_BIN)" # Contract v2 (docs/boot-contract.md): three trees, one per partition. # root/ -> A: (ext2 root): /sbin/init, our programs in /bin, /etc, /scripts # data/ -> B: (FAT32, /mnt/disk0): rootfs/ minus scripts/, + the ABI fixtures in bin/ # share/ -> C: (exFAT, /mnt/share): share/ staging-v2: rust fixtures cprogs ports rm -rf $(STAGING_V2) && mkdir -p $(STAGING_V2)/data/bin $(STAGING_V2)/share cp -r root $(STAGING_V2)/root && rm -f $(STAGING_V2)/root/README.md cd $(STAGING_V2)/root && mkdir -p sbin bin usr/bin tmp root home/tyler mnt/disk0 mnt/share cp $(RUST_OUT)/init $(STAGING_V2)/root/sbin/init cp $(RUST_OUT)/rbsh $(STAGING_V2)/root/bin/rbsh for p in $(filter-out sh,$(RUST_PROGRAMS)); do cp $(RUST_OUT)/$$p $(STAGING_V2)/root/bin/; done for p in $(FOUNDRY_C); do cp $(C_OUT)/$$p $(STAGING_V2)/root/bin/; done for p in $(FOUNDRY_PORTS); do cp $(PORTS_OUT)/bin/$$p $(STAGING_V2)/root/bin/; done ln -s dash $(STAGING_V2)/root/bin/sh cp rootfs/scripts/dash-test.sh $(STAGING_V2)/root/scripts/ cd rootfs && for e in $$(ls -A); do [ "$$e" = scripts ] || cp -r "$$e" $(STAGING_V2)/data/; done cp $(FIXTURES)/* $(STAGING_V2)/data/bin/ cp share/* $(STAGING_V2)/share/ image-v2: staging-v2 ROOT_MIB=$(ROOT_MIB) DATA_MIB=$(DATA_MIB) SHARE_MIB=$(SHARE_MIB) \ scripts/mkimage-v2.sh $(STAGING_V2) $(IMAGE_V2) "$(DATA_BIN)" # ---- booting ---------------------------------------------------------------- run: image kernel CONTRACT=$(CONTRACT) scripts/run-qemu.sh $(IMAGE) $(KBOOT) $(KTARGET) $(OUT) # What the expected-line checks need to know about the pinned kernel. TEST_ENV := KERNEL_BANNER="$(KERNEL_BANNER)" KERNEL_VERSION=$(KERNEL_VERSION) test: test-$(CONTRACT) # v2: boots a writable copy (out/test-v2.img) so the host can check what # the run left behind: fsck of all three file systems, and the file the # self-test wrote to the share (scripts/check-v2-image.sh). test-v2: image-v2 kernel cp $(IMAGE_V2) $(OUT)/test-v2.img $(TEST_ENV) CONTRACT=v2 BIN_COUNT=$(words $(DATA_BIN)) ROOT_BIN_COUNT=$$(ls $(STAGING_V2)/root/bin | wc -l) \ EXTRA_EXPECT=scripts/expect-v2.txt \ scripts/run-headless.sh $(OUT)/test-v2.img $(KBOOT_HL) $(KTARGET) $(OUT)/v2 scripts/check-v2-image.sh $(OUT)/test-v2.img test-v1: image-v1 kernel $(TEST_ENV) BIN_COUNT=$(words $(ALL_BIN)) EXTRA_EXPECT=scripts/expect-foundry.txt \ scripts/run-headless.sh $(IMAGE_V1) $(KBOOT_HL) $(KTARGET) $(OUT) # The baseline: the kernel's own disk.img through the very same v1 checks # (without Foundry OS's own programs and expected lines, which it lacks). test-reference: kernel $(TEST_ENV) BIN_COUNT=$(words $(BIN_ORDER)) \ scripts/run-headless.sh $(KTARGET)/disk.img $(KBOOT_HL) $(KTARGET) $(OUT)/reference compare: image-v1 kernel FOUNDRY_BIN="$(FOUNDRY_C) $(FOUNDRY_PORTS)" FOUNDRY_FILES="$(FOUNDRY_FILES)" scripts/compare-image.sh $(IMAGE_V1) $(KTARGET)/disk.img $(OUT) # rbsh's parser is pure Rust, so it is also tested on the host. rbsh-test: mkdir -p $(OUT)/host-tests rustc --edition 2024 --test rust/rbsh/host-tests/parse_test.rs -o $(OUT)/host-tests/parse_test $(OUT)/host-tests/parse_test # K1 (kernel 0.21.0+): the kernel's own build takes our image as disk 0. # Uses its own target dir, so out/kernel-target (the reference) is untouched. k1-check: image kernel-src scripts/k1-check.sh $(IMAGE) $(KSRC) $(OUT)/kernel-k1-target "$(RUST_PROGRAMS)" # ---- toolchain --------------------------------------------------------------- # musl is built once; delete out/sysroot (or run `make musl`) to rebuild it. musl: toolchain/musl/fetch.sh cache toolchain/musl/build.sh cache $(OUT)/musl-build $(SYSROOT) $(SYSROOT)/lib/musl-pie.specs: toolchain/musl/musl.sha256 toolchain/musl/musl-pie.specs.in toolchain/musl/build.sh $(MAKE) musl clean: rm -rf out distclean: clean rm -rf cache