Pages

mercredi 26 juin 2013

make sans Makefile, utilisation des règles implicites

La commande GNU make possède énormément de commandes implicites. En fait, tout est regroupé dans un Makefile 'par défaut' avec plein de règles. Celui-ci est visible en tapant `make -pf /dev/null` dans un terminal.

Rien qu'avec ça, on peux compiler des fichiers C, C++, archive, latex, etc. Il y a de quoi faire en fait.

Par exemple, je crée un fichier C nommé test.c

int main(int ac, char ** av) {
  return ac;
}

compilation: `make test`
Ceci crée l’exécutable test. Mais si on veut avoir des fichiers objets, il faut le demander explicitement: `make test.o test`.

Maintenant on remplace test.c par test.cpp et on inclut iostream.

compilation: `make test`
avec un objet: `make test.o test`.

Ah ! En fait non, le second ne fonctionne pas.
test.o est compilé avec cc et comme iostream est linker avec une lib, l'implémentation du constructeur std::ios_base::Init n'est pas trouvée. L'idéal serait d'utiliser g++.

Si ce n'est qu'une question de compilateur, changeons-le !

Tout d'abord, la règles de compilation d'un .o vers un exécutable:

$ make -pf/dev/null | grep '%: %.o' -A3
%: %.o
#  commands to execute (built-in):
        $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@

Ça utilise pour compiler la variable LINK.o. Soit, qu'est ça valeur ?

$ make -pf/dev/null | g 'LINK.o ='
LINK.o = $(CC) $(LDFLAGS) $(TARGET_ARCH)

Qui fait référence à CC. On s'approche, vérifions:

$ make -pf/dev/null | g 'CC ='
CC = cc

Bingo \o/. Il suffit donc de changer la valeur de CC.

On recommence: `make CC=g++ test.o test` et ça fonctionne :).

Cette petite excursion permet mine de rien de découvrir pas mal de variables. Au passage, il est plus intéressant de les utiliser que d'en recréer des nouvelles. Idem dans les règles de dépendances. S'il faut les personnaliser, autant garder les mêmes commandes à exécuter.

Par exemple, pour compiler un .cpp en .o:

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

Généralement les sources sont également dépendantes de fichier d'en-tête. Si on fait une règle qui prend en compte cela, autant garder la même commande:
(petite parenthèse pour dire que gcc possède l'option -MM pour lister les dépendances entre les fichiers.)

bidule.o: bidule.cpp bidule.h machin.h
        $(COMPILE.cpp) $(OUTPUT_OPTION) $<

Ainsi, quand CXXFLAGS sera modifié, la règle le prendra en compte car COMPILE.cpp = COMPILE.cc
Et COMPILE.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c

À noter que la variable CPPFLAGS fait référence à la commande cpp (ou option -E de gcc) et concerne le C-preprocessor.

Bien sûr, la technique du Makefile seul a des limites. Par exemple, s'il y a plusieurs fichiers C, je peux créer tous les fichiers objets avec `make *.o` mais l’exécutable ne sera dépendant que d'un fichier. Au final les règles par défaut ne permettront pas de créer l’exécutable, il faudra faire `gcc *.o` ou un Makefile avec au minimum a.out: *.o; $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@.

Voilà, ce fut un petit post pour découvrir l’existence des règles implicites et des variables prédéfinies :).

manual de make