From 2ef00c98de1b96f83f7b996682b19b81d363ba49 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sun, 9 Aug 2026 15:04:43 +0200 Subject: [PATCH] build: add the root Makefile the previous commit should have carried ff19320 removed gateway/Makefile but the git add that was meant to stage its replacement aborted on an already-staged pathspec, so the deletion landed alone and main briefly had no Makefile at all. This is the other half. Co-Authored-By: Claude --- CHANGELOG.md | 7 ++++ CLAUDE.md | 35 ++++++++++++++----- Makefile | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 Makefile diff --git a/CHANGELOG.md b/CHANGELOG.md index f1ec4fe..219e57a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ until the first tagged release. ## [Unreleased] +### Changed + +- One Makefile at the repo root now drives firmware, gateway and sim; `gateway/Makefile` is + removed. `make test` means the same thing from any directory. +- Firmware targets source `~/esp-idf/export.sh` themselves, so `idf.py` resolves without + having to remember. Override the location with `IDF_EXPORT=`. + ## [0.2.2] — 2026-07-19 ### Changed diff --git a/CLAUDE.md b/CLAUDE.md index 5cbad9d..37723a8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -89,19 +89,36 @@ intelligence belongs in the gateway or in Tatlock itself. ## Commands -```bash -# Firmware (requires `source ~/esp-idf/export.sh` first; ESP-IDF ≥ 5.4, this box has 5.5) -cd firmware && idf.py build -idf.py -p /dev/ttyACM0 flash monitor +**One Makefile at the root drives all three components.** There is deliberately no +`gateway/Makefile` any more — `make test` meant "the gateway's tests" or "nothing" +depending on which directory you were standing in, and now it means the same thing +everywhere (D-27). -# Gateway -cd gateway && make setup # venv + dev deps (no ML models) -make setup-speech # additionally install faster-whisper + piper +```bash +make help # every target, self-documenting + +make test # gateway pytest; reports firmware + sim as undetermined +make lint # ruff check + format --check +make typecheck # mypy + +make setup # gateway venv + dev deps (no ML models) +make setup-speech # additionally faster-whisper + piper make run # uvicorn on :8600 with reload -make test # pytest (tests/test_health.py, test_tatlock.py, test_commands.py) -make lint typecheck # .venv/bin/ruff + .venv/bin/mypy + +make build-firmware # sources export.sh for you, then idf.py build +make flash PORT=/dev/ttyACM0 # flash + monitor +make serve-sim # face simulator on :8601 ``` +**The firmware targets source `~/esp-idf/export.sh` themselves.** `idf.py` is not on +`PATH` until that runs, so the old `cd firmware && idf.py build` fails with "command +not found" for anyone who forgets — the same class of failure as four other tool +misses on this host. Override with `IDF_EXPORT=/export.sh` on another machine; +the target fails loudly with that hint if the file is absent. + +`make test` never reports green for the firmware. It has no suite, so it is +**undetermined**, printed explicitly rather than skipped silently (D-26). + ## Liveness - **Gateway (`desklock-gateway` container, port 8600):** confirmed live — `docker ps` diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..64cb92e --- /dev/null +++ b/Makefile @@ -0,0 +1,98 @@ +# desklock — one entry point for a three-component repo. +# +# firmware/ ESP-IDF application for the device. No tests, no release flow. +# gateway/ Python service on :8600. The only component with a test suite. +# sim/ a static page that mimics the device face in a browser. +# +# This lives at the root and the components have no Makefiles of their own, so +# `make test` means the same thing wherever you are standing. A per-component +# Makefile makes it mean "some of the tests" depending on your working +# directory, which is the `git -C` failure in another costume (D-27). +# +# Paths resolve here rather than in callers (D-10). Two of them bite: +# +# ESP-IDF is invisible until export.sh is sourced, so `idf.py` is +# "command not found" for anyone who forgets — the same class of failure as +# the four tool-resolution misses recorded in D-24. The firmware targets +# source it themselves. +# +# `python3` on this host is 3.8, which cannot parse the gateway's sources. +# PYTHON names 3.12 explicitly and is overridable for other machines. + +PYTHON ?= python3.12 +IDF_EXPORT ?= $(HOME)/esp-idf/export.sh +GATEWAY := $(CURDIR)/gateway +VENV := $(GATEWAY)/.venv + +.DEFAULT_GOAL := help + +.PHONY: help +help: ## Show this help + @grep -hE '^[a-z][a-z0-9_-]*:.*?## ' $(MAKEFILE_LIST) \ + | awk 'BEGIN{FS=":.*?## "}{printf " \033[36m%-16s\033[0m %s\n", $$1, $$2}' + +# --- the three D-27 required targets ----------------------------------------- + +.PHONY: test +test: test-gateway ## Run every component's tests that exist + @echo " -- firmware: no test suite (undetermined, not passing)" + @echo " -- sim: a static page, nothing to test" + +.PHONY: lint +lint: lint-gateway ## Lint every component that has a linter + +# --- gateway ------------------------------------------------------------------ + +.PHONY: setup +setup: ## Create the gateway venv and install dev deps (no ML models) + cd $(GATEWAY) && $(PYTHON) -m venv .venv && .venv/bin/pip install -e ".[dev]" + +.PHONY: setup-speech +setup-speech: ## Additionally install faster-whisper and piper + cd $(GATEWAY) && .venv/bin/pip install -e ".[dev,speech]" + +.PHONY: run +run: ## Run the gateway on :8600 with reload + cd $(GATEWAY) && .venv/bin/uvicorn desklock_gateway.main:app --host 0.0.0.0 --port 8600 --reload + +.PHONY: test-gateway +test-gateway: ## Gateway pytest suite + @cd $(GATEWAY) && .venv/bin/pytest + +.PHONY: lint-gateway +lint-gateway: ## ruff check and format --check over the gateway + cd $(GATEWAY) && .venv/bin/ruff check src tests && .venv/bin/ruff format --check src tests + +.PHONY: typecheck +typecheck: ## mypy over the gateway sources + cd $(GATEWAY) && .venv/bin/mypy src + +# --- firmware ----------------------------------------------------------------- +# +# Each target sources export.sh in its own shell. That is deliberate: make runs +# every recipe line in a fresh shell, so exporting in one target would not carry +# to the next, and a caller who sources it by hand still works because sourcing +# twice is harmless. + +.PHONY: build-firmware +build-firmware: ## Build the ESP-IDF firmware (sources export.sh for you) + @test -f $(IDF_EXPORT) || { echo "FAIL — no ESP-IDF at $(IDF_EXPORT); set IDF_EXPORT=/export.sh"; exit 1; } + . $(IDF_EXPORT) && cd firmware && idf.py build + +.PHONY: flash +flash: ## Flash and monitor the device (PORT=/dev/ttyACM0 by default) + @test -f $(IDF_EXPORT) || { echo "FAIL — no ESP-IDF at $(IDF_EXPORT); set IDF_EXPORT=/export.sh"; exit 1; } + . $(IDF_EXPORT) && cd firmware && idf.py -p $(or $(PORT),/dev/ttyACM0) flash monitor + +# --- sim ---------------------------------------------------------------------- + +.PHONY: serve-sim +serve-sim: ## Serve the face simulator on :8601 + cd sim/face && $(PYTHON) -m http.server 8601 + +# --- housekeeping ------------------------------------------------------------- + +.PHONY: clean +clean: ## Remove the gateway venv and caches + rm -rf $(VENV) $(GATEWAY)/.pytest_cache $(GATEWAY)/.ruff_cache $(GATEWAY)/.mypy_cache + find $(GATEWAY) -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true