RustForge

foundry

Foundry OS: the operating system built on the Rustbelt kernel

foundry / Makefile
8998 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# 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)) GETTY_GREETING="Rustbelt getty: shell on this terminal" \
	    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