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:
### BEGMark the template beginning.INGREDIENTSBuild variables.UTENSILSShell commands.RECIPESBuild and extra rules.SPECSpecial targets and specifications.#### ENDMark the template end.
What we call a rule is made of:
targetsName of a goal (action or a file) we want to make.prerequisitesFiles required (targets dependencies) for theruleto execute.recipeLines that begins with aTABcharacter, 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
MAKEpredifined variable- The C compilation implicit rule
- Illustration of a
make all- C build recap
- multi-threaded
makewith--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.cbecomessrc/main.c- generate the
OBJ_DIRbased onSRC_DIR- compilation rule uses multiple source and object directories
cleanrule--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
makeandrunthe default goalinforule print the$(NAME)recipe without executing itprint-<variable>rule prints the value of the given variableupdaterule 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
MAKEpredifined variable- The C compilation implicit rule
- Illustration of a
make all - C build recap
- multi-threaded
makewith--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
CCandCFLAGSvalues,$(NAME),clean,fclean,allandreas 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 renamingcleanandfcleanto the more GNU conventionalmostlycleanandcleanrespectively). -
MAKEis 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'sMAKEvariable inherit from the caller'sMAKEvalue. We pass it the--no-print-directoryflag 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
%.otarget compile the.cinto.o, the-ctells to compile the.cwithout linking the.oand the-oindicate how to name the.oresulting from the.c. Afterward the$(NAME)is in charge of linking the.ointo a binary$(NAME)file whose name is specified with the-oflag. -
For the
recommand 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 allwouldn't work with a multi-threadedmakewith--jobsoption. -
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 namedrein your project directory and runmake re. It won't work.Now if you do the same with
allit won't cause any problem, as we know prerequisites are completed before their target andallhas the sole action of invoking$(NAME), as long as a rule doesn't have a recipe,.PHONYis 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
allappears 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 .
CPPFLAGSis 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
infofunction 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
infostatement. -
The order in which the rules are written does not matter as long as our default goal
allappears first (the rule that will be triggered by a simplemakecommand).
#------------------------------------------------#
# 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.cbecomessrc/main.c - generate the
OBJ_DIRbased onSRC_DIR - compilation rule uses multiple source and object directories
cleanrule--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
backslashto increase the readability ofSRCScontent 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 ofSRCSrepresented by%becomes itself%plus the$(SRC_DIR)/alteration, somain.cbecomessrc/main.c.OBJSwill then use the same process to convertsrc/main.cintosrc/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_DUPwill generate theOBJ_DIRbased onSRC_DIRstructure withmkdir -pthat 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: %.cbecomes$(OBJ_DIR)/%.o: $(SRC_DIR)/%.csince our structure uses multiple source and object directories. -
In the
cleanrule we add--recursivetoRMto removeOBJ_DIRand 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
-MMDtoCPPFLAGSour compiler will automatically generate a list of dependencies for each object file encountered during the compilation. The-MPoption prevents errors that are triggered if a header file has been deleted or renamed. -
We change our old
OBJ_DIR = objfor 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
includedirective 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.ointo.dusing substitution reference on theOBJScontent. -
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
arcreates a static library during the linking step of the build.-rto replace the older object files with the new ones;-cto create the library if it does not exist and-sto 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)
runis a simple rule thatmakeandrunthe default goal. We start the shell command with thehyphensymbol 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
inforule will execute a simplemakecommand with--dry-runto print the$(NAME)recipe without executing it,--always-maketomakeeven 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-CCwill outputCC=clang.
update:
git stash
git pull
git submodule update --init
git stash pop
.PHONY: update
- The
updaterule will update the repository to its last version, as well as its submodules.stashcommands 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.