|
1 | | -# kbox - Linux kernel as a library, rootless chroot via seccomp-unotify |
2 | | -# Build: make [BUILD=release] |
3 | | -# Test: make check |
4 | | - |
5 | | -CC ?= gcc |
6 | | -CFLAGS ?= |
7 | | -LDFLAGS ?= |
8 | | -BUILD ?= debug |
9 | | - |
10 | | -ARCH ?= $(shell uname -m) |
11 | | - |
12 | | -CFLAGS += -std=gnu11 -D_GNU_SOURCE -Wall -Wextra -Wpedantic -Wshadow |
13 | | -CFLAGS += -Wno-unused-parameter |
14 | | -CFLAGS += -Iinclude -Isrc |
15 | | - |
16 | | -ifeq ($(BUILD),release) |
17 | | - CFLAGS += -O2 -DNDEBUG |
18 | | -else |
19 | | - CFLAGS += -O0 -g3 -fsanitize=address,undefined -fno-omit-frame-pointer |
20 | | - LDFLAGS += -fsanitize=address,undefined |
21 | | -endif |
22 | | - |
23 | | -# LKL library |
24 | | -LKL_DIR ?= lkl-$(ARCH) |
25 | | -LKL_LIB = $(LKL_DIR)/liblkl.a |
26 | | - |
27 | | -LDFLAGS += -L$(LKL_DIR) -L$(LKL_DIR)/lib |
28 | | -LDLIBS = -llkl -lpthread -ldl -lm -lrt |
29 | | - |
30 | | -# Optional: SLIRP networking (set KBOX_HAS_SLIRP=1 to enable) |
31 | | -ifdef KBOX_HAS_SLIRP |
32 | | - SLIRP_DIR = externals/minislirp |
33 | | - SLIRP_HDR = $(SLIRP_DIR)/src/libslirp.h |
34 | | - CFLAGS += -DKBOX_HAS_SLIRP -I$(SLIRP_DIR)/src |
35 | | - SLIRP_SRCS = $(wildcard $(SLIRP_DIR)/src/*.c) |
36 | | - SLIRP_OBJS = $(SLIRP_SRCS:.c=.o) |
37 | | -endif |
38 | | - |
39 | | -# Optional: Web observatory (set KBOX_HAS_WEB=1 to enable) |
40 | | -ifdef KBOX_HAS_WEB |
41 | | - CFLAGS += -DKBOX_HAS_WEB |
42 | | - WEB_ASSET_SRC = $(SRC_DIR)/web-assets.c |
| 1 | +# Build: make [BUILD=release] |
| 2 | +# Config: make config (interactive menuconfig) |
| 3 | +# make defconfig (all features enabled) |
| 4 | +# Test: make check |
| 5 | + |
| 6 | +.DEFAULT_GOAL := all |
| 7 | + |
| 8 | +# Kconfig integration |
| 9 | + |
| 10 | +KCONFIG_DIR := tools/kconfig |
| 11 | +KCONFIG_CONF := configs/Kconfig |
| 12 | + |
| 13 | +# Load configuration (safe include -- no error if .config is absent) |
| 14 | +-include .config |
| 15 | + |
| 16 | +# Targets that don't require .config |
| 17 | +CONFIG_TARGETS := config defconfig oldconfig savedefconfig clean distclean indent \ |
| 18 | + check-unit fetch-lkl fetch-minislirp install-hooks \ |
| 19 | + guest-bins stress-bins rootfs |
| 20 | +CONFIG_GENERATORS := config defconfig oldconfig |
| 21 | + |
| 22 | +# Require .config for build targets. |
| 23 | +# Note: 'make defconfig && make' is the correct two-step sequence. |
| 24 | +# 'make defconfig all' does NOT work because .config is parsed at |
| 25 | +# Make startup, before any recipe runs. |
| 26 | +BUILD_GOALS := $(filter-out $(CONFIG_TARGETS),$(or $(MAKECMDGOALS),all)) |
| 27 | +HAS_CONFIG_GEN := $(filter $(CONFIG_GENERATORS),$(MAKECMDGOALS)) |
| 28 | +ifneq ($(BUILD_GOALS),) |
| 29 | +ifeq ($(HAS_CONFIG_GEN),) |
| 30 | +ifeq ($(wildcard .config),) |
| 31 | + $(info ) |
| 32 | + $(info *** Configuration file ".config" not found!) |
| 33 | + $(info *** Please run 'make config' or 'make defconfig' first.) |
| 34 | + $(info ) |
| 35 | + $(error Configuration required) |
43 | 36 | endif |
44 | | - |
45 | | -# Source files |
46 | | -SRC_DIR = src |
47 | | -SRCS = $(SRC_DIR)/main.c \ |
48 | | - $(SRC_DIR)/cli.c \ |
49 | | - $(SRC_DIR)/syscall-nr.c \ |
50 | | - $(SRC_DIR)/lkl-wrap.c \ |
51 | | - $(SRC_DIR)/fd-table.c \ |
52 | | - $(SRC_DIR)/procmem.c \ |
53 | | - $(SRC_DIR)/path.c \ |
54 | | - $(SRC_DIR)/identity.c \ |
55 | | - $(SRC_DIR)/elf.c \ |
56 | | - $(SRC_DIR)/mount.c \ |
57 | | - $(SRC_DIR)/probe.c \ |
58 | | - $(SRC_DIR)/image.c \ |
59 | | - $(SRC_DIR)/seccomp-bpf.c \ |
60 | | - $(SRC_DIR)/seccomp-notify.c \ |
61 | | - $(SRC_DIR)/shadow-fd.c \ |
62 | | - $(SRC_DIR)/seccomp-dispatch.c \ |
63 | | - $(SRC_DIR)/seccomp-supervisor.c \ |
64 | | - $(SRC_DIR)/net-slirp.c \ |
65 | | - $(SRC_DIR)/web-telemetry.c \ |
66 | | - $(SRC_DIR)/web-events.c \ |
67 | | - $(SRC_DIR)/web-server.c |
68 | | - |
69 | | -ifdef KBOX_HAS_WEB |
70 | | - SRCS += $(WEB_ASSET_SRC) |
71 | 37 | endif |
72 | | - |
73 | | -ifdef KBOX_HAS_SLIRP |
74 | | - SRCS += $(SLIRP_SRCS) |
75 | 38 | endif |
76 | 39 |
|
77 | | -OBJS = $(SRCS:.c=.o) |
78 | | -TARGET = kbox |
79 | | - |
80 | | -# Unit test files (no LKL dependency) |
81 | | -TEST_DIR = tests/unit |
82 | | -TEST_SRCS = $(TEST_DIR)/test-runner.c \ |
83 | | - $(TEST_DIR)/test-fd-table.c \ |
84 | | - $(TEST_DIR)/test-path.c \ |
85 | | - $(TEST_DIR)/test-identity.c \ |
86 | | - $(TEST_DIR)/test-syscall-nr.c \ |
87 | | - $(TEST_DIR)/test-elf.c |
88 | | - |
89 | | -# Unit tests link only the pure-computation sources (no LKL) |
90 | | -TEST_SUPPORT_SRCS = $(SRC_DIR)/fd-table.c \ |
91 | | - $(SRC_DIR)/path.c \ |
92 | | - $(SRC_DIR)/identity.c \ |
93 | | - $(SRC_DIR)/syscall-nr.c \ |
94 | | - $(SRC_DIR)/elf.c |
95 | | - |
96 | | -TEST_OBJS = $(TEST_SRCS:.c=.o) $(TEST_SUPPORT_SRCS:.c=.o) |
97 | | -TEST_TARGET = tests/unit/test-runner |
98 | | - |
99 | | -# Guest test programs (compiled statically, run inside kbox) |
100 | | -GUEST_DIR = tests/guest |
101 | | -GUEST_SRCS = $(wildcard $(GUEST_DIR)/*-test.c) |
102 | | -GUEST_BINS = $(GUEST_SRCS:.c=) |
103 | | - |
104 | | -# Stress test programs (compiled statically, run inside kbox) |
105 | | -STRESS_DIR = tests/stress |
106 | | -STRESS_SRCS = $(wildcard $(STRESS_DIR)/*.c) |
107 | | -STRESS_BINS = $(STRESS_SRCS:.c=) |
| 40 | +# Include modular build fragments |
| 41 | +include mk/toolchain.mk |
| 42 | +include mk/features.mk |
| 43 | +include mk/deps.mk |
| 44 | +include mk/tests.mk |
| 45 | +include mk/format.mk |
108 | 46 |
|
109 | | -# Rootfs image |
110 | | -ROOTFS = alpine.ext4 |
111 | | - |
112 | | -# ---- Top-level targets ---- |
113 | | - |
114 | | -.PHONY: all clean check check-unit check-integration check-stress guest-bins stress-bins rootfs fetch-lkl fetch-minislirp install-hooks web-assets indent |
| 47 | +# Top-level targets |
| 48 | +.PHONY: all clean distclean config defconfig oldconfig savedefconfig install-hooks |
115 | 49 |
|
116 | 50 | all: $(TARGET) |
117 | | -ifneq ($(wildcard .git),) |
| 51 | +ifneq ($(wildcard .git/hooks),) |
118 | 52 | all: | .git/hooks/pre-commit |
119 | 53 | endif |
120 | 54 |
|
121 | | -$(TARGET): $(OBJS) | $(LKL_LIB) |
122 | | - $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS) |
| 55 | +$(TARGET): $(OBJS) $(LKL_LIB) |
| 56 | + @echo " LD $@" |
| 57 | + $(Q)$(CC) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS) |
| 58 | + |
| 59 | +# Rebuild all objects when .config changes (CFLAGS may differ). |
| 60 | +$(OBJS): $(wildcard .config) |
123 | 61 |
|
124 | 62 | %.o: %.c |
125 | | - $(CC) $(CFLAGS) -c -o $@ $< |
| 63 | + @echo " CC $<" |
| 64 | + $(Q)$(CC) $(CFLAGS) -MMD -MP -c -o $@ $< |
126 | 65 |
|
127 | 66 | # Auto-install git hooks on first build (skipped in worktrees where .git is a file). |
128 | 67 | .git/hooks/pre-commit: scripts/pre-commit.hook |
129 | | - @if [ -d .git/hooks ]; then $(MAKE) -s install-hooks; fi |
130 | | - |
131 | | -# Auto-fetch LKL if missing |
132 | | -$(LKL_LIB): |
133 | | - @echo "LKL library not found at $(LKL_DIR). Fetching..." |
134 | | - ./scripts/fetch-lkl.sh $(ARCH) |
135 | | - |
136 | | -# Auto-fetch minislirp if missing (shallow clone, no submodule). |
137 | | -# $(wildcard) evaluates at parse time, so if minislirp has not been |
138 | | -# fetched yet SLIRP_SRCS is empty. Guard: fetch and re-exec make so |
139 | | -# the wildcard picks up the newly-cloned sources. |
140 | | -ifdef KBOX_HAS_SLIRP |
141 | | -# Auto-fetch minislirp if the header is missing and a build target |
142 | | -# was requested (skip for clean/fetch-only goals). |
143 | | -ifneq ($(filter clean,$(MAKECMDGOALS)),clean) |
144 | | -ifeq ($(wildcard $(SLIRP_HDR)),) |
145 | | -$(shell ./scripts/fetch-minislirp.sh >&2) |
146 | | -SLIRP_SRCS = $(wildcard $(SLIRP_DIR)/src/*.c) |
147 | | -SLIRP_OBJS = $(SLIRP_SRCS:.c=.o) |
148 | | -endif |
149 | | -endif |
150 | | - |
151 | | -fetch-minislirp: |
152 | | - ./scripts/fetch-minislirp.sh |
153 | | -endif |
154 | | - |
155 | | -# ---- Test targets ---- |
156 | | - |
157 | | -check: check-unit check-integration check-stress |
158 | | - |
159 | | -check-unit: $(TEST_TARGET) |
160 | | - ./$(TEST_TARGET) |
161 | | - |
162 | | -# Unit tests are built WITHOUT linking LKL. |
163 | | -# We define LKL stubs for functions referenced by test support code. |
164 | | -$(TEST_TARGET): $(TEST_SRCS) $(TEST_SUPPORT_SRCS) |
165 | | - $(CC) $(CFLAGS) -DKBOX_UNIT_TEST -o $@ $^ $(LDFLAGS) |
166 | | - |
167 | | -check-integration: $(TARGET) guest-bins stress-bins $(ROOTFS) |
168 | | - ./scripts/run-tests.sh ./$(TARGET) $(ROOTFS) |
169 | | - |
170 | | -check-stress: $(TARGET) stress-bins $(ROOTFS) |
171 | | - ./scripts/run-stress.sh ./$(TARGET) $(ROOTFS) || \ |
172 | | - echo "(stress test failures are non-blocking -- see TODO.md)" |
173 | | - |
174 | | -# ---- Guest / stress binaries (static, no ASAN) ---- |
175 | | -# These are cross-compiled on Linux and placed into the rootfs. |
176 | | -# They must be statically linked and cannot use sanitizers. |
177 | | - |
178 | | -guest-bins: $(GUEST_BINS) |
179 | | - |
180 | | -$(GUEST_DIR)/%-test: $(GUEST_DIR)/%-test.c |
181 | | - $(CC) -std=gnu11 -Wall -Wextra -O2 -static -o $@ $< |
| 68 | + $(Q)if [ -d .git/hooks ]; then $(MAKE) install-hooks; fi |
182 | 69 |
|
183 | | -stress-bins: $(STRESS_BINS) |
184 | | - |
185 | | -$(STRESS_DIR)/%: $(STRESS_DIR)/%.c |
186 | | - $(CC) -std=gnu11 -Wall -Wextra -O2 -static -pthread -o $@ $< |
187 | | - |
188 | | -# ---- Rootfs ---- |
189 | | - |
190 | | -rootfs: $(ROOTFS) |
191 | | - |
192 | | -$(ROOTFS): scripts/mkrootfs.sh scripts/alpine-sha256.txt $(GUEST_BINS) $(STRESS_BINS) |
193 | | - ALPINE_ARCH=$(ARCH) ./scripts/mkrootfs.sh |
194 | | - |
195 | | -# ---- Utilities ---- |
196 | | - |
197 | | -# Fetch LKL from nightly release if not cached locally. |
198 | | -# To force re-download: rm -rf lkl-x86_64 && make fetch-lkl |
199 | | -fetch-lkl: |
200 | | - ./scripts/fetch-lkl.sh $(ARCH) |
201 | | - |
202 | | -# Install git hooks from scripts/*.hook into .git/hooks/. |
203 | | -# Skips hooks that already exist (preserves user customizations). |
204 | 70 | install-hooks: |
205 | | - @for hook in scripts/*.hook; do \ |
| 71 | + $(Q)for hook in scripts/*.hook; do \ |
206 | 72 | name=$$(basename "$$hook" .hook); \ |
207 | 73 | if [ ! -e .git/hooks/"$$name" ] && [ ! -L .git/hooks/"$$name" ]; then \ |
208 | 74 | ln -s ../../"$$hook" .git/hooks/"$$name"; \ |
209 | 75 | echo "Installed $$name hook"; \ |
210 | 76 | fi; \ |
211 | 77 | done |
212 | 78 |
|
213 | | -# Generate compiled-in web assets from web/ directory. |
214 | | -# Re-run when any web/ file changes. |
215 | | -ifdef KBOX_HAS_WEB |
216 | | -WEB_SRCS_ALL = $(shell find web -type f \( -name '*.html' -o -name '*.css' -o -name '*.js' -o -name '*.svg' \) 2>/dev/null) |
217 | | -$(WEB_ASSET_SRC): $(WEB_SRCS_ALL) scripts/gen-web-assets.sh |
218 | | - ./scripts/gen-web-assets.sh |
219 | | -web-assets: $(WEB_ASSET_SRC) |
220 | | -endif |
| 79 | +# Kconfig targets |
221 | 80 |
|
222 | | -# ---- Formatting ---- |
| 81 | +# Fetch Kconfiglib on demand, then run the appropriate tool. |
| 82 | +config: | $(KCONFIG_DIR)/menuconfig.py |
| 83 | + $(Q)KCONFIG_CONFIG=.config python3 $(KCONFIG_DIR)/menuconfig.py $(KCONFIG_CONF) |
| 84 | + @echo " CONFIG .config" |
223 | 85 |
|
224 | | -CLANG_FORMAT := $(shell command -v clang-format-20 2>/dev/null || \ |
225 | | - command -v clang-format 2>/dev/null) |
226 | | -SHFMT := shfmt |
227 | | -BLACK := black |
| 86 | +defconfig: | $(KCONFIG_DIR)/menuconfig.py |
| 87 | + @echo " CONFIG defconfig" |
| 88 | + $(Q)KCONFIG_CONFIG=.config python3 $(KCONFIG_DIR)/defconfig.py --kconfig $(KCONFIG_CONF) configs/defconfig |
228 | 89 |
|
229 | | -C_SRCS_FMT := $(wildcard src/*.c src/*.h include/kbox/*.h \ |
230 | | - tests/unit/*.c tests/unit/*.h \ |
231 | | - tests/guest/*.c tests/stress/*.c) |
232 | | -SH_SRCS_FMT := $(wildcard scripts/*.sh) |
233 | | -PY_SRCS_FMT := $(wildcard scripts/gdb/*.py) |
| 90 | +oldconfig: | $(KCONFIG_DIR)/menuconfig.py |
| 91 | + @echo " CONFIG oldconfig" |
| 92 | + $(Q)KCONFIG_CONFIG=.config python3 $(KCONFIG_DIR)/oldconfig.py $(KCONFIG_CONF) |
234 | 93 |
|
235 | | -indent: |
236 | | -ifeq ($(CLANG_FORMAT),) |
237 | | - $(error clang-format not found; install clang-format or clang-format-20) |
238 | | -endif |
239 | | - @$(CLANG_FORMAT) --version | grep -q 'version 20' || \ |
240 | | - { echo "error: clang-format version 20 required"; exit 1; } |
241 | | - $(CLANG_FORMAT) -i $(C_SRCS_FMT) |
242 | | - $(SHFMT) -i 4 -fn -ci -sr -bn -w $(SH_SRCS_FMT) |
243 | | - $(BLACK) -q $(PY_SRCS_FMT) |
| 94 | +savedefconfig: | $(KCONFIG_DIR)/menuconfig.py |
| 95 | + @echo " CONFIG savedefconfig" |
| 96 | + $(Q)KCONFIG_CONFIG=.config python3 $(KCONFIG_DIR)/savedefconfig.py --kconfig $(KCONFIG_CONF) --out configs/defconfig |
244 | 97 |
|
245 | | -clean: |
246 | | - rm -f $(OBJS) $(TARGET) $(TEST_TARGET) $(TEST_DIR)/*.o |
247 | | - rm -f src/*.o src/web-assets.c |
248 | | - rm -f $(GUEST_BINS) $(STRESS_BINS) |
| 98 | +$(KCONFIG_DIR)/menuconfig.py: |
| 99 | + @echo " FETCH kconfiglib" |
| 100 | + $(Q)scripts/fetch-kconfiglib.sh |
| 101 | + |
| 102 | +# Clean |
249 | 103 |
|
250 | | -# ---- Dependencies ---- |
251 | | -# Auto-generate with gcc -MM if needed; keep it simple for now. |
252 | | -$(SRC_DIR)/main.o: include/kbox/cli.h include/kbox/image.h |
253 | | -$(SRC_DIR)/cli.o: include/kbox/cli.h |
254 | | -$(SRC_DIR)/probe.o: include/kbox/probe.h src/seccomp-defs.h |
255 | | -$(SRC_DIR)/image.o: include/kbox/image.h include/kbox/mount.h include/kbox/identity.h include/kbox/probe.h src/lkl-wrap.h src/net.h src/seccomp.h src/shadow-fd.h |
256 | | -$(SRC_DIR)/shadow-fd.o: src/shadow-fd.h src/lkl-wrap.h src/syscall-nr.h |
257 | | -$(SRC_DIR)/seccomp-dispatch.o: src/seccomp.h src/seccomp-defs.h src/fd-table.h src/lkl-wrap.h src/procmem.h include/kbox/path.h include/kbox/identity.h src/shadow-fd.h src/net.h |
258 | | -$(SRC_DIR)/seccomp-supervisor.o: src/seccomp.h src/seccomp-defs.h src/syscall-nr.h |
259 | | -$(SRC_DIR)/seccomp-bpf.o: src/seccomp.h src/seccomp-defs.h src/syscall-nr.h |
260 | | -$(SRC_DIR)/seccomp-notify.o: src/seccomp.h src/seccomp-defs.h |
261 | | -$(SRC_DIR)/net-slirp.o: src/net.h src/lkl-wrap.h src/syscall-nr.h |
262 | | -$(SRC_DIR)/web-telemetry.o: src/web.h src/lkl-wrap.h src/syscall-nr.h |
263 | | -$(SRC_DIR)/web-events.o: src/web.h |
264 | | -$(SRC_DIR)/web-server.o: src/web.h src/fd-table.h src/lkl-wrap.h src/syscall-nr.h |
| 104 | +clean: |
| 105 | + @echo " CLEAN" |
| 106 | + $(Q)rm -f $(OBJS) $(OBJS:.o=.d) $(TARGET) $(TEST_TARGET) $(TEST_DIR)/*.o |
| 107 | + $(Q)rm -f src/*.o src/*.d src/web-assets.c |
| 108 | + $(Q)rm -f $(GUEST_BINS) $(STRESS_BINS) |
| 109 | + |
| 110 | +distclean: clean |
| 111 | + @echo " CLEAN distclean" |
| 112 | + $(Q)rm -f .config .config.old |
| 113 | + $(Q)rm -rf $(KCONFIG_DIR) |
| 114 | + |
| 115 | +# Auto-generated header dependencies (-MMD -MP writes .d alongside .o) |
| 116 | +-include $(OBJS:.o=.d) |
0 commit comments