There exist several types of make1). This document will treat those:
Most widespread is gmake, it is probably that which offers the most possibilities. If you want to have a maximum of compatibility on various systems, better is worth to use gmake3).
When you type make with invites orders you carry out one or the other according to your operating system. If you are on NetBSD it will be pmake. If it is on Linux will be gmake. Throughout this document, functionalities of gmake will be developed. If at the time of the examples something does not function, try to type gmake rather than make.
At runtime gmake will read in the current directory the makefile. In this order gmake will seek for GNUMakefile, Makefile, makefile. If you carry out a makefile in the spirit GNU name GNUMakefile therefore if pmake is carried out it will not find the file and stops. This avoids exclamations: “Oh yes that work only with gmake!”.
For more information on make:
Convention:
– Makefile – : indicate the contents of a file Makefile (compatible gmake, pmake) – GNUMakefile – : indicate the contents of Makefile (gmake) The first question which should be wondered is:
The utility is not inevitably obvious. Especially if one never did not program his life and/or passed from long hours to recompile a small end of program still and still, because tiredness or laziness makes us pass in empirical programming mode, to test all the possible values. When we looks at the first line of the man of make (BSD) we can read:
make - maintain program dependencies
There it’s necessary to remember the stages of generations of a executable. Let us take C compilation’s stages. To pass from a simple textual file to functional executable. There are several stages
Cpp will read a file .c and to generate a file .i the purpose of this stage is to carry out a certain number of handling of text:
The output file still looks like C but does not contain any more the directives (#define, #ifdef or #include) and the macros are expanded
Cc1 will read a file .i and to generate a file .s This stage will interpret C code and will transform it into pseudo-assembler. At this point the compiler can detects syntaxicals errors.
As will a file .s and to write a file .o This stage will read the code assembler and to transform it into machine language. Most important it is especially the creation of the output file says object file. This object file can be transmitted, with its header (.h) to another programmer. It will be able to use the functions contained in the file object but without it being able to see the original C code.
Ld will take a whole of files objects or archive files (.a or .lib) and will bind/link them between and will solve the symbols addresses. It will allow that a function can find and carry out another function which is not defined in the same file object, but in another, compiled separately.
Once this stage is finished, ld generates an executable
Ar will create an archive file which will contain, the set of objects files in parameters.
Ranlib creates an index in a file files. Ranlib will have to be executed each time the archive file is modified.
.c -> cpp (gcc -E) -> .i .i -> cc1 (gcc -S) -> .s .s -> as (gcc -c) -> .o .o ou .a -> ld (gcc) -> a.out (ou elf, pe...)
What is generally longest, at the time of the creation of a program, this is the generation of the various files objects. If we wants modify a function contained in one of the object files this is not necessary to recompile all of them, this is enough to regenerate the concerned object file.
fonct_a __ fich_ab.c --[gcc -c]-> fich_ab.o ______[ld]-> abcdef
fonct_b / /
/
fonct_c __ fich_cd.c --[gcc -c]-> fich_cd.o ___/
fonct_d / /
/
fonct_e __ fich_ef.c --[gcc -c]-> fich_ef.o /
fonct_f /
If fonct_a is modified, we just need to recompile the file fich_ab.c and to bind all the files objects to obtain the executable with fonct_a modified in the code. Obviously, it appears less tiring to recompile everything when we do not remember on which files we were working.
But as the data processing specialist pretending there is a way to automate this task, by a tool.
This tool, is make. But make can be useful for other compilers like g++, latex, and even of system administrators (to update bases NIS).
Basically, make functions simply by comparing the dates of the files. It is just enough to specify the files that make must supervise. For that it is necessary to write a rule. A rule is written in this form:
nom_du_fichier : dépendance_à_satisfaire
les_choses_à_faire
encore_des_trucs_à_faire
toujours_des_bidules_à_faire
qui_souvent_finissent_par_créer_le_fichier
A small example to clarify all that. The great classic of the hello world, but is it up to date ? Create a repertory and do copy and paste in a Makefile file the following lines.
-- Makefile -- hello : touch hello --
Publish the file and check that there is a tabulation well in front of ‘touch hello‘. Then, made following operations.
sh> ls Makefile
With this stage, there is one file: Makefile
sh> make touch hello
Make is carried out, it tries to solve the dependencies of the rule hello. However the first of the dependencies it is the file which has the same name as the rule. \ \ Then make raises the question: Est this that the file ' hello’ exists?. \ \ Not, there does not exist \ \ Ensuite there wonders: Ai I of the dependencies to be satisfied?. \ \ Not, it does not have of it there \ \ Enfin it can carry out the orders to create the file: ' touch hello’
sh> ls Makefile hello
One sees clearly that the file hello now exists.
sh> make `hello' is up to date.
Make is carried out, it tries to solve the dependencies of the rule hello. The file hello exists. Then make checks the dependencies of the rule hello to compare their dates with that of the file hello. But there are dependencies, make for of not concluded that the file hello is up to date, therefore it does not need to remake the stages to create the file
In the preceding part, one employed wrongly a certain number of terms. For example, the concept of dependencies does not correspond to nothing. But it is often what one hears (in engine room). From now one will employ the good terms. A rule is made up:
A rule can be written in two ways. Once will start by writing the target followed of ‘:’, then then them pre-necessary. And on each line which will follow the target, one will be able to write an order, but each line of orders will have imperatively to start with a tabulation. All that is alignment and interval are with the appreciation of the artist. ex:
cible : prérequis
it drank is necessary thenecessary ones, bases for us. With these bases one can using orders, tools to build this target. One will take another example. Recopy these lines in Makefile. Take guard with the tabulations and check that all the files hello, coucou and salut indeed were unobtrusive.
-- Makefile -- hello : coucou salut touch hello coucou: touch coucou salut : touch salut --
With these rules one says: to make the file hello one needs the file coucou and the file salut. Here how this comprises make:
> rm coucou hello salut > ls Makefile > make touch coucou touch salut touch hello > ls Makefile coucou hello salut > rm hello > make touch hello
If one erases the target hello, whereas all them pre-necessary exist, then only the target is recreated.
> rm coucou > make touch coucou touch hello
If it one erases to it pre-necessary coucou, then it pre-necessary and the target are recreated
Make is equipped with variables being able to contain text. To assign a value to a variable, following syntax is used:
VARIABLE = un texte
To exploit the contents of a variable, it is necessary to make precede the identifier by a $.
UNEAUTREVARIABLE = $(VARIABLE) ou uneregle : unecommande $(VARIABLE)
One can also call a variable without using the brackets, but one exposes oneself to certain errors.
-- Makefile --
V = hihi
VARIABLE = un texte
all:
echo $(VARIABLE)
--
Carry out make, look at the result. Then modify the line:
echo $(VARIABLE) par echo $VARIABLE
In one of the cases one will not obtain the awaited result, for this reason it is better always to use the brackets to exploit a variable. When one assigns a value to a variable this one takes the values of the right part of the expression, until the end of the line. But this one should not contain any of these characters: ‘:‘, ‘=‘, ‘#‘. If one wants to affect several lines has a variable to place a backslash ‘\’ before the end of the line.
-- Makefile --
SRC = fonctions_simple.c \
fonctions_un_peu_plus_dures.c \
fonctions_dures.c \
fonctions_plus_dures.c \
fonctions_tres_dures.c \
fonctions_grave_dures.c \
fonctions_meta_dures.c
all:
echo $(SRC)
--
Several functions of handling of text are included. But they here will not be detailed. You can find the other functions of handling of text in ‘info make’. A function is written in the following way (either with ‘()’ or with ‘{}’):
$(FONCTION ARGUMENTS)
ou
${FONCTION ARGUMENTS}
patsubst5) makes it possible to carry out substitutions.
$(patsubst MOTIFDECHERCHE,MOTIFDUSUBSTITUTION,LAVARIABLE)
The character ‘%’ is a méta character. It represents all the possibilities of characters (except well on space and the tabulation) that the reason for research can satisfy. If ‘%’ is present in the reason for substitution it the value of the ‘%’ in the terrible reason for recherche6). If an error intervenes during the execution of make, for this example, type gmake instead of make.
-- GNUMakefile --
SRC = fichier_source_de_fonctions_simples.c \
fichier_source_de_fonctions_un_peu_plus_durs.c \
fichier_source_de_fonctions_durs.c \
fichier_source_de_fonctions_plus_durs.c \
fichier_source_de_fonctions_tres_durs.c \
fichier_source_de_fonctions_grave_durs.c \
fichier_source_de_fonctions_meta_durs.c
OBJ = $(patsubst fichier_source%.c,fichier_objet%.o,$(SRC))
all:
echo $(OBJ)
--
Generally one will be satisfied to substitute just the suffix (c towards o).
$(patsubst %.c,%.o,$(SRC))
This function can be also written in this form:
$(SRC:.c=.o)
It is the only form accepted by pmake. The character ‘%’ is available in this form that for gmake.
-- Makefile --
SRC = fichier_source_de_fonctions_simples.c \
fichier_source_de_fonctions_un_peu_plus_durs.c \
fichier_source_de_fonctions_durs.c \
fichier_source_de_fonctions_plus_durs.c \
fichier_source_de_fonctions_tres_durs.c \
fichier_source_de_fonctions_grave_durs.c \
fichier_source_de_fonctions_meta_durs.c
OBJ = $(SRC:.c=.o)
all:
echo $(OBJ)
--
For the moment we saw that make is able, with rules, to compare dates of files and to handle text. For this part it is necessary to prepare some files cans for the tests.
> touch fonctions_simples.c \
fonctions_un_peu_plus_dures.c \
fonctions_dures.c \
fonctions_plus_dures.c \
fonctions_tres_dures.c \
fonctions_grave_dures.c \
fonctions_meta_dures.c
> echo 'int main(int argc, char **argv) {return 0;}' > main.c
One now will carry out small Makefile, which has as a characteristic to be compatible with the various types existing of make.
-- Makefile --
NAME = bidon
SRC = fonctions_simples.c \
fonctions_un_peu_plus_dures.c \
fonctions_dures.c \
fonctions_plus_dures.c \
fonctions_tres_dures.c \
fonctions_grave_dures.c \
fonctions_meta_dures.c \
main.c
CFLAGS = -Wall
OBJ = $(SRC:.c=.o)
$(NAME) : $(OBJ)
$(CC) $(OBJ) -o $(NAME)
--
> make cc -Wall -c -o fonctions_simples.o fonctions_simples.c cc -Wall -c -o fonctions_un_peu_plus_dures.o \ fonctions_un_peu_plus_dures.c cc -Wall -c -o fonctions_dures.o fonctions_dures.c cc -Wall -c -o fonctions_plus_dures.o fonctions_plus_dures.c cc -Wall -c -o fonctions_tres_dures.o fonctions_tres_dures.c cc -Wall -c -o fonctions_grave_dures.o \ fonctions_grave_dures.c cc -Wall -c -o fonctions_meta_dures.o fonctions_meta_dures.c cc -Wall -c -o main.o main.c cc fonctions_simples.o fonctions_un_peu_plus_dures.o \ fonctions_dures.o fonctions_plus_dures.o \ fonctions_tres_dures.o fonctions_grave_dures.o \ fonctions_meta_dures.o main.o -o bidon
Here, we observe the orders called upon by make. However, in Makefile, one calls upon explicitly that only one order:
$(CC) $(OBJ) -o $(NAME)
It is well the last line which one finds. But then from which the other orders come? It is where make becomes magic. However one can find the response in the statement of the rule.
$(NAME) : $(OBJ) $(CC) $(OBJ) -o $(NAME)
To make $(name) one must carry out $(CC) $(OBJ) -o $(NAME). But to make this order the files objects contained in $(OBJ) must be present. Make then is able all alone to build these files objects thanks to implicit rules. Make knows a certain number of implicit rules. To build a file object, make will carry out the suitable order. For a file object it is:
$(CC) $(CFLAGS) -c -o src.o src.c $(CC) $(CFLAGS) $(CPPFLAGS) -c -o src.o src.c (pour gmake)
There are other magic points, in any moment one defined variable DC however make knows a value of it to him. It is the case for other variables (for gmake in any case):
There exists another type of variable which is used in the implicit rules such as CFLAGS which makes it possible to modify the order without having to rewrite it. This type of variable is called the variables not very doubtful automatiques.(un as explanation) One will rewrite this implicit rule. Do not forget to erase the files objects and the target before testing this example.
-- Makefile --
NAME = bidon
SRC = fonctions_simples.c \
fonctions_un_peu_plus_dures.c \
fonctions_dures.c \
fonctions_plus_dures.c \
fonctions_tres_dures.c \
fonctions_grave_dures.c \
fonctions_meta_dures.c \
main.c
CFLAGS = -Wall
OBJ = $(SRC:.c=.o)
$(NAME) : $(OBJ)
$(CC) $(OBJ) -o $(NAME)
.c.o :
@echo
@echo cible $@
@echo source $<
$(CC) $(CFLAGS) -c $< -o $@
--
Now what says this rule? To convert a source file into file object, there is no pre-necessary, seemingly, but make knows that it needs the source file. It will be, in this case the alone pre-necessary one.
.c.o : $(CC) $(CFLAGS) -c $< -o $@
The variable $@ represents the target and the variable $< le 1er des pré-requis.
Pour le fichier main.o on aurait pu écrire :
main.o : main.c $(CC) $(CFLAGS) -c main.c -o main.o
This says if one must write all the rules for each file object that will be a little long. You can all Makefile of base
Each order can be preceded by a prefix which modifies the interpretation of the order by make. Here the list of the prefixes:
‘@’ does not print the order before carrying out it.‘-’ does not stop the execution of make if this order fails.‘+’ does not stop the execution of make if this order fails even with the options - Q, - T, - K, - N, etc... What makes it possible to make some fancy tricks.
-- Makefile --
NAME = bidon
SRC = fonctions_simples.c \
fonctions_un_peu_plus_dures.c \
fonctions_dures.c \
fonctions_plus_dures.c \
fonctions_tres_dures.c \
fonctions_grave_dures.c \
fonctions_meta_dures.c \
main.c
CFLAGS = -Wall
OBJ = $(SRC:.c=.o)
RM = rm
$(NAME) : $(OBJ)
@echo . $(NAME) done
@$(CC) $(OBJ) -o $(NAME)
.c.o :
@echo -n .
@$(CC) $(CFLAGS) -c $< -o $@
all : $(NAME)
clean :
-$(RM) $(OBJ)
--
Veiled a use of the ‘@’ and ‘-‘. Look at appearance of the rules all and clean. By convention these two rules must always be present in a makefile. Moreover they make it possible to call upon make in this form: make clean all.
For facility a certain number of operation in the rules, there exist local variables with the rule. These variables are sometimes called: Variables automatiques Here the list of most current:
$@ Represents the name of the current target of the rule.$< Représente le nom du premier des pré-requis.$^ Représente les noms de l’ensemble des pré-requis (pour gmake).$> Représente names of the whole of pre-necessary (for pmake).
$? Represents the names of the whole of pre-necessary more recent than the target.$* contains only the prefix to cible.(Voir it the example) the variables do not have only the operator ‘=’ to affect values. Here the list of all the operators:
‘=’ known as recursiv If the right part of the expression contains a reference, like a variable or a function, this one is evaluated only at the time of the resolution of the variable.7)‘:=’ known as static If the right part of the expression contains a reference this one will be solved as of the assignment of the variable.‘+=’ known as concat concatene at the end of the current value of the variable the value of the right part of the expression.‘?=’ known as condi assigns the value to the variable only if this variable does not have a value.‘!=’ known as shell This operator exists only for pmake, equivalent it under gmake is the function “$(shell cmd)“. So the right part of the expression is solved, if it there has references, and is carried out in Shell.
-- GNUMakefile --
DATE_REC = $(shell sleep 2; date)
DATE_STA := $(shell sleep 2; date)
APP ?= salut
APP += les
APP += amiches
APP ?= ta mere
rec_or_sta :
@echo Recursif ou statique ?
@echo before rec : $(DATE_REC)
@echo before sta : $(DATE_STA)
sleep 7;
@echo after rec : $(DATE_REC)
@echo after sta : $(DATE_STA)
append_and_cond :
@echo $@
@echo $APP;
--
Look at the behavior of DATE_STA well and DATE_REC, the variable affected into recursive are evaluated has each time that it is called. Whereas the other is evaluated good once for all at the time of its evaluation. For the other types of evaluation the example speaks about him even
As one saw before, a rule has a target. This target represents a file to be produced. It can happen that one needs to create a rule which does not need a target: it is the case of the rule all or clean. One can make this small joke with any person not including/understanding well the operation of make. If a person had suddenly placed a file which is named as the rule, then make would consider that it is not necessary to carry out the rule. As a ‘touch all’ or a ‘touch clean’ would give for result:
> make clean 'clean' is up to date.
There is a special rule which makes it possible to avoid the checking between the target of a rule and a file bearing the same name possibly present. It is enough to add:
.PHONY : clean all
There are other special rules... seek in the docs lazy people !
For the moment all the rules which one saw, only one target had. A rule with multiple targets can be useful in several cases. If one do not want to write the same orders for two targets. If one knows two targets which have the same pre-necessary ones.
-- Makefile --
all : salut coucou hihi bouh
echo \< $<
echo \> $>
echo \^ $^
echo \? $?
echo \* $*
salut coucou hihi bouh :
echo $@
.PHONY .SILENT : salut coucou hihi bouh all
--
A rule with multiple targets can be very useful to traverse repertories or to make it possible to add thenecessary ones. For example, in the case of C programming, the makefile which one carried out for the moment never do not check if headers (fichier.h) were modified since the last time. Here a simple way:
-- Makefile -- NAME = bidule SRC = main.c truc.c chose.c INC = gen.h syst.h OBJ = $(SRC:.c=.o) $(NAME) : $(OBJ) $(CC) -o $(NAME) $(OBJ) $(OBJ) : $(INC) --
The only disadvantage of this method if the file chose.c does not include syst.h, will be recreated if the file syst.h is modified.
(in work)
In this part, one will develop more complex functionalities to solve a problem given
We would like to be able to compile the files objects in another repertory to avoid certain nuisances. For example at the school the accounts users are on the network. The successive writings and readings to write the files objects can involve, if 400 people do it at the same time, certain nuisances. To avoid all that, it would be enough to place the files objects in a repertory locally on the machine while making it possible make to continue to check these dependences
By writing this document we discovered the functionalities of pmake. In spite of what one could believe pmake in almost as many possibilities as gmake. For containing the functionalities in the binary one as in gmake, it is necessary to include the files which contains these functionalities. What makes it possible to write a makefile in two lines.
-- Makefile -- PROG = bidon .include "bsd.prog.mk" --
This makefile makes it possible to build the program with the source file of the same name as the program with the suffix .c (bidon.c in this case), as well as the man associated the suffix ‘[1-9]’ (bidon.1). It thanks to this file is included “bsd.prog.mk“, than one has access to all the functionalities of a large makefile. In this form with the rules: “clean“, “cleandir“, “depend“, “install“, “tags“, etc...
But also with the preset variables: RM, CC, LIBC, LIBCURSES, etc.
This is rather practical! You have a header? This is enough for you to call pmake with the rule “depend” and a file “depend” is created. This file is included at the time of the interpretation of the makefile and make it possible to add the dependencies of the source file.
You have several source files ? Add Variable SRCS, and give the list of the files to be used.
You do not want create man with your sources? Add this line to your makefile:
NOMAN = noman
You want to build the file object in another repertory?
No the problem... Add variable BSDOBJDIR in your makefile:
BSDOBJDIR = obj
In conclusion, if you are on a system with pmake and that you do not hope to make a makefile for another system which a BSD, use this process... adonf™.
-- Makefile -- PROG = bidon NOMAN = noman BSDOBJDIR = /tmp/obj SRCDIR = src SRC = $(SRCDIR)/fonctions_dures.c \ $(SRCDIR)/fonctions_grave_dures.c \ $(SRCDIR)/fonctions_meta_dures.c \ $(SRCDIR)/fonctions_plus_dures.c \ $(SRCDIR)/fonctions_simples.c \ $(SRCDIR)/fonctions_tres_dures.c \ $(SRCDIR)/fonctions_un_peu_plus_dures.c \ $(SRCDIR)/main.c .include "bsd.prog.mk" --
For these examples you must carry out the following operations:
> mkdir src obj
> touch src/fonctions_simples.c \
src/fonctions_un_peu_plus_dures.c \
src/fonctions_dures.c \
src/fonctions_plus_dures.c \
src/fonctions_tres_dures.c \
src/fonctions_grave_dures.c \
src/fonctions_meta_dures.c
> echo 'int main(int argc,char **argv) {return 0;}' >src/main.c
-- GNUMakefile --
NAME = bidon
SRCDIR = src
OBJDIR = obj
SRC = $(SRCDIR)/fonctions_dures.c \
$(SRCDIR)/fonctions_grave_dures.c \
$(SRCDIR)/fonctions_meta_dures.c \
$(SRCDIR)/fonctions_plus_dures.c \
$(SRCDIR)/fonctions_simples.c \
$(SRCDIR)/fonctions_tres_dures.c \
$(SRCDIR)/fonctions_un_peu_plus_dures.c \
$(SRCDIR)/main.c
OBJ = $(subst $(SRCDIR),$(OBJDIR),$(SRC:.c=.o))
$(NAME) : $(OBJ)
@echo Making $@
@$(CC) -o $@ $(OBJ)
all : $(NAME)
clean :
-$(RM) $(OBJ)
fclean : clean
-$(RM) $(NAME) *~
re : clean all
$(OBJDIR) :
mkdir -p $(OBJDIR)
$(OBJDIR)/ @echo Compile $*
@$(CC) -c $< -o $@
.PHONY : re fclean clean all
--
With gmake one can use the meta-character %+ to recover a reason and to re-use it in thenecessary ones. One recovers the reason with the variable $* in the part orders.