# 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
