Makefiles fundamentals (03/08/2021)

Utilities

Presentation

Makefiles are tasks automation files composed of rules of the form:

target: dependencies
    commands

When make is called, the makefile is evaluated as follow:

  1. The first rule in the makefile (or the one specified if any) is evaluated.
  2. If one or more dependencies are targeted by other rules, these rules are evaluated too.
  3. Once all dependencies were analysed, if the target of the rule does not already exist, or if one of the dependency files is more recent than it, the commands are executed.

Basic operation

Minimal makefile

To build a project “test” composed of the three files main.c, depend.h and depend.c, the minimal makefile is:

test: main.o depend.o
    gcc -o test main.o depend.o

depend.o: depend.c
    gcc -o depend.o -c depend.c

main.o: main.c depend.h
    gcc -o main.o -c main.c

A more complete version

# This rule allow the building of multiple executable by putting them in its dependencies.
all: test

test: main.o depend.o
    gcc -o test main.o depend.o

depend.o: depend.c
    gcc -o depend.o -c depend.c

main.o: main.c depend.h
    gcc -o main.o -c main.c

# This rule allow the deletion of the intermediate files by invoking "make clean".
clean:
    rm -rf *.o

# This rule allow the deletion of all the generated files by invoking "make mrproper".
mrproper: clean
    rm -rf test

Variables

Variables definition

A variable is defined like this:

variable=value

And used like this:

$(variable)

Variables can be used to make changes easier to manage. One can hold the compiler name, another the compilation flags, and so on…

CC=gcc           # The compiler name.
CFLAGS=-W -Wall  # The compiler flags.
LDFLAGS=         # The linker flags.
EXEC=test        # The executable list.

# Note that these variable names are just conventions.

all: $(EXEC)

test: main.o depend.o
    $(CC) -o test main.o depend.o $(LDFLAGS)

depend.o: depend.c
    $(CC) -o depend.o -c depend.c $(CFLAGS)

main.o: main.c depend.h
    $(CC) -o main.o -c main.c $(CFLAGS)

clean:
    rm -rf *.o

mrproper: clean
    rm -rf $(EXEC)

Internal variables

It exist internal variables that can be used in makefile’s rules creation. Here is some:

  • $@ : The target’s name.
  • $< : The name of the first dependency.
  • $^ : The dependencies list.
  • $? : The list of dependencies that are more recent than the target.
  • $* : The name of the file without its suffix.
CC=gcc
CFLAGS=-W -Wall
LDFLAGS=
EXEC=test

all: $(EXEC)

test: main.o depend.o
    $(CC) -o $@ $^ $(LDFLAGS)

depend.o: depend.c
    $(CC) -o $@ -c $< $(CFLAGS)

main.o: main.c depend.h
    $(CC) -o $@ -c $< $(CFLAGS)

clean:
    rm -rf *.o

mrproper: clean
    rm -rf $(EXEC)

Inference rules

Inference rules are generic rules that are called by default, by using the ‘%’ symbols. They can be used to lighten a makefile. For example, to generalize the creation of a .o from a .c:

CC=gcc
CFLAGS=-W -Wall
LDFLAGS=
EXEC=test

all: $(EXEC)

test: main.o depend.o
    $(CC) -o $@ $^ $(LDFLAGS)

# Any other dependency must be specified separately.
main.o: depend.h

%.o: %.c
    $(CC) -o $@ -c $< $(CFLAGS)

clean:
    rm -rf *.o

mrproper: clean
    rm -rf $(EXEC)

The .PHONY target

.PHONY is a special target whose dependencies are always rebuilt. This is useful if there is a “clean” file in the directory for example.

CC=gcc
CFLAGS=-W -Wall
LDFLAGS=
EXEC=test

all: $(EXEC)

test: main.o depend.o
    $(CC) -o $@ $^ $(LDFLAGS)

main.o: depend.h

%.o: %.c
    $(CC) -o $@ -c $< $(CFLAGS)

.PHONY: clean mrproper

clean:
    rm -rf *.o

mrproper: clean
    rm -rf $(EXEC)

Generating the files list

CC=gcc
CFLAGS=-W -Wall
LDFLAGS=
EXEC=test
# The SRC variable will contain the list of the .c files.
SRC= $(wildcard *.c) # The "wildcard" command allow the use of the joker *.
# The OBJ variable will contain the list of the .o files.
OBJ= $(SRC:.c=.o) # This syntax will copy the content of SRC, replacing ".c" by ".o".

all: $(EXEC)

test: $(OBJ) # The makefile will work even if source files are added to the project.
    $(CC) -o $@ $^ $(LDFLAGS)

main.o: depend.h

%.o: %.c
    $(CC) -o $@ -c $< $(CFLAGS)

.PHONY: clean mrproper

clean:
    rm -rf *.o

mrproper: clean
    rm -rf $(EXEC)

Silent commands

The output of the commands executed by make can be deactivated by putting a @ symbol at their start:

CC=gcc
CFLAGS=-W -Wall
LDFLAGS=
EXEC=test
SRC= $(wildcard *.c)
OBJ= $(SRC:.c=.o)

all: $(EXEC)

test: $(OBJ) # The makefile will work even if source files are added to the project.
    @$(CC) -o $@ $^ $(LDFLAGS)

main.o: depend.h

%.o: %.c
    @$(CC) -o $@ -c $< $(CFLAGS)

.PHONY: clean mrproper

clean:
    @rm -rf *.o

mrproper: clean
    @rm -rf $(EXEC)

Conditions

Conditional statements can be used in makefiles. For example to change compilation flags in function of a DEBUG variable:

DEBUG=y
CC=gcc
ifeq ($(DEBUG),y)
    CFLAGS=-W -Wall -g
    LDFLAGS=
else
    CFLAGS=-W -Wall
    LDFLAGS=
EXEC=test
SRC= $(wildcard *.c)
OBJ= $(SRC:.c=.o)

all: $(EXEC)

test: $(OBJ)
    @$(CC) -o $@ $^ $(LDFLAGS)

main.o: depend.h

%.o: %.c
    @$(CC) -o $@ -c $< $(CFLAGS)

.PHONY: clean mrproper

clean:
    @rm -rf *.o

mrproper: clean
    @rm -rf $(EXEC)

Sub-makefiles

A project can be split in different makefiles called by a “master” makefile. To do this an instruction and a special variable exist:

# The export instruction make a variable reachable by a sub-makefile.
export variable=value
# The MAKE variable call the sub-makefile.
$(MAKE)

For example, if the “test” project is a sub-project of the “master” project:

# Master makefile
DEBUG=y
export CC=gcc
ifeq ($(DEBUG),y)
    export CFLAGS=-W -Wall -g
    export LDFLAGS=
else
    export CFLAGS=-W -Wall
    export LDFLAGS=
TEST_DIR=test
EXEC=$(TEST_DIR)/test

all: $(EXEC)

$(EXEC): $(OBJ)
    @(cd $(TEST_DIR) && $(MAKE)) # The cd command move to the sub-directory and then the sub-makefile is called.

.PHONY: clean mrproper $(EXEC) # The sub-makefile will always be called.

clean:
    @(cd $(TEST_DIR) && $(MAKE) $@) # The sub-makefile can be called with a specific target.

mrproper: clean
    @(cd $(TEST_DIR) && $(MAKE) $@)
# Test makefile
EXEC=test
SRC= $(wildcard *.c)
OBJ= $(SRC:.c=.o)

all: $(EXEC)

test: $(OBJ)
    @$(CC) -o $@ $^ $(LDFLAGS)

main.o: depend.h

%.o: %.c
    @$(CC) -o $@ -c $< $(CFLAGS)

.PHONY: clean mrproper

clean:
    @rm -rf *.o

mrproper: clean
    @rm -rf $(EXEC)

Something to say?

Leave a Reply

Your email address will not be published.

anonymous (06/22/2021 at 02:56 PM)

Incredible my friend

Reply