The syntax comes in the form:

target: prereq
	command
	command
	command

In makefile, target and files are very related - targets usually make a file of that name.

Makefiles use timestamps in order to resolve prerequisites. If the prereq timestamp is newer than target, then target will be retriggered

Makefiles prioritize more specific matches. For example:

all.c:
	command
	
%.c:
	command # will not match all.c however

Each line is run in a “new shell”
Shell is /bin/sh default. You can set it by changing the variable SHELL
$$ for single dollar sign

use the special $(MAKE) to recursively call make (passes flags for you)
.PHONY to have make stop confusing it with files

Special Rules

Static Pattern Rules

There are also static pattern rules, in this syntax:

targets... (space seperated: target-pattern: prereq-patterns...)
	commands
objects = foo.o bar.o all.o
all: $(objects)
	$(CC) $^ -o all

# Syntax - targets ...: target-pattern: prereq-patterns ...
# In the case of the first target, foo.o, the target-pattern matches foo.o and sets the "stem" to be "foo".
# It then replaces the '%' in prereq-patterns with that stem
$(objects): %.o: %.c
	$(CC) -c $^ -o $@

Pattern Rules

Pattern rules in the following syntax:

Target pattern: preqreq-pattern

Double Colon

Double-Colon rules allow multiple rules for same target with no syntax. (else second set would run)

blah::
	echo "hello 1"
	
blah::
	echo "hello 2"

Special Targets

These targets are not intended to be files, but rather “special” ones that serve a purpose

clean - usually cleans out the build folder, intermediate build files, etc
all - builds everything necessary

Implicit Targets / Rules

n.o is made from n.c using $(CC) -c $(CPPFLAGS) $(CFLAGS) $^ -o $@
n.o is made from n.cc or n.cpp with the command $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $^ -o $@
n is made by linking n.o by running $(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@

Notice how they use the following variables:

  • CC - C compiler, default cc
  • CXX - C++ compiler, default g++
  • *FLAGS - flags for C/C++ compiler
  • CPPFLAGS - counterintuitively, extra flags for C preprocessor
  • LDFLAGS - linker flags

Keywords/Syntax

@ silences a command
- suppresses an error

Variables

Assign variables like

first = smth
second := smth

there is a difference between = and :=.
= is recursive, so it looks for variables when command is used vs defined.
:= is simply expanded, only those defined get expanded
?= sets variables if they have not ye been set
+= to append

Quotes have no effect, but you might need them for shell commands

Reference variables using $(var) or $v for single letter

Automatic variables

@ - A special variable that refers to the target name. Useful when you have multiple targets and you want to refer to each one

? - Prereqs newer than target

^ - All prereqs

< - First prereq

* - A wildcard. Can be used in targets, prereqs or in the wildcard function. Searches filesystem for matching filenames. Recommended to use in wildcard function since it doesn’t get expanded in variable defs. WARNING: it’s left AS-IS if it doesnt match

% - Bit of a confusing match, come back t it later
Case 1: Matching mode. Matches one or more characters in a string (“stem”)
Case 2: Replacing mode. Takes the stem that was matched and replaces it

Functions

filter function: $(filter PATTERN..., TEXT)

$(patsubst pattern,replacement,text)

  • Find whitespace separated word in a text that match the pattern and replace them with replacement. % can be used as wildcard and use as stem
  • Shorthand: $(text:pattern=replacement)
  • $(text:suffix=replacement) for suffixes

Juicy Example (Template)

# Thanks to Job Vranish (https://spin.atomicobject.com/2016/08/26/makefile-c-projects/)
TARGET_EXEC := final_program
 
BUILD_DIR := ./build
SRC_DIRS := ./src
 
# Find all the C and C++ files we want to compile
# Note the single quotes around the * expressions. The shell will incorrectly expand these otherwise, but we want to send the * directly to the find command.
SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s')
 
# Prepends BUILD_DIR and appends .o to every src file
# As an example, ./your_dir/hello.cpp turns into ./build/./your_dir/hello.cpp.o
OBJS := $(SRCS:%=$(BUILD_DIR)/%.o)
 
# String substitution (suffix version without %).
# As an example, ./build/hello.cpp.o turns into ./build/hello.cpp.d
DEPS := $(OBJS:.o=.d)
 
# Every folder in ./src will need to be passed to GCC so that it can find header files
INC_DIRS := $(shell find $(SRC_DIRS) -type d)
# Add a prefix to INC_DIRS. So moduleA would become -ImoduleA. GCC understands this -I flag
INC_FLAGS := $(addprefix -I,$(INC_DIRS))
 
# The -MMD and -MP flags together generate Makefiles for us!
# These files will have .d instead of .o as the output.
CPPFLAGS := $(INC_FLAGS) -MMD -MP
 
# The final build step.
$(BUILD_DIR)/$(TARGET_EXEC): $(OBJS)
	$(CXX) $(OBJS) -o $@ $(LDFLAGS)
 
# Build step for C source
$(BUILD_DIR)/%.c.o: %.c
	mkdir -p $(dir $@)
	$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
 
# Build step for C++ source
$(BUILD_DIR)/%.cpp.o: %.cpp
	mkdir -p $(dir $@)
	$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
 
 
.PHONY: clean
clean:
	rm -r $(BUILD_DIR)
 
# Include the .d makefiles. The - at the front suppresses the errors of missing
# Makefiles. Initially, all the .d files will be missing, and we don't want those
# errors to show up.
-include $(DEPS)