Building oops-sdk

There is one command and it is bin/oops-sdk.

./bin/oops-sdk check

What you need

clang, ar and make, and nothing else. oops-sdk is the bottom of the target-side stack - it depends on no sibling checkout, and a clone of only this repository builds.

On Windows, put a clang 21 on PATH and build. The toolchain has to cross-compile to a FreeBSD-derived target, which clang does natively - a -target x86_64-unknown-freebsd freestanding build needs no sysroot and no Linux. A Windows shell with no clang gets as far as the first compile and stops with CreateProcess ... failed, which looks like a broken makefile and is not; that error means the toolchain is missing, not that the host is wrong.

This is the first choice in CONVENTIONS section 8 and not just tidiness: a container and a WSL distribution both reach these sources across a filesystem boundary, measured at about 10ms per file open against about 1ms locally, and a build opens enough files for that to be most of its running time.

A Linux container is the authoritative version. Mount the checkout and run ./bin/oops-sdk check and make test inside any image that has clang, make and binutils; the checkout is all the build needs. When a local toolchain and silkeh/clang:21 disagree, the container is right.

WSL is the last resort, for a machine with neither.

Why the build is a cross-compile even on Linux

Nothing this repository produces runs on the machine that built it. Every object is compiled for the target:

flag what it is for
-target x86_64-unknown-freebsd the platform's ABI. Same architecture as the build host, different operating system - which is exactly the case that gets forgotten, because the code compiles without it and links against the wrong libc
-ffreestanding, -fno-builtin there is no hosted C environment on the other side. The compiler must not assume a printf exists, and must not rewrite a loop into a memcpy that nothing provides
-nostdlib the payload links against the platform's own libraries, resolved at load time
-fPIC the result is mapped wherever the loader puts it
-fno-stack-protector the guard symbols come from a runtime that is not linked
-std=c11 the language, stated rather than inherited from whatever clang defaults to this year
-Wall -Wextra -Werror see below

-Werror is the first gate. A clean compile of every source, at -Wall -Wextra -Werror, is a real check rather than a formality: the sources build with no warnings today, and a new one that does not is a failure rather than a line of output somebody scrolls past.

There is now a second gate: a host test suite. make test builds the subsystems for the build machine (not the target) and runs unit and integration tests against them, with the display getters stubbed so the draw canvas and the tiler run on plain memory. A subsystem that cross-compiles but computes the wrong answer is caught here rather than on hardware.

The verbs

verb what it does
build make all - the objects, then the liboops.a archive that proves they link
test make test - the host unit and integration suite
check a clean build at -Werror; run before anything is called done
clean make clean

lint, fmt and doc fail loudly rather than exiting 0. Nothing implements them yet, and a verb that runs nothing and reports success would make oops all call this repository green without having checked it - the failure conventions section 3 exists to prevent. Running one prints what would have to be built first.

What a consumer has to do: Source Inclusion (.mk)

A payload or application includes the makefile helper, which provides OOPS_SDK_INCLUDE and OOPS_SDK_C_SRCS:

OOPS_SDK ?= $(abspath ../oops-sdk)
include $(OOPS_SDK)/oops-sdk.mk

CFLAGS += $(OOPS_SDK_INCLUDE)

mypayload: $(MY_OBJS) $(OOPS_SDK_C_SRCS)
    $(CC) $(CFLAGS) $(LINK_FLAGS) -o $@ $(MY_OBJS) $(OOPS_SDK_C_SRCS)

Why Source Inclusion over Static Archives (.a)

  1. Freestanding Flag-Matching: The consumer's codegen flags (-target, -ffreestanding, -fno-builtin, -fno-stack-protector, -fPIC, optimization levels) are authoritative across the entire codebase. A .a freezes them at SDK build time, risking ABI drift and subtle runtime breakage.
  2. Weak-Symbol Safety: Every platform entry point in oops-sdk is __attribute__((weak)) and dynamically checked. Compiling sources directly ensures the linker does not drop weak members during archive symbol resolution.
  3. Dead-Code Stripping: The consumer retains full dead-code stripping via -ffunction-sections -fdata-sections -Wl,--gc-sections.

The Makefile's all target builds liboops.a strictly as a local compile gate (-Werror) for ./bin/oops-sdk check, while consumers link directly via $(OOPS_SDK_C_SRCS).

From the collection

Once this repository is registered with the collection, OOPS reaches it the same way it reaches every other:

./bin/oops check oops-sdk
./bin/oops all                  # the meta gates, then every project's own gate

The collection's BUILDING.md covers bootstrap, gates, all and the rest.