# todo-spinel. See README.md for what each piece is. # # make build dist/app for this machine (x86-64, dynamic glibc) # make build-arm64 dist/app-linux-arm64 (static, aarch64) # make test test/run.sh against dist/app, plain and under GC stress # make deploy ship dist/app-linux-arm64 to Carloku SHELL := bash .SHELLFLAGS := -euo pipefail -c .DELETE_ON_ERROR: TC ?= $(HOME)/.local/share/bakeoff-toolchains SPINEL_HOST ?= $(TC)/spinel-src/spinel/bin/spinel SPINEL_ARM64?= $(TC)/spinel-arm64/bin/spinel TEP_SRC ?= $(TC)/spinel-src/tep SPINELKIT ?= $(TC)/spinel-src/spinelkit RUBY ?= $(shell PATH="$(HOME)/.pixi/bin:$$PATH" command -v ruby) ARM64 := $(abspath ../../scripts/arm64.sh) SQLITE_VER := 3500400 SQLITE_ZIP := .cache/src/sqlite-amalgamation-$(SQLITE_VER).zip SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-$(SQLITE_VER).zip SQLITE_SHA := 1d3049dd0f830a025a53105fc79fd2ab9431aea99e137809d064d8ee8356b032 SQLITE_DIR := .cache/src/sqlite-amalgamation-$(SQLITE_VER) # One connection, used from one thread; no extension loading (static glibc # can't dlopen anyway). SQLITE_DEFS := -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_DQS=0 \ -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS PATCHES := $(sort $(wildcard patches/*.patch)) SOURCES := app.rb $(wildcard assets/static/*) VERSION := $(shell git rev-parse --short HEAD 2>/dev/null || echo dev) export LANG := C.UTF-8 export LC_ALL := C.UTF-8 export SPINELKIT_DIR := $(SPINELKIT) export TEP_QUIET := 1 .PHONY: build build-arm64 test clean clean-deps bump unbump deploy deps FORCE build: dist/app build-arm64: dist/app-linux-arm64 # ---- version stamp --------------------------------------------------------- # Rewritten only when the commit changes, so it doesn't force rebuilds. version.rb: FORCE @printf '# Written by make; not checked in.\nAPP_VERSION = "%s"\n' '$(VERSION)' > $@.tmp @if cmp -s $@.tmp $@; then rm $@.tmp; else mv $@.tmp $@; fi # ---- third-party sources (kept by clean-deps) ------------------------------- $(SQLITE_ZIP): mkdir -p $(@D) curl -sSfL -o $@.part $(SQLITE_URL) echo "$(SQLITE_SHA) $@.part" | sha256sum -c --quiet mv $@.part $@ $(SQLITE_DIR)/sqlite3.c: $(SQLITE_ZIP) python3 -I -m zipfile -e $< .cache/src touch $@ # ---- per-target compiled deps (removed by clean-deps) ----------------------- # A patched copy of tep per target: tep bakes the paths of its C shims # (sphttp.o, tep_sqlite.o) into the translated source, so each target's # copy holds that target's objects. .cache/%/tep/.patched: $(PATCHES) rm -rf .cache/$*/tep mkdir -p .cache/$* cp -a $(TEP_SRC) .cache/$*/tep rm -rf .cache/$*/tep/.git for p in $(PATCHES); do patch -s -p1 -d .cache/$*/tep < $$p; done touch $@ # Host (x86-64): the system cc. .cache/x86/libsqlite3.a: $(SQLITE_DIR)/sqlite3.c mkdir -p $(@D) cc -O2 $(SQLITE_DEFS) -c $< -o .cache/x86/sqlite3.o ar rcs $@ .cache/x86/sqlite3.o .cache/x86/tep/tep/sphttp.o: .cache/x86/tep/.patched cc -O2 -c .cache/x86/tep/tep/sphttp.c -o $@ .cache/x86/tep/native/sqlite/tep_sqlite.o: .cache/x86/tep/.patched $(SQLITE_DIR)/sqlite3.c cc -O2 -I$(SQLITE_DIR) -c .cache/x86/tep/native/sqlite/tep_sqlite.c -o $@ # arm64: gcc inside the emulated arm64 root (slow: SQLite takes minutes). .cache/arm64/libsqlite3.a: $(SQLITE_DIR)/sqlite3.c mkdir -p $(@D) $(ARM64) cc -O2 $(SQLITE_DEFS) -c $< -o .cache/arm64/sqlite3.o $(ARM64) ar rcs $@ .cache/arm64/sqlite3.o .cache/arm64/tep/tep/sphttp.o: .cache/arm64/tep/.patched $(ARM64) cc -O2 -c .cache/arm64/tep/tep/sphttp.c -o $@ .cache/arm64/tep/native/sqlite/tep_sqlite.o: .cache/arm64/tep/.patched $(SQLITE_DIR)/sqlite3.c $(ARM64) cc -O2 -I$(SQLITE_DIR) -c .cache/arm64/tep/native/sqlite/tep_sqlite.c -o $@ X86_DEPS := .cache/x86/libsqlite3.a .cache/x86/tep/tep/sphttp.o .cache/x86/tep/native/sqlite/tep_sqlite.o ARM64_DEPS := .cache/arm64/libsqlite3.a .cache/arm64/tep/tep/sphttp.o .cache/arm64/tep/native/sqlite/tep_sqlite.o deps: $(X86_DEPS) $(ARM64_DEPS) # ---- the app --------------------------------------------------------------- # tep translates app.rb (writing .app.tep.rb beside it) and runs Spinel. dist/app: $(SOURCES) version.rb $(X86_DEPS) mkdir -p dist .cache/x86/tmp TMPDIR=$(abspath .cache/x86/tmp) SPINEL=$(SPINEL_HOST) LIBRARY_PATH=$(abspath .cache/x86) \ $(RUBY) .cache/x86/tep/bin/tep build app.rb -o $@ # The host Spinel turns Ruby into C (fast, native); scripts/arm64-cc then # compiles and links that C statically inside the arm64 root. dist/app-linux-arm64: $(SOURCES) version.rb $(ARM64_DEPS) mkdir -p dist .cache/arm64/tmp TMPDIR=$(abspath .cache/arm64/tmp) SPINEL=$(abspath scripts/spinel-cross) \ SPINEL_HOST=$(SPINEL_HOST) SPINEL_ARM64=$(SPINEL_ARM64) \ ARM64_WORK=$(abspath .cache/arm64/work) ARM64_LIB_DIR=$(abspath .cache/arm64) \ $(RUBY) .cache/arm64/tep/bin/tep build app.rb -o $@ file $@ | grep -q 'ARM aarch64.*statically linked' # ---- the rest ---------------------------------------------------------------- # The second run collects garbage every few KB, which shakes out values # the generated code forgot to root (one such bug is why tep is patched). test: dist/app test/run.sh dist/app SPINEL_GC_STRESS=1 test/run.sh dist/app clean: rm -rf dist .app.tep.rb version.rb .cache/x86/tmp .cache/arm64/tmp .cache/arm64/work # Every compiled third-party artifact goes; the downloaded SQLite source stays. clean-deps: clean rm -rf .cache/x86 .cache/arm64 BUMP_FROM := BUILD_NOTE = "todo-spinel" BUMP_TO := BUILD_NOTE = "todo-spinel (bumped)" bump: grep -qF '$(BUMP_FROM)' app.rb sed -i 's/^$(BUMP_FROM)$$/$(BUMP_TO)/' app.rb unbump: grep -qF '$(BUMP_TO)' app.rb sed -i 's/^$(BUMP_TO)$$/$(BUMP_FROM)/' app.rb MESSAGE ?= Deploy todo-spinel $(VERSION) deploy: dist/app-linux-arm64 carlos deploy --account bfj --app todo-spinel --target linux-arm64 --message "$(MESSAGE)" dist/app-linux-arm64