The Makefile for a one-binary C project, explained piece by piece.
CC = gcc
CFLAGS = -std=gnu23 -Og -g3 -Wall -Werror -Wextra -Wwrite-strings
OUT = out
BIN = $(OUT)/tinybox
all: $(BIN)
# Any out/<name> from <name>.c, e.g. `make out/scratch`
$(OUT)/%: %.c Makefile | $(OUT)
$(CC) $(CFLAGS) -o $@ $<
$(OUT):
mkdir -p $@
tinybox: $(BIN)
scratch: $(OUT)/scratch
run: $(BIN)
./$(BIN)
clean:
rm -rf $(OUT)
.PHONY: all run clean tinybox scratch
Rules
target: prerequisites
recipe
- make rebuilds
targetif it doesn’t exist or any prerequisite is newer (by file modification time). That’s the whole algorithm. - The recipe lines must start with a tab. Spaces give
*** missing separator. Stop. - A rule with no recipe (
tinybox: $(BIN)) just says “to make this, make that”.
Variables
CC, CFLAGS are conventional names (make’s built-in rules use them too).
Use with $(NAME). Override from the command line: make CFLAGS=-O2.
Automatic variables
Inside a recipe:
$@: the target (out/tinybox)$<: the first prerequisite (tinybox.c)$^: all prerequisites
The default target, and why it’s called all
Plain make builds the first target in the file, whatever its name.
Calling it all is a convention (GNU Coding Standards), not a requirement.
Pattern rules
$(OUT)/%: %.c means “out/<anything> is built from <anything>.c”. One
rule covers out/tinybox, out/scratch, and any future file. Some IDEs
(e.g. CLion) only list named targets, which is why tinybox: and scratch:
exist as aliases.
The Makefile as a prerequisite
If only tinybox.c is listed, changing CFLAGS doesn’t trigger a rebuild:
make says Nothing to be done and your new warning flag silently does
nothing. Listing Makefile fixes that. ($< still picks tinybox.c,
since it’s first.)
Order-only prerequisites: | $(OUT)
Everything after | must exist before the recipe runs, but its timestamp
is ignored. That matters for directories: a directory’s mtime changes
whenever a file inside it is added or removed, so as a normal prerequisite it
would make targets look out of date constantly.
.PHONY
Marks targets that are names, not files. Without it:
- A file called
cleanin the directory would makemake cleanthink it’s up to date. - make goes looking for built-in rules. It has one for
foofromfoo.c, so without.PHONY,make tinyboxwould buildout/tinyboxand then rungcc $(CFLAGS) tinybox.c out/tinybox -o tinybox, building a stray./tinyboxand feeding the binary in as an extra input. Phony targets skip the built-in rule search.
(Fun fact: with no Makefile at all, make scratch works: the built-in rule
runs cc scratch.c -o scratch.)
Handy flags
make -n: dry run, print the commands without running themmake -B: rebuild everything regardless of timestampsmake -p: dump the database, including all built-in rules
References: info make (the GNU make manual), especially “Rule Syntax”,
“Pattern Rules”, “Automatic Variables”, “Phony Targets”, “Prerequisite Types”.