Comment puis-je avoir un Makefile reconstruire automatiquement les fichiers source qui incluent un fichier d'en-tête modifié? (En C / C ++)

92

J'ai le makefile suivant que j'utilise pour construire un programme (un noyau, en fait) sur lequel je travaille. C'est à partir de zéro et j'apprends le processus, donc ce n'est pas parfait, mais je pense que c'est assez puissant à ce stade pour mon niveau d'expérience dans l'écriture de makefiles.

AS  =   nasm
CC  =   gcc
LD  =   ld

TARGET      =   core
BUILD       =   build
SOURCES     =   source
INCLUDE     =   include
ASM         =   assembly

VPATH = $(SOURCES)

CFLAGS  =   -Wall -O -fstrength-reduce -fomit-frame-pointer -finline-functions \
            -nostdinc -fno-builtin -I $(INCLUDE)
ASFLAGS =   -f elf

#CFILES     =   core.c consoleio.c system.c
CFILES      =   $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
SFILES      =   assembly/start.asm

SOBJS   =   $(SFILES:.asm=.o)
COBJS   =   $(CFILES:.c=.o)
OBJS    =   $(SOBJS) $(COBJS)

build : $(TARGET).img

$(TARGET).img : $(TARGET).elf
    c:/python26/python.exe concat.py stage1 stage2 pad.bin core.elf floppy.img

$(TARGET).elf : $(OBJS)
    $(LD) -T link.ld -o $@ $^

$(SOBJS) : $(SFILES)
    $(AS) $(ASFLAGS) $< -o $@

%.o: %.c
    @echo Compiling $<...
    $(CC) $(CFLAGS) -c -o $@ $<

#Clean Script - Should clear out all .o files everywhere and all that.
clean:
    -del *.img
    -del *.o
    -del assembly\*.o
    -del core.elf

Mon principal problème avec ce makefile est que lorsque je modifie un fichier d'en-tête qu'un ou plusieurs fichiers C incluent, les fichiers C ne sont pas reconstruits. Je peux résoudre ce problème assez facilement en faisant en sorte que tous mes fichiers d'en-tête soient des dépendances pour tous mes fichiers C, mais cela entraînerait effectivement une reconstruction complète du projet chaque fois que je modifierais / ajouterais un fichier d'en-tête, ce qui ne serait pas très gracieux.

Ce que je veux, c'est que seuls les fichiers C qui incluent le fichier d'en-tête que je modifie soient reconstruits et que l'ensemble du projet soit à nouveau lié. Je peux faire le lien en faisant en sorte que tous les fichiers d'en-tête soient des dépendances de la cible, mais je ne peux pas comprendre comment rendre les fichiers C invalides lorsque leurs fichiers d'en-tête inclus sont plus récents.

J'ai entendu dire que GCC a quelques commandes pour rendre cela possible (afin que le makefile puisse d'une manière ou d'une autre déterminer quels fichiers doivent être reconstruits) mais je ne peux pas pour la vie de moi trouver un exemple d'implémentation réel à regarder. Quelqu'un peut-il publier une solution qui permettra ce comportement dans un makefile?

EDIT: Je devrais clarifier, je suis familier avec le concept de mettre les cibles individuelles et d'avoir chaque target.o exiger les fichiers d'en-tête. Cela m'oblige à modifier le fichier makefile chaque fois que j'inclus un fichier d'en-tête quelque part, ce qui est un peu pénible. Je recherche une solution qui puisse dériver les dépendances du fichier d'en-tête par elle-même, ce que je suis assez certain d'avoir vu dans d'autres projets.

Nicolas Flynt
la source

Réponses:

30

Comme déjà indiqué ailleurs sur ce site, consultez cette page: Génération de dépendance automatique

En bref, gcc peut créer automatiquement pour vous des fichiers de dépendances .d, qui sont des mini fragments de makefile contenant les dépendances du fichier .c que vous avez compilé. Chaque fois que vous modifiez le fichier .c et le compilez, le fichier .d sera mis à jour.

En plus d'ajouter l'indicateur -M à gcc, vous devrez inclure les fichiers .d dans le makefile (comme Chris l'a écrit ci-dessus). Il y a des problèmes plus compliqués dans la page qui sont résolus à l'aide de sed, mais vous pouvez les ignorer et faire un «nettoyage» pour effacer les fichiers .d chaque fois que make se plaint de ne pas pouvoir créer un fichier d'en-tête qui n'existe plus .

Udi Meiri
la source
2
Je me trompe peut-être, mais je pense que GCC a en fait ajouté une fonctionnalité pour essayer de contourner ce problème de sed. Consultez gcc.gnu.org/onlinedocs/gcc-4.3.1/gcc/Preprocessor-Options.html spécifiquement -MP.
Eugene Marcotte
Oui, -MP existe depuis GCC 3, existe dans clang et icc, et annule le besoin de sed. bruno.defraine.net/techtips/makefile-auto-dependencies-with-gcc/…
hmijail pleure les résignés
La dernière version du lien dans la réponse contient des exemples utilisant des indicateurs GCC.
MadScientist
20

Vous pouvez ajouter une commande 'make depend' comme d'autres l'ont indiqué, mais pourquoi ne pas demander à gcc de créer des dépendances et de compiler en même temps:

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

-include $(DEPS)

%.o: %.c
    $(CC) -c $(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 pour dire, le fichier obj/_file__c.ogénéré _file_.dcontiendra toujours _file_.o, non obj/_file_c.o.

Martin Fido
la source
18
Cela n'a pas fonctionné pour moi. Par exemple, le makefile a généré et exécuté ceci: g++ -c -Wall -Werror -MM -MF main.d -o main.o main.cpp j'ai obtenu le fichier main.d, mais main.o était de 0 octet. Cependant, l'indicateur -MMD semble faire exactement ce qui était requis. Donc, ma règle de travail makefile est devenue:$(CC) -c $(CFLAGS) -MMD -o $@ $<
Darren Cook
17

Cela équivaut à la réponse de Chris Dodd , mais utilise une convention de dénomination différente (et par coïncidence n'exige pas la sedmagie. Copié à partir d' un duplicata ultérieur .


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

depend: .depend

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

include .depend

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

dmckee --- chaton ex-modérateur
la source
4
Pourquoi n'épelez-vous pas SOURCES? Ne nécessite pas particulièrement beaucoup plus de caractères et n'est pas aussi obscurci que "SRCS" qui peut ressembler à un acronyme.
HelloGoodbye
@HelloGoodbye Un peu de style. J'ai toujours utilisé, et toujours vu, SRCSet OBJS. Je suis d'accord la plupart du temps, mais tout le monde devrait savoir de quoi il s'agit.
sherrellbc
@sherrellbc Ce que les gens «devraient» savoir et ce que les gens savent réellement sont souvent deux choses différentes: P
HelloGoodbye
7

Vous devrez créer des cibles individuelles pour chaque fichier C, puis répertorier le fichier d'en-tête en tant que dépendance. Vous pouvez toujours utiliser vos cibles génériques et placer simplement les .hdépendances par la suite, comme ceci:

%.o: %.c
        @echo Compiling $<...
        $(CC) $(CFLAGS) -c -o $@ $<

foo.c: bar.h
# And so on...
mipadi
la source
4

Fondamentalement, vous devez créer dynamiquement les règles makefile pour reconstruire les fichiers objets lorsque les fichiers d'en-tête changent. Si vous utilisez gcc et gnumake, c'est assez simple; il suffit de mettre quelque chose comme:

$(OBJDIR)/%.d: %.c
        $(CC) -MM -MG $(CPPFLAGS) $< | sed -e 's,^\([^:]*\)\.o[ ]*:,$(@D)/\1.o $(@D)/\1.d:,' >$@

ifneq ($(MAKECMDGOALS),clean)
include $(SRCS:%.c=$(OBJDIR)/%.d)
endif

dans votre makefile.

Chris Dodd
la source
2
Je comprends en quelque sorte cela, sauf que (mis à part le fait que les indicateurs -MM et -MG sont nouveaux), je ne comprends pas à quoi sert la ligne de texte cryptique à la recherche de regex. Cela ne va pas me rendre heureux, mes coéquipiers ... ^ _ ^ Je vais essayer et voir si j'ai des résultats.
Nicholas Flynt
sed est l'abréviation de "stream editor" qui peut modifier un flux de texte sans avoir besoin d'utiliser un fichier. C'est un outil Unix standard et plus petit et plus rapide donc il est utilisé plus souvent que awk ou perl.
Zan Lynx
Ah, il y a le problème: je fais ça sous Windows.
Nicholas Flynt
3

Au-delà de ce que @mipadi a dit, vous pouvez également explorer l'utilisation du '-M option ' pour générer un enregistrement des dépendances. Vous pouvez même les générer dans un fichier séparé (peut-être 'depend.mk') que vous incluez ensuite dans le makefile. Ou vous pouvez trouver une make dependrègle ' ' qui édite le fichier makefile avec les dépendances correctes (termes Google: "ne pas supprimer cette ligne" et dépendre).

Jonathan Leffler
la source
1

Aucune des réponses n'a fonctionné pour moi. Par exemple, la réponse de Martin Fido suggère que gcc peut créer un fichier de dépendances, mais quand j'ai essayé de générer des fichiers objets vides (zéro octet) pour moi sans aucun avertissement ni erreur. Cela pourrait être un bogue gcc. Je suis sur

$ gcc --version gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-16)

Voici donc mon Makefile complet qui fonctionne pour moi; c'est une combinaison de solutions + quelque chose qui n'a été mentionné par personne d'autre (par exemple "règle de remplacement de suffixe" spécifiée comme .cc.o :):

CC = g++
CFLAGS = -Wall -g -std=c++0x
INCLUDES = -I./includes/

# LFLAGS = -L../lib
# LIBS = -lmylib -lm

# List of all source files
SRCS = main.cc cache.cc

# Object files defined from source files
OBJS = $(SRCS:.cc=.o)

# # define the executable file 
MAIN = cache_test

#List of non-file based targets:
.PHONY: depend clean all

##  .DEFAULT_GOAL := all

# List of dependencies defined from list of object files
DEPS := $(OBJS:.o=.d)

all: $(MAIN)

-include $(DEPS)

$(MAIN): $(OBJS)
    $(CC) $(CFLAGS) $(INCLUDES) -o $(MAIN) $(OBJS) $(LFLAGS) $(LIBS)

#suffix replacement rule for building .o's from .cc's
#build dependency files first, second line actually compiles into .o
.cc.o:
    $(CC) $(CFLAGS) $(INCLUDES) -c -MM -MF $(patsubst %.o,%.d,$@) $<
    $(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<

clean:
    $(RM) *.o *~ $(MAIN) *.d

Remarquez que j'ai utilisé .cc .. Le Makefile ci-dessus est facile à ajuster pour les fichiers .c.

Notez également l'importance de ces deux lignes:

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

donc gcc est appelé une fois pour construire d'abord un fichier de dépendance, puis compile en fait un fichier .cc. Et ainsi de suite pour chaque fichier source.

Tagar
la source
0

Solution plus simple: utilisez simplement le Makefile pour que la règle de compilation .c à .o dépende du ou des fichiers d'en-tête et de tout ce qui est pertinent dans votre projet en tant que dépendance.

Par exemple, dans le Makefile quelque part:

DEPENDENCIES=mydefs.h yourdefs.h Makefile GameOfThrones.S07E01.mkv

::: (your other Makefile statements like rules 
:::  for constructing executables or libraries)

# Compile any .c to the corresponding .o file:
%.o: %.c $(DEPENDENCIES)
        $(CC) $(CFLAGS) -c -o $@ $<
Richard Elkins
la source
-1

Je crois que la mkdepcommande est ce que vous voulez. Il scanne en fait les fichiers .c pour les #includelignes et crée une arborescence de dépendances pour eux. Je pense que les projets Automake / Autoconf l'utilisent par défaut.

jdizzle
la source