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 disk.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 attr.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 reflink schattr 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 # Hardware virtualisation, when the host has it. # # Not a micro-optimisation. Without KVM, QEMU emulates every instruction, and # measured on this tree that is where most of "the system feels slow" came from: # `ls /bin` took 120 ms emulated against 20 ms with KVM, on identical code. Every # guess about the kernel being at fault has to be made against the KVM number, # or it is really a measurement of TCG. # # Probed rather than assumed, because /dev/kvm is absent on a machine without # the extension, inside another VM, or when the user is not in the kvm group -- # and a hard -enable-kvm turns each of those into a QEMU that refuses to start. # `-cpu host` only makes sense with KVM: it is the physical CPU, which an # emulator cannot present. ifneq ($(wildcard /dev/kvm),) QEMU_ACCEL = -enable-kvm -cpu host else QEMU_ACCEL = -cpu qemu64,+rdtscp endif # 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 # An absolute pointing device, on an xHCI controller. # # A PS/2 mouse is relative, so inside a VM the guest cursor and the host's # accumulate independently and drift apart -- they are simply never in the same # place. usb-tablet reports where the pointer *is*, so the two coincide. # # EMPTY BY DEFAULT, and that is not an oversight. The driver binds the tablet # and identifies it correctly, but no reports have yet been observed arriving # on its interrupt endpoint -- so switching the default pointer to it would # replace a cursor that drifts with one that does not move. Opt in while # working on it: # # make qemu QEMU_INPUT='-device qemu-xhci,id=xhci -device usb-tablet,bus=xhci.0' # # See kernel/drivers/usb/hid/usb_hid.c, tablet_report(). QEMU_INPUT ?= # ----------------------------------------------------------------------- # 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 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 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 # Drop the objects when RAMROOT is toggled, in *either* direction. # # RAMROOT changes CFLAGS, and make cannot see that: an object compiled one way # is happily reused the other way. The `ramroot` target already wiped on the way # in, and nothing wiped on the way out -- so a plain `make` after `make ramroot` # kept every RAMDISK_ROOT object and linked the 24 MB ramdisk back in. The # result still booted, from the ramdisk, and the only outward symptom was that # stage2 now had 25 MB to load through PIO one sector at a time. Measured: # 25,413,120 bytes against 836,840 for the same kernel built clean. # # Evaluated when the Makefile is read, so the wipe happens before anything is # compiled against the wrong setting. RAMROOT_STATE := $(if $(RAMROOT),on,off) RAMROOT_STAMP := $(BUILD)/ramroot.state $(shell mkdir -p $(BUILD); \ if [ "$$(cat $(RAMROOT_STAMP) 2>/dev/null)" != "$(RAMROOT_STATE)" ]; then \ rm -rf $(BUILD)/kernel $(BUILD)/lib $(BUILD)/cmd; \ printf '%s' '$(RAMROOT_STATE)' > $(RAMROOT_STAMP); \ fi) # ----------------------------------------------------------------------- # 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. # OVMF is in this list because it is no longer optional: with the BIOS loader # gone, nothing in this tree boots without firmware, and `make clean` wipes # build/ including the copy fetched from the host. Leaving it out meant a clean # followed by a build produced a tree where every run script threw. all: esp $(BUILD)/schfs.img $(BUILD)/usbstick.img # ...and the firmware, added to `all` further down. It cannot be listed here: # make expands a rule's prerequisites when it reads the line, and $(OVMF_CODE) # is defined below this point, so naming it here appends nothing at all and # does so silently. # schos.img is gone with the BIOS loader that gave it its shape. It was a raw # disk with the boot sector at LBA 0, stage2 next and the kernel at a fixed # offset -- a layout only a loader that seeks by sector number can use. UEFI # firmware wants a filesystem, so what boots now is the staged ESP, offered to # QEMU through its virtual FAT. # # That is a development arrangement, not an installable disk. A real GPT image # with a real FAT partition is the installer's job. 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 \ boot/common/ui.c boot/common/ui.h @mkdir -p $(BUILD)/uefi clang $(UEFI_CFLAGS) -c boot/uefi/main.c -o $(BUILD)/uefi/main.obj clang $(UEFI_CFLAGS) -c boot/common/ui.c -o $(BUILD)/uefi/ui.obj lld-link -subsystem:efi_application -entry:EfiMain -nodefaultlib $(BUILD)/uefi/main.obj $(BUILD)/uefi/ui.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 $@ ovmf: $(OVMF_CODE) $(OVMF_VARS) # The other half of `all`, now that OVMF_CODE and OVMF_VARS have values. all: $(OVMF_CODE) $(OVMF_VARS) 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,format=raw -drive file=$(BUILD)/schfs.img,format=raw,index=1,media=disk -m 1G -smp 1 $(QEMU_ACCEL) -serial mon:stdio $(QEMU_DISPLAY) $(QEMU_INPUT) # 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 # projects/ -- things that run *on* SchOS rather than things that *are* SchOS. # # Deliberately not a dependency of anything above: `make` never descends here, # and the kernel build does not know these exist. They are ordinary programs # built against the sysroot's libc, exactly as toybox is, and the boundary # between them and the OS is syscalls and protocols rather than a shared header # nobody meant to share. See projects/README.md. # # Staged into $(SYSROOT) so the next image build picks them up; run # `make projects && make` to get them onto a disk. PROJECTS ?= schcom projects: @test -d $(MUSLPREFIX)/include || { \ echo "projects: no musl in $(MUSLPREFIX) -- run 'make ports' first" >&2; \ exit 1; } @for p in $(PROJECTS); do \ echo "==> projects/$$p"; \ $(MAKE) -C projects/$$p \ CC='$(PORTCC)' \ CFLAGS='-O2 -static -D__SCHOS__ -I$(MUSLPREFIX)/include -fno-stack-protector' \ LDFLAGS='-static -L$(MUSLPREFIX)/lib' \ DESTDIR='$(abspath $(SYSROOT))' install || exit 1; \ done # ----------------------------------------------------------------------- # 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 # One El Torito entry, pointing at a FAT image holding the loader and kernel. # sysroot-install because the disc also carries the tree as a real hierarchy. schos.iso: $(BUILD)/kernel/kernel $(BUILD)/uefi/BOOTX64.EFI sysroot-install 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) $(QEMU_ACCEL) \ -cdrom schos.iso -boot d \ -m 1G -smp 2 -machine pc-i440fx-9.2 # ----------------------------------------------------------------------- # 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/attr.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 the staged ESP holding a kernel @# from the previous build. The next test run then boots that one 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 # ----------------------------------------------------------------------- # The same thing with hardware virtualisation. Opt-in, never the default: the # test suite's timings were established under TCG and changing the execution # engine underneath them is a variable to introduce on purpose. # # Worth knowing what it does and does not buy. Measured with the guest timing # itself, `ls /bin | wc -l` goes from 2.9s to 1.1s. Boot does *not* improve -- # it gets worse -- because stage2 loads the kernel by PIO one sector at a time, # and under KVM every one of those port accesses is a VM exit, where under TCG # it is cheap. # Schcom, end to end, checked from outside the machine. # # Boots, starts the compositor and a client, and then screendumps the # framebuffer and asserts the pixels on the host. That last part is the whole # value: the compositor saying "I composited it" proves nothing, and a # screenshot nobody inspects proves less. Needs `make projects` first. schcom-test: esp $(BUILD)/schfs.img sh tools/posix/schcom_test.sh # The firmware and the disks, in one place. Every run target below boots the # same way, because there is only one way to boot now: OVMF, the staged ESP as # the first disk, the SchFS volume as the second. Keeping the argument list in # a variable is not tidiness -- four copies of a pflash pair is how one of them # ends up with a writable firmware image and nobody notices. OVMF_DRIVES = -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,format=raw \ -drive file=$(BUILD)/schfs.img,format=raw,index=1,media=disk # Kept as an explicit spelling, but `make qemu` now picks KVM up on its own # whenever /dev/kvm exists -- see QEMU_ACCEL. This target differs only in # insisting on it, which is what you want when the point is to find out why the # host has no /dev/kvm rather than to quietly get a slow machine. qemu-kvm: esp $(BUILD)/schfs.img $(OVMF_CODE) $(OVMF_VARS) $(QEMU) -enable-kvm -serial mon:stdio $(QEMU_DISPLAY) $(QEMU_INPUT) -cpu host \ $(OVMF_DRIVES) -m 1G -smp 2 -machine pc-i440fx-9.2 qemu: esp $(BUILD)/schfs.img $(OVMF_CODE) $(OVMF_VARS) $(QEMU) -serial mon:stdio $(QEMU_DISPLAY) $(QEMU_INPUT) $(QEMU_ACCEL) \ $(OVMF_DRIVES) -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: esp $(BUILD)/schfs.img $(OVMF_CODE) $(OVMF_VARS) $(QEMU) -serial mon:stdio $(QEMU_DISPLAY) $(QEMU_ACCEL) \ $(OVMF_DRIVES) -m 1G -smp 2 -machine pc-i440fx-9.2 # ----------------------------------------------------------------------- # Misc # ----------------------------------------------------------------------- # Nothing that is a real file with real prerequisites belongs here. schos.img # used to be listed, and being phony made make skip the timestamp check and # re-run its four dd commands -- including zeroing five megabytes -- on every # single invocation, including ones where nothing had changed. The file is gone # with the BIOS loader, but the rule it taught applies to whatever replaces it. # 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 projects sysroot-install qemu qemu-kvm schcom-test \ 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 ovmf esp uefi .PRECIOUS: $(BUILD)/%.o # 'clean' removes generated output only. dist/ (upstream tarballs) survives, # so a rebuild never needs the network. clean: rm -rf $(BUILD) 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)