Makefiles
This post is an automatic translation from French. You can read the original version here.
The stream from our friend Imil from last Saturday was dedicated to good old make, and it was a chance to review the basics of Makefiles. Nothing too complicated, but it feels good and I still learned a few things along the way! Here are some personal notes, which might be useful to others besides myself.
The basics
A makefile is mainly composed of two types of blocks:
-
Variable declarations
1
VARIABLE = value
-
Recipe descriptions for generating files
1 2 3
target: dependency1 dependency2 dependency3 ... dependencyN (tab) command line (tab) command line
Warning: It must be a tab, not spaces!
For example:
CC = gcc
monprog: main.o bidule.o
${CC} -o monprog main.o bidule.o
main.o: main.c
${CC} -c main.c
bidule.o: bidule.c
${CC} -c bidule.cYou can clearly see the structure of the “recipes”: to build the target main.o, for example, you need main.c and you apply the command lines in order. Here, it’s easy, there’s only one (gcc -c main.c).
By the way, you can see the use of a variable. Here, curly braces are used. I didn’t know this, but you can also use parentheses! Moreover, if you don’t use any, the $ will only apply to the first character… :-(
Invoking make
To execute one of the recipes, you invoke make with the target you want to build as an argument. For example, to build main.o:
1
make main.o
If necessary, the recipes for the dependencies will also be executed:
$make monprog
cc -c bidule.c
cc -c main.c
cc -o monprog main.o bidule.o
$That’s pretty much the whole point of the thing!
To determine whether a recipe needs to be executed or not, make relies on file modification times.
By default, make launches the first target in the Makefile when run without arguments. Therefore, it’s best to place the recipe generating the executable first.
Writing generic rules
You don’t need to write rules for absolutely every file… Especially when those rules are exactly the same. So you have the option to create generic rules.
CC = gcc
OBJ = main.o bidule.o fonk.o
all: projet
projet: $(OBJ)
$(CC) -o projet $(OBJ)
%.o: %.c
$(CC) -c $<The last rule here is a generic rule. It will be applied for building any .o file.
Another syntax for writing a generic rule:
.c.o:
$(CC) -c $<Which can be read as “To transform a .c into a .o”… And as a result, the order is reversed: the dependency first, the target second!
As you can see above, there are special variables that allow you to write generic rules in a… well, generic way. There are (at least) five of them, but the first three seem the most useful to me:
- $@ : The target of the rule
- $< : The first dependency
- $^ : The dependencies
- $? : The dependencies newer than the target
- $* : The target name, without suffix
While we’re at it, it’s also possible to silence a command line and not display it. To do this, simply prefix the line with @. For example:
CC = gcc
OBJ = main.o bidule.o fonk.o
all: projet
projet: $(OBJ)
@$(CC) -o projet $(OBJ)
%.o: %.c
@$(CC) -c $<Implicit rules
The make tool applies, in addition to the compilation rules described in the Makefile, a set of default rules. For the GNU version of make, you can find them here:
If you want to take advantage of these rules, it’s best to follow good habits when choosing variable names. For C, you would use:
- ${CC} for the compiler name
- ${CFLAGS} for compilation options
- ${LDFLAGS} for linking options
There are many more: for C++, Lex, Yacc, Fortran, etc. You can find the list here:
The .PHONY target
The .PHONY target is a special target that lets make know that certain rules don’t result in the creation of a file, and therefore it shouldn’t rely on the modification date of the dependencies for those rules. You would use it, for example, for a “clean” target that removes temporary files, or a “mrproper” target that really cleans up.
build:
gcc -o foo foo.c
.PHONY: clean
clean:
rm -f *.oA few useful things
Assignment with ?=
If you assign a variable using the = operator, the old content of that variable is erased. With ?=, you can assign a value only if the variable hasn’t already been declared previously. For example:
CC ?= gcc
test: main.c
${CC} -o test main.cThis is useful, for example, when this Makefile can be called from another Makefile.
Overriding a variable from the command line
When a variable is defined in the Makefile, you can override it by specifying it on the command line. For example:
CC = gcc
test: main.c
${CC} -o test main.c$make CC=echo
echo -o test main.c
-o test main.cAs you can see, the ${CC} variable is overridden by the command line. Contrary to what I thought, whether it was assigned with = or ?= doesn’t matter: the command line takes priority (and when you think about it, that makes perfect sense!)
Conditionals
You can also use conditionals… Handy!
DEBUG = 1
build:
ifeq ($(DEBUG), 1)
gcc -Wall -Werror -o foo foo.c
else
gcc -o foo foo.c
endifWildcards
To avoid long lists of files to type by hand, and especially to maintain and update:
SRC= $(wildcard *.c)
OBJ= $(SRC:.c=.o)BSD style… Wow!
Honestly, this is classy! Granted, I don’t think it’s ultra portable, but it’s worth a look:
PROG = fin
.include <bsd.prog.mk>It’ll be hard to get more concise than that!!!