Every project accumulates a set of incantations: how you run the tests, how you build the image, how you lint, how you start the thing locally. These usually live in a README under a heading nobody reads, or in someone's shell history, or worst of all in their head. I've taken to putting them in a Makefile instead, even when there's not a line of C anywhere in sight.
The point isn't compilation. The point is that make is a discoverable, near-universal verb. A new person clones the repo, types make, and gets a list. No "which task runner did this project pick" guessing game, no fifty-line npm scripts block, just targets with names.
.PHONY: test lint run image
test: ; pytest -q
lint: ; ruff check . && black --check .
run: ; uvicorn app:main --reload
image: ; docker build -t myapp:dev .
That's it. No $(CC), no pattern rules, no header dependencies. Just named shortcuts to commands I'd otherwise forget. The .PHONY line matters because none of these produce a file called test, and without it make will get confused the day someone adds a test/ directory.
Yes, there are purpose-built task runners that do this with nicer syntax. But they're one more thing to install, and make is already on every machine I touch. It's not elegant. It's the boring tool that's already there, doing a small job well, and being the front door so nobody has to read my shell history to run the tests.