Makefile
Makefiles are files in which developers define rules βοΈ to compile their sources, build an executable, and run it.
- π You define the dependencies between source files
- π Only the strict minimum is recompiled
- π‘ Users write simple commands such as
make build
(no "gcc [...]") - π Commonly used to build projects on Linux
Makefiles are mainly used in C/C++, while they can be used for any language. Modern projects usually use CMake (can generate a Makefile).
π Official documentation (GNU)
Create an empty Makefile π±
$ touch Makefile # alt: "makefile" "GNUmakefile"
$ nano Makefile # edit
Execute a rule π΄
# in the directory with the Makefile
$ make # run the first rule
$ make rule_name # run a specific rule
Getting started
Breakdown of a rule π‘
target: deps
compilation_command
- π―
target
is the file that the command will generate (ex:a.out
). - πͺ΄
deps
is a list of targets, that need to be compiled first - π΄
compilation_command
is a command that uses the dependencies to build the target.
Example π₯
We got a file main.c
and we want to generate an executable a.out
. We will first define the rule for the executable:
# β bad
a.out: main.c
gcc -o a.out main.c
We can compile and run our program.
But this is not the correct approach. β οΈ
If main.c
had many dependencies, then all will be listed as dependencies of a.out
. Every time we ask make
to compile the latest a.out
, every dependency will be checked instead of one.
Instead, we usually use intermediate files, such as .o
in C.
# β
correct
a.out: main.o
gcc -o a.out main.o
We don't need to specify how a .o
is compiled, as this is one of the compilation rules π that Makefile knows. Still, you can do it:
main.o: main.c
gcc -c main.c -o main.o
For other languages or in some cases, you may have to define your own compilation rules. For instance, in C, we would have:
# define how, from any .c, we can get the .o
%.o: %.c
gcc -c -o $@ $<
The final makefile content will be something like this:
a.out: main.o
gcc -o a.out main.o
%.o: %.c
gcc -c -o $@ $<
Dependencies
Makefile dependencies are files that when changed, imply that we need to recompile our target.
In C, we could describe dependencies like this:
- π± Usually, each
.o
is associated with their.c
- π Usually, each
.o
is associated with their.h
- π A
.o
can be associated with the.h
they use - ποΈ A
.o
should be associated with the.o
matching the.h
they use - β We don't usually see a
.o
associated with another.c
Because of the existing compilation rules, some dependencies are implicit and can be omitted. Giving us:
main.o: file1.o file2.o
file1.o: file1.h
file2.o: # none
Makefile Basics
Variables
We usually start a Makefile by defining some variables, to avoid copy-pasting code and ease the maintenance of our Makefile π.
Some quite used names for variables are CC
and CFLAGS
.
CC=gcc # C Compiler
CFLAGS=-Wall # C Compiler flags
CXX=g++ # C++ Compiler
CXXFLAGS=-std=c++11 # C++ Compiler flags
To use them in some rules:
my_program: deps
$(CC) $(CFLAGS) -o my_program ...
β οΈ Variables such as CC
are known to makefile pre-defined rules, so you don't need to write your own rule to set some compilation flags.
Multilines
You can use \
for multiline instructions:
O_FILES = file1.o file2.o \
main.o
PHONY rules
Makefile only runs a task if the output or its dependencies have been updated. Tasks marked as .PHONY
are always executed.
.PHONY: clean xxx yyy zzz
# 'make clean'
clean:
rm -rf a.out *.o
# ...
Advanced Makefiles
Assignment Operators
There are multiple operators to assign variables:
-
=
: assign a value to a variable -
:=
: immediately compute the value of the variable -
::=
: only compute the value when the variable is actually used -
?=
: assign if the variable isn't defined -
+=
: append our value to the variable -
!=
: execute a command and store the output in the variable
β‘οΈ We usually prefer always using :=
rather than =
.
External Build Folder
To add a build folder (or a source folder), simply use paths inside all targets. Note that the build folder must be created inside the makefile.
You can do that using prerequisite (|
) which are rules executed before evaluating any dependencies.
SRC_DIR := src
BUILD_DIR := build
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp | $(BUILD_DIR)
$(CXX) $(CXXFLAGS) -c -o $@ $<
# prerequisite: create the build directory
$(BUILD_DIR):
mkdir -p $@
Makefile utilities
Makefiles support many commands inside $()
:
-
$(wildcard *.cpp)
: find files matching the glob-pattern -
$(strip string)
: remove leading and trailing whitespaces -
$(shell command)
: execute a shell command -
$(findstring XXX,input_str)
: execute a shell command -
$(subst from,to,input_str)
: replacefrom
withto
ininput_str
-
$(eval XXX += yyy)
: you can execute code inside eval -
$(foreach var,list,op)
: executeop
on each entry oflist
-
$(addprefix prefix, list)
: prefix all elements withprefix
-
$(notdir list)
: generate a list of all filenames
A common usage is to automate source and object files detection:
# Source files and object files
SRCS := $(wildcard $(SRC_DIR)/*.cpp)
OBJS := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(SRCS))
Makefile if statements
You can use ifeq
to check if two values are equal (resp. ifneq
). You can use ifdef
to check if a variable is defined (resp. ifndef
).
ifeq (v1,v2)
# some code
endif
ifdef XXX
# some code
endif
Random notes
Define macros
You can define macros and call them wherever you want.
# note: for some variables such as $@, $<, etc.
# you need to add another "$": $$@, $$<, etc.
define my_function
@echo "Compiling $(2) into $(1)"
endef
my_rule:
$(call generate_rule, build/main.o, src/main.cpp)
Simple Text Substitution
You can use text:from=to
to substitute from
with to
in text
.
Include Another Makefile
You can create separate makefiles, for instance, if you have multiple compilers, you can create a file (often, a .mk
) with variables for each compiler, and conditionally import them.
include gcc.mk # include cannot fail
-include opt.mk # include can fail
Verbosity
By default, when we run a command from a makefile, it is echoed inside the terminal. We can use @
to disable this "echo".
my_target:
@echo "This is a command"
π» To-do π»
Stuff that I found, but never read/used yet.
$ automake --add-missing
$ autoreconf
$ automake
automake: error: 'configure.ac' is required
$ autoreconf
autoreconf: 'configure.ac' or 'configure.in' is required