GNU Make

mathieu.fourre


Let’s use Make

You have already use make this year but many of you haven’t really understood it’s power.

Why use Make

Goals

The goal is to make you confortable enough with make so that you can use it during the piscine. As mentioned in the Editors page, the piscine is about efficiency. Using (and reusing) a makefile is a good first step.

Documentation

The official documentation is your best friend. It’s very complete.

click to see your best friend

Why Make is not that easy to learn

The strength and weakness of make is that there are many implicite rules.

For example:

OBJ = main.o func.o

main: main.o
    gcc main.o -o main

main.o: main.c
    gcc -c main.c -o main.o

func.o: func.c
    gcc -c func.c -o func.o

can be written:

OBJ = main.o func.o

main: $(OBJ)

Here are some of the default rules (use make -p to see them all):

# default
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c

OUTPUT_OPTION = -o $@

%.o: %.c
#  recipe to execute (built-in):
    $(COMPILE.c) $(OUTPUT_OPTION) $<

%: %.o
#  recipe to execute (built-in):
    $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@

doc: Implicit-Rules

Implicit variables

As you can see, these implicit rules use standardised variables. You may change the value of these variables.

For example, during 42sh, you will use something like this.

CC=gcc # explicitly set the c compiler
CFLAGS=-Wall -Werror -Wextra -pedantic -std=c99

These variables are very useful and flexible.

Here are some of them:

CC= # c compiler
CFLAGS= # c flags
CPPFLAGS= # c pre-processor flags (ex: -D_GNU_SOURCE)
LDFLAGS= # linker flags
LDLIBS= # libraries (ex: -lm)

doc: Implicit-Variables

Changing variables value only in specifc rules

Sometimes, you may want to change the value of a variable only for a rule.

To do this, you need to add a line just above the rule like so:

rule_with_debug: CFLAGS += -g
rule_with_debug: file.o # normal dependancies

doc: appending more text to variables

Pattern rules

You can use % as a sort of wildcard. For example, to define a rule that create a .foo from a .bar file with the same name, you can do:

%.foo: %.bar
    # do things

you can also set prefixes:

folder1/%.foo: folder2/%.bar
    # do things

doc: Pattern-Rules

Automatic variables

I you are following you may be wondering how you can write a rule while using %. Indeed, this doesn’t work:

%.o: %.c
    gcc -c %.c -o %.o

To do this, you will have to use automatic variables.

For example:

all: file.o

%.o: %.c header.h
    echo $@ # file.o
    echo $< # file.c
    echo $^ # file.c header.h
    echo $* # file

doc: Automatic-variables

Functions

Make comes with many functions. They can be used to define variables for example.

doc: Functions

To call a function do $(function arg1, arg2, ...).

Let’s see some useful functions:

ALL_FILES_IN_SRC = $(wildcard src/*)

ALL_DIRECTORIES_IN_SRC = $(dir $(wildcard src/*/)

ONLY_FOO = $(filter-out bar, foo bar)

BAR_FOO_LOSE = $(sort foo bar lose)

HELLO_I_AM_BOB = $(patsubst %jack, hello_%bob, i_am_jack)

PREFIXFOO = $(addprefix prefix, foo)

FOOSUFFIX = $(addsuffix suffic, foo)

Exercice

Your goal is to prepare a makefile to help building a small project and its test-suite.

The test-suite uses Criterion which is a simple-to-use testing framework. (I strongly encourage you to use it to test you piscine exercises)

The project tree looks like this:

$ tree make_exo
make_exo/
├── Makefile
├── src
│   ├── add
│   │   ├── add.c
│   │   └── add.h
│   ├── mul
│   │   ├── mul.c
│   │   └── mul.h
│   ├── prog.c
│   └── sub
│       ├── sub.c
│       └── sub.h
└── tests
    └── testfile.c

5 directories, 9 files

Download the skeleton here. (reminder: tar -xvf make_exo.tar.gz )

Your job is to write the Makefile for this project. Try to make it as clean and generic as possible !

Here are the rule you will implement:

Here are the outputs of the reference Makefile. Use them as a guide on what to do and which flag to add for each rule:

$ make # release
gcc -Wall -Werror -Wextra -pedantic -std=c99 -Isrc/add/ -Isrc/mul/ -Isrc/sub/ -O2 -DNDBUG   -c -o src/prog.o src/prog.c
gcc -Wall -Werror -Wextra -pedantic -std=c99 -Isrc/add/ -Isrc/mul/ -Isrc/sub/ -O2 -DNDBUG   -c -o src/add/add.o src/add/add.c
gcc -Wall -Werror -Wextra -pedantic -std=c99 -Isrc/add/ -Isrc/mul/ -Isrc/sub/ -O2 -DNDBUG   -c -o src/mul/mul.o src/mul/mul.c
gcc -Wall -Werror -Wextra -pedantic -std=c99 -Isrc/add/ -Isrc/mul/ -Isrc/sub/ -O2 -DNDBUG   -c -o src/sub/sub.o src/sub/sub.c
gcc   src/prog.o src/add/add.o src/mul/mul.o src/sub/sub.o   -o prog
$ make debug
gcc -Wall -Werror -Wextra -pedantic -std=c99 -Isrc/add/ -Isrc/mul/ -Isrc/sub/ -g -fsanitize=address   -c -o src/prog.o src/prog.c
gcc -Wall -Werror -Wextra -pedantic -std=c99 -Isrc/add/ -Isrc/mul/ -Isrc/sub/ -g -fsanitize=address   -c -o src/add/add.o src/add/add.c
gcc -Wall -Werror -Wextra -pedantic -std=c99 -Isrc/add/ -Isrc/mul/ -Isrc/sub/ -g -fsanitize=address   -c -o src/mul/mul.o src/mul/mul.c
gcc -Wall -Werror -Wextra -pedantic -std=c99 -Isrc/add/ -Isrc/mul/ -Isrc/sub/ -g -fsanitize=address   -c -o src/sub/sub.o src/sub/sub.c
gcc -fsanitize=address  src/prog.o src/add/add.o src/mul/mul.o src/sub/sub.o   -o prog
$ make test # this rule also execute the binary test-suite
gcc -Wall -Werror -Wextra -pedantic -std=c99 -Isrc/add/ -Isrc/mul/ -Isrc/sub/ -g -fsanitize=address   -c -o src/add/add.o src/add/add.c
gcc -Wall -Werror -Wextra -pedantic -std=c99 -Isrc/add/ -Isrc/mul/ -Isrc/sub/ -g -fsanitize=address   -c -o src/mul/mul.o src/mul/mul.c
gcc -Wall -Werror -Wextra -pedantic -std=c99 -Isrc/add/ -Isrc/mul/ -Isrc/sub/ -g -fsanitize=address   -c -o src/sub/sub.o src/sub/sub.c
gcc -Wall -Werror -Wextra -pedantic -std=c99 -Isrc/add/ -Isrc/mul/ -Isrc/sub/ -g -fsanitize=address   -c -o tests/testfile.o tests/testfile.c
gcc -fsanitize=address -lcriterion  src/add/add.o src/mul/mul.o src/sub/sub.o tests/testfile.o   -o test-suite
./test-suite
[----] tests/testfile.c:19: Assertion failed: The expression (add(1, 1)) == (3) is false.
[FAIL] add::fail: (0.00s)
[====] Synthesis: Tested: 5 | Passing: 4 | Failing: 1 | Crashing: 0 
make: *** [Makefile:28: test] Error 1
$ make clean
rm -f prog
rm -f test-suite
rm -f src/prog.o
rm -f  src/add/add.o  src/mul/mul.o  src/sub/sub.o
rm -f  tests/testfile.o tests/testfile.o

Remember, the true goal is to make it as generic and elegent as possible not to brut-force your way by hard coding everything :)

Impress us !

Correction

A correction will be given either during the lesson or afterwards. It will be on Discord and here on this repo. This will not be the only nor the best solution to the problem but do take the time to read it as it uses advanced concepts ! makefile

Fun fact, this page was generated using pandoc and make

link