notes on writing good makefiles
this is basic, but always have an all, clean, and install target. it's the standard. don't give them stupid names.
in your install target, use
PREFIX and DESTDIR
variables, so staged installs can be done.
don't use GNU or BSD extensions, ever. if you need to use one, it's a project structure issue. restructure your project. if you REALLY need to use one, you shouldn't be using make. switch to meson.
don't do that thing where you silence the compile commands and add an “echo CC target” command. it makes it harder to see what the build is doing.
if your makefile is posix compliant, which it should be, declare .POSIX: at the top of the makefile. optionally, you can add a comment noting which posix standard you aim to comply to. all of them are quite featureless except posix 2024, so i recommend that.
all targets that don't produce an output of the same name should be marked .PHONY. this is things like all, clean, install, format, etc.
stop using ?= the whole time. most of the time = does what you think ?= does. it gets the job done.
suffix rules are ok. put your source files at the project root, so you can use a suffix rule. if you put them in folders, you will be tempted to use a pattern rule, which is a GNU extension. this is silly, when you could just use a suffix rule.
don't do autogenerate dependencies with
-MMD and an -include directive.
it's flaky and ugly and puts a bunch of junk .d
files everywhere. if you have explicit rules for every target, you should
make the effort to declare the header dependencies manually. if you use a
suffix rule, i have
a
script that uses the compiler's -MMD flag to
autogenerate a full static dependency list. you can use it like
ls *.c | deps >> Makefile
sometimes you will think you need a GNU extension to do something simple. you actually don't. if you want to use GNU $(shell ...) , != does the same thing and is posix 2024 compliant. if you want to use GNU $(wildcard ...) , you can combine != with a shell glob like ls *.c.
if you want to use the GNU .ONESHELL special target, you can just use && and backslash continuations between lines.
if you use pkg-config in your makefile, add a variable like PKG_CONFIG = pkg-config, so i can set it to something else, or add --static to it.
don't use := . the behaviour is ambiguous between GNU, BSD and solaris makes, and is not standardized by posix. use ::= for GNU style behaviour or :::= for BSD style behaviour.
set the CC variable to
cc.
c99 /
c17, which
is the posix default, has different flags on different OSes, which can
sometimes cause problems.
don't set CC to
gcc, or any other
specific compiler for that matter, as the user might not have it. when you
assume, you make an ass of you AND me.
it's ok to use -include and include, but not sinclude. it's ok to include multiple files in one directive, but it's not ok to use a glob expansion to include multiple files at once.
always declare dependencies. i often see people just completely neglect to declare file level dependencies and just treat make like a task runner. if you don't declare your dependencies, there's not much point using make. you should even declare dependencies for files that you don't touch, like vendored or generated files. one day it will save you a big headache on an incremental build.
if you gitignore your objects and declare your dependencies properly, you should never in your entire life have to run make clean again.
refrain from doing complicated shell logic in the makefile. it's ugly and often not needed.
don't ever use recursive make. it's never the right thing to do. if you want to split your makefile into multiple files, you can use include. recursive make can cause a lot of issues, because the parent make knows nothing about the child make's dependency graph, so incremental rebuilds are hard.