Makefile Way¶
Source: hooks/ways/softwaredev/environment/makefile/makefile.md
Frontmatter
| Field | Value |
|---|---|
description |
Makefile as the standard project task runner — build, test, lint, format, docs, release, and custom project commands |
vocabulary |
makefile make target build lint linter test format clean install publish release dist docs help phony check adr npm cargo pip docker repo artifacts dependencies quality ci runner |
commands |
\bmake\b |
files |
Makefile$|makefile$|GNUmakefile$|.mk$ |
refire |
0.1 |
macro |
append |
scope |
agent, subagent |
requires |
['Bash(awk:)', 'Bash(make:)', 'Bash(sort:)', 'Bash(tr:)'] |
A Makefile is the project's task runner. It's the answer to "how do I build/test/lint this repo?" regardless of language or toolchain.
Why Make¶
- Zero dependencies — installed on every Unix system, available on Windows via WSL/MSYS
- Discoverable —
make helplists what's available; reading the Makefile shows how - Composable — targets chain together; CI and humans run the same commands
- Language-agnostic — wraps npm, cargo, pip, go, or anything else
When to Use¶
- New repo: Scaffold a Makefile early — it's the project's control panel
- Existing repo with no Makefile: Propose one when you see scattered build/test commands
- Existing Makefile: Use it. Run
make helpor read targets before inventing ad-hoc commands
Standard Targets¶
Prefer these conventional names. Not every project needs all of them.
| Target | Purpose | Example |
|---|---|---|
help |
List available targets with descriptions | (self-documenting, see below) |
install |
Install dependencies | npm ci, pip install -r requirements.txt |
build |
Compile / bundle | cargo build, npm run build |
test |
Run test suite | pytest, npm test, go test ./... |
lint |
Run linters | eslint ., ruff check ., golangci-lint run |
format |
Auto-format code | prettier --write ., ruff format . |
clean |
Remove build artifacts | rm -rf dist/ build/ node_modules/ |
docs |
Generate documentation | mkdocs build, typedoc |
release |
Tag + publish a release | npm publish, cargo publish |
dist |
Create distributable artifacts | tar, docker build |
adr |
ADR management (if project uses ADRs) | docs/scripts/adr $(CMD) |
check |
Run all quality gates (lint + test) | Combines lint and test targets |
Self-Documenting Help¶
Use this pattern — targets document themselves via ## comments:
.DEFAULT_GOAL := help
help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'
Then every target gets a description:
Running make help produces:
Writing Targets¶
.PHONY: test lint format check clean
# Use .PHONY for targets that don't produce files
# Use tabs (not spaces) for recipe indentation
# Use @ prefix to suppress command echo when the output is self-evident
test: ## Run test suite
pytest -x --tb=short
lint: ## Run linters
ruff check .
format: ## Auto-format code
ruff format .
check: lint test ## Run all quality gates
clean: ## Remove build artifacts
rm -rf dist/ build/ *.egg-info
Composing with Project Tools¶
Make wraps the project's actual tools — it doesn't replace them:
# ADR integration
adr: ## ADR management (usage: make adr CMD="new core title")
docs/scripts/adr $(CMD)
# Docker
up: ## Start services
docker compose up -d
down: ## Stop services
docker compose down
# Multi-language: each target calls the right tool
test-backend: ## Run backend tests
cd backend && cargo test
test-frontend: ## Run frontend tests
cd frontend && npm test
test: test-backend test-frontend ## Run all tests
Guidelines¶
- Read before writing: If a Makefile exists, run
make helpor read it before adding targets - Don't duplicate: If
npm testworks,make testshould callnpm test - Keep recipes short: A target should be 1-3 commands. Complex logic belongs in a script that Make calls
- Use variables for tunables: Versions, paths, flags — put them at the top of the Makefile
- CI parity: CI should run
make check(ormake lint && make test), so the commands live in one place