Makefile, dépendances d'en-tête


97

Disons que j'ai un makefile avec la règle

%.o: %.c
 gcc -Wall -Iinclude ...

Je veux que * .o soit reconstruit chaque fois qu'un fichier d'en-tête change. Plutôt que d'élaborer une liste de dépendances, à chaque fois qu'un fichier d'en-tête /includechange, tous les objets du répertoire doivent être reconstruits.

Je ne peux pas penser à une bonne façon de changer la règle pour s'adapter à cela, je suis ouvert aux suggestions. Des points bonus si la liste des en-têtes ne doit pas être codée en dur


Après avoir écrit ma réponse ci-dessous, j'ai regardé dans la liste associée et j'ai trouvé: stackoverflow.com/questions/297514/… qui semble être un doublon. La réponse de Chris Dodd est équivalente à la mienne, bien qu'elle utilise une convention de dénomination différente.
dmckee --- ex-moderator chaton

Réponses:


116

Si vous utilisez un compilateur GNU, le compilateur peut assembler une liste de dépendances pour vous. Fragment Makefile:

depend: .depend

.depend: $(SRCS)
        rm -f ./.depend
        $(CC) $(CFLAGS) -MM $^ -MF  ./.depend;

include .depend

ou

depend: .depend

.depend: $(SRCS)
        rm -f ./.depend
        $(CC) $(CFLAGS) -MM $^ > ./.depend;

include .depend

SRCSest une variable pointant vers toute votre liste de fichiers source.

Il y a aussi l'outil makedepend, mais je ne l'ai jamais autant aimégcc -MM


2
J'aime cette astuce, mais comment puis-je dependexécuter uniquement lorsque les fichiers sources ont changé? Il semble fonctionner à chaque fois, peu importe ...
poursuite du

2
@chase: Eh bien, j'ai fait par erreur la dépendance sur les fichiers objets, alors qu'elle devrait évidemment être sur les sources et que l'ordre de dépendance était incorrect pour les deux cibles également. C'est ce que j'obtiens en tapant de mémoire. Essayez-le maintenant.
dmckee --- ex-moderator chaton

4
Est-il possible d'ajouter avant chaque fichier un préfixe pour montrer qu'il se trouve dans un autre répertoire, par exemple build/file.o?
RiaD

J'ai changé SRCS en OBJECTS, où OBJECTS est une liste de mes fichiers * .o. Cela a semblé empêcher Dependent de s'exécuter à chaque fois et a également détecté les modifications apportées aux fichiers d'en-tête uniquement. Cela semble contraire aux commentaires précédents .. est-ce que je manque quelque chose?
BigBrownBear00

2
Pourquoi le point-virgule est-il nécessaire? si j'essaye cela sans lui, ou avec -MF ./.depend n'étant pas le dernier argument, il enregistre uniquement les dépendances du dernier fichier dans $ (SRCS).
humodz

72

La plupart des réponses sont étonnamment compliquées ou erronées. Cependant, des exemples simples et robustes ont été publiés ailleurs [ codereview ]. Certes, les options fournies par le préprocesseur gnu sont un peu déroutantes. Cependant, la suppression de tous les répertoires de la cible de construction avec -MMest documentée et non un bogue [ gpp ]:

Par défaut, CPP prend le nom du fichier d'entrée principal, supprime tous les composants du répertoire et tout suffixe de fichier tel que «.c», et ajoute le suffixe d'objet habituel de la plate-forme.

L' -MMDoption (un peu plus récente) est probablement ce que vous voulez. Pour être complet, un exemple de makefile qui prend en charge plusieurs répertoires src et des répertoires de construction avec quelques commentaires. Pour une version simple sans répertoire de compilation, voir [ codereview ].

CXX = clang++
CXX_FLAGS = -Wfatal-errors -Wall -Wextra -Wpedantic -Wconversion -Wshadow

# Final binary
BIN = mybin
# Put all auto generated stuff to this build dir.
BUILD_DIR = ./build

# List of all .cpp source files.
CPP = main.cpp $(wildcard dir1/*.cpp) $(wildcard dir2/*.cpp)

# All .o files go to build dir.
OBJ = $(CPP:%.cpp=$(BUILD_DIR)/%.o)
# Gcc/Clang will create these .d files containing dependencies.
DEP = $(OBJ:%.o=%.d)

# Default target named after the binary.
$(BIN) : $(BUILD_DIR)/$(BIN)

# Actual target of the binary - depends on all .o files.
$(BUILD_DIR)/$(BIN) : $(OBJ)
    # Create build directories - same structure as sources.
    mkdir -p $(@D)
    # Just link all the object files.
    $(CXX) $(CXX_FLAGS) $^ -o $@

# Include all .d files
-include $(DEP)

# Build target for every single object file.
# The potential dependency on header files is covered
# by calling `-include $(DEP)`.
$(BUILD_DIR)/%.o : %.cpp
    mkdir -p $(@D)
    # The -MMD flags additionaly creates a .d file with
    # the same name as the .o file.
    $(CXX) $(CXX_FLAGS) -MMD -c $< -o $@

.PHONY : clean
clean :
    # This should remove all generated files.
    -rm $(BUILD_DIR)/$(BIN) $(OBJ) $(DEP)

Cette méthode fonctionne car s'il existe plusieurs lignes de dépendance pour une seule cible, les dépendances sont simplement jointes, par exemple:

a.o: a.h
a.o: a.c
    ./cmd

est équivalent à:

a.o: a.c a.h
    ./cmd

comme mentionné à: Makefile plusieurs lignes de dépendance pour une seule cible?


1
J'aime cette solution. Je ne veux pas taper la commande make depend. Utile !!
Robert

1
Il y a une erreur d'orthographe dans la valeur de la variable OBJ: le CPPdevrait lireCPPS
ctrucza

1
C'est ma réponse préférée; +1 pour vous. C'est le seul sur cette page qui ait du sens, et couvre (pour ce que je peux voir) toutes les situations où une recompilation est nécessaire (évitant une compilation inutile, mais suffisante)
Joost

1
Hors de la boîte, cela n'a pas réussi à localiser les en-têtes pour moi même si hpp et cpp sont tous les deux sur le même répertoire.
villasv

1
si vous avez vos fichiers source ( a.cpp, b.cpp) ./src/, cette substitution ne serait-elle pas effectuée $(OBJ)=./build/src/a.o ./build/src/b.o?
galois

26

Comme je l'ai posté ici, gcc peut créer des dépendances et compiler en même temps:

DEPS := $(OBJS:.o=.d)

-include $(DEPS)

%.o: %.c
    $(CC) $(CFLAGS) -MM -MF $(patsubst %.o,%.d,$@) -o $@ $<

Le paramètre '-MF' spécifie un fichier dans lequel stocker les dépendances.

Le tiret au début de '-include' indique à Make de continuer lorsque le fichier .d n'existe pas (par exemple lors de la première compilation).

Notez qu'il semble y avoir un bogue dans gcc concernant l'option -o. Si vous définissez le nom de fichier de l'objet sur obj / _file__c.o, le fichier généré .d contiendra toujours le fichier .o, pas obj / _file__c.o.


4
Lorsque j'essaie cela, tous mes fichiers .o sont créés en tant que fichiers vides. J'ai mes objets dans un sous-dossier build (donc $ OBJECTS contient build / main.o build / smbus.o build / etc ...) et cela crée certainement les fichiers .d comme vous l'avez décrit avec le bogue apparent, mais c'est certainement ne construit pas du tout les fichiers .o, alors que c'est le cas si je supprime -MM et -MF.
bobpaul

1
L'utilisation de -MT résoudra la note dans les dernières lignes de votre réponse qui met à jour la cible de chaque liste de dépendances.
Godric Seer

3
@bobpaul parce que man gccdit -MMimplique -E, qui "s'arrête après le prétraitement". Vous avez besoin à la -MMDplace de: stackoverflow.com/a/30142139/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

23

Que diriez-vous de quelque chose comme:

includes = $(wildcard include/*.h)

%.o: %.c ${includes}
    gcc -Wall -Iinclude ...

Vous pouvez également utiliser les caractères génériques directement, mais j'ai tendance à trouver que j'en ai besoin à plus d'un endroit.

Notez que cela ne fonctionne bien que sur les petits projets, car cela suppose que chaque fichier objet dépend de chaque fichier d'en-tête.


merci, je faisais que cela soit beaucoup plus compliqué que nécessaire
Mike

15
Cela fonctionne, cependant, le problème avec ceci est que chaque fichier objet est recompilé, chaque fois qu'un petit changement est effectué, c'est-à-dire que si vous avez 100 fichiers source / en-tête et que vous apportez un petit changement à un seul, tous les 100 sont recompilés .
Nicholas Hamilton

1
Vous devriez vraiment mettre à jour votre réponse pour dire que c'est une manière très inefficace de le faire car elle reconstruit TOUS les fichiers chaque fois que TOUT fichier d'en-tête est modifié. Les autres réponses sont bien meilleures.
xaxxon

2
C'est une très mauvaise solution. Bien sûr, cela fonctionnera sur un petit projet, mais pour toute équipe de taille de production et de construction, cela entraînera un temps de compilation terrible et deviendra l'équivalent de l'exécution à make clean allchaque fois.
Julien Guertault

Dans mon test, cela ne fonctionne pas du tout. La gccligne n'est pas exécutée du tout, mais la règle intégrée ( %o: %.crègle) est exécutée à la place.
Penghe Geng

4

La solution de Martin ci-dessus fonctionne très bien, mais ne gère pas les fichiers .o qui résident dans des sous-répertoires. Godric souligne que l'indicateur -MT résout ce problème, mais il empêche simultanément le fichier .o d'être écrit correctement. Ce qui suit prendra en charge ces deux problèmes:

DEPS := $(OBJS:.o=.d)

-include $(DEPS)

%.o: %.c
    $(CC) $(CFLAGS) -MM -MT $@ -MF $(patsubst %.o,%.d,$@) $<
    $(CC) $(CFLAGS) -o $@ $<

3

Cela fera très bien le travail, et même gérer les sous-répertoires spécifiés:

    $(CC) $(CFLAGS) -MD -o $@ $<

testé avec gcc 4.8.3


3

Voici un deux lignes:

CPPFLAGS = -MMD
-include $(OBJS:.c=.d)

Cela fonctionne avec la recette de création par défaut, tant que vous avez une liste de tous vos fichiers objets dans OBJS.


1

Je préfère cette solution, à la réponse acceptée par Michael Williamson, elle détecte les modifications apportées aux sources + fichiers en ligne, puis aux sources + en-têtes, et enfin aux sources uniquement. L'avantage ici est que toute la bibliothèque n'est pas recompilée si seulement quelques modifications sont apportées. Ce n'est pas une considération énorme pour un projet avec quelques fichiers, mais si vous avez 10 ou 100 sources, vous remarquerez la différence.

COMMAND= gcc -Wall -Iinclude ...

%.o: %.cpp %.inl
    $(COMMAND)

%.o: %.cpp %.hpp
    $(COMMAND)

%.o: %.cpp
    $(COMMAND)

2
Cela ne fonctionne que si vous n'avez rien dans vos fichiers d'en-tête qui nécessiterait la recompilation de tous les fichiers cpp autres que le fichier d'implémentation correspondant.
matec

0

Ce qui suit fonctionne pour moi:

DEPS := $(OBJS:.o=.d)

-include $(DEPS)

%.o: %.cpp
    $(CXX) $(CFLAGS) -MMD -c -o $@ $<

0

Une version légèrement modifiée de la réponse de Sophie qui permet de sortir les fichiers * .d dans un dossier différent (je ne collerai que la partie intéressante qui génère les fichiers de dépendance):

$(OBJDIR)/%.o: %.cpp
# Generate dependency file
    mkdir -p $(@D:$(OBJDIR)%=$(DEPDIR)%)
    $(CXX) $(CXXFLAGS) $(CPPFLAGS) -MM -MT $@ $< -MF $(@:$(OBJDIR)/%.o=$(DEPDIR)/%.d)
# Generate object file
    mkdir -p $(@D)
    $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $< -o $@

Notez que le paramètre

-MT $@

est utilisé pour garantir que les cibles (c'est-à-dire les noms de fichier objet) dans les fichiers * .d générés contiennent le chemin complet des fichiers * .o et pas seulement le nom du fichier.

Je ne sais pas pourquoi ce paramètre n'est PAS nécessaire lors de l'utilisation de -MMD en combinaison avec -c (comme dans la version de Sophie ). Dans cette combinaison, il semble écrire le chemin complet des fichiers * .o dans les fichiers * .d. Sans cette combinaison, -MMD écrit également uniquement les noms de fichiers purs sans aucun composant de répertoire dans les fichiers * .d. Peut-être que quelqu'un sait pourquoi -MMD écrit le chemin complet lorsqu'il est combiné avec -c. Je n'ai trouvé aucun indice dans la page de manuel g ++.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.