Addressed to beginners and not to newcomers, the idea behind this tutorial is to focus on the essential. Anything that is not directly related to the template we are going to explore will not be covered here. On the other hand everything that is covered in this tutorial will be carefully detailed.
Initially intended to help 42 students to step up their Makefile skills through a C-focused documented template that evolves gradually, version by version. With the aim of making them more digestible and even tasty 🍔
Allow 1 hour to complete this tutorial, plus some additional time to work with the examples. For those of you who are wondering, as I write this line, I have invested a total of 102 hours and 50 minutes in creating this tutorial.
TL;DR Refer to the bold text.
→ GitHub Page ←
→ GitHub Repo ←
Roadmap
- GitHub Page
- A ready to make project of each template version
- bold text that acts as a summary
- v1 Simplest C project
- v2 Project that include headers
- v3 Project with any kind of directory structure
- v4 Static library + introduce auto-gen dependencies
- v5 Project that uses libraries
- POSIX Makefile
This tutorial is designed to be read line by line linearly at first.
Then it can be quickly navigated thanks to the:
- Brief of each version which is visible from the Index.
- Text in bold that compile the essence of this tutorial.
- Return to Index ↑ buttons at the end of each version.
Each version of the template has an assigned directory in the projects directory of the repository, to play with a Makefile open a terminal and run:
git clone https://github.com/clemedon/Makefile_tutor.git
cd Makefile_tutor/projects
cd <template_version>
make <target>
PS1 C++
users have to replace CC = clang
with CXX = g++
and CFLAGS
with CXXFLAGS
.
PS2 feel free to fork me so as to remove everything that does not interest you and customize me according to your needs.
Each version of the template has 3 sections:
- Structure is the project structure sketch.
- Brief is a summary made from the bold parts of the template comments.
- Template is a commented Makefile template with comments (that are always placed at the end of the template part that concerns them).
Our template will be articulated around the following parts:
### BEG
Mark the template beginning.INGREDIENTS
Build variables.UTENSILS
Shell commands.RECIPES
Build and extra rules.SPEC
Special targets.#### END
Mark the template end.
According to make a rule
is made of:
targets
are the names of the goals we want to make. It can be a file or an action, the latter leading to a pseudo target.prerequisites
are the goals that must be achieved so that therule
can execute. Thus prerequisites are the targets dependencies.recipe
is the set of lines that begins with aTAB
character and appear in a rule context.
target: prerequisite
recipe line 1
recipe line 2
…
Like every Makefile the template uses a combination of Makefile syntax and shell
script syntax. The shell script syntax is reserved and limited to recipe
lines, by default those lines have to start with a TAB
character to be
differentiated by make (and passed to the shell). The Makefile syntax is
used for all the other lines.
About the equal signs:
:=
simply expand the defined variable (like C=
).=
recursively expand the defined variable (the expression is expanded afterward, when (and each time) the variable is used).
A := Did $(C)
B = Did $(C)
C = you understand ?
all:
$(info $(A)) # output "Did"
$(info $(B)) # output "Did you understand ?"
About variables note that ${VAR}
and $(VAR)
is exactly the same.
Automatic Variables expansion:
$<
leftmost prerequisite$@
current target$^
all prerequisites$(@D)
directory part of the file name of the target$(@F)
file part of the file name of the target
Cf. Version 1 / Automatic variables in practice.
Version 1 / Simplest C project
- 42 C coding style conventions
- builtin variables
- The C compilation implicit rule
- pattern rule contains
%
character- Automatic variables in practice
- Illustration of a
make all
- C build recap
- parallelization enabled by
make --jobs
- the
.PHONY:
special target
Version 2 / Project that include headers
- preprocessor's flags
- print a custom message
- C compilation implicit rule is overwritten
- default goal
all
appears first.SILENT:
silences the rules output
Version 3 / Project with any kind of directory structure
- split the line with a
backslash
- substitution reference so
main.c
becomessrc/main.c
- generate the
OBJ_DIR
based onSRC_DIR
- compilation rule uses multiple source and object directories
- when a header file is modified the executable will rebuild
- automatically generate a list of dependencies
- build directory
- dependency files must be included
- hyphen symbol to prevent make from complaining
- creates a static library
Version 5 / Project that uses libraries
- system library linked by default
addprefix
make function- flags and libraries used by the linker
dir
function- Build with a library
- Linking with a library
- builds each of the required libraries
- call rules recursively
make
andrun
the default goal<target>
recipe without executing it- print the value of an arbitrary variable
- update the git repository
The simplest, build a program called icecream
with the following structure:
before build: after build:
. .
├── Makefile ├── Makefile
└── main.c ├── main.o
├── main.c
└── icecream
- 42 C coding style conventions
- builtin variables
- The C compilation implicit rule
- pattern rule contains
%
character - Automatic variables in practice
- Illustration of a
make all
- C build recap
- parallelization enabled by
make --jobs
- the
.PHONY:
special target
####################################### BEG_1 ####
NAME := icecream
#------------------------------------------------#
# INGREDIENTS #
#------------------------------------------------#
# SRCS source files
# OBJS object files
#
# CC compiler
# CFLAGS compiler flags
SRCS := main.c
OBJS := main.o
CC := clang
CFLAGS := -Wall -Wextra -Werror
#------------------------------------------------#
# UTENSILS #
#------------------------------------------------#
# RM force remove
# MAKEFLAGS make flags
RM := rm -f
MAKEFLAGS += --no-print-directory
#------------------------------------------------#
# RECIPES #
#------------------------------------------------#
# all default goal
# $(NAME) linking .o -> binary
# clean remove .o
# fclean remove .o + binary
# re remake default goal
all: $(NAME)
$(NAME): $(OBJS)
$(CC) $(OBJS) -o $(NAME)
clean:
$(RM) $(OBJS)
fclean: clean
$(RM) $(NAME)
re:
$(MAKE) fclean
$(MAKE) all
#------------------------------------------------#
# SPEC #
#------------------------------------------------#
.PHONY: clean fclean re
####################################### END_1 ####
- The choice of the
CC
andCFLAGS
values,NAME
,clean
,fclean
,all
andre
as the basic rules as well as not using a wildcard to auto generate the sources list is guided by the 42 C coding style conventions, do not hesitate to disagree and change it (like renamingclean
andfclean
to the more GNU conventionalmostlyclean
andclean
respectively).
MAKE
andMAKEFLAGS
are builtin variables likeCFLAGS
and a lot of others that you can find in the data-base (make --print-data-base
command).MAKE
value corresponds to themake
executable being run andMAKEFLAGS
to its flags. When a Makefile is executed from another Makefile, the called'sMAKE
MAKEFLAGS
variables inherit from the caller'sMAKE
MAKEFLAGS
values.
Here we append --no-print-directory
to MAKEFLAGS
content to have a clearer
output, try to remove it and make re
to see the difference.
- The C compilation implicit rule looks like this:
%.o: %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
Where %.o
expands to each objects, %.c
to each sources, $@
to the first
target (which is %.o
) and $<
to the leftmost prerequisite (which is %.c
).
As their name implies implicit rules are implicit and do not need to be
written. As well as the builtin variables, all the implicit rules can be found
in the data-base, accessible with make -p -f/dev/null | less
command.
- A pattern rule is a rule whose target contains a
%
character (here%.o: %.c
). This character means "exactly one of them". It is used here to say that each.o
requires a.c
with the same name and the$(CC)…
recipe line will execute as many times as there are.o: .c
pairs, thus creating for each source its corresponding object, one at a time.
- Automatic variables in practice:
# Linking step
$(NAME): $(OBJS)
$(CC) $^ -o $@
$@
expands to the current target, here we could have used $(NAME)
instead.
$^
expands to all prerequisites, here we could have used $(OBJS)
instead
but not $<
that expands to the leftmost prerequisite and so only the first
item found in $(OBJS)
.
# Compiling step
%.o: %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
$<
expands to the leftmost prerequisite, here it could not have been
replaced with $(SRCS)
because $(SRCS)
expands to all the sources at once
which is not the case of the pattern rule %.c
. On the other hand here $<
is
equivalent to $^
, both will always expand to one source: the current one
expanded by %.c
. For the same reasons $@
expands to %.o
not to $(OBJS)
.
- Illustration of a
make all
:
all: $(NAME) 3 ← 2
$(NAME): $(OBJS) 2 ← 1
$(CC) $(OBJS) -o $(NAME)
%.o: %.c 1 ← 0
$(CC) $(CFLAGS) -c -o $@ $<
The all
target requires icecream
that requires the objects
that require the
sources
that requires… a programmer. In other words all
creates icecream
with the objects created with the sources that you have created.
Make will first trace its path to the lower level where it finds a raw material
3 → 2 → 1 → 0
(the sources
) and then do it backward while building each
resource that is required by the direct upper level encountered 0 → 1 → 2 → 3
(the icecream
, our final goal).
- C build recap
%.o
target requires the sources to be compiled into objects, the-c
option tells the compiler to only compile without linking. The-o
option is used to specify the objects name. Afterward the$(NAME)
target requires the linking the objects into a binary file whose name is specified with the-o
flag.
- For the
re
rule we have no choice but make an external call to our Makefile because we should not rely on the order in which prerequisites are specified. For examplere: fclean all
wouldn't not be reliable if parallelization was enabled bymake --jobs
.
- The prerequisites given to the
.PHONY:
special target become targets that make will run regardless of whether a file with that name exists. In short these prerequisites are our targets that don't bear the name of a file.
Try to remove the .PHONY: re
, create a file named re
at the Makefile level
in the project directory and run make re
. It won't work.
Now if you do the same with all
it won't cause any problem, as we know
prerequisites are completed before their targets and all
has the sole action
of invoking $(NAME)
, as long as a rule doesn't have a recipe, .PHONY
is not
necessary.
As above but for a project that includes header files:
before build: after build:
. .
├── Makefile ├── Makefile
├── main.c ├── main.o
└── icecream.h ├── main.c
├── icecream.h
└── icecream
- preprocessor's flags
- print a custom message
- C compilation implicit rule is overwritten
- default goal
all
appears first .SILENT:
silences the rules output
####################################### BEG_2 ####
NAME := icecream
#------------------------------------------------#
# INGREDIENTS #
#------------------------------------------------#
# SRCS source files
# OBJS object files
#
# CC compiler
# CFLAGS compiler flags
# CPPFLAGS preprocessor flags
SRCS := main.c
OBJS := main.o
CC := clang
CFLAGS := -Wall -Wextra -Werror
CPPFLAGS := -I .
CPPFLAGS
is dedicated to preprocessor's flags like-I <include_dir>
, it allows you to no longer have to write the full path of a header but only its file name in the sources:#include "icecream.h"
instead of#include "../../path/to/include/icecream.h"
.
#------------------------------------------------#
# UTENSILS #
#------------------------------------------------#
# RM force remove
# MAKEFLAGS make flags
RM := rm -f
MAKEFLAGS += --no-print-directory
#------------------------------------------------#
# RECIPES #
#------------------------------------------------#
# all default goal
# $(NAME) linking .o -> binary
# %.o compilation .c -> .o
# clean remove .o
# fclean remove .o + binary
# re remake default goal
all: $(NAME)
$(NAME): $(OBJS)
$(CC) $(OBJS) -o $(NAME)
$(info CREATED $(NAME))
%.o: %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
$(info CREATED $@)
clean:
$(RM) $(OBJS)
fclean: clean
$(RM) $(NAME)
re:
$(MAKE) fclean
$(MAKE) all
- The
info
function is used here to print a custom message about what has just been built.
We prefer info
to shell echo
because it is a make function. Also unlike
echo
that can only be used inside a recipe, info
can be used anywhere in a
Makefile which makes it powerful for debugging.
- The C compilation implicit rule is overwritten with an explicit equivalent
which let us add an
info
statement to it.
- The order in which the rules are written does not matter as long as our
default goal
all
appears first (the rule that will be triggered by a simplemake
command).
#------------------------------------------------#
# SPEC #
#------------------------------------------------#
.PHONY: clean fclean re
.SILENT:
- Normally make prints each line of a rule's recipe before it is executed. The
special target
.SILENT:
silences the rules output specified as prerequisites, when it is used without prerequisites it silents all the rules (implicit included).
To silence at the recipe-line level we can prefix the wanted recipe lines with an @
symbol.
####################################### END_2 ####
As above but a more complex project structure with multiple source directories and their corresponding object directories:
before build: after build:
. .
├── src ├── src
│ ├── base │ ├── base
│ │ ├── water.c │ │ ├── water.c
│ │ └── milk.c │ │ └── milk.c
│ ├── arom │ ├── arom
│ │ └── coco.c │ │ └── coco.c
│ └── main.c │ └── main.c
├── include ├── obj
│ └── icecream.h │ ├── base
└── Makefile │ │ ├── water.o
│ │ └── milk.o
│ ├── arom
│ │ └── coco.o
│ └── main.o
├── include
│ └── icecream.h
├── Makefile
└── icecream
- split the line with a
backslash
- substitution reference so
main.c
becomessrc/main.c
- generate the
OBJ_DIR
based onSRC_DIR
- compilation rule uses multiple source and object directories
####################################### BEG_3 ####
NAME := icecream
#------------------------------------------------#
# INGREDIENTS #
#------------------------------------------------#
# SRC_DIR source directory
# OBJ_DIR object directory
# SRCS source files
# OBJS object files
#
# CC compiler
# CFLAGS compiler flags
# CPPFLAGS preprocessor flags
SRC_DIR := src
OBJ_DIR := obj
SRCS := \
main.c \
arom/coco.c \
base/milk.c \
base/water.c
SRCS := $(SRCS:%=$(SRC_DIR)/%)
OBJS := $(SRCS:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
CC := clang
CFLAGS := -Wall -Wextra -Werror
CPPFLAGS := -I include
- We can split the line by ending it with a
backslash
to increase the readability ofSRCS
content and facilitate its modification.
- A string substitution reference substitutes the value of each item of a
variable with the specified alterations.
$(SRCS:%=$(SRC_DIR)/%)
means that each item ofSRCS
represented by%
becomes itself (%
) plus the$(SRC_DIR)/
alteration, somain.c
becomessrc/main.c
.OBJS
will then use the same process to convertsrc/main.c
intoobj/main.o
, based onOBJ_DIR
and the new value ofSRCS
.
#------------------------------------------------#
# UTENSILS #
#------------------------------------------------#
# RM force remove
# MAKEFLAGS make flags
# DIR_DUP duplicate directory tree
RM := rm -f
MAKEFLAGS += --no-print-directory
DIR_DUP = mkdir -p $(@D)
DIR_DUP
will generate theOBJ_DIR
based onSRC_DIR
structure withmkdir -p
which creates the directory and the parents directories if missing, and$(@D)
that will expand to the directory part of the target, as we've seen in the syntax section.
This will work with every possible kind of src directory structure.
#------------------------------------------------#
# RECIPES #
#------------------------------------------------#
# all default goal
# $(NAME) linking .o -> binary
# %.o compilation .c -> .o
# clean remove .o
# fclean remove .o + binary
# re remake default goal
all: $(NAME)
$(NAME): $(OBJS)
$(CC) $(OBJS) -o $(NAME)
$(info CREATED $(NAME))
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
$(DIR_DUP)
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
$(info CREATED $@)
clean:
$(RM) $(OBJS)
fclean: clean
$(RM) $(NAME)
re:
$(MAKE) fclean
$(MAKE) all
- The compilation rule
.o: %.c
becomes$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
, since our structure uses dedicated source and object directories.
#------------------------------------------------#
# SPEC #
#------------------------------------------------#
.PHONY: clean fclean re
.SILENT:
####################################### END_3 ####
Builds a library so there are no main.c
. We generate dependencies
that are stored with the objects. Therefor we rename the obj
directory into a
more general .build
directory.
before build: after build:
. .
├── src ├── src
│ ├── base │ ├── base
│ │ ├── water.c │ │ ├── water.c
│ │ └── milk.c │ │ └── milk.c
│ └── arom │ └── arom
│ └── coco.c │ └── coco.c
├── include ├── include
│ └── icecream.h │ └── icecream.h
└── Makefile ├── .build
│ ├── base
│ │ ├── water.o
│ │ ├── water.d
│ │ ├── milk.o
│ │ └── milk.d
│ └── arom
│ ├── coco.o
│ └── coco.d
├── Makefile
└── libicecream.a
- when a header file is modified the executable will rebuild
- automatically generate a list of dependencies
- build directory
- dependency files must be included
- hyphen symbol to prevent make from complaining
- creates a static library
####################################### BEG_4 ####
NAME := libicecream.a
#------------------------------------------------#
# INGREDIENTS #
#------------------------------------------------#
# SRC_DIR source directory
# SRCS source files
#
# BUILD_DIR object directory
# OBJS object files
# DEPS dependency files
#
# CC compiler
# CFLAGS compiler flags
# CPPFLAGS preprocessor flags
SRC_DIR := src
SRCS := \
arom/coco.c \
base/milk.c \
base/water.c
SRCS := $(SRCS:%=$(SRC_DIR)/%)
BUILD_DIR := .build
OBJS := $(SRCS:$(SRC_DIR)/%.c=$(BUILD_DIR)/%.o)
DEPS := $(OBJS:.o=.d)
CC := clang
CFLAGS := -Wall -Wextra -Werror
CPPFLAGS := -MMD -MP -I include
AR := ar
ARFLAGS := -r -c -s
- Unlike with sources, when a header file is modified make has no way of knowing this and will not consider the executable to be out of date, and therefor will not rebuild it. In order to change this behavior we should add the appropriate header files as additional prerequisites:
#before #after
main.o: main.c main.o: main.c icecream.h
clang -c $< -o $@ clang -c $< -o $@
-
Doing this manually for multiple sources and headers is both tedious and error prone. By adding
-MMD
toCPPFLAGS
our compiler will automatically generate a list of dependencies for each object file encountered during the compilation. The-MP
option prevents errors that are triggered if a header file has been deleted or renamed.Dependency files must be included into our Makefile right after the objects creation so to obtain their names we copy
OBJS
intoDEPS
and use substitution reference to turn.o
part of their name into.d
.
- We change our old
OBJ_DIR = obj
for aBUILD_DIR = .build
, a hidden build directory that will contain our dependency files in addition to our objects.
- A static library is not a binary but a collection of objects so we use
ar
to creates a static library during the linking step of the build.-r
to replace the older objects with the new ones with-c
to create the library if it does not exist and-s
to write an index into the archive or update an existing one.
#------------------------------------------------#
# UTENSILS #
#------------------------------------------------#
# RM force remove
# MAKEFLAGS make flags
# DIR_DUP duplicate directory tree
RM := rm -f
MAKEFLAGS += --no-print-directory
DIR_DUP = mkdir -p $(@D)
#------------------------------------------------#
# RECIPES #
#------------------------------------------------#
# all default goal
# $(NAME) link .o -> library
# %.o compilation .c -> .o
# clean remove .o
# fclean remove .o + binary
# re remake default goal
all: $(NAME)
$(NAME): $(OBJS)
$(AR) $(ARFLAGS) $(NAME) $(OBJS)
$(info CREATED $(NAME))
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
$(DIR_DUP)
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
$(info CREATED $@)
-include $(DEPS)
clean:
$(RM) $(OBJS) $(DEPS)
fclean: clean
$(RM) $(NAME)
re:
$(MAKE) fclean
$(MAKE) all
- Dependency files are written in the make language and must be included
into our Makefile to be read. The
include
directive work the same as C#include
, it tells make to suspend its current Makefile reading and read the included files before continuing. Make sure to include the dependencies after they are created → after the compilation rule that invoke-MMD
via$(CPPFLAGS)
.
- The purpose of the
-include $(DEPS)
initial hyphen symbol is to prevent make from complaining when a non-zero status code is encountered, which can be caused here by a missing files from our generated dependency files list.
#------------------------------------------------#
# SPEC #
#------------------------------------------------#
.PHONY: clean fclean re
.SILENT:
####################################### END_4 ####
Builds an icecream
program that uses libbase
and libarom
libraries. Both libraries are v4 based.
before build: after build:
. .
├── src ├── src
│ └── main.c │ └── main.c
├── lib ├── lib
│ ├── libbase │ ├── libbase
│ │ ├── src │ │ ├── src
│ │ │ ├── water.c │ │ │ ├── water.c
│ │ │ └── milk.c │ │ │ └── milk.c
│ │ ├── include │ │ ├── include
│ │ │ └── base.h │ │ │ └── base.h
│ │ └── Makefile │ │ ├── .build
│ └── libarom │ │ │ ├── water.o
│ ├── src │ │ │ ├── water.d
│ │ ├── coco.c │ │ │ ├── milk.o
│ │ └── cherry.c │ │ │ └── milk.d
│ ├── include │ │ ├── Makefile
│ │ └── arom.h │ │ └── libbase.a
│ └── Makefile │ └── libarom
├── include │ ├── src
│ └── icecream.h │ │ ├── coco.c
└── Makefile │ │ └── cherry.c
│ ├── include
│ │ └── arom.h
│ ├── .build
│ │ ├── coco.o
│ │ ├── coco.d
│ │ ├── cherry.o
│ │ └── cherry.d
│ ├── Makefile
│ └── libarom.a
├── include
│ └── icecream.h
├── .build
│ ├── main.o
│ └── main.d
├── Makefile
└── icecream
- system library linked by default
addprefix
make function- flags and libraries used by the linker
dir
function- Build with a library
- Linking with a library
- builds each of the required libraries
- call rules recursively
# @author clemedon (Clément Vidon)
####################################### BEG_5 ####
NAME := icecream
#------------------------------------------------#
# INGREDIENTS #
#------------------------------------------------#
# LIBS libraries to be used
# LIBS_TARGET libraries to be built
#
# INCS header file locations
#
# SRC_DIR source directory
# SRCS source files
#
# BUILD_DIR build directory
# OBJS object files
# DEPS dependency files
#
# CC compiler
# CFLAGS compiler flags
# CPPFLAGS preprocessor flags
# LDFLAGS linker flags
# LDLIBS libraries name
LIBS := arom base m
LIBS_TARGET := \
lib/libarom/libarom.a \
lib/libbase/libbase.a
INCS := include \
lib/libarom/include \
lib/libbase/include
SRC_DIR := src
SRCS := main.c
SRCS := $(SRCS:%=$(SRC_DIR)/%)
BUILD_DIR := .build
OBJS := $(SRCS:$(SRC_DIR)/%.c=$(BUILD_DIR)/%.o)
DEPS := $(OBJS:.o=.d)
CC := clang
CFLAGS := -Wall -Wextra -Werror
CPPFLAGS := $(addprefix -I,$(INCS)) -MMD -MP
LDFLAGS := $(addprefix -L,$(dir $(LIBS_TARGET)))
LDLIBS := $(addprefix -l,$(LIBS))
- We can notice that the
m
library fromLIBS
is not mentionned inLIBS_TARGET
for the reason thatm
is a system library (libm
for mathematical functions found inmath.h
). Unlike thelibc
which is linked by default (we don't need to provide-lc
flag to our linker) thelibm
is not linked by default.
- In
CPPFLAGS
we useaddprefix
that, as its name suggests is a make function that allows you to add a prefix, here a-I
to each of the item found in$(INCS)
(that contains the paths to our project and libraries headers).
LDFLAGS
andLDLIBS
contain the flags and libraries that will be used by the linkerld
to link the library to our project sources.
- The
dir
function means that we want to keep only directory part of the given item, there exists anotdir
function that does the opposite to keep only the file name of the given item.
- Build with a library requires three flags:
-I
tell the compiler where to find the lib header files,-L
tells the linker where to look for the library and-l
the name of this library (without its conventionallib
prefix).
For example: -I lib/libarom/include -L lib/libarom -l arom
#------------------------------------------------#
# UTENSILS #
#------------------------------------------------#
# RM force remove
# MAKEFLAGS make flags
# DIR_DUP duplicate directory tree
RM := rm -f
MAKEFLAGS += --silent --no-print-directory
DIR_DUP = mkdir -p $(@D)
#------------------------------------------------#
# RECIPES #
#------------------------------------------------#
# all default goal
# $(NAME) link .o -> archive
# $(LIBS) build libraries
# %.o compilation .c -> .o
# clean remove .o
# fclean remove .o + binary
# re remake default goal
# run run the program
# info print the default goal recipe
all: $(NAME)
$(NAME): $(OBJS) $(LIBS_TARGET)
$(CC) $(LDFLAGS) $(OBJS) $(LDLIBS) -o $(NAME)
$(info CREATED $(NAME))
$(LIBS_TARGET):
$(MAKE) -C $(@D)
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
$(DIR_DUP)
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
$(info CREATED $@)
-include $(DEPS)
clean:
for f in $(dir $(LIBS_TARGET)); do $(MAKE) -C $$f clean; done
$(RM) $(OBJS) $(DEPS)
fclean: clean
for f in $(dir $(LIBS_TARGET)); do $(MAKE) -C $$f fclean; done
$(RM) $(NAME)
re:
$(MAKE) fclean
$(MAKE) all
- Linking with a library requires special attention to the order of the
linking flags. In our case we need to make sure that
$(LDFLAGS)
and$(LDLIBS)
passes respectively before and after the$(OBJS)
in the linking recipe.
$(LIBS_TARGET)
rule builds each of the required libraries found in theINGREDIENTS
part. It is a$(NAME)
prerequisite for the same reason as$(OBJS)
because our final goal needs the libraries as well as the objects to be built.
- As both rules
clean
andfclean
appear in the Makefile of all our$(LIBS_TARGET)
we can call these rules for each of them recursively using a shellfor
loop. Here again we use thedir
function to only keep the directory part of the library.
#------------------------------------------------#
# SPEC #
#------------------------------------------------#
.PHONY: clean fclean re
.SILENT:
####################################### end_5 ####
.PHONY: run
run: re
-./$(NAME)
run
is a simple rule thatmake
andrun
the default goal. We start the shell command with thehyphen
symbol to prevent make from interrupting its own execution if our program execution returns a non-zero value.
info-%:
$(MAKE) --dry-run --always-make $* | grep -v "info"
info-<target>
rule will execute amake <target>
command with--dry-run
to print the<target>
recipe without executing it,--always-make
tomake
even if the targets already exist andgrep -v
to filter the output.$*
expands to the value given in place of the%
at the rule call. For example withmake info-re
$*
will expand tore
.
print-%:
$(info '$*'='$($*)')
- The
print-<variable>
that works likeprint-<rule>
will print the value of an arbitrary variable, for example amake print-CC
will outputCC=clang
.
.PHONY: update
update:
git stash
git pull
git submodule update --init
git stash pop
- The
update
rule will update the git repository to its last version, as well as its git submodules.stash
commands saves eventual uncommitted changes and put them back in place once the update is done.
Topic | Website |
---|---|
doc | docs.w3cub.com |
manual | gnu.org |
a richer tutorial | makefiletutorial.com |
order-only exquisite | stackoverflow.com |
c libraries | docencia.ac.upc.edu |
auto-deps gen | make.mad-scientist.net |
auto-deps gen | scottmcpeak.com |
auto-deps gen | microhowto.info |
include statement | gnu.org |
redis makefile | github.com |
cvidon 42
clemedon icloud