Makefile_tutor
Makefile_tutor copied to clipboard
This project aims to create a crystal clear tutorial on a cryptic looking topic.
MAKEFILE TUTOR (GNU UNIX)
Summary · Usage · Glossary · Syntax · Index · Sources · Contact
Summary
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 documented template that evolves gradually, step by step. With the aim of making them more digestible and even tasty 🍔
TL;DR Confer to the bold text.
→ GitHub Page ←
→ GitHub Repo ←
[ DONE ]
- GitHub Page.
- projects directory to try each template version.
- Bold text that compile the whole tutorial into a quick summary.
- v1 Minimal makefile.
- v2 Include directory.
- v3 Multiple source directories.
- v3 Corresponding target directories for objects.
- v3 Creation of target directories when they don't exist.
- v4 Make a library.
- v4 Auto-dependency generation.
- v4 Dependency management (build only the necessary).
- v4 Build directory for objects and deps.
[ SOON ]
- v5 Make with library.
- v5 Fully automated and auto scalability.
- v5 String functions.
[ TODO ]
- v6 Make C and C++.
- Makefile generator.
- BSD compatibility (and POSIX compliance).
- DOS compatibility.
Usage
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 templates has an assigned directory in the projects directory of the repository, to play with a makefile open a terminal and run:
git clone [email protected]:clemedon/Makefile_tutor.git
cd Makefile_tutor && cd project
cd <template_version>
make <any_rule>
PS C++
users can replace CC = clang
with CXX = g++
and CFLAGS
with
CXXFLAGS
.
Glossary
Each version of our template has 3 sections:
- Structure The project structure type.
- Brief Compilation of the bold text from the template comments.
- Template Our makefile 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 and specifications. -
#### END
Mark the template end.
What we call a rule
is made of:
-
targets
Name of a goal (action or a file) we want to make. -
prerequisites
Files required (targets dependencies) for therule
to execute. -
recipe
Lines that begins with aTAB
character, appear in a rule context.
target: prerequisite
recipe line 1
recipe line 2
...
Syntax
Like every makefile our 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.
Equal signs:
-
:=
simply expand the defined variable (like C equal sign). -
=
recursively expand the defined variable (the expression is expanded afterward, when the variable is used).
A := Yes $(C)
B = Yes $(C)
C = you got it
all:
$(info $(A)) # output "Yes"
$(info $(B)) # output "Yes you got it"
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
Template
Index
The first part focuses on building a functional makefile in 3 steps.
Version 1 / base
- 42 C coding style conventions
MAKE
predifined variable- The C compilation implicit rule
- Illustration of a
make all
- C build recap
- multi-threaded
make
with--jobs
- the
.PHONY:
special target
Version 2 / simple
- preprocessor's flags
- output of a descriptive message
- C compilation implicit rule is overwritten
- rules are written in their order of execution
.SILENT:
silences the rules
Version 3 / structured
- 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
clean
rule--recursive
The second part presents various useful makefiles and more advanced features.
Version 4 / for library
- 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 / with libraries
SOON
Bonus
make
andrun
the default goalinfo
rule print the$(NAME)
recipe without executing itprint-<variable>
rule prints the value of the given variableupdate
rule update the repository
Version 1
v1 Structure
The simplest, build a program called icecream
with the following structure:
before build: after build:
. .
├── Makefile ├── Makefile
└── main.c ├── main.o
├── main.c
└── icecream
v1 Brief
- 42 C coding style conventions
-
MAKE
predifined variable - The C compilation implicit rule
- Illustration of a
make all
- C build recap
- multi-threaded
make
with--jobs
- the
.PHONY:
special target
v1 Template
####################################### 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 cleaning command
RM := rm --force
MAKE := $(MAKE) --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) $^ -o $@
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-detect source files are specific to 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
is a predefined variable whose value corresponds to the make executable being run, for this reason we pass our options to it by incrementation. When a makefile is executed from another makefile, the called'sMAKE
variable inherit from the caller'sMAKE
value. We pass it the--no-print-directory
flag for a cleaner output, remove it to see the difference. -
The C compilation implicit rule looks like this:
%.o: %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
Where %.o
evaluates to each object, %.c
to each source, $@
to the first
target (which is %.o
) and $<
to the leftmost prerequisite (which is %.c
).
Note that $@
can be replaced by $(OBJS)
and $<
by $(SRCS)
.
As their name implies implicit rules are implicit and do not need to be
written. All the implicit rules can be found in the data-base, accessible
with a make -p -f/dev/null | less
shell command.
-
Illustration of a
make all
:
all: $(NAME) 3 ← 2
$(NAME): $(OBJS) 2 ← 1
$(CC) $^ -o $@
%.o: %.c 1 ← 0
$(CC) $(CFLAGS) -c -o $@ $<
The all
rule requires icecream
that requires objects
that require
sources
that require... a programmer. In other words all
creates icecream
with the .o
created with the .c
that you are supposed to create.
Make will first trace its path to the lower level where it finds a raw material
3 → 2 → 1 → 0
(source files
) and then do it in the opposite direction while
building each resource that is required by the direct upper level 0 → 1 → 2 → 3
(target
).
-
C build recap
%.o
target compile the.c
into.o
, the-c
tells to compile the.c
without linking the.o
and the-o
indicate how to name the.o
resulting from the.c
. Afterward the$(NAME)
is in charge of linking the.o
into a binary$(NAME)
file whose name is specified with the-o
flag. -
For the
re
command 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 work with a multi-threadedmake
with--jobs
option. -
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 namedre
in your project directory and runmake 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 target andall
has the sole action of invoking$(NAME)
, as long as a rule doesn't have a recipe,.PHONY
is not necessary.
Return to Index ↑
Version 2
v2 Structure
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
v2 Brief
- preprocessor's flags
- output of a descriptive message
- C compilation implicit rule is overwritten
-
default goal
all
appears first -
.SILENT:
silences the rules
v2 Template
####################################### 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 source files:#include "icecream.h"
.
#------------------------------------------------#
# UTENSILS #
#------------------------------------------------#
# RM cleaning command
# MAKE make command
RM := rm --force
MAKE := $(MAKE) --no-print-directory
#------------------------------------------------#
# RECIPES #
#------------------------------------------------#
# all default goal
# %.o compilation .c -> .o
# $(NAME) linking .o -> binary
# clean remove .o
# fclean remove .o + binary
# re remake default goal
all: $(NAME)
$(NAME): $(OBJS)
$(CC) $^ -o $@
$(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 use of the
info
function to output of a descriptive message in the basic rules. -
The C compilation implicit rule is overwritten with an explicit version in which we can add an
info
statement. -
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 specified as prerequisites, when it is used without prerequisites it silents all the rules (implicit ones like C compilation included).
To silence at the line level we can prefix the wanted recipe lines with an @
symbol.
####################################### END_2 ####
Return to Index ↑
Version 3
v3 Structure
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
v3 Brief
- 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
-
clean
rule--recursive
v3 Template
####################################### 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
intosrc/main.o
, dedicated to theOBJ_DIR
.
#------------------------------------------------#
# UTENSILS #
#------------------------------------------------#
# RM cleaning command
# MAKE make command
RM := rm --force
MAKE := $(MAKE) --no-print-directory
DIR_DUP = mkdir -p $(@D)
-
DIR_DUP
will generate theOBJ_DIR
based onSRC_DIR
structure withmkdir -p
that create the directory and the parents directories if missing, and$(@D)
automatic variable that we have already seen.
This will work with every possible kind of src directory structure.
#------------------------------------------------#
# RECIPES #
#------------------------------------------------#
# all default goal
# %.o compilation .c -> .o
# $(NAME) linking .o -> binary
# clean remove .o
# fclean remove .o + binary
# re remake default goal
all: $(NAME)
$(NAME): $(OBJS)
$(CC) $^ -o $@
$(info CREATED $(NAME))
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
$(DIR_DUP)
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
$(info CREATED $@)
clean:
$(RM) --recursive $(OBJ_DIR)
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 multiple source and object directories. -
In the
clean
rule we add--recursive
toRM
to removeOBJ_DIR
and its content recursively.
#------------------------------------------------#
# SPEC #
#------------------------------------------------#
.PHONY: clean fclean re
.SILENT:
####################################### END_3 ####
Return to Index ↑
Version 4
v4 Structure
Builds a library so we remove main.c
. We generate dependencies that
are stored with the object files thus we rename obj
directory into .build
.
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
└── icecream.a
v4 Brief
- 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
v4 Template
####################################### BEG_4 ####
NAME := icecream.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)
-include $(DEPS)
CC := clang
CFLAGS := -Wall -Wextra -Werror
CPPFLAGS := -MMD -MP -I include
AR := ar
ARFLAGS := -r -c -s
- Unlike source files, 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 an 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. -
We change our old
OBJ_DIR = obj
for aBUILD_DIR = .build
, a hidden build directory that will contain our object files as well as our dependency files. -
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 the current makefile reading and read the included files before continuing. We obtain the name of the dependencies by duplicating.o
into.d
using substitution reference on theOBJS
content. -
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. -
A library is not a binary but a collection of object files so we use
ar
creates a static library during the linking step of the build.-r
to replace the older object files with the new ones;-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 cleaning command
# MAKE make command
RM := rm --force
MAKE := $(MAKE) --no-print-directory
DIR_DUP = mkdir -p $(@D)
#------------------------------------------------#
# RECIPES #
#------------------------------------------------#
# all default goal
# %.o compilation .c -> .o
# $(NAME) link .o -> library
# clean remove .o
# fclean remove .o + binary
# re remake default goal
all: $(NAME)
$(NAME): $(OBJS)
$(AR) $(ARFLAGS) $@ $<
$(info CREATED $(NAME))
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
$(DIR_DUP)
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
$(info CREATED $@)
clean:
$(RM) --recursive $(BUILD_DIR)
fclean: clean
$(RM) $(NAME)
re:
$(MAKE) fclean
$(MAKE) all
#------------------------------------------------#
# SPEC #
#------------------------------------------------#
.PHONY: clean fclean re
.SILENT:
####################################### END_4 ####
Return to Index ↑
Version 5
v5 Structure
Builds a an icecream
program that uses a 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
v5 Brief
SOON
v5 Template
SOON
Return to Index ↑
Bonus
Extra rules
.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 execution if our program execution returns a non-zero value.
.PHONY: info
info:
$(MAKE) --dry-run --always-make | grep -v "info"
- The
info
rule will execute a simplemake
command with--dry-run
to print the$(NAME)
recipe without executing it,--always-make
tomake
even if the targets already exist and filter the output withgrep
.
.PHONY: FORCE
print-%: FORCE
$(info '$*'='$($*)')
- The
print-<variable>
rule prints the value of the given variable, for exampleprint-CC
will outputCC=clang
.
update:
git stash
git pull
git submodule update --init
git stash pop
.PHONY: update
- The
update
rule will update the repository to its last version, as well as its submodules.stash
commands saves eventual uncommitted changes and put them back in place once the update is done.
Sources
- doc / w3cub
- manual / gnu
- a richer tutorial / makefiletutorial
- order-only exquisite / stackoverflow
- c libraries / docencia
- auto-deps gen / mad-scientist
- auto-deps gen / scottmcpeak
- auto-deps gen / microhowto
- include statement / gnu
- redis makefiel / github
Contact
cvidon 42
clemedon icloud
Copyright 2022 Clément Vidon. All Rights Reserved.