Make is a build system. Other build systems are
- scons
- cmake
- bazel
- ninja
- rake
- ant
- maven
hello:
echo "Hello, World"
targets: prerequisites
command;
command;
Make clean
it is not a special word, just good convention
some_file:
touch some_file
clean:
rm -f some_file
Variables
can only be strings
files := file1 file2
some_file: $(files)
touch some_file
file1:
touch file1
file2:
touch file2
clean:
rm -f file1 file2 some_file
don’t use quotes when initialising variables
reference using either $(variable)
or ${variable}
Make all
The topmost target is run by default if no target specified
So make this the all target
all: target1 target2
target1:
target2:
Automatic Variables
$@
: target name
$?
: all prerequisites newer than the target
$^
: all prerequisites
$<
: first prerequisite
f1.0 f2.0:
echo $@ #name of target
# equivalent to
f1.0:
echo f1.0
f2.0:
echo f2.0
Wildcards
wrap wildcards in $(wildcard *.c)
Implicit Rules
Make loves c compilation. And every time it expresses its love, things get confusing. Perhaps the most confusing part of Make is the magic/automatic rules that are made. Make calls these “implicit” rules. I don’t personally agree with this design decision, and I don’t recommend using them, but they’re often used and are thus useful to know. Here’s a list of implicit rules:
- Compiling a C program:
n.o
is made automatically fromn.c
with a command of the form$(CC) -c $(CPPFLAGS) $(CFLAGS) $^ -o $@
- Compiling a C++ program:
n.o
is made automatically fromn.cc
orn.cpp
with a command of the form$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $^ -o $@
- Linking a single object file:
n
is made automatically fromn.o
by running the command$(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@
The important variables used by implicit rules are:
CC
: Program for compiling C programs; defaultcc
CXX
: Program for compiling C++ programs; defaultg++
CFLAGS
: Extra flags to give to the C compilerCXXFLAGS
: Extra flags to give to the C++ compilerCPPFLAGS
: Extra flags to give to the C preprocessorLDFLAGS
: Extra flags to give to compilers when they are supposed to invoke the linker
Commands and execution
Command Echoing/Silencing
Add an @
before a command to stop it from being printed
You can also run make with -s
to add an @
before each line
all:
@echo "This make line will not be printed"
echo "But this will"
Command Execution
Each command is run in a new shell (or at least the effect is as such)
all:
cd ..
# The cd above does not affect this line, because each command is effectively run in a new shell
echo `pwd`
# This cd command affects the next because they are on the same line
cd ..;echo `pwd`
# Same as above
cd ..; \
echo `pwd`
Default Shell
The default shell is /bin/sh
. You can change this by changing the variable SHELL:
SHELL=/bin/bash
cool:
echo "Hello from bash"
Double dollar sign
If you want a string to have a dollar sign, you can use $$
. This is how to use a shell variable in bash
or sh
.
Note the differences between Makefile variables and Shell variables in this next example.
make_var = I am a make variable
all:
# Same as running "sh_var='I am a shell variable'; echo $sh_var" in the shell
sh_var='I am a shell variable'; echo $$sh_var
# Same as running "echo I am a make variable" in the shell
echo $(make_var)
Error handling with -k
, -i
, and -
Add -k
when running make to continue running even in the face of errors. Helpful if you want to see all the errors of Make at once.
Add a -
before a command to suppress the error
Add -i
to make to have this happen for every command.
one:
# This error will be printed but ignored, and make will continue to run
-false
touch one
Interrupting or killing make
Note only: If you ctrl+c
make, it will delete the newer targets it just made.
Recursive use of make
To recursively call a makefile, use the special $(MAKE)
instead of make
because it will pass the make flags for you and won’t itself be affected by them.
new_contents = "hello:\n\ttouch inside_file"
all:
mkdir -p subdir
printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
cd subdir && $(MAKE)
clean:
rm -rf subdir
Export, environments, and recursive make
When Make starts, it automatically creates Make variables out of all the environment variables that are set when it’s executed.
# Run this with "export shell_env_var='I am an environment variable'; make"
all:
# Print out the Shell variable
echo $$shell_env_var
# Print out the Make variable
echo $(shell_env_var)
The export
directive takes a variable and sets it the environment for all shell commands in all the recipes:
shell_env_var=Shell env var, created inside of Make
export shell_env_var
all:
echo $(shell_env_var)
echo $$shell_env_var
As such, when you run the make
command inside of make, you can use the export
directive to make it accessible to sub-make commands. In this example, cooly
is exported such that the makefile in subdir can use it.
new_contents = "hello:\n\techo \$$(cooly)"
all:
mkdir -p subdir
printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
@echo "---MAKEFILE CONTENTS---"
@cd subdir && cat makefile
@echo "---END MAKEFILE CONTENTS---"
cd subdir && $(MAKE)
# Note that variables and exports. They are set/affected globally.
cooly = "The subdirectory can see me!"
export cooly
# This would nullify the line above: unexport cooly
clean:
rm -rf subdir
You need to export variables to have them run in the shell as well.
one=this will only work locally
export two=we can run subcommands with this
all:
@echo $(one)
@echo $$one
@echo $(two)
@echo $$two
.EXPORT_ALL_VARIABLES
exports all variables for you.
.EXPORT_ALL_VARIABLES:
new_contents = "hello:\n\techo \$$(cooly)"
cooly = "The subdirectory can see me!"
# This would nullify the line above: unexport cooly
all:
mkdir -p subdir
printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
@echo "---MAKEFILE CONTENTS---"
@cd subdir && cat makefile
@echo "---END MAKEFILE CONTENTS---"
cd subdir && $(MAKE)
clean:
rm -rf subdir
Arguments to make
There’s a nice list of options that can be run from make. Check out --dry-run
, --touch
, --old-file
.
You can have multiple targets to make, i.e. make clean run test
runs the clean
goal, then run
, and then test
.
SHELL := /bin/bash
CC := gcc
CFLAGS := -ggdb -Wall -O3
INSTALL := sudo pacman -S
SRC_DIR := /home/subzcuber/
BUILD_DIR :=
all:
# compile the entire program
install:
# compile program, and copy the executables etc where they should reside. run a simple test
uninstall:
# delete all the files `install` would create
clean:
# delete all files from the current dir that are normally created by building the program
dist:
# create tarball for the distribution
check:
# run tests
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)