Makefile, Header-Abhängigkeiten

97

Angenommen, 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 erstellen /include, müssen alle Objekte im Verzeichnis neu erstellt werden, wenn sich eine Header-Datei ändert.

Ich kann mir keine gute Möglichkeit vorstellen, die Regel zu ändern, um dies zu berücksichtigen. Ich bin offen für Vorschläge. Bonuspunkte, wenn die Liste der Header nicht fest codiert sein muss

Mike
quelle
Nachdem ich meine Antwort unten geschrieben hatte, schaute ich in die Themenliste und fand: stackoverflow.com/questions/297514/…, das ein Duplikat zu sein scheint. Chris Dodds Antwort entspricht meiner, obwohl sie eine andere Namenskonvention verwendet.
dmckee --- Ex-Moderator Kätzchen

Antworten:

115

Wenn Sie einen GNU-Compiler verwenden, kann der Compiler eine Liste von Abhängigkeiten für Sie zusammenstellen. 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

Wobei SRCSeine Variable auf Ihre gesamte Liste der Quelldateien verweist.

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

dmckee
quelle
2
Ich mag diesen Trick, aber wie kann ich dependnur ausgeführt werden, wenn sich die Quelldateien geändert haben? Es scheint jedes Mal zu laufen, egal ...
Verfolgungsjagd
2
@chase: Nun, ich habe fälschlicherweise die Abhängigkeit von den Objektdateien gemacht, wenn sie offensichtlich von den Quellen sein sollte und die Reihenfolge der Abhängigkeit auch für die beiden Ziele falsch war. Das bekomme ich zum Tippen aus dem Gedächtnis. Versuchen Sie es jetzt.
dmckee --- Ex-Moderator Kätzchen
4
Ist es möglich, vor jeder Datei ein Präfix hinzuzufügen, um anzuzeigen, dass es sich in einem anderen Verzeichnis befindet, z build/file.o.
RiaD
Ich habe SRCS in OBJECTS geändert, wobei OBJECTS eine Liste meiner * .o-Dateien sind. Dies schien zu verhindern, dass die Abhängigkeit jedes Mal ausgeführt wird, und es wurden auch nur Änderungen an den Header-Dateien vorgenommen. Dies scheint den vorherigen Kommentaren zu widersprechen. Fehlt mir etwas?
BigBrownBear00
2
Warum ist das Semikolon notwendig? Wenn ich das ohne es versuche oder wenn -MF ./.depend nicht das letzte Argument ist, werden nur die Abhängigkeiten der letzten Datei in $ (SRCS) gespeichert.
Humodz
71

Die meisten Antworten sind überraschend kompliziert oder falsch. Einfache und robuste Beispiele wurden jedoch an anderer Stelle veröffentlicht [ codereview ]. Zugegeben, die vom gnu-Präprozessor bereitgestellten Optionen sind etwas verwirrend. Das Entfernen aller Verzeichnisse aus dem Build-Ziel mit -MMist jedoch dokumentiert und kein Fehler [ gpp ]:

Standardmäßig übernimmt CPP den Namen der Haupteingabedatei, löscht alle Verzeichniskomponenten und alle Dateisuffixe wie '.c' und fügt das übliche Objektsuffix der Plattform hinzu.

Die (etwas neuere) -MMDOption ist wahrscheinlich das, was Sie wollen. Der Vollständigkeit halber ein Beispiel für ein Makefile, das mehrere src-Verzeichnisse unterstützt und Verzeichnisse mit einigen Kommentaren erstellt. 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 $@

# 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)

Diese Methode funktioniert, weil bei mehreren Abhängigkeitslinien für ein einzelnes Ziel die Abhängigkeiten einfach verbunden werden, z.

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

ist äquivalent zu:

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

wie unter: Mehrere Abhängigkeitslinien für ein einzelnes Ziel erstellen?

Sophie
quelle
1
Ich mag diese Lösung. Ich möchte den Befehl make abhäng nicht eingeben. Nützlich !!
Robert
1
Es gibt einen Rechtschreibfehler im Wert der OBJ-Variablen: Der CPPsollte lesenCPPS
ctrucza
1
Dies ist meine bevorzugte Antwort; +1 für dich. Dies ist die einzige auf dieser Seite, die Sinn macht und (soweit ich sehen kann) alle Situationen abdeckt, in denen eine Neukompilierung erforderlich ist (unnötige Kompilierung vermeiden, aber ausreichend)
Joost
1
Standardmäßig konnten die Header für mich nicht gefunden werden, obwohl sich hpp und cpp beide im selben Verzeichnis befinden.
Villasv
1
Wenn Sie Ihre Quelldateien ( a.cpp, b.cpp) haben ./src/, würde diese Ersetzung dann nicht erfolgen $(OBJ)=./build/src/a.o ./build/src/b.o?
Galois
26

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

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

-include $(DEPS)

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

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. bei der ersten Kompilierung).

Beachten Sie, dass es in gcc einen Fehler bezüglich der Option -o zu geben scheint. Wenn Sie den Dateinamen des Objekts auf obj / _file__c.o setzen, enthält die generierte Datei .d weiterhin die Datei .o und nicht obj / _file__c.o.

Martin Fido
quelle
4
Wenn ich dies versuche, werden alle meine .o-Dateien als leere Dateien erstellt. Ich habe meine Objekte in einem Build-Unterordner (also enthält $ OBJECTS build / main.o build / smbus.o build / etc ...) und das erstellt sicherlich die .d-Dateien, wie Sie sie mit dem offensichtlichen Fehler beschrieben haben, aber es ist sicher erstellt die .o-Dateien überhaupt nicht, wohingegen dies der Fall ist, wenn ich -MM und -MF entferne.
Bobpaul
1
Durch die Verwendung von -MT wird der Hinweis in den letzten Zeilen Ihrer Antwort aufgelöst, wodurch das Ziel jeder Abhängigkeitsliste aktualisiert wird.
Godric Seer
3
@bobpaul weil man gccsagt -MMimpliziert -E, was "nach der Vorverarbeitung stoppt". Sie benötigen -MMDstattdessen: stackoverflow.com/a/30142139/895245
Ciro Santilli 20 冠状 病 六四 事件 20
23

Wie wäre es mit so etwas wie:

includes = $(wildcard include/*.h)

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

Sie können die Platzhalter auch direkt verwenden, aber ich finde, ich brauche sie an mehr als einem Ort.

Beachten Sie, dass dies nur bei kleinen Projekten gut funktioniert, da davon ausgegangen wird, dass jede Objektdatei von jeder Header-Datei abhängt.

Michael Williamson
quelle
danke, ich machte das viel komplizierter als nötig
Mike
14
Dies funktioniert jedoch. Das Problem dabei ist, dass jede Objektdatei jedes Mal neu kompiliert wird, wenn eine kleine Änderung vorgenommen wird, dh wenn Sie 100 Quell- / Header-Dateien haben und nur eine kleine Änderung vornehmen, werden alle 100 neu kompiliert .
Nicholas Hamilton
1
Sie sollten Ihre Antwort wirklich aktualisieren, um zu sagen, dass dies eine sehr ineffiziente Methode ist, da bei jeder Änderung einer Header-Datei ALLE Dateien neu erstellt werden. Die anderen Antworten sind viel besser.
Xaxxon
2
Dies ist eine sehr schlechte Lösung. Sicher, es wird bei einem kleinen Projekt funktionieren, aber für jedes Team mit Produktionsgröße und Build wird dies zu einer schrecklichen Kompilierungszeit führen und make clean alljedes Mal gleichbedeutend mit dem Ausführen sein.
Julien Guertault
In meinem Test funktioniert das überhaupt nicht. Die gccZeile wird überhaupt nicht ausgeführt, sondern stattdessen die integrierte Regel ( %o: %.cRegel).
Penghe Geng
4

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

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

-include $(DEPS)

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

Dies erledigt den Job einwandfrei und behandelt sogar die angegebenen Unterverzeichnisse:

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

testete es mit gcc 4.8.3

g24l
quelle
3

Hier ist ein Zweiliner:

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

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

tbodt
quelle
1

Ich bevorzuge diese Lösung gegenüber der akzeptierten Antwort von Michael Williamson. Sie erfasst Änderungen an Quellen + Inline-Dateien, dann an Quellen + Headern und schließlich nur an Quellen. Vorteil hierbei ist, dass die gesamte Bibliothek nicht neu kompiliert 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)
Nicholas Hamilton
quelle
2
Dies funktioniert nur, wenn Ihre Header-Dateien nichts enthalten, was eine Neukompilierung anderer CPP-Dateien als der entsprechenden Implementierungsdatei erforderlich machen würde.
Matec
0

Folgendes funktioniert für mich:

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

-include $(DEPS)

%.o: %.cpp
    $(CXX) $(CFLAGS) -MMD -c -o $@ $<
Marcel Keller
quelle
0

Eine leicht modifizierte Version von Sophies Antwort , mit der die * .d-Dateien in einem anderen Ordner ausgegeben werden können (ich werde nur den interessanten Teil einfügen, der die Abhängigkeitsdateien generiert):

$(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 $@

Beachten Sie, dass der Parameter

-MT $@

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

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 auch 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 keinen Hinweis in der g ++ - Manpage gefunden.

MaximumFPS
quelle