it-swarm.com.de

Makefile, Header-Abhängigkeiten

Nehmen wir an, ich habe ein Makefile mit der Regel

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

Ich möchte, dass * .o neu erstellt wird, wenn sich eine Header-Datei ändert. Anstatt eine Liste von Abhängigkeiten zu erarbeiten, müssen bei jeder Änderung der Header-Datei in /include alle Objekte im Verzeichnis neu erstellt werden. 

Ich kann mir keine nette Möglichkeit vorstellen, die Regel entsprechend zu ändern. Ich bin offen für Vorschläge. Bonuspunkte, wenn die Liste der Header nicht hart codiert sein muss 

80
Mike

Wenn Sie einen GNU - Compiler verwenden, kann der Compiler eine Liste von Abhängigkeiten für Sie erstellen. Makefile-Fragment:

depend: .depend

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

include .depend

oder

depend: .depend

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

include .depend

dabei ist SRCS eine Variable, die auf Ihre gesamte Liste der Quelldateien verweist.

Es gibt auch das Tool makedepend, das mir aber nie so gut gefallen hat wie gcc -MM

109
dmckee

Die meisten Antworten sind überraschend kompliziert oder falsch. Einfache und robuste Beispiele wurden jedoch an anderer Stelle veröffentlicht [ codereview ]. Zugegebenermaßen sind die Optionen, die der Gnu-Präprozessor bietet, etwas verwirrend. Das Entfernen aller Verzeichnisse aus dem Build-Ziel mit -MM ist jedoch dokumentiert und kein Fehler [ gpp ]:

Standardmäßig verwendet CPP den Namen der Haupteingabedatei, löscht alleVerzeichniskomponenten und alle Dateisuffixe wie '.c' 'und fügt die Das übliche Objektsuffix der Plattform.

Die (etwas neuere) -MMD-Option ist wahrscheinlich das, was Sie wollen. Der Vollständigkeit halber ein Beispiel für ein Makefile, das mehrere SRC-Verzeichnisse und Build-Verzeichnisse mit einigen Kommentaren unterstützt. Eine einfache Version ohne Build-Verzeichnisse finden Sie unter [ 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 [email protected]

# 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 [email protected]

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

Diese Methode funktioniert, da die Abhängigkeiten, wenn es mehrere Abhängigkeitslinien für ein einzelnes Ziel gibt, einfach zusammengefügt werden, z.

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

ist äquivalent zu:

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

wie bereits erwähnt: Erstellen Sie mehrere Abhängigkeitslinien für ein einzelnes Ziel?

47
Sophie

Wie ich hier geschrieben habe gcc kann Abhängigkeiten erstellen und gleichzeitig kompilieren:

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

-include $(DEPS)

%.o: %.c
    $(CC) $(CFLAGS) -MM -MF $(patsubst %.o,%.d,[email protected]) -o [email protected] $<

Der Parameter '-MF' gibt eine Datei an, in der die Abhängigkeiten gespeichert werden sollen.

Der Bindestrich am Anfang von '-include' weist Make an, fortzufahren, wenn die D-Datei nicht vorhanden ist (z. B. beim ersten Kompilieren).

Beachten Sie, dass es in gcc einen Fehler in Bezug auf die Option -o gibt. Wenn Sie den Objektnamen auf "obj/_file__c.o" setzen, enthält die generierte file .d weiterhin file .o, nicht obj/_file__c.o.

24
Martin Fido

Wie wäre es mit etwas wie:

includes = $(wildcard include/*.h)

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

Sie können die Platzhalter auch direkt verwenden, aber ich neige dazu, dass ich sie an mehreren Stellen brauche.

Beachten Sie, dass dies nur für kleine Projekte gut funktioniert, da davon ausgegangen wird, dass jede Objektdatei von jeder Headerdatei abhängt.

22

Martins Lösung funktioniert gut, verarbeitet jedoch keine .o-Dateien, die sich in Unterverzeichnissen befinden. Godric weist darauf hin, dass das -MT-Flag dieses Problem löst, gleichzeitig aber verhindert, dass die .o-Datei korrekt geschrieben wird. Das Folgende wird sich um beide Probleme kümmern:

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

-include $(DEPS)

%.o: %.c
    $(CC) $(CFLAGS) -MM -MT [email protected] -MF $(patsubst %.o,%.d,[email protected]) $<
    $(CC) $(CFLAGS) -o [email protected] $<
4
michael

Dies erledigt die Arbeit gut und erledigt sogar die angegebenen Subdirs:

    $(CC) $(CFLAGS) -MD -o [email protected] $<

testete es mit gcc 4.8.3

3
g24l

Eine leicht modifizierte Version von Sophies answer , die die Ausgabe der * .d-Dateien in einen anderen Ordner ermöglicht (ich füge nur den interessanten Teil ein, der die Abhängigkeitsdateien generiert):

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

Beachten Sie, dass der Parameter

-MT [email protected]

wird verwendet, um sicherzustellen, dass die Ziele (d. h. die Objektdateinamen) in den generierten * .d-Dateien den vollständigen Pfad zu den * .o-Dateien enthalten und nicht nur den Dateinamen.

Ich weiß nicht, warum dieser Parameter NICHT benötigt wird, wenn -MMD in Kombination mit -c verwendet wird (wie in Sophies version ). In dieser Kombination scheint es, den vollständigen Pfad der * .o-Dateien in die * .d-Dateien zu schreiben. Ohne diese Kombination schreibt -MMD nur die reinen Dateinamen ohne Verzeichniskomponenten in die * .d-Dateien. Vielleicht weiß jemand, warum -MMD in Kombination mit -c den vollständigen Pfad schreibt. Ich habe in der g ++ - Manpage keinen Hinweis gefunden.

0
MaximumFPS

Folgendes funktioniert für mich:

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

-include $(DEPS)

%.o: %.cpp
    $(CXX) $(CFLAGS) -MMD -c -o [email protected] $<
0
Marcel Keller

Ich bevorzuge diese Lösung gegenüber der akzeptierten Antwort von Michael Williamson. Sie erfasst Änderungen an Quellen + Inline-Dateien, dann Quellen + Kopfzeilen und schließlich nur Quellen. Vorteil hierbei ist, dass die gesamte Bibliothek nicht neu übersetzt wird, wenn nur wenige Änderungen vorgenommen werden. Keine große Überlegung für ein Projekt mit ein paar Dateien, aber wenn Sie 10 oder 100 Quellen haben, werden Sie den Unterschied bemerken.

COMMAND= gcc -Wall -Iinclude ...

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

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

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

Hier ist ein Doppelliner:

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

Dies funktioniert mit dem Standardrezept "make", sofern Sie eine Liste aller Objektdateien in OBJS haben.

0
tbodt