Files
deck_in_a_dash/Makefile
T
ml 4e83ebbf50
build / build (push) Successful in 20s
Add daily notification wakeups and a make watchapp packaging target
Schedules a one-shot Pebble wakeup for the configured notification
time, re-arming it on every launch and config change, and vibrates/
refreshes the report when it fires (foreground or fresh launch).
Also adds a `make watchapp` target that builds the watchapp via the
Pebble SDK and copies a versioned .pbw to dist/, matching `make
package`'s version resolution — deliberately not wired into CI, which
has no Pebble SDK installed.
2026-07-17 20:48:31 +02:00

245 lines
11 KiB
Makefile

CC ?= cc
CFLAGS ?= -std=c99 -Wall -Wextra -O2
# Static by default so dist/deck-engine and dist/interpreter-cli (and the
# `package` tarball built from them) don't depend on the build machine's
# glibc version at runtime - a binary linked against a newer glibc than a
# user's system has refuses to run at all ("GLIBC_2.3x not found"), which
# is exactly the kind of thing a downloaded release package must not hit.
# Override with `LDFLAGS= make` if static libc/libm aren't available.
LDFLAGS ?= -static
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
# ---- watch app (Pebble) ----
WATCH_DIR := watch
WATCH_PBW := $(WATCH_DIR)/build/watch.pbw
.PHONY: all test clean images i18n scripts package watchapp
all: $(ENGINE_BINARY) $(INTERP_BINARY) images i18n scripts
$(ENGINE_BINARY): $(ENGINE_OBJS) $(ENGINE_MAIN_OBJ)
@mkdir -p $(DIST_DIR)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
$(INTERP_BINARY): $(INTERP_LIB_OBJS) $(INTERP_MAIN_OBJ) $(INTERP_TAROT_TEXT_OBJS)
@mkdir -p $(DIST_DIR)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^
# The HTML report expects card art at img/<file> 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>.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 <code>.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) $(LDFLAGS) -o $@ $^ $(LDLIBS)
$(SIGNIFICANCE_TEST_BIN): $(OBJ_DIR)/$(INTERP_SRC_DIR)/significance.o $(SIGNIFICANCE_TEST_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^
$(JSON_TEST_BIN): $(OBJ_DIR)/$(INTERP_SRC_DIR)/json.o $(OBJ_DIR)/$(INTERP_SRC_DIR)/reading_io.o $(JSON_TEST_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^
$(NARRATIVE_TEST_BIN): $(OBJ_DIR)/$(INTERP_SRC_DIR)/narrative.o $(INTERP_TAROT_TEXT_OBJS) $(NARRATIVE_TEST_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -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) $(LDFLAGS) -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-<version>-linux-<arch>.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+<sha> 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.
# Builds the Pebble watchapp (`pebble build` in watch/, via its own
# wscript/pebble_sdk waf tooling - not a plain C toolchain, so this is a
# separate target from `all` rather than one of its prerequisites) and
# copies the resulting .pbw into dist/, versioned the same way `package`
# above versions the CLI tarball (current git tag, or
# `make watchapp VERSION=1.2.3` to override). Requires the Pebble SDK
# (`pebble` on PATH, plus Python 3 + Pillow for watch/scripts/*.py - see
# watch/wscript) - this is NOT checked by the root `make`/`make test`,
# so a machine without the Pebble SDK can still build/test the CLI.
watchapp:
cd $(WATCH_DIR) && pebble build
@mkdir -p $(DIST_DIR)
@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; \
PBW_NAME="deck-in-a-dash-$$V.pbw"; \
cp $(WATCH_PBW) "$(DIST_DIR)/$$PBW_NAME"; \
echo "Wrote $(DIST_DIR)/$$PBW_NAME"