build: add the root Makefile the previous commit should have carried
Test, Build and Push / test-gateway (push) Successful in 12s
Test, Build and Push / release (push) Skipped
Test, Build and Push / build-gateway (push) Skipped

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 <noreply@anthropic.com>
This commit is contained in:
2026-08-09 15:04:43 +02:00
co-authored by Claude
parent ff193203c9
commit 2ef00c98de
3 changed files with 131 additions and 9 deletions
+7
View File
@@ -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
+26 -9
View File
@@ -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=<path>/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`
+98
View File
@@ -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=<path>/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=<path>/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