Most Makefiles in modern projects are task runners, and .PHONY is what makes them reliable.
Targets such as clean, test or run don't produce a file named after themselves. That works by accident until someone creates a file or directory called test: make sees it exists with no prerequisites, decides it is up to date and silently does nothing.
Declare such targets phony:
.PHONY: all clean testA phony target is always considered out of date, so its recipe always runs, and make doesn't look for a file with that name.
Conventions worth following:
allis the first target and builds everything;cleanremoves generated files;test,lint,installdo what they say.- A phony target can depend on other targets:
check: lint testruns both. - Use
rm -fincleanso it doesn't fail when files are already gone.
As a task runner, make gives every project the same entry points (make test, make run), handles dependencies between tasks, and is installed almost everywhere. A self-documenting help target is a common touch: put ## description after each target and print them with a little grep and awk.
Set .DEFAULT_GOAL := help if you'd rather show help than build when someone types just make.
Example
.PHONY: help test lint check clean
.DEFAULT_GOAL := help
help: ## show this help
@grep -E '^[a-z]+:.*## ' $(MAKEFILE_LIST) | awk -F':.*## ' '{printf "%-8s %s\n", $$1, $$2}'
test: ## run the tests
python3 -m pytest -q
lint: ## check style
ruff check .
check: lint test ## lint, then test
clean: ## remove build output
rm -rf build dist .pytest_cache
# $ make
# help show this help
# test run the tests
# ...Remember
- Mark non-file targets with .PHONY so they always run
- Conventional targets: all, clean, test, install
- A phony target can just depend on others, like check: lint test
Watch out for
- Forgetting .PHONY and a directory named test or build makes the target silently do nothing.