The syntax comes in the form:
target: prereq
command
command
commandIn 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 ccCXX- C++ compiler, default g++*FLAGS- flags for C/C++ compilerCPPFLAGS- counterintuitively, extra flags for C preprocessorLDFLAGS- 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)