web-dev-qa-db-fra.com

Makefile (génération automatique de dépendances)

juste pour une terminologie rapide: 

#basic makefile rule
target: dependencies
    recipe

Le problème: je veux générer les dépendances automatiquement.

Par exemple, j'espère transformer ceci: 

#one of my targets
file.o: file.cpp 1.h 2.h 3.h 4.h 5.h 6.h 7.h 8.h another.h lots.h evenMore.h
    $(COMPILE)

Dans ceci:

#one of my targets
file.o: $(GENERATE)
    $(COMPILE)

et je ne suis pas trop sûr si c'est possible .. 

Ce que je sais:

Je peux utiliser cet indicateur de compilateur: 

g++ -MM file.cpp

et il retournera la cible et la dépendance appropriées.
de l’exemple, cela renverrait: 

file.o: file.cpp 1.h 2.h 3.h 4.h 5.h 6.h 7.h 8.h another.h lots.h evenMore.h  

cependant, 'make' ne me permet PAS d'écrire explicitement le code Shell dans la section cible ou de dépendance d'une règle :(
Je sais qu’il existe une fonction 'make' appelée Shell

mais je ne peux pas vraiment en faire une dépendance et faire de la magie car elle s’appuie sur la macro $ @ qui représente la cible .. ou du moins je pense que c’est le problème qui se pose. 

J'ai même essayé de remplacer la dépendance "file.cpp" par cette fonction makefile et cela ne fonctionnerait pas non plus.

#it's suppose to turn the $@ (file.o) into file.cpp
THE_CPP := $(addsuffix $(.cpp),$(basename $@))

#one of my targets
file.o: $(THE_CPP) 1.h 2.h 3.h 4.h 5.h 6.h 7.h 8.h another.h lots.h evenMore.h
    $(COMPILE)
#this does not work

Donc, partout sur Google, il semble y avoir deux solutions. que je ne comprends pas tout à fait.
De GNU Make Manual

Un site qui dit que GNU Make Manual one est obsolète

Ma dernière question est donc la suivante: est-il possible de le faire comme je le souhaite,
et si non, quelqu'un peut-il décomposer le code d'un de ces sites et m'expliquer en détail comment il fonctionne? Je vais implémenter l'une de ces manières si nécessaire, mais je suis fatigué de simplement coller un morceau de code dans mon fichier make avant de le comprendre.

25
Trevor Hickey

Les versions plus récentes de GCC ont une option -MP qui peut être utilisée avec -MD. J'ai simplement ajouté -MP et -MD à la variable CPPFLAGS de mon projet (je n'ai pas écrit de recette personnalisée pour la compilation de C++) et j'ai ajouté une ligne "-include $ (SRC: .cpp = .d)".

L'utilisation de -MD et -MP donne un fichier de dépendance qui inclut à la fois les dépendances (sans avoir à utiliser des fichiers sed bizarres) et des cibles factices (afin que la suppression des fichiers d'en-tête ne provoque pas d'erreurs).

26
teambob

Pour manipuler les noms de fichiers alors que vous savez déjà quelles devraient être les dépendances, vous pouvez utiliser une règle de modèle:

file.o: %.o : %.cpp 1.h 2.h 3.h 4.h 5.h 6.h 7.h 8.h another.h lots.h evenMore.h
    $(COMPILE)

Et vous pouvez réutiliser la règle pour d'autres cibles:

# Note these two rules without recipes:
file.o: 1.h 2.h 3.h 4.h 5.h 6.h 7.h 8.h another.h lots.h evenMore.h
anotherFile.o: 4.h 9.h yetAnother.h

file.o anotherFile.o: %.o : %.cpp
    $(COMPILE)

Mais si vous voulez que Make établisse automatiquement la liste des dépendances, le meilleur moyen (que je sache) est Génération avancée de dépendances automatiques . Cela ressemble à ceci:

%.o : %.cc
        @g++ -MD -c -o $@ $<
        @cp $*.d $*.P; \
             sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
                 -e '/^$$/ d' -e 's/$$/ :/' < $*.d >> $*.P; \
             rm -f $*.d

-include *.P

Fondamentalement, quand il construit file.o, il génère également file.d. Ensuite, il exécute file.d via une commande sed déconcertante qui transforme la liste des dépendances en une règle sans recettes. La dernière ligne est une instruction à include de telles règles existantes. La logique ici est subtile et ingénieuse: vous n'avez pas réellement besoin des dépendances la première fois que vous construisez foo.o, car Make sait déjà que foo.o doit être construit, car il n'existe pas. La prochaine fois que vous exécuterez Make, il utilisera la liste de dépendances créée la dernière fois. Si vous modifiez l'un des fichiers pour créer une nouvelle dépendance qui ne figure pas dans la liste, Make reconstruira quand même foo.o car vous avez modifié un fichier qui était une dépendance . Essayez, ça marche vraiment!

20
Beta

Excellentes réponses mais dans ma construction, j'ai placé les fichiers .obj dans un sous-répertoire basé sur le type de construction (c'est-à-dire: debug vs. release). Ainsi, par exemple, si je construis un débogage, je place tous les fichiers objet dans un dossier build/debug. Essayer d'obtenir la commande multiligne sed ci-dessus pour utiliser le bon dossier de destination était une tâche abrupte, mais après quelques essais, je suis tombé sur une solution qui convient parfaitement à ma construction. J'espère que ça va aider quelqu'un d'autre aussi.

Voici un extrait:

# List my sources
CPP_SOURCES := foo.cpp bar.cpp

# If I'm debugging, change my output location
ifeq (1,$(DEBUG))
  OBJ_DIR:=./obj/debug
  CXXFLAGS+= -g -DDEBUG -O0 -std=c++0x
else
  CXXFLAGS+= -s -O2 
  OBJ_DIR:=./obj/release
endif

# destination path macro we'll use below
df = $(OBJ_DIR)/$(*F)

# create a list of auto dependencies
AUTODEPS:= $(patsubst %.cpp,$(OBJ_DIR)/%.d,$(CPP_SOURCES))

# include by auto dependencies
-include $(AUTODEPS)

.... other rules

# and last but not least my generic compiler rule
$(OBJ_DIR)/%.o: %.cpp 
    @# Build the dependency file
    @$(CXX) -MM -MP -MT $(df).o -MT $(df).d $(CXXFLAGS) $< > $(df).d
    @# Compile the object file
    @echo " C++ : " $< " => " $@
    @$(CXX) -c $< $(CXXFLAGS) -o $@

Passons maintenant aux détails: La première exécution de CXX dans ma règle de construction générique est la plus intéressante. Notez que je n'utilise aucune commande "sed". Les nouvelles versions de gcc font tout ce dont j'avais besoin (j'utilise gcc 4.7.2).

-MM construit la règle de dépendance principale, y compris les en-têtes de projet mais pas les en-têtes système. Si je le laissais comme ça, mon fichier .obj n'aurait PAS le bon chemin. J'utilise donc l'option -MT pour spécifier le chemin "réel" vers ma destination .obj. (en utilisant la macro "df" que j'ai créée).
J'utilise également une deuxième option -MT pour m'assurer que le fichier de dépendance obtenu (c'est-à-dire: le fichier .d) a le chemin correct, et qu'il est inclus dans la liste des cibles et comporte donc les mêmes dépendances que la source. fichier.

Le dernier mais non le moindre est l'inclusion de l'option -MP. Cela indique à gcc de créer également des règles de stub pour chaque en-tête, ce qui résout le problème qui se produit si je supprime un en-tête, ce qui entraîne une erreur générée par make.

Je soupçonne que depuis que j'utilise gcc pour toute la génération de dépendances au lieu de passer à sed, la construction est plus rapide (bien que je n’aie pas encore prouvé que cette construction est relativement petite à ce stade). Si vous voyez des moyens de l'améliorer, je suis toujours ouvert aux suggestions. Prendre plaisir

7
Zoccadoum

Pour mémoire, voici comment je génère des dépendances automatiquement maintenant:

CPPFLAGS = -std=c++1y -MD -MP 

SRC = $(wildcard *.cpp)
all: main

main: $(SRC:%.cpp=%.o)
    g++ $(CPPFLAGS) -o $@ $^

-include $(SRC:%.cpp=%.d)

Les indicateurs de compilation -MD et -MP aident à résoudre le problème.

4
Trevor Hickey

Tout d'abord, vous pouvez avoir THE_CPP=$(patsubst %.o,%.cpp,$@)

Ensuite, vous pouvez exécuter make -p pour comprendre les règles internes de make

Une façon habituelle de faire pourrait être de générer les dépendances de makefile dans des fichiers *.md:

%.o: %.c
       $(COMPILE.c) $(OUTPUT_OPTION) $< -MMD -MF $(patsubst %.c,%.md,$@)

et plus tard dans votre Makefiley compris avec quelque chose comme

-include $(wildcard *.md)

Mais vous pouvez aussi envisager d’utiliser d’autres constructeurs comme omake et beaucoup d’autres

4

WOOO! J'ai réussi à obtenir le code dans le post de Beta pour travailler sur un petit projet test.
Je tiens à noter que, si vous utilisez la bash Shell (que j'étais auparavant), vous devrez ajouter un caractère d'échappement devant la livre. signe pour échapper à faire du reste de l'expression un commentaire. (voir 4ème ligne de code)

%.o : %.cpp  
    g++ -c -MD -o $@ $<  
    cp $*.d $*.P; \  
    sed -e 's/\#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \  
        -e '/^$$/ d' -e 's/$$/ :/' < $*.d >> $*.P; \  
    rm -f $*.d  
-include *.P  

Maintenant, je veux partager les informations que j'ai trouvées dans Gestion de projets avec GNU Make, 3rd Edition. parce qu’il signale des problèmes importants à ce sujet et fournit un code que je ne comprends toujours pas encore.
Une méthode semblable à celle qui se trouve sur la page de manuel Création apparaît dans le livre .
Cela ressemble à ceci: 

include $(subst .c,.d,$(SOURCES))

%.d: %.c
    $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
    sed 's,\($*\).o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
    rm -f $@.$$$$

C'est ce que je crois qui se passe.
Tout de suite, 'make' veut inclure un fichier ".d" pour chaque fichier source.
Comme il n’existait au départ aucun fichier .d, le bloc de code est réexécuté afin de créer tous les fichiers .d manquants.
Cela signifie que make recommencera jusqu'à ce que chaque fichier .d soit créé et inclus dans le fichier Make.
Chaque fichier ".d" correspond à ce que dit Bêta: une cible avec un ensemble de dépendances et AUCUNE recette. 

Si un fichier d'en-tête est modifié, les règles incluses dans ces règles devront d'abord mettre à jour les dépendances. C'est ce qui me jette un peu, comment se fait-il que la partie de code puisse être appelée à nouveau? Il est utilisé pour mettre à jour les fichiers .d. Par conséquent, si un fichier .h est modifié, comment est-il appelé? En dehors de cela, je me rends compte que la règle par défaut est utilisée pour compiler l'objet. Toutes les clarifications/idées fausses à cette explication sont appréciées.


Plus loin dans le livre, il souligne des problèmes avec cette méthode et des problèmes qui, à mon avis, existent également dans l'implémentation de la génération de dépendance automatique avancée.
Problème 1: C'est inefficace. 'make' doit redémarrer chaque fois qu'il crée un fichier .d
Problème 2: make génère des messages d'avertissement pour tous les fichiers .d manquants. Ce qui est généralement une gêne et peut être masqué en ajoutant un "-" devant l'instruction include.
Problème 3: Si vous supprimez un fichier src parce qu'il n'est plus nécessaire, 'make' plantera à la prochaine tentative de compilation car un fichier .d contient le src manquant comme dépendance. , et parce qu’il n’ya pas de règle pour recréer ce code, make refusera d’aller plus loin. 

Ils disent qu'une solution à ces problèmes est la méthode de Tromey, mais le code est très différent du code sur le site Web. C'est peut-être juste parce qu'ils ont utilisé des macros, en ont fait un appel de fonction et l'ont écrit légèrement différent. Je suis encore à la recherche, mais je voulais partager certaines des découvertes que j'ai faites jusqu'à présent. Espérons que cela ouvre un petit morceau plus de discussion et me rapproche du fond de tout cela. 

1
Trevor Hickey

Je préfère utiliser la fonction $ (Shell ...) avec find. Voici un exemple de l'un de mes Makefiles:

SRCDIR = src
OBJDIR = obj
LIBDIR = lib
DOCDIR = doc

# Get Only the Internal Structure of Directories from SRCDIR
STRUCTURE := $(Shell find $(SRCDIR) -type d)

#Filter-out hidden directories
STRUCTURE := $(filter-out $(Shell find $(SRCDIR)/.* -type d),$(STRUCTURE))

# Get All Files From STRUCTURE
CODEFILES := $(addsuffix /*,$(STRUCTURE))
CODEFILES := $(wildcard $(CODEFILES))


## Filter Only Specific Files
SRCFILES := $(filter %.c,$(CODEFILES))
HDRFILES := $(filter %.h,$(CODEFILES))
OBJFILES := $(subst $(SRCDIR),$(OBJDIR),$(SRCFILES:%.c=%.o))
DOCFILES := $(addprefix $(DOCDIR)/,             \
            $(addsuffix .md,                    \
            $(basename $(SRCFILES))))


# Filter Out Function main for Libraries
LIBDEPS := $(filter-out $(OBJDIR)/main.o,$(OBJFILES))

Dans cette approche, j'obtiens d'abord toute la structure du répertoire interne, avec n'importe quelle profondeur. Ensuite, je récupère tous les fichiers dans la structure. À ce stade, je peux utiliser les filtres, filtre-out, adduffix, etc. pour obtenir exactement ce dont j'ai besoin à chaque fois. 

Cet exemple couvre les fichiers * .c, mais vous pouvez également le remplacer par * .cpp.

0
user4713908