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
- π―
targetis the file that the command will generate (ex:a.out). - πͺ΄
depsis a list of targets, that need to be compiled first - π΄
compilation_commandis 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
.ois associated with their.c - π Usually, each
.ois associated with their.h - π A
.ocan be associated with the.hthey use - ποΈ A
.oshould be associated with the.omatching the.hthey use - β We don't usually see a
.oassociated 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): replacefromwithtoininput_str$(eval XXX += yyy): you can execute code inside eval$(foreach var,list,op): executeopon 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