--- name: makefile-conventions description: > Structures Makefiles as a universal command interface using modular includes and self-documenting help. Use when the user wants to add a make target, create a Makefile, organize build commands, set up a make-based workflow, or asks how to structure make targets. Also activate before modifying any Makefile or mk/ file in this project. metadata: version: "1.0.1" tags: "category:conventions, tool:makefile" --- # Makefile Conventions Use Make as the universal command interface. All project operations are accessible via `make`, regardless of the underlying implementation language. ## Workflow 1. **Investigate the project** — Before writing targets, check: - Does a Makefile already exist? Read it. - What scripts, tools, or build steps does the project use? - Is there an existing `mk/` directory? 2. **Decide placement** — If the target belongs to an existing concern group, add it there. If it starts a new concern, create a new modular makefile. See [makefile-structure](references/makefile-structure.md) for the full modular pattern. 3. **Write the target** — Follow these conventions: - Declare every target `.PHONY` unless it produces a file - Add a `## Description` comment for the help system - Delegate complex logic to scripts - Use `@` prefix for clean output - Quote variable references: `"$(VAR)"` 4. **Update help** — Add the new target to the appropriate concern group in `make help` output. 5. **Verify** — Run `make help` to confirm the target appears. Run the target to confirm it works. ## Complete Example Root Makefile pattern — includes domain modules and provides grouped help: ```makefile .DEFAULT_GOAL := help include mk/test.mk include mk/deploy.mk .PHONY: help help: ## Show this help @echo "Project - Make Commands" @echo "======================" @echo "" @echo "Testing:" @echo " make test - Show test help" @echo " make test sh - Run shellcheck" @echo "" @echo "Development:" @echo " make check - Check prerequisites" ``` For the full modular delegation pattern (domain `.mk` files, subdirectory Makefiles, subcommand delegation), see [makefile-structure](references/makefile-structure.md). ## Conventions ### Structure - One main `Makefile` at the project root - Modular makefiles in `mk/` for each concern domain - Domains with subcommands get `mk//Makefile` - `.DEFAULT_GOAL := help` so bare `make` shows help - Never use `%: @:` catch-all patterns. Use conditional explicit subcommand declarations instead (see Subcommand Delegation below). ### Naming - Targets: lowercase, hyphenated for multi-word - Subcommands: space separation (`make test sh`) - Variables: UPPER_CASE ### Help system - `make` or `make help` shows all commands grouped by concern - `make ` shows domain-specific help - When a domain has 2 or more subcommands, bare `make ` must show subcommand help. Do not make it execute a default action. ### Delegation Makefiles are the interface, not the implementation: ```makefile deploy: @./scripts/deploy.sh "$(ENV)" ``` ### Subcommand delegation When a domain uses `$(MAKE) -C` for space-separated subcommands (`make db migrate`), Make sees `migrate` as a separate goal and errors. Solve this with conditional explicit targets — not a catch-all: ```makefile .PHONY: db db: @$(MAKE) -C mk/db $(filter-out $@,$(MAKECMDGOALS)) # Only activate when 'db' is on the command line ifneq ($(filter db,$(MAKECMDGOALS)),) .PHONY: migrate rollback migrate rollback: @: endif ``` This ensures: - `make db migrate` — works (migrate is a known no-op) - `make db typo` — errors (typo has no rule) - `make typo` — errors (conditional is inactive) Never use `%: @:` — it swallows all unknown targets silently, making typos invisible. ## Example Scenario User: "Add a make target for running database migrations" 1. Reads existing Makefile — finds `mk/` structure 2. No existing `db.mk` — creates `mk/db.mk` and `mk/db/Makefile` with `migrate`, `rollback`, `status` 3. Adds `include mk/db.mk` to main Makefile 4. Adds Database section to `make help` output 5. Verifies: `make db migrate` works ## Common Failures - **No help text** — targets without descriptions are undiscoverable. Always update `make help`. - **Logic in Makefiles** — complex bash in a target is hard to debug. Delegate to scripts. - **Catch-all swallows errors** — `%: @:` silently succeeds for ANY unknown target, including typos within domains (`make skills typo`). Never use a blanket catch-all. Instead, declare valid subcommands explicitly inside conditional blocks.