URL: https://linuxfr.org/forums/programmationautre/posts/makefile-generique-pour-les-petits-projets Title: Makefile générique pour les petits projets Authors: needs Date: 2013年07月03日T21:18:03+02:00 License: CC By-SA Tags: make, compilation et makefile Score: 16 J'ai plein de petits projets (en C) et quand j'en commence un nouveau, ce qui prend du temps c'est de refaire un Makefile. Pourquoi ne pas utiliser une alternative comme CMake alors ? Et bien parce que j'aime tout ce qui est rustique. J'ai ainsi conçu un Makefile qui répond à mes besoins en matière de compilation, qui sont, pour un même projet : * Gérer la construction de plusieurs binaires * Avoir la possibilité de définir plusieurs "modes" de compilation (Release, debug, ...) * Générer automatiquement les dépendances, et en une seule fois (pas de `make depends`) * Créer les fichiers objets dans un répertoire dédié pour avoir une arborescence lisible * Être Capable d'utiliser des fichiers en C et C++ pour un même binaire * Avoir une cible `make clean` pour chaque binaire * Pouvoir changer facilement de compilateur * Et enfin, que la configuration de la compilation soit aisée Je voulais aussi accessoirement que mon Makefile utilise le plus possible les possibilités offerte par make, et qu'il reste court, c'est-à-dire ne pas dépasser les 100 lignes. Je souhaitais aussi le rendre compatible avec d'autre version de Make comme celle utilisée par les BSD. Mais je ne m'y suis pas encore penché pour l'instant. Néanmoins, il est compatible GNU Make 3.81 et GNU Make 3.82. Ah oui, une dernière broutille, mon Makefile [ne devait pas être récursif](http://iq0.com/notes/nonrecursive.make.html). # La configuration de la compilation # J'ai placé la configuration dans un fichier appelé `conf.mk`, qui pilote le fichier qui contient les instructions pour construire les binaires : `Makefile`. Techniquement, la première ligne du `Makefile` est : ```make include conf.mk ``` Le but est de ne pas avoir à modifier le `Makefile` lorsque l'on configure la compilation. (Vous comprendrez un peu plus bas pourquoi il vaut mieux pas trop le modifier ;) ) Le fichier `conf.mk` indique quels sont les binaires à construire, à partir de quel fichiers. Voici le template que je copie-colle à côté de mon Makefile à chaque nouveau projet : ```make # # Configuration de la compilation # # Options pour le compilateur : CFLAGS := -Wall # Il est possible de définir plusieurs mode de compilation. # Utilisez la macro "MODE" pour sélectionner le mode. # CFLAGS_RELEASE := -02 # CFLAGS_DEBUG := -g -Wall # MODE := RELEASE # Compilateur, par défaut "gcc" # CC := g++ # Nom du dossier ou sont placé les fichiers objets, par défaut ".build" # BUILDDIR := .objs # # Binaires # # Noms de tout les binaires construit par le Makefile BINS := prog1 # Pour chaque programme, indiquez les sources, et les options # pour l'éditeur de liens (optionnel). Il est possible d'utiliser '*'. SRCS_prog1 := src/*.c LDFLAGS_prog1 := ``` J'utilise intentionnellement `:=` au lieu de `=` pour des question de performances : utiliser `:=` permet d'évaluer immédiatement l'expression qui suit. Si j'utilise `=`, alors make considère que c'est une macro et remplacera bêtement chaque occurrences par son contenu, et l'évaluation ne se fera qu'à postériori. Si cette macro contenait une commande, cette dernière serait alors plusieurs fois évaluée, ce qui est rarement l'effet voulut. Cela dit, il n'y a pas de commandes dans cet exemple. [Cet article](http://iq0.com/notes/nonrecursive.make.html)1 (Section 5.2) explique probablement mieux que moi la différence. # Comment l'utiliser # Placez le template ci-dessus à la racine de votre projet, avec le Makefile que je vais décrire un peu plus bas. Voici ce que je peux faire avec le fichier de configuration que j'ai montré ci-dessus : * `make` ou `make all` pour construire tout les binaires (ici un seul : prog1) * `make prog1` pour construire uniquement un binaire en particulier * `make clean` pour nettoyer tous les fichiers objets * `make clean_prog1` pour nettoyer uniquement les fichiers objets d'un binaire (ici prog1) Les dépendances, les fichiers objets, tout est généré automatiquement. **Soyez prudent lorsque vous utilisez `make clean`** et faites un backup de votre projet : il n'est pas certain que les fichiers supprimés soient les bons, l'erreur est humaine. # Le makefile # Voici à présent le `Makefile` à placer à coté de votre `conf.mk`... Ah bon, c'est illisible ? :D ```make include conf.mk CC ?= gcc BUILDDIR ?= .build CFLAGS ?= $(CFLAGS_$(MODE)) ifeq ($(MAKECMDGOALS),) MAKECMDGOALS = $(BINS) endif all: $(BINS) # Génère les dépendances et la compilation d'un fichier source define COMP_template OBJ = $$(subst $$(suffix $(1)),.o,./$$(BUILDDIR)/$(1)) DEP = $$(OBJ:.o=.d) $$(OBJ): $(1) @$(CC) -o $$@ -c $(1) $(CFLAGS) $$(DEP): $(1) @echo -n "$$(dir $$@)"> $$@ @if ! $(CC) -MM $(1) 2> /dev/null>> $$@; then> $$@; fi; endef # Génère la compilation d'un binaire define BIN_template SRCS := $$(wildcard $$(SRCS_$(1))) ALL_SRCS := $$(ALL_SRCS) $$(SRCS) OBJS_$(1) := $$(filter %.o,$$(foreach s,$$(sort $$(suffix $$(SRCS))),$$(patsubst %$$(s),./$$(BUILDDIR)/%.o,$$(SRCS)))) DEPS_$(1) := $$(OBJS_$(1):.o=.d) $(1): $$(OBJS_$(1)) @$(CC) $$^ $$(LDFLAGS_$(1)) -o $$@ ifeq ($$(filter $(1), $(MAKECMDGOALS)),$(1)) $$(shell mkdir -p $$(sort $$(dir $$(OBJS_$(1))))) -include $$(DEPS_$(1)) else clean_$(1): @rm -f $$(OBJS_$(1)) $$(DEPS_$(1)) @rm -f $(1) ALL_CLEANS += clean_$(1) endif endef $(foreach b,$(BINS),$(eval $(call BIN_template,$(b)))) $(foreach s,$(sort $(ALL_SRCS)),$(eval $(call COMP_template,$(s)))) .PHONY: all clean $(ALL_CLEANS) clean: $(ALL_CLEANS) $(foreach d,$(sort $(dir $(addprefix $(BUILDDIR)/,$(ALL_SRCS)))),$(shell rmdir -p $(d) 2> /dev/null)) ``` Pour ceux qui aimerait bien y voir plus clair (c'est compréhensible), voici quelques informations : * `MACRO ?= valeur`, assigne 'valeur' à `MACRO` uniquement si `MACRO` n'a pas été définit avant, une sorte de valeur par défaut. * Make propose [quelques fonctions bien utiles](http://www.gnu.org/software/make/manual/make.html#Functions), que se soit pour manipuler du texte ou faire des boucles. * J'utilise à deux reprise [l'astuce montré dans la doc sur eval](http://www.gnu.org/software/make/manual/make.html#Eval-Function) pour générer dynamiquement les cibles avec leurs dépendances. (On pourrait considérer les macros `COMP_template` et `BIN_template` comme des fonctions) * `$(MAKECMDGOALS)` contient [la liste des cibles à construire](http://www.gnu.org/software/make/manual/make.html#Goals). Par exemple, si j’exécute `make prog1`, `$(MAKECMDGOALS)` contiendra `prog1` * Les `$$` que vous voyez à plusieurs reprise, permettes d'échapper le `$`. Voir [la documentation sur la fonction eval](http://www.gnu.org/software/make/manual/make.html#Eval-Function) qui explique pourquoi c'est utile. J'utilise beaucoup les fonctions proposées par make, allez jeter un coup d’œil [à la documentation](http://www.gnu.org/software/make/manual/make.html) si vous voulez en apprendre plus. # Conclusion # En guise de conclusion, je dirais que les Makefiles, c'est comme le Perl : illisible mais puissant. Nan, plus sérieusement, j'espère que ce Makefile vous sera utile. C'est un projet qui n'est pas terminé, je reviens dessus quelque fois pour corriger des bugs ou l'améliorer. La syntaxe de make fait peur, mais dieu que c'est puissant ! _C'est plus facile d'écrire un Makefile que de ne pas faire de fautes d'orthographe ;)_ (1): Rien à voir, mais [ce site contient une flopée d'articles](http://iq0.com/notes/index.html) qui en intéressera plus d'un !