# Building SchOS Supported hosts: **Linux, macOS, FreeBSD/OpenBSD/NetBSD, and Windows** (under Git Bash or MSYS2). The build is developed from both Windows and Linux, so a change that only works on one of them is a bug. ```sh make # kernel + UEFI loader + root filesystem make qemu # build and boot it make iso # a bootable ISO (the live system) for VMware or real hardware ./write.sh sdX # write that ISO to a USB stick (Linux; it checks what it is writing over) ``` The first build downloads what it needs (musl, toybox, Alpine's packages -- a few hundred megabytes, kept in `dist/`) and compiles Xorg's dependencies not at all: the packages are Alpine's binaries, pinned in `ports/alpine/world.lock`. See [packages](../architecture/packages.md). ## Prerequisites | Need | Why | Windows | Linux | macOS | BSD | |---|---|---|---|---|---| | `clang`, `ld.lld`, `llvm-objcopy`, `llvm-objdump` | cross-compiles the kernel, the 16/32-bit boot code, and musl userland | LLVM installer | `clang lld llvm` | `brew install llvm` | `pkg install llvm` | | `make` | — | Git Bash | `make` | Xcode CLT | `gmake` | | `perl` | generates the interrupt vector table | Git Bash | usually present | present | `pkg install perl5` | | `python3` | boot logo, crash-screen QR, the numbers in the README | python.org (`python`) | `python3` | `brew install python` | `pkg install python3` | | `qemu-system-x86_64` | running and testing | `scoop install qemu` | `qemu-system-x86` | `brew install qemu` | `pkg install qemu` | | `curl`, `tar` | fetching ports | Git Bash | present | present | present | | `xorriso` **or** `mkisofs` | `make iso` only | `scoop install cdrtools` | `xorriso` | `brew install xorriso` | `pkg install xorriso` | On NixOS, `nix-shell` supplies every row above — see below. ## NixOS ```sh nix-shell # then: make, make qemu, make iso ``` `shell.nix` in the repository root declares the lot. Worth using rather than assembling it by hand, for one specific reason: **`make` falls back to the GNU toolchain for the kernel but the UEFI loader genuinely cannot be built without clang and `lld-link`.** A machine with only gcc therefore produces a kernel that builds and a loader that silently stays whatever it already was — which left a stale boot splash on this tree for half a day before anyone looked at the screen rather than the build output. PowerShell is the one thing the shell does not supply, and `tools/windows/` needs it. See the note at the end of this file. A host C compiler (`cc`) is also needed, for the tools that build the disk images. On Windows that must be **mingw-w64's gcc**, not clang: clang defaults to an MSVC target there, and `tools/mkfs.c` needs `` and ``, which the Windows SDK does not have. ## Why clang rather than a cross-gcc clang is a cross-compiler by construction, so one ordinary LLVM install targets everything this tree needs — bare-metal ELF, code linked below one megabyte, and Linux/musl userland — from any host. A cross binutils+gcc would be a day of setup per platform that then rots, and on macOS it means brewing `x86_64-elf-gcc` before anything builds at all. The Makefile selects clang+lld whenever both are on `PATH`. To force the GNU toolchain instead: ```sh make USE_GNU=1 # needs a cross toolchain that emits x86_64 ELF make USE_GNU=1 TOOLPREFIX=x86_64-elf- ``` ## Testing ```sh tools/windows/bootcheck.ps1 # boots, drives the shell, asserts on output tools/windows/bootcheck.ps1 -Tty -Ps # plus terminal behaviour and process listing make schfs-check # SchFS unit, object and image suites ``` `bootcheck` is PowerShell, and PowerShell 7 (`pwsh`) runs on Linux, macOS and the BSDs as well as Windows — `brew install powershell`, or your distribution's `powershell` package. It finds QEMU on `PATH`. Booting to a prompt proves nothing on its own: a filesystem can mount and still fail the first real command, which has happened here. `bootcheck` types commands at the serial console and greps the transcript for the answers they should produce. ## Ports `musl`, `toybox`, `mksh`, lwIP, uACPI, zstd, ncurses and the rest of `ports/` are built from source into `sysroot/`; Alpine's packages are unpacked into the root from `dist/alpine/`. **This does not work on a Windows host** -- `make` prints `ports: skipped` and uses whatever is already staged in `sysroot/`. Building them needs a Linux host (or any other Unix); do it once and the staged result serves the Windows builds. ## Where the tools live `tools/` is grouped by what runs the file, so it is obvious at a glance which tools a given host can use: ``` tools/ windows/ .ps1 PowerShell (pwsh runs these on Linux and macOS too) posix/ .sh shell python/ .py *.c host programs -- compiled by the build, run everywhere *.pl perl, likewise ``` The `.c` and `.pl` files stay at the top level because they are not host-specific: the build compiles or invokes them on every platform. A script that needs the repo root derives it from its own location, so anything moved between these directories has to have that adjusted — the PowerShell tools use `Split-Path (Split-Path $PSScriptRoot -Parent) -Parent`, two levels up. ## Known host differences, already handled Listed because they are the shape of the next such bug, not because anything is outstanding: - `` is glibc/musl only. macOS and the BSDs take `major()` and `minor()` from ``. - `sha256sum` exists on Linux and Git Bash; macOS has `shasum -a 256`, FreeBSD/NetBSD `sha256`, OpenBSD `cksum -a sha256`. `tools/posix/sha256check.sh` finds whichever is present. - `nproc` is coreutils, so absent on macOS and BSD, where it is `sysctl -n hw.ncpu`. - In the PowerShell tools, path literals must use forward slashes: `Join-Path $root 'build\x'` produces a *filename containing a backslash* on a Unix host. ## Fedora, in one command ```sh tools/posix/setup-fedora.sh --check # what is missing tools/posix/setup-fedora.sh # install it ``` Moving an existing checkout to a Linux machine: see [../history/moving-to-linux.md](../history/moving-to-linux.md). ## Working on two machines Claude Code's project memory is kept in the repo and moved with: ```sh tools/posix/memory-sync.sh status tools/posix/memory-sync.sh export # after a session that learned something; then commit tools/posix/memory-sync.sh import # after cloning or pulling elsewhere ```