Make Cheatsheet
Functions
Use this Make reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Calling a function
Functions are $(name arg1,arg2). The name and the first argument are separated
by whitespace, later arguments by commas, and leading whitespace in an argument
is stripped while trailing whitespace is not. There is no way to escape a comma
inside an argument, so define COMMA := , and pass $(COMMA).
COMMA := , SPACE := $() $() $(info $(subst $(SPACE),$(COMMA),a b c)) # a,b,c
Everything make manipulates is a whitespace-separated word list. There are no types, no arrays, and no numbers.
Text and word functions
Values below are real output from GNU make 4.4 with
FILES = src/net/tcp.c src/main.c include/api.h.
| Call | Result |
|---|---|
$(subst src/,build/,$(FILES)) | build/net/tcp.c build/main.c include/api.h |
$(patsubst %.c,%.o,$(FILES)) | src/net/tcp.o src/main.o include/api.h |
$(FILES:%.c=%.o) | same, shorthand form |
$(FILES:.c=.o) | suffix-only shorthand |
$(strip a b ) | a b |
$(findstring net,$(FILES)) | net, or empty |
$(filter %.c,$(FILES)) | src/net/tcp.c src/main.c |
$(filter-out %.h,$(FILES)) | src/net/tcp.c src/main.c |
$(sort c a b a) | a b c, sorted and deduplicated |
$(words $(FILES)) | 3 |
$(word 2,$(FILES)) | src/main.c, 1-indexed |
$(wordlist 1,2,$(FILES)) | src/net/tcp.c src/main.c |
$(firstword $(FILES)) | src/net/tcp.c |
$(lastword $(FILES)) | include/api.h |
$(sort) deduplicating is the trick people forget. $(sort $(DIRS)) is the
idiomatic unique-ify, and it is also why sorted output is not always the order
you wrote.
Filename functions
| Call | Result |
|---|---|
$(dir src/net/tcp.c) | src/net/, trailing slash kept |
$(notdir src/net/tcp.c) | tcp.c |
$(basename src/main.c) | src/main, strips the suffix, keeps the path |
$(suffix src/main.c) | .c |
$(addprefix -I,include src) | -Iinclude -Isrc |
$(addsuffix .o,main util) | main.o util.o |
$(join a b,1 2) | a1 b2, pairwise |
$(wildcard src/*.c) | expands the glob, empty if nothing matches |
$(abspath ./f.mk) | /path/to/f.mk, no symlink resolution |
$(realpath ./f.mk) | resolves symlinks, empty if the file is missing |
$(wildcard) hits the filesystem when it is expanded, so with := it sees only
files that existed when make started. A file created by an earlier recipe will
not appear.
SRC := $(wildcard src/*.c) $(wildcard src/*/*.c) # not recursive SRC := $(shell find src -name '*.c') # recursive, one fork
Control-flow functions
| Call | Meaning |
|---|---|
$(if COND,then,else) | COND is true when it expands to a non-empty string |
$(or a,b,c) | first non-empty argument |
$(and a,b,c) | last argument, or empty if any is empty |
$(foreach v,LIST,BODY) | expand BODY once per word, results joined by spaces |
$(call fn,a,b) | expand a variable as a function, args as $(1), $(2) |
$(eval TEXT) | parse TEXT as makefile syntax, at expansion time |
$(value V) | the unexpanded text of V |
$(origin V) | undefined, default, environment, file, command line, override, automatic |
$(flavor V) | undefined, recursive, or simple |
$(let a b,1 2,BODY) | GNU 4.4, bind local names |
$(intcmp a,b,lt,eq,gt) | GNU 4.4, integer comparison |
$(if) tests emptiness, not truth. $(if 0,yes,no) is yes, because 0 is a
non-empty string. Wrap in $(strip ...) when the value may be spaces.
$(info $(if $(strip $(DEBUG)),debug,release))
$(info $(foreach d,include vendor/include,-I$(d))) # -Iinclude -Ivendor/includecall and eval, generating rules
$(call) turns a variable into a function. $(eval) feeds text back to the
parser, which is how you generate rules in a loop.
reverse = $(2) $(1) $(info $(call reverse,a,b)) # b a define compile $(1).o: $(1).c @echo " CC $$@ from $$<" endef $(eval $(call compile,main))
The $$ inside a define destined for eval is required. The text is expanded
once by call, so $$@ becomes $@ and only then reaches the rule parser.
Recipe lines inside a define still need a real leading tab.
MODULES := auth billing search $(foreach m,$(MODULES),$(eval $(call compile,$(m))))
Debug generated rules by printing instead of evaluating:
$(info $(call compile,main))
Shell, file, and diagnostics
| Call | Behavior |
|---|---|
$(shell cmd) | runs cmd, newlines collapse to spaces, trailing newline stripped |
.SHELLSTATUS | exit status of the last $(shell), GNU 4.2+ |
$(file <in.txt) | read a file, GNU 4.2+ |
$(file > out.txt,text) | write, >> appends, sidesteps the ARG_MAX limit |
$(info msg) | print to stdout, expands to nothing |
$(warning msg) | print to stderr with file:line: prefix |
$(error msg) | print to stderr and stop with exit status 2 |
$(guile ...) | only if make was built with Guile, rarely available |
BRANCH := $(shell git rev-parse --abbrev-ref HEAD) ifneq ($(.SHELLSTATUS),0) $(error not a git repository) endif # response file, for a link line too long for argv big: $(OBJ) $(file > $@.rsp,-o $@ $(OBJ)) $(CC) @$@.rsp
$(shell) runs during expansion, so a $(shell) in a recursive = variable
forks once per reference. Assign it with := unless you specifically want it
re-run.
The one-liners worth memorizing
# object list from sources, mirroring the tree into build/ SRC := $(shell find src -name '*.c') OBJ := $(SRC:src/%.c=build/%.o) # every unique directory the objects need to exist DIRS := $(sort $(dir $(OBJ))) # include flags from a directory list CPPFLAGS += $(addprefix -I,include vendor/include) # require a variable, with a useful message check-var = $(if $(strip $($1)),,$(error $1 is required)) release: ; $(call check-var,VERSION) # a value with a default that the command line can still override PREFIX ?= /usr/local # true when a program is on PATH HAVE_JQ := $(if $(shell command -v jq),yes,no)