CC ?= cc CFLAGS ?= -std=c99 -Wall -Wextra -O2 LDLIBS := -lm BUILD_DIR := build OBJ_DIR := $(BUILD_DIR)/obj DIST_DIR := dist DIST_IMG_DIR := $(DIST_DIR)/img DIST_I18N_DIR := $(DIST_DIR)/i18n # ---- engine ---- ENGINE_SRC_DIR := engine/src ENGINE_THIRD_PARTY := engine/third_party/astronomy/astronomy.c ENGINE_TEST_DIR := engine/tests ENGINE_I18N_DIR := engine/i18n ENGINE_SCRIPTS_DIR := engine/scripts RES_IMG_DIR := res/img # Durable copy of the user's real birth data, outside dist/ (which is # disposable - `make clean`, or just deleting the directory, wipes it). # Gitignored: see .gitignore. USER_PROPS_BACKUP := $(ENGINE_SCRIPTS_DIR)/user.properties.local # Engine logic shared by the CLI and the test binary (no main()). ENGINE_SRCS := $(ENGINE_SRC_DIR)/rng.c $(ENGINE_SRC_DIR)/tarot.c $(ENGINE_SRC_DIR)/tarot_data.c \ $(ENGINE_SRC_DIR)/astro.c $(ENGINE_SRC_DIR)/reading.c $(ENGINE_SRC_DIR)/i18n.c \ $(ENGINE_THIRD_PARTY) ENGINE_OBJS := $(patsubst %.c,$(OBJ_DIR)/%.o,$(ENGINE_SRCS)) ENGINE_MAIN_OBJ := $(OBJ_DIR)/$(ENGINE_SRC_DIR)/main.o ENGINE_TEST_OBJ := $(OBJ_DIR)/$(ENGINE_TEST_DIR)/smoke_test.o ENGINE_BINARY := $(DIST_DIR)/deck-engine ENGINE_TEST_BINARY := $(BUILD_DIR)/smoke_test # ---- interpreter ---- # Mostly compiles only this module's own sources - significance.h/ # reading_io.h/narrative.h reach into engine/src/ for struct/enum # *definitions* (plain #includes) without linking any engine/src/*.c (or # the vendored Astronomy Engine). That's what keeps those modules (and # their tests) independent of the engine's actual ephemeris/tarot-draw # implementation - see significance.c's and reading_io.c's own comments # for why each duplicates a small table instead of linking astro.c. # # The one deliberate exception is engine/src/tarot_data.c (card/position # names and Waite meaning text) and its own i18n.c dependency: both are # pure, deterministic lookup tables with no ephemeris, no randomness, and # (since the interpreter never calls i18n_load) no file I/O either, so # linking them doesn't compromise interpreter/tests/'s "no real # ephemeris/tarot-draw call" independence - it just avoids duplicating # ~40 short strings of Waite's own card text a second time for # main.c's/guidance.c's full-spread readout. See CLAUDE.md. INTERP_SRC_DIR := interpreter/src INTERP_TEST_DIR := interpreter/tests INTERP_LIB_SRCS := $(INTERP_SRC_DIR)/significance.c $(INTERP_SRC_DIR)/json.c \ $(INTERP_SRC_DIR)/reading_io.c $(INTERP_SRC_DIR)/narrative.c \ $(INTERP_SRC_DIR)/guidance.c INTERP_LIB_OBJS := $(patsubst %.c,$(OBJ_DIR)/%.o,$(INTERP_LIB_SRCS)) INTERP_MAIN_OBJ := $(OBJ_DIR)/$(INTERP_SRC_DIR)/main.o # Shared with the engine build (same source, same flags - see the # comment above); reused as-is rather than compiled twice. INTERP_TAROT_TEXT_OBJS := $(OBJ_DIR)/$(ENGINE_SRC_DIR)/tarot_data.o $(OBJ_DIR)/$(ENGINE_SRC_DIR)/i18n.o INTERP_BINARY := $(DIST_DIR)/interpreter-cli SIGNIFICANCE_TEST_OBJ := $(OBJ_DIR)/$(INTERP_TEST_DIR)/significance_test.o JSON_TEST_OBJ := $(OBJ_DIR)/$(INTERP_TEST_DIR)/json_test.o NARRATIVE_TEST_OBJ := $(OBJ_DIR)/$(INTERP_TEST_DIR)/narrative_test.o GUIDANCE_TEST_OBJ := $(OBJ_DIR)/$(INTERP_TEST_DIR)/guidance_test.o SIGNIFICANCE_TEST_BIN := $(BUILD_DIR)/significance_test JSON_TEST_BIN := $(BUILD_DIR)/json_test NARRATIVE_TEST_BIN := $(BUILD_DIR)/narrative_test GUIDANCE_TEST_BIN := $(BUILD_DIR)/guidance_test # Linux CLI release tarball - see the `package` target below. ARCH := $(shell uname -m) PACKAGE_DIR := $(BUILD_DIR)/package .PHONY: all test clean images i18n scripts package all: $(ENGINE_BINARY) $(INTERP_BINARY) images i18n scripts $(ENGINE_BINARY): $(ENGINE_OBJS) $(ENGINE_MAIN_OBJ) @mkdir -p $(DIST_DIR) $(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) $(INTERP_BINARY): $(INTERP_LIB_OBJS) $(INTERP_MAIN_OBJ) $(INTERP_TAROT_TEXT_OBJS) @mkdir -p $(DIST_DIR) $(CC) $(CFLAGS) -o $@ $^ # The HTML report expects card art at img/ next to deck-engine, so # every build refreshes a copy alongside it (cheap: 22 small JPEGs). images: @mkdir -p $(DIST_IMG_DIR) cp $(RES_IMG_DIR)/*.jpeg $(DIST_IMG_DIR)/ # deck-engine expects translation files at i18n/.lang next to it # (default --i18n-dir), so every build refreshes a copy alongside it, # same as images/. Add a new language by dropping another .lang # file into engine/i18n/ - no code or Makefile change needed. i18n: @mkdir -p $(DIST_I18N_DIR) cp $(ENGINE_I18N_DIR)/*.lang $(DIST_I18N_DIR)/ # run-engine.sh/run-interpreter.sh are refreshed every build. # # user.properties is backed up/restored across dist/ wipes rather than # just "seeded once": if dist/user.properties exists and looks filled in # (no leftover YOUR_... placeholder), it's copied out to # USER_PROPS_BACKUP; otherwise, if that backup exists (e.g. dist/ was # just deleted), it's copied back in; otherwise (genuinely first run) # dist/user.properties is seeded from the placeholder template, same as # before. scripts: @mkdir -p $(DIST_DIR) cp $(ENGINE_SCRIPTS_DIR)/run-engine.sh $(DIST_DIR)/run-engine.sh chmod +x $(DIST_DIR)/run-engine.sh cp $(ENGINE_SCRIPTS_DIR)/run-interpreter.sh $(DIST_DIR)/run-interpreter.sh chmod +x $(DIST_DIR)/run-interpreter.sh @if [ -f $(DIST_DIR)/user.properties ] && ! grep -q 'YOUR_' $(DIST_DIR)/user.properties; then \ cp $(DIST_DIR)/user.properties $(USER_PROPS_BACKUP); \ elif [ -f $(USER_PROPS_BACKUP) ]; then \ cp $(USER_PROPS_BACKUP) $(DIST_DIR)/user.properties; \ elif [ ! -f $(DIST_DIR)/user.properties ]; then \ cp $(ENGINE_SCRIPTS_DIR)/user.properties.template $(DIST_DIR)/user.properties; \ fi test: $(ENGINE_TEST_BINARY) $(SIGNIFICANCE_TEST_BIN) $(JSON_TEST_BIN) $(NARRATIVE_TEST_BIN) $(GUIDANCE_TEST_BIN) ./$(ENGINE_TEST_BINARY) ./$(SIGNIFICANCE_TEST_BIN) ./$(JSON_TEST_BIN) ./$(NARRATIVE_TEST_BIN) ./$(GUIDANCE_TEST_BIN) $(ENGINE_TEST_BINARY): $(ENGINE_OBJS) $(ENGINE_TEST_OBJ) $(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) $(SIGNIFICANCE_TEST_BIN): $(OBJ_DIR)/$(INTERP_SRC_DIR)/significance.o $(SIGNIFICANCE_TEST_OBJ) $(CC) $(CFLAGS) -o $@ $^ $(JSON_TEST_BIN): $(OBJ_DIR)/$(INTERP_SRC_DIR)/json.o $(OBJ_DIR)/$(INTERP_SRC_DIR)/reading_io.o $(JSON_TEST_OBJ) $(CC) $(CFLAGS) -o $@ $^ $(NARRATIVE_TEST_BIN): $(OBJ_DIR)/$(INTERP_SRC_DIR)/narrative.o $(INTERP_TAROT_TEXT_OBJS) $(NARRATIVE_TEST_OBJ) $(CC) $(CFLAGS) -o $@ $^ $(GUIDANCE_TEST_BIN): $(OBJ_DIR)/$(INTERP_SRC_DIR)/guidance.o $(OBJ_DIR)/$(INTERP_SRC_DIR)/significance.o \ $(INTERP_TAROT_TEXT_OBJS) $(GUIDANCE_TEST_OBJ) $(CC) $(CFLAGS) -o $@ $^ $(OBJ_DIR)/%.o: %.c @mkdir -p $(dir $@) $(CC) $(CFLAGS) -c -o $@ $< # Builds a versioned Linux CLI release tarball at # dist/deck-in-a-dash--linux-.tar.gz. Version defaults to # the current git tag (vX.Y.Z, tag prefix stripped) - push a tag to drive # a release. Override with `make package VERSION=1.2.3`, or just run it # untagged for a local dev build (gets a 0.0.0-dev+ placeholder # version, with a warning). # # Copies files into the staging dir by explicit name only - never a # wildcard/recursive copy of dist/ itself - so a real, filled-in # dist/user.properties (birth data, gitignored, private) can never # accidentally end up in a package. The package gets the placeholder # template instead, same as a first-time `make` produces. package: all @V="$(VERSION)"; \ if [ -z "$$V" ]; then \ if TAG=$$(git describe --tags --exact-match --match 'v[0-9]*.[0-9]*.[0-9]*' 2>/dev/null); then \ V=$${TAG#v}; \ else \ V="0.0.0-dev+$$(git rev-parse --short HEAD)"; \ echo "WARNING: HEAD is not on a vX.Y.Z tag - building placeholder version $$V (push a tag to drive a real release version)" >&2; \ fi; \ fi; \ case "$$V" in \ [0-9]*.[0-9]*.[0-9]*) ;; \ *) echo "PREFLIGHT FAIL: VERSION '$$V' is not a semantic version (expected X.Y.Z, optionally with a -pre+meta suffix)" >&2; exit 1;; \ esac; \ PKG_NAME="deck-in-a-dash-$$V-linux-$(ARCH)"; \ PKG_STAGE="$(PACKAGE_DIR)/$$PKG_NAME"; \ echo "Packaging $$PKG_NAME"; \ rm -rf "$$PKG_STAGE"; \ mkdir -p "$$PKG_STAGE"; \ cp $(ENGINE_BINARY) $(INTERP_BINARY) "$$PKG_STAGE/"; \ cp $(DIST_DIR)/run-engine.sh $(DIST_DIR)/run-interpreter.sh "$$PKG_STAGE/"; \ cp -r $(DIST_IMG_DIR) $(DIST_I18N_DIR) "$$PKG_STAGE/"; \ cp $(ENGINE_SCRIPTS_DIR)/user.properties.template "$$PKG_STAGE/user.properties"; \ cp LICENSE README.md "$$PKG_STAGE/"; \ cp docs/reading.md "$$PKG_STAGE/READING.md"; \ tar -czf "$(DIST_DIR)/$$PKG_NAME.tar.gz" -C "$(PACKAGE_DIR)" "$$PKG_NAME"; \ rm -rf "$(PACKAGE_DIR)"; \ echo "Wrote $(DIST_DIR)/$$PKG_NAME.tar.gz" clean: rm -rf $(BUILD_DIR) $(ENGINE_BINARY) $(INTERP_BINARY) $(DIST_IMG_DIR) $(DIST_I18N_DIR) \ $(DIST_DIR)/run-engine.sh $(DIST_DIR)/run-interpreter.sh # user.properties holds the user's own data - never removed by clean.