Makefile_tutor

MAKEFILE TUTOR (GNU)

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 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 ←

CC BY-SA 4.0


Roadmap

Usage

This tutorial is designed to be read line by line linearly at first.

Then it can be quickly navigated thanks to the:

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.

Glossary

Each version of the template has 3 sections:

Our template will be articulated around the following parts:

According to make a rule is made of:

target: prerequisite
    recipe line 1
    recipe line 2
    

Syntax

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:

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:

Cf. Version 1 / Automatic variables in practice.

Template

Index

Version 1 / Simplest C project

Version 2 / Project that include headers

Version 3 / Project with any kind of directory structure

Version 4 / Static library

Version 5 / Project that uses libraries

Bonus

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

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        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 ####

<hr>

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.

<hr>

%.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.

<hr>

<hr>

# 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).

<hr>

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).

<hr>

<hr>

<hr>

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.

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

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 .
#------------------------------------------------#
#   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

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.

<hr>

<hr>

#------------------------------------------------#
#   SPEC                                         #
#------------------------------------------------#

.PHONY: clean fclean re
.SILENT:

To silence at the recipe-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

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

<hr>

#------------------------------------------------#
#   UTENSILS                                     #
#------------------------------------------------#
# RM        force remove
# MAKEFLAGS make flags
# DIR_DUP   duplicate directory tree

RM          := rm -f
MAKEFLAGS   += --no-print-directory
DIR_DUP     = mkdir -p $(@D)

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
#------------------------------------------------#
#   SPEC                                         #
#------------------------------------------------#

.PHONY: clean fclean re
.SILENT:

####################################### END_3 ####

Return to Index ↑

Version 4

v4 Structure

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

v4 Brief

v4 Template

####################################### 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
#before                     #after
main.o: main.c              main.o: main.c icecream.h
    clang -c $< -o $@           clang -c $< -o $@

<hr>

<hr>

#------------------------------------------------#
#   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

<hr>

#------------------------------------------------#
#   SPEC                                         #
#------------------------------------------------#

.PHONY: clean fclean re
.SILENT:

####################################### END_4 ####

Return to Index ↑

Version 5

v5 Structure

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

v5 Brief

v5 Template

# @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))

<hr>

<hr>

<hr>

<hr>

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

<hr>

<hr>

#------------------------------------------------#
#   SPEC                                         #
#------------------------------------------------#

.PHONY: clean fclean re
.SILENT:

####################################### end_5 ####

Return to Index ↑

Bonus

Extra rules

.PHONY: run
run: re
    -./$(NAME)
info-%:
    $(MAKE) --dry-run --always-make $* | grep -v "info"
print-%:
    $(info '$*'='$($*)')
.PHONY: update
update:
    git stash
    git pull
    git submodule update --init
    git stash pop

Sources

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

Contact

cvidon   42
clemedon icloud