# Build and test the calog broker core and engine adapters.
#
# Layout:
#   src/        project source; one subdir per script language
#               (src/lua, src/mybasic, src/squirrel); core + actor live in src/
#   tests/      test programs
#   vendor/     third-party engine sources, built from source (lua, mybasic,
#               squirrel-src) -- no system engine packages, so the build is
#               reproducible
#   obj/        all object files (ours and the vendored engines)
#   bin/        all binaries
#
# Core is strict (-Wconversion/-Wsign-conversion) + ASan/UBSan; adapters drop the
# two conversion warnings (engine headers use wide macros). Vendored engines build
# relaxed and un-sanitized but link into the sanitized binaries so cross-boundary
# heap misuse is still caught. -MMD/-MP generate header dependencies automatically.

CC        = gcc
CXX       = g++
STRICT    = -Wall -Wextra -Werror -Wconversion -Wsign-conversion
WARN      = -Wall -Wextra -Werror
SAN       = -fsanitize=address,undefined
DEP       = -MMD -MP
COREFLAGS = -std=c11 $(STRICT) $(DEP) -g -O1 $(SAN)
ADPFLAGS  = -std=c11 $(WARN) $(DEP) -g -O1 $(SAN)
LDFLAGS   = $(SAN)

# Our headers: src/ plus the per-language subdirs. VPATH lets the object rules find
# a source by name without spelling out its directory.
INC       = -Isrc -Isrc/lua -Isrc/mybasic -Isrc/squirrel -Isrc/js
VPATH     = src:src/lua:src/mybasic:src/squirrel:src/js:tests

CORE      = obj/value.o obj/broker.o

# --- vendored my-basic (C) ---
MBDIR     = vendor/mybasic
MBINC     = -I$(MBDIR)
MBFLAGS   = -std=gnu11 -w -g -O1 -DMB_DOUBLE_FLOAT

# --- vendored Lua 5.4.6 (C) ---
# The platform define is chosen from uname, the way upstream Lua's own Makefile
# does, so the build is correct on Linux / macOS / other POSIX without hand-editing.
# LUA_USE_LINUX and _MACOSX enable dlopen-based package loading (loadlib.c); on
# Linux that needs -ldl. The standalone interpreter (lua.c, the only file that uses
# readline) is not built, so there is no readline dependency. The library is every
# src/*.c except the two standalone mains (lua.c, luac.c).
LUADIR    = vendor/lua
LUAINC    = -I$(LUADIR)/src
# Check $(OS) first -- Windows sets OS=Windows_NT in the environment (make inherits
# it) and has no `uname`, so probing uname there yields an empty string that would
# wrongly select POSIX. Only fall back to `uname` on Unix. (The rest of the project
# is Unix-only anyway -- pthreads, sanitizers, setarch -- so the Windows branch just
# keeps the Lua define correct, it does not by itself make a Windows build work.)
ifeq ($(OS),Windows_NT)
  LUAPLAT     =
  LUAPLATLIBS =
else
  UNAMES := $(shell uname -s)
  ifeq ($(UNAMES),Linux)
    LUAPLAT     = -DLUA_USE_LINUX
    LUAPLATLIBS = -ldl
  else ifeq ($(UNAMES),Darwin)
    LUAPLAT     = -DLUA_USE_MACOSX
    LUAPLATLIBS =
  else
    LUAPLAT     = -DLUA_USE_POSIX
    LUAPLATLIBS =
  endif
endif
LUAFLAGS  = -std=c99 -w -g -O2 $(LUAPLAT)
LUASRC    = $(filter-out $(LUADIR)/src/lua.c $(LUADIR)/src/luac.c, $(wildcard $(LUADIR)/src/*.c))
LUAOBJ    = $(patsubst $(LUADIR)/src/%.c,obj/%.o,$(LUASRC))
LUALIBS   = $(LUAPLATLIBS) -lm

# --- vendored Squirrel 3.2 (C++) ---
# -D_SQ64 -DSQUSEDOUBLE so SQInteger/SQFloat are 64-bit int / double; the adapter
# MUST share those defines so the ABI (object layout) matches the VM.
SQDIR     = vendor/squirrel-src
SQDEF     = -D_SQ64 -DSQUSEDOUBLE
SQINC     = -I$(SQDIR)/include
SQXXFLAGS = -std=c++11 -w -g -O1 $(SQDEF) $(SQINC) -I$(SQDIR)/squirrel
SQSRC     = $(wildcard $(SQDIR)/squirrel/*.cpp)
SQOBJ     = $(patsubst $(SQDIR)/squirrel/%.cpp,obj/%.o,$(SQSRC))

# --- vendored Duktape 2.7.0 (C, JavaScript): single amalgamated source. ---
DUKDIR    = vendor/duktape
DUKINC    = -I$(DUKDIR)
DUKFLAGS  = -std=c99 -w -g -O1
DUKOBJ    = obj/duktape.o

BINS = bin/testBroker bin/testLua bin/testMyBasic bin/testPolyglot bin/testActor \
       bin/testEngineLua bin/testSquirrel bin/testEngineSquirrel bin/testJs bin/testEngineJs \
       bin/embed

all: $(BINS)

# ---- object rules, grouped by flag set (sources resolved via VPATH) ----

# strict C, no threads
STRICTOBJ = obj/broker.o obj/value.o obj/luaEngine.o obj/squirrelEngine.o obj/jsEngine.o obj/testBroker.o
$(STRICTOBJ): obj/%.o: %.c | obj
	$(CC) $(COREFLAGS) $(INC) -c -o $@ $<

# strict C, threaded
THREADOBJ = obj/context.o obj/testActor.o obj/testEngineLua.o obj/testEngineSquirrel.o obj/testEngineJs.o
$(THREADOBJ): obj/%.o: %.c | obj
	$(CC) $(COREFLAGS) $(INC) -pthread -c -o $@ $<

# relaxed C + Lua headers
LUAADP = obj/luaAdapter.o obj/testLua.o
$(LUAADP): obj/%.o: %.c | obj
	$(CC) $(ADPFLAGS) $(INC) $(LUAINC) -c -o $@ $<

# relaxed C + my-basic headers
MBADP = obj/mybasicAdapter.o obj/testMyBasic.o
$(MBADP): obj/%.o: %.c | obj
	$(CC) $(ADPFLAGS) $(INC) $(MBINC) -DMB_DOUBLE_FLOAT -c -o $@ $<

# relaxed C + Squirrel headers
SQADP = obj/squirrelAdapter.o obj/testSquirrel.o
$(SQADP): obj/%.o: %.c | obj
	$(CC) $(ADPFLAGS) $(INC) $(SQDEF) $(SQINC) -c -o $@ $<

# relaxed C + Duktape headers
JSADP = obj/jsAdapter.o obj/testJs.o
$(JSADP): obj/%.o: %.c | obj
	$(CC) $(ADPFLAGS) $(INC) $(DUKINC) -c -o $@ $<

# polyglot test pulls in both Lua and my-basic
obj/testPolyglot.o: testPolyglot.c | obj
	$(CC) $(ADPFLAGS) $(INC) $(LUAINC) $(MBINC) -DMB_DOUBLE_FLOAT -c -o $@ $<

# ---- vendored engine objects (also land in obj/) ----
obj/myBasic.o: $(MBDIR)/myBasic.c | obj
	$(CC) $(MBFLAGS) -c -o $@ $<

obj/%.o: $(LUADIR)/src/%.c | obj
	$(CC) $(LUAFLAGS) -c -o $@ $<

obj/%.o: $(SQDIR)/squirrel/%.cpp | obj
	$(CXX) $(SQXXFLAGS) -c -o $@ $<

obj/duktape.o: $(DUKDIR)/duktape.c | obj
	$(CC) $(DUKFLAGS) $(DUKINC) -c -o $@ $<

# ---- library archives ----
# libcalog.a is calog itself (core + actor + every engine adapter/binding). The
# vendored engines are separate archives; a host links libcalog.a plus whichever
# engine archives it uses -- unused adapters, and their engine deps, stay unlinked
# (static members are pulled only when referenced). See examples/embed.c.
CALOGLIB = obj/value.o obj/broker.o obj/context.o \
           obj/luaAdapter.o obj/luaEngine.o obj/jsAdapter.o obj/jsEngine.o \
           obj/squirrelAdapter.o obj/squirrelEngine.o obj/mybasicAdapter.o

lib/libcalog.a: $(CALOGLIB) | lib
	ar rcs $@ $^
lib/liblua.a: $(LUAOBJ) | lib
	ar rcs $@ $^
lib/libduktape.a: $(DUKOBJ) | lib
	ar rcs $@ $^
lib/libsquirrel.a: $(SQOBJ) | lib
	ar rcs $@ $^
lib/libmybasic.a: obj/myBasic.o | lib
	ar rcs $@ $^

# ---- binaries (link libcalog.a + the vendored engine archives they use) ----
bin/testBroker: obj/testBroker.o lib/libcalog.a | bin
	$(CC) $(LDFLAGS) -o $@ $^

bin/testActor: obj/testActor.o lib/libcalog.a | bin
	$(CC) $(LDFLAGS) -pthread -o $@ $^

bin/testLua: obj/testLua.o lib/libcalog.a lib/liblua.a | bin
	$(CC) $(LDFLAGS) -o $@ $^ $(LUALIBS)

bin/testEngineLua: obj/testEngineLua.o lib/libcalog.a lib/liblua.a | bin
	$(CC) $(LDFLAGS) -pthread -o $@ $^ $(LUALIBS)

bin/testMyBasic: obj/testMyBasic.o lib/libcalog.a lib/libmybasic.a | bin
	$(CC) $(LDFLAGS) -o $@ $^ -lm

bin/testPolyglot: obj/testPolyglot.o lib/libcalog.a lib/liblua.a lib/libmybasic.a | bin
	$(CC) $(LDFLAGS) -o $@ $^ $(LUALIBS)

bin/testSquirrel: obj/testSquirrel.o lib/libcalog.a lib/libsquirrel.a | bin
	$(CC) $(LDFLAGS) -o $@ $^ -lstdc++ -lm

bin/testEngineSquirrel: obj/testEngineSquirrel.o lib/libcalog.a lib/libsquirrel.a | bin
	$(CC) $(LDFLAGS) -pthread -o $@ $^ -lstdc++ -lm

bin/testJs: obj/testJs.o lib/libcalog.a lib/libduktape.a | bin
	$(CC) $(LDFLAGS) -o $@ $^ -lm

bin/testEngineJs: obj/testEngineJs.o lib/libcalog.a lib/libduktape.a | bin
	$(CC) $(LDFLAGS) -pthread -o $@ $^ -lm

# example host: embeds calog + JS via calog.h only, linking the two archives.
obj/embed.o: examples/embed.c src/calog.h | obj
	$(CC) $(COREFLAGS) -Isrc -pthread -c -o $@ $<

bin/embed: obj/embed.o lib/libcalog.a lib/libduktape.a | bin
	$(CC) $(LDFLAGS) -pthread -o $@ $^ -lm

obj bin lib:
	mkdir -p $@

test: all
	./bin/testBroker && ./bin/testLua && ./bin/testMyBasic && ./bin/testPolyglot && \
	./bin/testActor && ./bin/testEngineLua && ./bin/testSquirrel && ./bin/testEngineSquirrel && \
	./bin/testJs && ./bin/testEngineJs

# ThreadSanitizer build of the actor core and the Lua engine path (cannot combine
# with ASan). Recompiled from source under TSan; the vendored Lua objects are
# linked un-sanitized (each VM is single-threaded, so that is sound). Run under
# `setarch -R` (ASLR off): some kernels hand out more mmap randomization than TSan's
# shadow allocator tolerates, which aborts it before any test runs.
tsan: $(LUAOBJ) | bin
	$(CC) -std=c11 $(WARN) -g -O1 -fsanitize=thread -pthread $(INC) -o bin/testActorTsan \
		tests/testActor.c src/context.c src/value.c src/broker.c
	setarch -R ./bin/testActorTsan
	$(CC) -std=c11 $(WARN) -g -O1 -fsanitize=thread -pthread $(INC) $(LUAINC) -o bin/testEngineLuaTsan \
		tests/testEngineLua.c src/lua/luaEngine.c src/lua/luaAdapter.c src/context.c src/value.c src/broker.c \
		$(LUAOBJ) $(LUALIBS)
	setarch -R ./bin/testEngineLuaTsan

# ThreadSanitizer build of the Squirrel engine path: the vendored VM is recompiled
# under TSan here (throwaway obj/*.tsan.o) so races are caught across the whole
# stack. Slower than `tsan`, so kept separate.
tsansq: $(SQSRC) | bin obj
	for f in $(SQSRC); do $(CXX) $(SQXXFLAGS) -fsanitize=thread -c $$f -o obj/$$(basename $${f%.cpp}).tsan.o; done
	$(CC) -std=c11 $(WARN) -g -O1 -fsanitize=thread -pthread $(INC) $(SQDEF) $(SQINC) -o bin/testEngineSquirrelTsan \
		tests/testEngineSquirrel.c src/squirrel/squirrelEngine.c src/squirrel/squirrelAdapter.c \
		src/context.c src/value.c src/broker.c \
		$(patsubst $(SQDIR)/squirrel/%.cpp,obj/%.tsan.o,$(SQSRC)) -lstdc++ -lm
	setarch -R ./bin/testEngineSquirrelTsan
	rm -f $(patsubst $(SQDIR)/squirrel/%.cpp,obj/%.tsan.o,$(SQSRC))

# ThreadSanitizer build of the JS engine path: the vendored Duktape heap is
# recompiled under TSan (throwaway obj) so races are caught across the whole stack.
tsanjs: | bin obj
	$(CC) $(DUKFLAGS) $(DUKINC) -fsanitize=thread -c $(DUKDIR)/duktape.c -o obj/duktape.tsan.o
	$(CC) -std=c11 $(WARN) -g -O1 -fsanitize=thread -pthread $(INC) $(DUKINC) -o bin/testEngineJsTsan \
		tests/testEngineJs.c src/js/jsEngine.c src/js/jsAdapter.c src/context.c src/value.c src/broker.c \
		obj/duktape.tsan.o -lm
	setarch -R ./bin/testEngineJsTsan
	rm -f obj/duktape.tsan.o

clean:
	rm -rf obj bin lib

-include $(wildcard obj/*.d)

.PHONY: all test tsan tsansq tsanjs clean
