RustForge

foundry

Foundry OS: the operating system built on the Rustbelt kernel

foundry / Makefile
9854 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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# 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
SBASE_OUT  := $(PORTS_OUT)/sbase
# 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 sbase staging-v1 staging-v2 kernel kernel-src run test test-v1 test-v2 test-reference compare k1-check unit 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 sbase          fetch + build sbase (M4) into out/ports/sbase; v2 puts it in /usr/lib/sbase"
	@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 unit           host unit tests (cargo test): 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 $@

# sbase (M4): ~100 small tools, built once into out/ports/sbase/bin/. They
# go to /usr/lib/sbase on the v2 root, off the default PATH (ports/README.md).
sbase: $(SBASE_OUT)/tools.txt

$(SBASE_OUT)/tools.txt: $(SYSROOT)/lib/musl-pie.specs ports/sbase/build.sh $(wildcard ports/sbase/patches/*.patch)
	ports/fetch.sh sbase cache
	ports/sbase/build.sh cache $(PORTS_OUT)/sbase-build $(SBASE_OUT)

# ---- 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 sbase
	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
	mkdir -p $(STAGING_V2)/root/usr/lib/sbase
	cp $(SBASE_OUT)/bin/* $(SBASE_OUT)/LICENSE $(STAGING_V2)/root/usr/lib/sbase/
	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)

# Host unit tests (`cargo test` on the build machine, no QEMU): rbsh's
# parser is pure Rust, so rust/rbsh/host-tests compiles it for the host.
# Run from the repository root so rust/.cargo/config.toml (which picks the
# Rustbelt target) does not apply.
unit:
	CARGO_TARGET_DIR=$(OUT)/host-target cargo test --manifest-path rust/rbsh/host-tests/Cargo.toml

rbsh-test: unit
	$(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