BUILD = build SYSROOT = sysroot # ----------------------------------------------------------------------- # Parallelism # # Nothing here is order-dependent beyond what the rules already declare, so # there is no reason to compile one file at a time. Override with `make -j1` # when you want readable output from a failing build. # ----------------------------------------------------------------------- # nproc is coreutils, so it exists on Linux and under Git Bash but not on # macOS or the BSDs, where the same number comes from sysctl. Falling through # to 4 is a working build either way, just a slower one. NPROC := $(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) MAKEFLAGS += -j$(NPROC) # Kernel object files — organized by subsystem KERN_ARCH = $(addprefix $(BUILD)/kernel/arch/x86_64/, \ acpi.o e820.o entry.o trapasm.o swtch.o \ trap.o vm.o lapic.o ioapic.o mp.o) KERN_CORE = $(addprefix $(BUILD)/kernel/core/, \ main.o kalloc.o dma.o spinlock.o sleeplock.o string.o console.o \ panic.o uaccess.o qr.o time.o) KERN_DEV = $(addprefix $(BUILD)/kernel/dev/, \ bio.o cdev.o device.o fb.o ide.o input.o irq.o kbd.o mouse.o \ pci.o text.o ttf.o uart.o vga.o ramdisk.o procfs.o) # Drivers for hardware that has to be *found* rather than assumed. Anything # discovered by walking a bus belongs here; the fixed-address legacy devices # (console, keyboard, PIT, the IDE ports) stay in dev/. # # drivers/// -- one directory per driver, so a driver that grows # a register header, a second source file or a firmware blob has somewhere to # put it. Classes match include/device.h's DEVCLASS_*. Everything flat was # already eight files with four called usb_something, and each driver added made # the directory harder to read. The compile rule uses `mkdir -p $(dir $@)`, so # the depth costs nothing. KERN_DRIVERS = $(BUILD)/kernel/drivers/registry.o KERN_DRIVERS += $(BUILD)/kernel/drivers/gpu/vga/vga_pci.o KERN_DRIVERS += $(BUILD)/kernel/drivers/gpu/virtio_gpu/virtio_gpu.o KERN_DRIVERS += $(BUILD)/kernel/drivers/net/e1000/e1000.o KERN_DRIVERS += $(BUILD)/kernel/drivers/bus/xhci/xhci.o KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/core/usb.o KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/hid/usb_hid.o KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/storage/usb_storage.o KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/hub/usb_hub.o KERN_FS = $(addprefix $(BUILD)/kernel/fs/, \ exec.o file.o fs.o log.o pipe.o procfs.o) # SchFS. Every one of these is the very same source the host tools compile, so # the kernel and mkschfs cannot disagree about the on-disk format: there is one # implementation, found and fixed on the host where a test takes a second. KERN_FS += $(addprefix $(BUILD)/kernel/fs/schfs/, \ btree.o space.o vol.o obj.o objw.o kmount.o vfs.o) # Sockets. Unix domain only for now; an IP stack becomes more files here # rather than a reshaping of what is already written. KERN_NET = $(addprefix $(BUILD)/kernel/net/, \ unix.o) KERN_PROC = $(addprefix $(BUILD)/kernel/proc/, \ proc.o) KERN_SYS = $(addprefix $(BUILD)/kernel/sys/, \ syscall.o sys_proc.o sys_fs.o sys_mem.o sys_net.o \ sys_time.o sys_sig.o sys_compat.o sys_poll.o linux_abi.o) # vectors.o lives alongside trapasm in the arch dir KERN_ARCH += $(BUILD)/kernel/arch/x86_64/vectors.o KERNOBJS = $(KERN_ARCH) $(KERN_CORE) $(KERN_DEV) $(KERN_DRIVERS) \ $(KERN_FS) $(KERN_NET) $(KERN_PROC) $(KERN_SYS) # entry.o must be first in link order; remove it from KERNOBJS for the rule KERN_NOENTRY = $(filter-out $(BUILD)/kernel/arch/x86_64/entry.o, $(KERNOBJS)) # ----------------------------------------------------------------------- # First-party userland # # lib/ startup glue + syscall stubs every cmd/ program links against # cmd/ one directory per program: cmd//main.c -> sysroot/bin/ # # To add a program: mkdir cmd/foo, write cmd/foo/main.c, add "foo" to CMDS. # Third-party software does NOT go here — see ports/. # ----------------------------------------------------------------------- ULIB = $(addprefix $(BUILD)/lib/, start.o glue.o usys.o) CMDS = init schinit fbinfo shmtest socktest inputtest CMDBINS = $(addprefix $(BUILD)/cmd/, $(CMDS)) # ----------------------------------------------------------------------- # Toolchain # ----------------------------------------------------------------------- UNAME_S := $(shell uname -s) UNAME_P := $(shell uname -p) # Host platform. # # WINHOST Windows, under Git Bash or MSYS2 # MACHOST macOS # BSDHOST FreeBSD / OpenBSD / NetBSD / DragonFly # (else) Linux # # All four are supported hosts: this is developed from both Windows and Linux, # and nothing here should care which. # # Building natively on Windows rather than in WSL, because WSL2 is a real Linux # kernel in a Hyper-V VM and so hard-requires virtualisation; turning that on # puts the Hyper-V hypervisor underneath the whole machine and demotes VMware # to running nested on top of it, which is a real and permanent cost to pay for # a build environment. ifneq (,$(filter MINGW% MSYS% CYGWIN%,$(UNAME_S))) WINHOST := 1 endif ifeq ($(UNAME_S),Darwin) MACHOST := 1 endif ifneq (,$(filter FreeBSD OpenBSD NetBSD DragonFly,$(UNAME_S))) BSDHOST := 1 endif QEMU = qemu-system-x86_64 # Display for every QEMU target. # # virtio-gpu is what the display class binds, and what a compositor should be # developed against: presenting is explicit, so the host never shows a # half-drawn frame. # # stdvga stays alongside it and is not optional. The bootloader draws the splash # through VBE in 16-bit real mode and there is no virtio path that early, so a # machine with only virtio-gpu boots blind until the kernel is up. Both devices: # virtio-gpu wins the display class, stdvga carries the splash and text console. QEMU_DISPLAY = -vga std -device virtio-gpu-pci # ----------------------------------------------------------------------- # Cross-compilation: clang everywhere it can be had # # The kernel, the bootloader and the ports all need a compiler targeting # something other than the host -- bare-metal ELF, 16/32-bit code below one # megabyte, and Linux/musl userland respectively. The traditional answer is a # cross binutils+gcc per host, which is a day of work per platform that then # rots, and on macOS means brewing x86_64-elf-* before anything builds at all. # # clang needs none of it: it is a cross-compiler by construction, so one # ordinary LLVM install produces every target from every host. That was already # how the Windows build worked; using it on all four hosts collapses what used # to be two divergent toolchain branches -- one of which had drifted into # passing GNU-ld-only flags to what is nowadays lld on FreeBSD and macOS -- into # a single path that gets exercised on every platform instead of one. # # GNU is kept as a fallback for hosts without clang, but it is the exception. # ----------------------------------------------------------------------- HAVE_CLANG := $(shell command -v clang 2>/dev/null) HAVE_LLD := $(shell command -v ld.lld 2>/dev/null) ifeq ($(origin USE_GNU), undefined) ifneq (,$(HAVE_CLANG)) ifneq (,$(HAVE_LLD)) USE_CLANG := 1 endif endif endif ifdef USE_CLANG # An explicit --target is what makes clang emit ELF rather than whatever the # host defaults to -- COFF on Windows, Mach-O on macOS. Without it every # object is silently the wrong format and the first thing to complain is the # linker. CC = clang --target=x86_64-elf CC32 = clang --target=i386-elf LD = ld.lld OBJCOPY = llvm-objcopy OBJDUMP = llvm-objdump # ld.lld has no --no-warn-rwx-segments: it does not emit that warning, so # there is nothing to suppress and the flag is a hard error. LDFLAGS = -m elf_x86_64 -nostdlib # The bootloader, the AP trampoline, initcode and the cmd/ programs are all # linked at addresses below one megabyte. GNU ld places a section wherever # -Ttext says; lld first checks it against a default image base of 0x400000 # and refuses, because for a hosted program that would be a bug. Saying the # base is zero is how you tell it this is not a hosted program. LD_LOWBASE = --image-base=0 # ports/ build *Linux userland* against musl -- a third target, distinct from # both the kernel (bare-metal ELF) and the host tools (native). # # -nostdlib with the crt files named explicitly, because clang otherwise # reaches for gcc's crtbeginT.o and libgcc, which need not exist on any # particular host. MUSLPREFIX = $(abspath $(BUILD))/ports/musl/prefix PORTCC = clang --target=x86_64-unknown-linux-musl -fuse-ld=lld PORTCRT = -nostdlib $(MUSLPREFIX)/lib/crt1.o $(MUSLPREFIX)/lib/crti.o PORTEND = $(MUSLPREFIX)/lib/crtn.o else # GNU fallback. Needs a cross toolchain anywhere the host's own gcc does not # already emit x86_64 ELF -- notably macOS, where TOOLPREFIX has to name a # brewed x86_64-elf-gcc. ifdef MACHOST TOOLPREFIX ?= x86_64-elf- else ifeq ($(UNAME_P),aarch64) TOOLPREFIX ?= x86_64-linux-gnu- else TOOLPREFIX ?= endif CC = $(TOOLPREFIX)gcc CC32 = $(CC) -m32 LD = $(TOOLPREFIX)ld OBJCOPY = $(TOOLPREFIX)objcopy OBJDUMP = $(TOOLPREFIX)objdump LDFLAGS = -m elf_x86_64 -nostdlib --no-warn-rwx-segments LD_LOWBASE = PORTCC = $(TOOLPREFIX)gcc PORTCRT = PORTEND = endif # ----------------------------------------------------------------------- # Host tools: mkfs, mkschfs, the SchFS suites, qrtest. Ordinary programs for # the machine doing the building, so they use its native compiler. # ----------------------------------------------------------------------- HOSTCC ?= cc ifdef WINHOST # mingw-w64's gcc, not clang: clang defaults to an MSVC target here, and # tools/mkfs.c includes and , which the Windows SDK # does not have. HOSTCC = gcc # mingw-w64's gcc warns about things glibc's does not (notably %lld/%zu in # printf against msvcrt). Those warnings are about the host build only and # say nothing about the image, so they must not stop it. HOSTWARN = -Wall # MinGW gcc appends .exe when the -o name has no extension, so a rule whose # target lacks it is never considered built -- mkfs was recompiled on every # single invocation of make, forever, because of this. EXE = .exe # `python3` is not on PATH from the python.org installer; `python` is. PYTHON = python else HOSTWARN = -Werror -Wall EXE = PYTHON = python3 endif # Host tools are generated output and live under build/, per docs/layout.md. # $(EXE) matters: MinGW appends it, and a rule whose target lacks it never # looks satisfied. MKFS = $(BUILD)/tools/mkfs$(EXE) MKSCHFS = $(BUILD)/tools/mkschfs$(EXE) SCHFS_TEST = $(BUILD)/tools/schfs_test$(EXE) SCHFS_OBJT = $(BUILD)/tools/schfs_objtest$(EXE) SCHFS_FSCK = $(BUILD)/tools/schfs_fsck$(EXE) OPT ?= -O0 # -ftls-model=local-exec: the kernel is *the* executable, never a shared # object, so its __thread variables (cpu, proc) are always at a fixed negative # offset from the thread pointer. Saying so lets the compiler emit a direct # `movq %rcx, %fs:-0x10` and never materialise the thread pointer as a value. # # GCC picked local-exec on its own for this code; clang did not, and generated # `movq %fs:0x0, %rax; addq $-0x10, %rax; ...` instead -- which reads the TCB # self-pointer at %fs:0, a word this kernel does not maintain and something # else has apparently always been scribbling on. Harmless for fifteen years # because nothing ever read it; instant triple fault the moment a compiler did. XFLAGS = -m64 -DX64 -mcmodel=large -mtls-direct-seg-refs -mno-red-zone XFLAGS += -ftls-model=local-exec # Probed once, with :=, not on every expansion of CFLAGS. CFLAGS is a # recursively-expanded variable, so a $(shell) appended to it re-ran the probe # on every single compile -- one extra gcc process per object file, for an # answer that cannot change during a build. NO_SSP := $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) CFLAGS = -fno-pic -static -fno-builtin -fno-strict-aliasing -Wall -MD -ggdb -fno-omit-frame-pointer CFLAGS += -ffreestanding -fno-common -nostdlib -I./include $(XFLAGS) $(OPT) CFLAGS += $(NO_SSP) $(CFLAGS_EXTRA) # sys/ also needs to find table.inc via a relative include CFLAGS_SYS = $(CFLAGS) -I./kernel/sys # ----------------------------------------------------------------------- # Ramdisk root (opt-in): make RAMROOT=1 # # Links a complete SCHFS image into the kernel and boots from it instead of # the IDE disk, so the physical disk can be reformatted from a running system. # Off by default because it adds RAMDISK_BLOCKS KB to the kernel, and stage2 # loads the kernel by PIO one sector at a time -- a 6 MB kernel takes visibly # longer to boot than a 350 KB one. # ----------------------------------------------------------------------- RAMDISK_BLOCKS ?= 6000 # RAMDISK_NINODES used to live here, sizing mkfs's preallocated inode table. # The ramdisk is a SchFS image now (see $(BUILD)/fs-ram.img) and SchFS allocates # object ids as it goes, so there is no table to size and nothing to tune. # Kept out of the ramdisk root. # # Empty right now, and the machinery is kept anyway, because this is a licensing # rule before it is a size one: the ramdisk image is linked *into the kernel # binary*, so anything staged in sysroot/ when it is built becomes part of a # distributable artifact. Anything that arrives in sysroot/ under a licence that # cannot be combined with Apache-2.0, or that simply is not ours to redistribute, # belongs on this list rather than in someone's memory. # # This is not hypothetical: a 33 MB test payload once landed here and overflowed # the 6 MB image, and mkfs's "increase FSSIZE" message then pointed at a constant # that had nothing to do with it. # usb.ids is 730 KB of a 6 MB image -- 12% of the ramdisk root -- and nothing # needs it to boot. It is a lookup table for a userspace tool, so it stays on # the disk image and out of the kernel binary. RAMDISK_EXCLUDE = usr/share/usb.ids RAMDISK_EXCLUDE_ARGS = $(foreach p,$(RAMDISK_EXCLUDE),--exclude $(p)) ifdef RAMROOT CFLAGS += -DRAMDISK_ROOT RAMDISK_OBJ = $(BUILD)/kernel/ramdisk_obj else RAMDISK_OBJ = endif # ----------------------------------------------------------------------- # Top-level targets # ----------------------------------------------------------------------- # usbstick.img is built here rather than on demand because bootcheck -Usb # cannot start QEMU without it, and a missing test image looks exactly like a # broken USB driver: QEMU refuses the -drive and the whole run produces nothing. all: schos.img $(BUILD)/usbstick.img schos.img: $(BUILD)/boot/bootblock $(BUILD)/boot/stage2 $(BUILD)/kernel/kernel $(BUILD)/schfs.img dd if=/dev/zero of=schos.img count=10000 dd if=$(BUILD)/boot/bootblock of=schos.img conv=notrunc dd if=$(BUILD)/boot/stage2 of=schos.img seek=1 conv=notrunc # seek is 1 + STAGE2_SECTORS, not a literal. It was 25 against a 24-sector # stage2; raising STAGE2_SECTORS for the boot logo without touching it wrote # the kernel straight over stage2's own tail. dd if=$(BUILD)/kernel/kernel of=schos.img seek=$$(( 1 + $(STAGE2_SECTORS) )) conv=notrunc kernel: $(BUILD)/kernel/kernel # ----------------------------------------------------------------------- # UEFI loader # # A PE/COFF executable, not ELF, so it needs a different target and a # different linker driver -- but the same clang install. That is the whole # reason the toolchain is clang: one compiler reaches every target here. # # -mno-red-zone is not optional. UEFI firmware may take an interrupt at any # point, and the red zone is memory below RSP that a handler is entitled to # clobber; leaving it enabled corrupts locals at random. # -fshort-wchar makes L"..." UTF-16, which is the only string type UEFI has. # ----------------------------------------------------------------------- UEFI_CFLAGS = --target=x86_64-unknown-windows -ffreestanding -fshort-wchar -mno-red-zone -fno-stack-protector -Wall -Wextra -O1 -I./include $(BUILD)/uefi/BOOTX64.EFI: boot/uefi/main.c boot/uefi/efi.h include/schboot.h @mkdir -p $(BUILD)/uefi clang $(UEFI_CFLAGS) -c boot/uefi/main.c -o $(BUILD)/uefi/main.obj lld-link -subsystem:efi_application -entry:EfiMain -nodefaultlib $(BUILD)/uefi/main.obj -out:$@ uefi: $(BUILD)/uefi/BOOTX64.EFI # The EFI System Partition, as a directory. QEMU serves a directory as FAT # with `-drive file=fat:rw:`, which is enough to boot and test without any # image-building tools -- mtools is not present on every supported host. # A real ESP image for `make iso` is still to come. $(BUILD)/esp/EFI/BOOT/BOOTX64.EFI: $(BUILD)/uefi/BOOTX64.EFI $(BUILD)/kernel/kernel @mkdir -p $(BUILD)/esp/EFI/BOOT $(BUILD)/esp/schos @cp $(BUILD)/uefi/BOOTX64.EFI $(BUILD)/esp/EFI/BOOT/BOOTX64.EFI @cp $(BUILD)/kernel/kernel $(BUILD)/esp/schos/kernel @cp boot/uefi/startup.nsh $(BUILD)/esp/startup.nsh @echo "ESP staged in $(BUILD)/esp" esp: $(BUILD)/esp/EFI/BOOT/BOOTX64.EFI # Boot the UEFI path under OVMF. The firmware ships with QEMU; the varstore # is copied because it must be writable. # # i440fx, not q35: the kernel's IDE driver speaks to the legacy ports at # 0x1f0, and q35 has no legacy IDE at all -- only AHCI. The root volume would # simply not be there. OVMF_CODE = $(BUILD)/OVMF_CODE.fd OVMF_VARS = $(BUILD)/OVMF_VARS.fd # OVMF, wherever this host keeps it. # # There is no common location: QEMU's own Windows build puts it beside the # binary as edk2-x86_64-code.fd, Fedora ships edk2-ovmf into # /usr/share/edk2/ovmf/OVMF_CODE.fd, Debian and Arch each differ again. Search # rather than assume, and say something useful when nothing is found -- an # unhelpful "cp: no such file" is a poor way to learn that a package is # missing. QEMU_SHARE = $(dir $(shell command -v $(QEMU) 2>/dev/null))share OVMF_CODE_CANDIDATES = $(QEMU_SHARE)/edk2-x86_64-code.fd /usr/share/edk2/ovmf/OVMF_CODE.fd /usr/share/OVMF/OVMF_CODE.fd /usr/share/ovmf/x64/OVMF_CODE.fd /usr/share/qemu/edk2-x86_64-code.fd OVMF_VARS_CANDIDATES = $(QEMU_SHARE)/edk2-i386-vars.fd /usr/share/edk2/ovmf/OVMF_VARS.fd /usr/share/OVMF/OVMF_VARS.fd /usr/share/ovmf/x64/OVMF_VARS.fd /usr/share/qemu/edk2-i386-vars.fd # Tested through the shell rather than with $(wildcard). On Windows, make is a # native binary using Win32 file calls while `command -v` hands back an MSYS # path like /c/Users/..., which $(wildcard) cannot see -- it silently returns # nothing and the firmware looks missing on a machine that has it. OVMF_CODE_SRC = $(shell for f in $(OVMF_CODE_CANDIDATES); do [ -f "$$f" ] && { echo "$$f"; break; }; done) OVMF_VARS_SRC = $(shell for f in $(OVMF_VARS_CANDIDATES); do [ -f "$$f" ] && { echo "$$f"; break; }; done) $(OVMF_CODE): @mkdir -p $(BUILD) @test -n "$(OVMF_CODE_SRC)" || { echo "no OVMF firmware found. Install it:"; echo " Fedora sudo dnf install edk2-ovmf"; echo " Debian sudo apt install ovmf"; echo " Arch sudo pacman -S edk2-ovmf"; exit 1; } cp "$(OVMF_CODE_SRC)" $@ chmod u+w $@ $(OVMF_VARS): @mkdir -p $(BUILD) @test -n "$(OVMF_VARS_SRC)" || { echo "no OVMF varstore found -- install the OVMF package (see above)"; exit 1; } cp "$(OVMF_VARS_SRC)" $@ chmod u+w $@ qemu-uefi: esp $(BUILD)/schfs.img $(OVMF_CODE) $(OVMF_VARS) $(QEMU) -machine pc-i440fx-9.2 -drive if=pflash,format=raw,unit=0,readonly=on,file=$(OVMF_CODE) -drive if=pflash,format=raw,unit=1,file=$(OVMF_VARS) -drive file=fat:rw:$(BUILD)/esp,index=0,media=disk -drive file=$(BUILD)/schfs.img,format=raw,index=1,media=disk -m 1G -smp 1 -cpu qemu64,+rdtscp -serial mon:stdio $(QEMU_DISPLAY) # Build the ramdisk-root variant. This is a target rather than just # "make RAMROOT=1" because toggling RAMROOT changes CFLAGS (-DRAMDISK_ROOT), # and make has no way to notice that: objects compiled for one setting would be # silently reused for the other, leaving ROOTDEV pointing at the wrong device. # Dropping the compiled objects first makes the switch always correct. ramroot: rm -rf $(BUILD)/kernel $(BUILD)/lib $(BUILD)/cmd $(MAKE) RAMROOT=1 cmd: $(CMDBINS) ports: $(MAKE) -C ports # ----------------------------------------------------------------------- # Bootable ISO # # Always a ramdisk-root build. An ISO that needed a second disk to find its # root filesystem would not be an install medium, it would be a puzzle -- and # read-only media cannot host a writable root anyway. Same object-wiping # preamble as the `ramroot` target, for the same reason: RAMROOT changes # CFLAGS and make cannot see that. # ----------------------------------------------------------------------- iso: rm -rf $(BUILD)/kernel $(BUILD)/lib $(BUILD)/cmd $(MAKE) RAMROOT=1 schos.iso # The UEFI loader is a dependency now: the disc carries a second El Torito # entry pointing at a FAT image that holds it, so one disc boots both firmwares. # sysroot-install because the disc also carries the tree as a real hierarchy. schos.iso: $(BUILD)/boot/cdboot $(BUILD)/boot/stage2 $(BUILD)/kernel/kernel \ $(BUILD)/uefi/BOOTX64.EFI sysroot-install STAGE2_SECTORS=$(STAGE2_SECTORS) SYSROOT=$(SYSROOT) sh tools/posix/mkiso.sh $@ # Boot the ISO the way a machine booting off a disc would: no hard disk at # all, so a kernel that still secretly depends on one fails here rather than # in someone's optical drive. qemu-iso: schos.iso $(QEMU) -serial mon:stdio $(QEMU_DISPLAY) -cpu qemu64,+rdtscp \ -cdrom schos.iso -boot d \ -m 1G -smp 2 -machine pc-i440fx-9.2 # ----------------------------------------------------------------------- # Bootloader — two stages. Stage 1 (bootblock) is the actual 512-byte MBR # sector: BIOS-imposed hard limit, so it only loads stage 2 off disk and # jumps to it. Stage 2 does VBE/A20/protected-mode setup, draws the boot # splash, and loads the kernel — not size-constrained the same way, just # padded out to a fixed STAGE2_SECTORS*512 bytes so the kernel's starting # sector on disk is predictable (kept in sync with STAGE2_SECTORS in # boot/bios/bootasm.S and boot/bios/stage2main.c). # ----------------------------------------------------------------------- # STAGE2_SECTORS used to be written out three times -- here, in boot/bios/bootasm.S # and in boot/bios/stage2main.c -- and docs/layout.md carried a warning about keeping # them in step. They are now all fed from this one variable via -D, so the # warning describes a problem that can no longer happen. Both C/asm files keep # an #ifndef fallback purely so they still read sensibly on their own. # # The ceiling is memory, not disk: stage2 is loaded at 0x7E00 and the multiboot # kernel-search buffer begins at 0x10000, so 65 sectors is the hard maximum. # stage2main.c static-asserts this. padstage2.pl fails the build if the compiled # binary overflows the budget, rather than truncating it -- a short stage2 would # shift the kernel's start sector and fail in a way that looks nothing like its # cause. STAGE2_SECTORS = 48 STAGE2_DEF = -DSTAGE2_SECTORS=$(STAGE2_SECTORS) # Byte budget is computed in the recipe with shell arithmetic, not $(shell expr # ... \* 512): make hands the backslash through untouched, sh then treats the # `*` as a glob, and expr gets a directory listing. It "worked" to the extent # that make carried on and padstage2.pl received nonsense. STAGE2_BYTES = $$(( $(STAGE2_SECTORS) * 512 )) $(BUILD)/boot/bootblock: boot/bios/bootasm.S @mkdir -p $(BUILD)/boot $(CC32) $(STAGE2_DEF) -fno-builtin -fno-pic -nostdinc -I./include -c boot/bios/bootasm.S -o $(BUILD)/boot/bootasm.o $(LD) -m elf_i386 -nostdlib $(LD_LOWBASE) -n -N -e start -Ttext 0x7C00 \ -o $(BUILD)/boot/bootblocktmp.o $(BUILD)/boot/bootasm.o $(OBJCOPY) -S -O binary -j .text $(BUILD)/boot/bootblocktmp.o $(BUILD)/boot/bootblock ./tools/sign.pl $(BUILD)/boot/bootblock # Stage 1 for optical media. Same job as bootblock, far less of it: El Torito # has already loaded stage2 alongside this sector by the time it runs, so it # only records that we came from a CD and jumps. See boot/bios/cdboot.S. $(BUILD)/boot/cdboot: boot/bios/cdboot.S @mkdir -p $(BUILD)/boot $(CC32) -fno-builtin -fno-pic -nostdinc -I./include -c boot/bios/cdboot.S -o $(BUILD)/boot/cdbootasm.o $(LD) -m elf_i386 -nostdlib $(LD_LOWBASE) -n -N -e start -Ttext 0x7C00 \ -o $(BUILD)/boot/cdboottmp.o $(BUILD)/boot/cdbootasm.o $(OBJCOPY) -S -O binary -j .text $(BUILD)/boot/cdboottmp.o $@ ./tools/sign.pl $@ $(BUILD)/boot/stage2: boot/bios/stage2.S boot/bios/stage2main.c include/logo.h @mkdir -p $(BUILD)/boot $(CC32) $(STAGE2_DEF) -fno-builtin -fno-pic -nostdinc -I./include -c boot/bios/stage2.S -o $(BUILD)/boot/stage2asm.o $(CC32) $(STAGE2_DEF) -fno-builtin -fno-pic -O -nostdinc -I./include -c boot/bios/stage2main.c -o $(BUILD)/boot/stage2main.o $(LD) -m elf_i386 -nostdlib $(LD_LOWBASE) -n -N -e stage2start -Ttext 0x7E00 \ -o $(BUILD)/boot/stage2tmp.o $(BUILD)/boot/stage2asm.o $(BUILD)/boot/stage2main.o $(OBJCOPY) -S -O binary -j .text -j .rodata -j .data $(BUILD)/boot/stage2tmp.o $(BUILD)/boot/stage2 ./tools/padstage2.pl $(BUILD)/boot/stage2 $(STAGE2_BYTES) # ----------------------------------------------------------------------- # Kernel binary # ----------------------------------------------------------------------- $(BUILD)/kernel/kernel: $(KERNOBJS) $(BUILD)/kernel/initcode_obj $(BUILD)/kernel/entryother_obj $(RAMDISK_OBJ) kernel/kernel.ld @mkdir -p $(BUILD)/kernel $(LD) $(LDFLAGS) -T kernel/kernel.ld -o $@ \ $(BUILD)/kernel/arch/x86_64/entry.o $(KERN_NOENTRY) \ $(BUILD)/kernel/initcode_obj $(BUILD)/kernel/entryother_obj $(RAMDISK_OBJ) $(OBJDUMP) -S $@ > $(BUILD)/kernel/kernel.asm # The ramdisk root image, and the object wrapping it for the linker. Built from # the same staged sysroot as the main image, just capped at RAMDISK_BLOCKS -- # mkfs's content dedup means the whole userland still fits in a few MB. # Built with mkschfs, not mkfs. # # SchFS is the only root the kernel will mount -- it panics in forkret() when # handed anything else -- so a v1 image here produced "schfs root: no SchFS # volume on the root device" and made `make iso` and `make ramroot` unbootable. # RAMDISK_NINODES is gone with it: SchFS has no preallocated inode table, which # is what that knob existed to size. $(BUILD)/fs-ram.img: $(MKSCHFS) $(BUILD)/sysroot.stamp @mkdir -p $(BUILD) @rm -f $@ $(MKSCHFS) $@ --size $(RAMDISK_BLOCKS) --label schos \ $(RAMDISK_EXCLUDE_ARGS) --sysroot $(SYSROOT) $(BUILD)/kernel/ramdisk_obj: $(BUILD)/fs-ram.img @mkdir -p $(BUILD)/kernel $(LD) -r -b binary -m elf_x86_64 -o $@ $< $(OBJCOPY) \ --redefine-sym _binary_build_fs_ram_img_start=_binary_ramdisk_start \ --redefine-sym _binary_build_fs_ram_img_end=_binary_ramdisk_end \ --redefine-sym _binary_build_fs_ram_img_size=_binary_ramdisk_size \ $@ # AP startup blob $(BUILD)/kernel/entryother: kernel/arch/x86_64/entryother.S @mkdir -p $(BUILD)/kernel $(CC) $(CFLAGS) -fno-pic -nostdinc -I./include -c $< -o $(BUILD)/kernel/entryother.o $(LD) $(LDFLAGS) $(LD_LOWBASE) -e start -Ttext 0x7000 -o $(BUILD)/kernel/entryothertmp.o $(BUILD)/kernel/entryother.o $(OBJCOPY) -S -O binary -j .text $(BUILD)/kernel/entryothertmp.o $@ # NOTE: ld -r -b binary derives its symbol names from the *path* of the input # file, so these --redefine-sym pairs must be kept in sync with the build # directory layout above. Move the file, and the left-hand names change. $(BUILD)/kernel/entryother_obj: $(BUILD)/kernel/entryother $(LD) -r -b binary -m elf_x86_64 -o $@ $< $(OBJCOPY) \ --redefine-sym _binary_build_kernel_entryother_start=_binary_entryother_start \ --redefine-sym _binary_build_kernel_entryother_end=_binary_entryother_end \ --redefine-sym _binary_build_kernel_entryother_size=_binary_entryother_size \ $@ # Init stub (first user-space code, embedded in the kernel image) $(BUILD)/lib/initcode: lib/initcode.S @mkdir -p $(BUILD)/lib $(CC) $(CFLAGS) -fno-pic -nostdinc -I./include -c $< -o $(BUILD)/lib/initcode.o $(LD) -m elf_x86_64 -nostdlib $(LD_LOWBASE) -N -e start -Ttext 0 -o $(BUILD)/lib/initcodetmp.o $(BUILD)/lib/initcode.o $(OBJCOPY) -S -O binary $(BUILD)/lib/initcodetmp.o $@ $(BUILD)/kernel/initcode_obj: $(BUILD)/lib/initcode @mkdir -p $(BUILD)/kernel $(LD) -r -b binary -m elf_x86_64 -o $@ $< $(OBJCOPY) \ --redefine-sym _binary_build_lib_initcode_start=_binary_initcode_start \ --redefine-sym _binary_build_lib_initcode_end=_binary_initcode_end \ --redefine-sym _binary_build_lib_initcode_size=_binary_initcode_size \ $@ # ----------------------------------------------------------------------- # vectors.S — generated from perl script # ----------------------------------------------------------------------- kernel/arch/x86_64/vectors.S: tools/vectors.pl perl tools/vectors.pl > $@ # ----------------------------------------------------------------------- # Compile rules for kernel/ # ----------------------------------------------------------------------- $(BUILD)/kernel/arch/x86_64/%.o: kernel/arch/x86_64/%.c @mkdir -p $(BUILD)/kernel/arch/x86_64 $(CC) $(CFLAGS) -c $< -o $@ $(BUILD)/kernel/arch/x86_64/%.o: kernel/arch/x86_64/%.S @mkdir -p $(BUILD)/kernel/arch/x86_64 $(CC) $(CFLAGS) -c $< -o $@ $(BUILD)/kernel/core/%.o: kernel/core/%.c @mkdir -p $(BUILD)/kernel/core $(CC) $(CFLAGS) -c $< -o $@ $(BUILD)/kernel/dev/%.o: kernel/dev/%.c @mkdir -p $(BUILD)/kernel/dev $(CC) $(CFLAGS) -c $< -o $@ # `$(dir $@)` rather than a fixed path: drivers are grouped into one directory # per device class (gpu/, net/, bus/, usb/), so the output directory varies with # the target and a hardcoded mkdir only creates the top one. $(BUILD)/kernel/drivers/%.o: kernel/drivers/%.c @mkdir -p $(dir $@) $(CC) $(CFLAGS) -c $< -o $@ $(BUILD)/kernel/fs/%.o: kernel/fs/%.c @mkdir -p $(BUILD)/kernel/fs $(CC) $(CFLAGS) -c $< -o $@ $(BUILD)/kernel/fs/schfs/%.o: kernel/fs/schfs/%.c @mkdir -p $(BUILD)/kernel/fs/schfs $(CC) $(CFLAGS) -I./kernel/fs/schfs -c $< -o $@ $(BUILD)/kernel/net/%.o: kernel/net/%.c @mkdir -p $(BUILD)/kernel/net $(CC) $(CFLAGS) -c $< -o $@ $(BUILD)/kernel/proc/%.o: kernel/proc/%.c @mkdir -p $(BUILD)/kernel/proc $(CC) $(CFLAGS) -c $< -o $@ # sys/ gets an extra -I so syscall.c can do #include "table.inc" $(BUILD)/kernel/sys/%.o: kernel/sys/%.c @mkdir -p $(BUILD)/kernel/sys $(CC) $(CFLAGS_SYS) -c $< -o $@ # ----------------------------------------------------------------------- # First-party userland: lib/ + cmd/ # ----------------------------------------------------------------------- $(BUILD)/lib/%.o: lib/%.c @mkdir -p $(BUILD)/lib $(CC) $(CFLAGS) -c $< -o $@ $(BUILD)/lib/%.o: lib/%.S @mkdir -p $(BUILD)/lib $(CC) $(CFLAGS) -c $< -o $@ $(BUILD)/cmd/%: cmd/%/main.c $(ULIB) @mkdir -p $(BUILD)/cmd $(CC) $(CFLAGS) -c $< -o $(BUILD)/cmd/$*.o $(LD) $(LDFLAGS) $(LD_LOWBASE) -e _start -Ttext 0x4000 -o $@ $(BUILD)/cmd/$*.o $(ULIB) $(OBJCOPY) -S $@ $(SYSROOT)/bin/%: $(BUILD)/cmd/% | $(SYSROOT)/bin cp $< $@ chmod 755 $@ $(SYSROOT)/bin: mkdir -p $@ # ----------------------------------------------------------------------- # Filesystem image # ----------------------------------------------------------------------- # ----------------------------------------------------------------------- # SchFS B-tree torture test (host program). # # The B-tree underpins every record type in SchFS, so it is developed and # tested here -- against a reference model, with simulated crashes -- before # it is linked into the kernel. Run it after any change to # kernel/fs/schfs/btree.c. # ----------------------------------------------------------------------- schfs-test: $(SCHFS_TEST) $(SCHFS_TEST) # Use ASan/UBSan when the runtime libraries are actually installed; they catch # exactly the pointer-arithmetic mistakes this code is most exposed to, but not # every machine has libasan, so fall back rather than failing the build. SAN := $(shell echo 'int main(void){return 0;}' > /tmp/.santest.c 2>/dev/null && \ gcc -fsanitize=address,undefined -o /tmp/.santest /tmp/.santest.c \ >/dev/null 2>&1 && echo -fsanitize=address,undefined) SCHFS_SRC = kernel/fs/schfs/btree.c kernel/fs/schfs/btree.h \ kernel/fs/schfs/space.c kernel/fs/schfs/vol.c \ kernel/fs/schfs/obj.c kernel/fs/schfs/objw.c \ kernel/fs/schfs/schfs_int.h include/schfs.h # -iquote, not -I: include/ has its own errno.h, stat.h, fcntl.h and types.h, # and -I would put them ahead of the system ones for a host program that needs # the real thing. -iquote applies only to "quoted" includes, so btree.h still # finds schfs.h while still means the C library's. HOSTCFLAGS = -Wall -Wextra -O1 -g $(SAN) -iquote ./include $(SCHFS_TEST): tools/schfs_test.c $(SCHFS_SRC) @mkdir -p $(BUILD)/tools @mkdir -p $(BUILD) $(HOSTCC) $(HOSTCFLAGS) -o $@ tools/schfs_test.c # The object layer -- files, directories, and whether deleting things gives the # space back. Runs against a memory-backed volume, so a full create/write/ # delete/remount cycle takes milliseconds instead of a rebuild and a boot. $(SCHFS_OBJT): tools/schfs_objtest.c $(SCHFS_SRC) @mkdir -p $(BUILD)/tools @mkdir -p $(BUILD) $(HOSTCC) $(HOSTCFLAGS) -o $@ tools/schfs_objtest.c schfs-objtest: $(SCHFS_OBJT) $(SCHFS_OBJT) # The image writer and checker. Both #include the real filesystem sources, so # there is exactly one implementation of the on-disk format: a host tool and the # kernel cannot drift apart because there is nothing to drift from. $(MKSCHFS): tools/mkschfs.c $(SCHFS_SRC) @mkdir -p $(BUILD)/tools @mkdir -p $(BUILD) $(HOSTCC) $(HOSTCFLAGS) -o $@ tools/mkschfs.c $(SCHFS_FSCK): tools/schfs_fsck.c $(SCHFS_SRC) @mkdir -p $(BUILD)/tools @mkdir -p $(BUILD) $(HOSTCC) $(HOSTCFLAGS) -o $@ tools/schfs_fsck.c mkschfs: $(MKSCHFS) schfs-fsck: $(SCHFS_FSCK) # End-to-end filesystem test: build an image from a directory tree, read it # back with schfs_fsck, and diff the result against the original. # # The oracle here is the host's own filesystem, which knows nothing about SchFS. # schfs-test proves the B-tree matches a model written alongside it; this proves # the *filesystem* matches a tree that exists independently of it, so a writer # and reader that agree on something wrong are still caught. schfs-image-test: $(MKSCHFS) $(SCHFS_FSCK) @sh tools/posix/schfs_roundtrip.sh $(MKSCHFS) $(SCHFS_FSCK) # Every SchFS suite. Run this after touching anything under kernel/fs/schfs. schfs-check: schfs-test schfs-objtest schfs-image-test # Crash the kernel on purpose and photograph the result. # # The crash screen is the one part of the system you cannot look at by using # the system, which is exactly why it rots. This makes "does it still render" # a question with a thirty-second answer. Needs a ramroot build first. crashshot: pwsh -File tools/windows/crashshot.ps1 # Read the QR code back off a captured crash screen. End-to-end on purpose: it # reads the pixels that were painted, so payload construction in panic context, # the encoder, the module scaling and the quiet zone are all covered by whether # text comes back. Run `make crashshot` first. Needs the same Python packages # as `qrcheck`. crashqr: $(PYTHON) tools/python/crashqr.py $(MKFS): tools/mkfs.c include/fs.h include/stat.h include/param.h @mkdir -p $(BUILD)/tools $(HOSTCC) $(HOSTWARN) -I. -o $(MKFS) tools/mkfs.c # Stage everything a running system needs into sysroot/, then image it. # # ports/ is built via its own Makefile rather than per-port from here, because # that is where the inter-port ordering lives (mksh and toybox both link # against musl, whose install prefix is under build/ — so `make clean` wipes it # and musl must be rebuildable as part of the normal chain, not out-of-band). # # On Windows the ports are NOT rebuilt. musl, mksh and toybox are Linux # userland built by configure scripts and kconfig, and getting those to run # under Git Bash against a clang cross-target is a separate project from # building the OS. Their outputs are already staged in sysroot/ and change # perhaps twice a year, so the cost of not rebuilding them here is close to # zero -- and it is stated out loud rather than silently skipped, because a # port that quietly does not rebuild is exactly the kind of thing that wastes # an afternoon later. # schinit's job manifests. Kept under etc/ in the repo and installed here, # because sysroot/ is generated and gitignored -- anything only ever written # straight into it is lost on a fresh clone, which is already true of # /etc/passwd and /etc/motd and should not become true of the boot # configuration as well. SCHINIT_JOBS = $(wildcard etc/schinit/*.job) etc/schinit/README ETC_FILES = $(wildcard etc/*) ETC_PLAIN = $(filter-out etc/schinit,$(ETC_FILES)) etc-install: $(SCHINIT_JOBS) $(ETC_PLAIN) @mkdir -p $(SYSROOT)/etc/schinit @cp $(ETC_PLAIN) $(SYSROOT)/etc/ @cp $(SCHINIT_JOBS) $(SYSROOT)/etc/schinit/ @echo "installed $(words $(ETC_PLAIN)) /etc file(s) and $(words $(SCHINIT_JOBS)) schinit manifest(s)" sysroot-install: $(addprefix $(SYSROOT)/bin/, $(CMDS)) etc-install ifdef WINHOST @echo "ports: skipped (Windows host) -- musl/mksh/toybox are used as already staged in $(SYSROOT)" @test -f $(SYSROOT)/bin/sh || { echo "ports: $(SYSROOT)/bin/sh is missing and cannot be built here; build ports on a Linux host once" >&2; exit 1; } else $(MAKE) -C ports CC=gcc endif # A stamp whose timestamp advances only when the staged tree actually changes. # # make cannot depend on "the contents of a directory", and sysroot/ is filled # by two different builds (cmd/ from here, ports/ recursively), so the images # below used to depend on the phony sysroot-install -- which meant every image # was rebuilt on every invocation, whether or not a single byte had moved. # That cost 100 seconds of a 108-second no-op build. # # Path, size and mtime rather than content hashes: reading every staged file # would undo the point, and a build system that lies about a file whose mtime # and size both stayed the same has bigger problems. The cmp-then-move keeps # the stamp's mtime unchanged when the summary is unchanged, which is the # whole trick -- writing it unconditionally would just move the problem. $(BUILD)/sysroot.stamp: sysroot-install @mkdir -p $(BUILD) @find $(SYSROOT) -type f -printf '%p %s %T@\n' | sort > $@.new @cmp -s $@.new $@ || mv -f $@.new $@ @rm -f $@.new # A small block device for the USB test to read. # # bootcheck -Usb attaches this as a usb-storage device and has the driver read # block 1 off it, which is the only check that separates "a device answered # INQUIRY" from "the driver can actually transfer data". # # Its own image, not the root volume and not build/fs.img: # - attaching the root volume twice (once as hdb, once over USB) broke the # xHCI interrupt check as well as the storage ones; # - build/fs.img is no longer built by anything, since SchFS became the root, # so pointing at it meant QEMU refused to start at all; # - at FSSIZE blocks it would now be ~600 MB, because BSIZE went from 1024 to # 4096. 16 MB is plenty to read one block from. USBSTICK = $(BUILD)/usbstick.img $(USBSTICK): $(MKFS) $(BUILD)/sysroot.stamp @mkdir -p $(BUILD) @rm -f $@ $(MKFS) $@ --size 4096 --sysroot $(SYSROOT) usbstick: $(USBSTICK) $(BUILD)/fs.img: $(MKFS) $(BUILD)/sysroot.stamp @mkdir -p $(BUILD) @echo "Creating SchOS File System Image..." @# A running QEMU holds this file open, and on Windows that makes rm fail @# with "Device or resource busy" -- which stops the build *after* the @# kernel has already relinked, leaving schos.img stale. The next test run @# then boots the previous kernel and the change appears to have done @# nothing, which has cost several debugging cycles. Say so plainly. @rm -f $@ || { \ echo ""; \ echo " $@ is in use -- a QEMU is still holding it."; \ echo " Close it, or: pwsh -Command \"Get-Process qemu-system-x86_64 |"; \ echo " Where-Object { -not \$$_.MainWindowTitle } | Stop-Process -Force\""; \ echo ""; \ exit 1; \ } $(MKFS) $@ --sysroot $(SYSROOT) # ----------------------------------------------------------------------- # QEMU # ----------------------------------------------------------------------- qemu: schos.img $(BUILD)/schfs.img $(QEMU) -serial mon:stdio $(QEMU_DISPLAY) -cpu qemu64,+rdtscp \ -hda schos.img -hdb $(BUILD)/schfs.img \ -m 1G -smp 2 -machine pc-i440fx-9.2 # The root filesystem. Built from the staged sysroot; the kernel mounts it as # / in forkret(). $(BUILD)/schfs.img: $(MKSCHFS) $(BUILD)/sysroot.stamp @mkdir -p $(BUILD) @rm -f $@ ./$(MKSCHFS) $@ --size 8000 --label schos --sysroot $(SYSROOT) # Boot with a SchFS volume attached as the second disk. # # Needs the ramdisk-root kernel ("make ramroot" first): with RAMROOT the root # filesystem comes out of memory and device 1 is free, which is exactly the # arrangement docs/architecture/schfs.md asks for -- a new-format disk that # fails to mount cannot stop the system booting. Without RAMROOT, device 1 is # the live v1 root and the SchFS probe correctly finds nothing there. # # Once up: cat /proc/schfs qemu-schfs: schos.img $(BUILD)/schfs.img $(QEMU) -serial mon:stdio $(QEMU_DISPLAY) -cpu qemu64,+rdtscp \ -hda schos.img -hdb $(BUILD)/schfs.img \ -m 1G -smp 2 -machine pc-i440fx-9.2 # ----------------------------------------------------------------------- # Misc # ----------------------------------------------------------------------- # schos.img is deliberately NOT here. It is a real file with real # prerequisites; listing it as phony made make skip the timestamp check and # re-run the four dd commands -- including zeroing five megabytes -- on every # single invocation, including ones where nothing had changed. # include/logo.h is generated but committed, exactly as include/font.h is: it # derives from art that changes about never, and making it a build dependency # would put Python on the critical path of every clean build for no benefit. # Run this by hand after changing the art. # The mark, not the wordmark. "SchOS Text Logo.png" is the text form and would # be illegible scaled to LOGO_PX; the small logo this used to name is gone, # replaced by the full logo, which is the same mark slightly redrawn. LOGO_SRC = Assets/SchOS Full Logo.png LOGO_PX = 128 logo: $(PYTHON) tools/python/mklogo.py "$(LOGO_SRC)" include/logo.h $(LOGO_PX) # The USB vendor/product name database, from linux-usb.org. # # Dual-licensed GPL-2.0-or-later **or** 3-clause BSD; SchOS takes the BSD # option, which is what makes it usable at all. See NOTICE. # # Cached in dist/ and refreshed only when you ask, NOT on every build. That was # the request, and it is the one thing here worth pushing back on: fetching on # every compilation makes an offline build fail, makes the same source produce # different images on different days, and puts a third-party web server on the # critical path of `make`. dist/ exists precisely so a rebuild never needs the # network -- see docs/layout.md. `make usb-ids` is a deliberate act; the file it # writes is committed and dated, so what shipped is always knowable. USB_IDS_URL = http://www.linux-usb.org/usb.ids usb-ids: @echo "==> fetching $(USB_IDS_URL)" @curl -sfL --max-time 60 -o dist/usb.ids.new "$(USB_IDS_URL)" || \ { echo " fetch failed; keeping the cached copy"; rm -f dist/usb.ids.new; exit 1; } @# A truncated or error-page download would otherwise silently replace a @# good database with rubbish. Check it looks like what it claims to be. @grep -q "^# Version:" dist/usb.ids.new || \ { echo " that is not a usb.ids file; keeping the cached copy"; \ rm -f dist/usb.ids.new; exit 1; } @mv dist/usb.ids.new dist/usb.ids @cp dist/usb.ids $(SYSROOT)/usr/share/usb.ids @grep -m1 "^# Version:" dist/usb.ids # Build the kernel's QR encoder on the host and check it. # # A QR symbol with a mistyped table entry still looks like a perfectly plausible # grid of black squares, so the self-checks in qrtest.c cover what can be # verified mechanically and the PNG covers the rest: point a phone at it. QRTEST = $(BUILD)/tools/qrtest$(EXE) $(QRTEST): tools/qrtest.c kernel/core/qr.c include/qr.h @mkdir -p $(BUILD)/tools $(HOSTCC) $(HOSTWARN) -I./include -o $@ tools/qrtest.c qrtest: $(QRTEST) @mkdir -p $(BUILD) ./$(QRTEST) $(BUILD)/qr.png # The check that actually decides whether the encoder works: build a symbol and # read it back with a real decoder, plus a module-for-module comparison against # a second encoder. Needs Python packages that are not part of the build, so it # is not wired into `make` -- install them wherever you like and point # PYTHONPATH at it: # # pip install --target build/pylibs opencv-python-headless numpy segno # PYTHONPATH=build/pylibs make qrcheck qrcheck: $(QRTEST) $(PYTHON) tools/python/qrcheck.py .PHONY: all kernel ramroot cmd ports sysroot-install qemu \ schfs-test schfs-objtest schfs-image-test schfs-check mkschfs \ schfs-fsck crashshot qemu-schfs iso qemu-iso clean distclean \ logo qrtest qrcheck crashqr usb-ids .PRECIOUS: $(BUILD)/%.o # 'clean' removes generated output only. dist/ (upstream tarballs) survives, # so a rebuild never needs the network. clean: rm -rf $(BUILD) schos.img schos.iso kernel/arch/x86_64/vectors.S distclean: clean rm -rf dist # Header dependencies, from gcc -MD. # # This used to be a set of globs including `$(BUILD)/kernel/**/*.d`. make's # wildcard has no recursive `**`: it behaved as a single `*` and matched # exactly one level, so build/kernel/core/*.d was picked up while # build/kernel/arch/x86_64/*.d and build/kernel/fs/schfs/*.d were not. # Everything under arch/ and schfs/ therefore had NO header dependency # tracking at all -- editing include/schfs.h or include/mmu.h left stale # objects that linked without complaint. find actually recurses. -include $(shell find $(BUILD) -name '*.d' 2>/dev/null)