115 votes

Makefile, dépendances des en-têtes

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

%.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'établir une liste de dépendances, chaque fois qu'un fichier d'en-tête dans le fichier /include change, alors tous les objets du répertoire doivent être reconstruits.

Je ne vois pas comment modifier la règle pour tenir compte de cette situation, mais je suis ouvert à toute suggestion. Des points bonus si la liste des en-têtes ne doit pas être codée en dur.

0 votes

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

135voto

dmckee Points 50318

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

depend: .depend

.depend: $(SRCS)
        rm -f "$@"
        $(CC) $(CFLAGS) -MM $^ -MF "$@"

include .depend

ou

depend: .depend

.depend: $(SRCS)
        rm -f "$@"
        $(CC) $(CFLAGS) -MM $^ > "$@"

include .depend

SRCS est une variable qui pointe vers la liste complète de vos fichiers sources.

Il existe également l'outil makedepend mais je ne l'ai jamais autant aimé que gcc -MM

3 votes

J'aime cette astuce, mais comment puis-je obtenir depend pour ne s'exécuter que lorsque les fichiers sources ont été modifiés ? Il semble s'exécuter à chaque fois...

2 votes

@chase : Eh bien, j'ai fait par erreur la dépendance sur les fichiers d'objets, alors qu'elle devrait évidemment être sur les sources et j'avais l'ordre de dépendance erroné pour les deux cibles, aussi. C'est ce qui arrive quand on tape de mémoire. Essayez maintenant.

4 votes

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

109voto

Sophie Points 625

La plupart des réponses sont étonnamment compliquées ou erronées. Cependant, des exemples simples et solides ont été postés ailleurs [ revue de code ]. Il est vrai que les options fournies par le préprocesseur gnu sont un peu confuses. Cependant, la suppression de tous les répertoires de la cible de construction avec -MM est documenté et n'est pas un bug [ gpp ] :

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

Le (un peu plus récent) -MMD est probablement ce que vous voulez. Pour être complet, voici un exemple de fichier makefile qui supporte plusieurs répertoires src et répertoires de compilation avec quelques commentaires. Pour une version simple sans répertoire de compilation, voir [ revue de code ].

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 ex :

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 ?

29voto

Martin Fido Points 636

Comme je l'ai posté aquí 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 comme étant obj/_file__c.o, alors le fichier généré est le même que celui de l'option -o. fichier .d contiendra toujours fichier .o, pas obj/_file__c.o.

4 votes

Lorsque j'essaie de le faire, tous mes fichiers .o sont créés en tant que fichiers vides. J'ai mes objets dans un sous-dossier de construction (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 cela ne construit certainement pas les fichiers .o du tout, alors que c'est le cas si je supprime -MM et -MF.

1 votes

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.

4 votes

@bobpaul parce que man gcc dit -MM implica -E qui "s'arrête après le prétraitement". Vous devez -MMD à la place : stackoverflow.com/a/30142139/895245

23voto

Michael Williamson Points 6210

Pourquoi pas quelque chose comme :

includes = $(wildcard include/*.h)

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

Vous pouvez également utiliser directement les caractères génériques, mais j'ai tendance à en avoir besoin à plusieurs endroits.

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

0 votes

Merci, j'ai fait en sorte que ce soit beaucoup plus compliqué que nécessaire.

18 votes

Cela fonctionne, mais le problème est que chaque fichier objet est recompilé à chaque fois qu'une petite modification est apportée, c'est-à-dire que si vous avez 100 fichiers source/en-tête, et que vous apportez une petite modification à un seul d'entre eux, les 100 seront recompilés.

1 votes

Vous devriez vraiment mettre à jour votre réponse pour dire que c'est un moyen très inefficace de le faire parce qu'il reconstruit TOUS les fichiers à chaque fois qu'UN fichier d'en-tête est modifié. Les autres réponses sont bien meilleures.

6voto

michael Points 678

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

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

-include $(DEPS)

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X