it-swarm.dev

Makefile (Auto-Abhängigkeitsgenerierung)

nur für eine schnelle Terminologie: 

#basic makefile rule
target: dependencies
    recipe

Das Problem: Ich möchte die Abhängigkeiten automatisch generieren.

Ich hoffe zum Beispiel, dies zu ändern: 

#one of my targets
file.o: file.cpp 1.h 2.h 3.h 4.h 5.h 6.h 7.h 8.h another.h lots.h evenMore.h
    $(COMPILE)

Das sehr gut finden:

#one of my targets
file.o: $(GENERATE)
    $(COMPILE)

und ich bin mir nicht sicher, ob es möglich ist .. 

Was ich weiß:

Ich kann dieses Compiler-Flag verwenden: 

g++ -MM file.cpp

und es wird das richtige Ziel und die richtige Abhängigkeit zurückgeben.
so aus dem Beispiel würde es zurückkommen: 

file.o: file.cpp 1.h 2.h 3.h 4.h 5.h 6.h 7.h 8.h another.h lots.h evenMore.h  

'make' erlaubt mir jedoch NICHT, explizit Shell-Code in den Ziel- oder Abhängigkeitsabschnitt einer Regel zu schreiben :(
Ich weiß, dass es eine 'make'-Funktion gibt, die als Shell bezeichnet wird.

aber ich kann das nicht ganz als Abhängigkeit einbinden und Parsing-Magie verwenden, weil es auf dem Makro $ @ basiert, das das Ziel darstellt. Oder zumindest glaube ich, dass das Problem das Problem ist 

Ich habe sogar versucht, die Abhängigkeit von "file.cpp" durch diese Makefile-Funktion zu ersetzen, und das funktioniert auch nicht.

#it's suppose to turn the [email protected] (file.o) into file.cpp
THE_CPP := $(addsuffix $(.cpp),$(basename [email protected]))

#one of my targets
file.o: $(THE_CPP) 1.h 2.h 3.h 4.h 5.h 6.h 7.h 8.h another.h lots.h evenMore.h
    $(COMPILE)
#this does not work

Überall auf Google scheint es zwei Lösungen zu geben. Beides, das ich nicht ganz begreife.
Von GNU Manuell erstellen

Eine Site, die sagt, dass GNU Make Manual eine veraltete ist

Meine letzte Frage lautet also: Ist es möglich, es so zu machen, wie ich es will,
und wenn nicht, kann jemand den Code von einer dieser Sites aufschlüsseln und mir im Detail erklären, wie er funktioniert. Ich werde es auf eine dieser Arten implementieren, wenn ich muss, aber ich bin müde, nur ein Stück Code in mein Makefile einzufügen, bevor ich es verstehe

25
Trevor Hickey

Neuere Versionen von GCC verfügen über die Option -MP, die mit -MD verwendet werden kann. Ich habe einfach -MP und -MD zu der CPPFLAGS-Variablen für mein Projekt hinzugefügt (ich habe kein benutzerdefiniertes Rezept zum Kompilieren von C++ geschrieben) und eine "-include $ (SRC: .cpp = .d)" -Zeile hinzugefügt.

Die Verwendung von -MD und -MP ergibt eine Abhängigkeitsdatei, die sowohl die Abhängigkeiten (ohne seltsame Verweise) als auch Dummy-Ziele (so dass das Löschen von Header-Dateien keine Fehler verursacht) enthält.

26
teambob

Um die Dateinamen zu ändern, wenn Sie bereits wissen, wie die Abhängigkeiten aussehen sollen, können Sie eine Musterregel verwenden:

file.o: %.o : %.cpp 1.h 2.h 3.h 4.h 5.h 6.h 7.h 8.h another.h lots.h evenMore.h
    $(COMPILE)

Und Sie können die Regel für andere Ziele wiederverwenden:

# Note these two rules without recipes:
file.o: 1.h 2.h 3.h 4.h 5.h 6.h 7.h 8.h another.h lots.h evenMore.h
anotherFile.o: 4.h 9.h yetAnother.h

file.o anotherFile.o: %.o : %.cpp
    $(COMPILE)

Wenn Sie jedoch möchten, dass Make die Liste der Abhängigkeiten automatisch herausfindet, ist der beste Weg (der mir bekannt ist) Advanced Auto-Dependency Generation . Es sieht aus wie das:

%.o : %.cc
        @g++ -MD -c -o [email protected] $<
        @cp $*.d $*.P; \
             sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
                 -e '/^$$/ d' -e 's/$$/ :/' < $*.d >> $*.P; \
             rm -f $*.d

-include *.P

Wenn er file.o erstellt, erstellt er auch file.d. Dann führt es file.d durch einen verwirrenden sed-Befehl, der die Liste der Abhängigkeiten in eine Regel ohne Rezepte umwandelt. Die letzte Zeile ist eine Anweisung, um include alle vorhandenen Regeln zu ändern. Die Logik hier ist subtil und genial: Sie brauchen die Abhängigkeiten beim ersten Erstellen von foo.o nicht wirklich, da Make bereits weiß, dass foo.o erstellt werden muss, da es nicht existiert. Bei der nächsten Ausführung von Make wird die zuletzt erstellte Abhängigkeitsliste verwendet. Wenn Sie eine der Dateien ändern, sodass tatsächlich eine neue Abhängigkeit vorliegt, die nicht in der Liste enthalten ist, wird Make foo.o trotzdem neu erstellt, da Sie eine Datei geändert haben, die eine Abhängigkeit war . Probieren Sie es aus, es funktioniert wirklich!

20
Beta

Hervorragende Antworten, aber in meinem Build stelle ich die OBJ-Dateien in ein Unterverzeichnis basierend auf dem Build-Typ (dh debug vs. release). Wenn ich beispielsweise Debug baue, lege ich alle Objektdateien in einen Build/Debug-Ordner. Es war eine verblüffende Aufgabe, zu versuchen, den oben genannten mehrzeiligen sed-Befehl dazu zu verwenden, den korrekten Zielordner zu verwenden, aber nach einigem Experimentieren bin ich auf eine Lösung gestoßen, die für meinen Build hervorragend geeignet ist. Hoffentlich hilft es auch jemand anderem.

Hier ist ein Ausschnitt:

# List my sources
CPP_SOURCES := foo.cpp bar.cpp

# If I'm debugging, change my output location
ifeq (1,$(DEBUG))
  OBJ_DIR:=./obj/debug
  CXXFLAGS+= -g -DDEBUG -O0 -std=c++0x
else
  CXXFLAGS+= -s -O2 
  OBJ_DIR:=./obj/release
endif

# destination path macro we'll use below
df = $(OBJ_DIR)/$(*F)

# create a list of auto dependencies
AUTODEPS:= $(patsubst %.cpp,$(OBJ_DIR)/%.d,$(CPP_SOURCES))

# include by auto dependencies
-include $(AUTODEPS)

.... other rules

# and last but not least my generic compiler rule
$(OBJ_DIR)/%.o: %.cpp 
    @# Build the dependency file
    @$(CXX) -MM -MP -MT $(df).o -MT $(df).d $(CXXFLAGS) $< > $(df).d
    @# Compile the object file
    @echo " C++ : " $< " => " [email protected]
    @$(CXX) -c $< $(CXXFLAGS) -o [email protected]

Nun zu den Details: Die erste Ausführung von CXX in meiner generischen Build-Regel ist die interessante. Beachten Sie, dass ich keine "sed" -Befehle verwende. Neuere Versionen von gcc machen alles, was ich brauche (ich verwende gcc 4.7.2).

-MM erstellt die Hauptabhängigkeitsregel, die Projekt-Header, aber keine System-Header enthält. Wenn ich es so beließ, hätte meine .obj-Datei NICHT den richtigen Pfad. Also benutze ich die Option -MT, um den "echten" Pfad zu meinem .obj-Ziel anzugeben. (mit dem von mir erstellten "df" -Makro).
Ich verwende auch eine zweite -MT-Option, um sicherzustellen, dass die resultierende Abhängigkeitsdatei (dh .d-Datei) den richtigen Pfad hat und dass sie in der Zielliste enthalten ist und daher die gleichen Abhängigkeiten wie die Quelle hat Datei.

Last but not least ist auch die Option -MP. Dadurch wird gcc angewiesen, für jeden Header auch Stub-Regeln zu erstellen, die das Problem lösen, das auftritt, wenn ich einen Header lösche, der make verursacht, um einen Fehler zu generieren.

Ich vermute, dass, da ich gcc für die gesamte Abhängigkeitsgenerierung verwende, anstatt zu sedieren, mein Build schneller ist (obwohl ich das noch nicht beweisen muss, da mein Build an diesem Punkt relativ klein ist). Wenn Sie sehen, wie ich das verbessern kann, bin ich immer offen für Vorschläge. Genießen

7
Zoccadoum

Für das Protokoll generiere ich jetzt automatisch Abhängigkeiten:

CPPFLAGS = -std=c++1y -MD -MP 

SRC = $(wildcard *.cpp)
all: main

main: $(SRC:%.cpp=%.o)
    g++ $(CPPFLAGS) -o [email protected] $^

-include $(SRC:%.cpp=%.d)

Die Compilerflags -MD und -MP helfen dabei.

4
Trevor Hickey

Erstens können Sie THE_CPP=$(patsubst %.o,%.cpp,[email protected]) haben

Dann können Sie make -p ausführen, um die integrierten Regeln von make zu verstehen.

Eine übliche Methode wäre, die Abhängigkeiten der Makefiles in *.md-Dateien zu generieren:

%.o: %.c
       $(COMPILE.c) $(OUTPUT_OPTION) $< -MMD -MF $(patsubst %.c,%.md,[email protected])

und später in Ihrer Makefileeinschließlich ihnen mit etwas ähnlichem

-include $(wildcard *.md)

Sie können jedoch auch andere Builder wie omake und viele andere verwenden

WOOO! Es ist mir gelungen, den Code in Betas Beitrag für ein kleines Testprojekt zu erhalten.
Ich sollte beachten, dass für alle anderen, die auf dieses Problem stoßen könnten, Wenn Sie die Bash-Shell (die ich verwendet habe) verwenden, müssen Sie vor dem Pfund einen Escape-Charakter hinzufügen Zeichen, um dem Rest des Ausdrucks einen Kommentar zu entziehen. (siehe 4. Codezeile)

%.o : %.cpp  
    g++ -c -MD -o [email protected] $<  
    cp $*.d $*.P; \  
    sed -e 's/\#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \  
        -e '/^$$/ d' -e 's/$$/ :/' < $*.d >> $*.P; \  
    rm -f $*.d  
-include *.P  

Jetzt möchte ich Informationen teilen, die ich in Projekte verwalten mit GNU Make, 3rd Edition, gefunden habe. weil es auf einige wichtige Punkte in dieser Angelegenheit hinweist und Code liefert, den ich noch nicht vollständig verstanden habe.
In dem Buch wird eine Methode angezeigt, die der Methode auf der Seite "Make" ähnelt.
Es sieht aus wie das: 

include $(subst .c,.d,$(SOURCES))

%.d: %.c
    $(CC) -M $(CPPFLAGS) $< > [email protected]$$$$; \
    sed 's,\($*\).o[ :]*,\1.o [email protected] : ,g' < [email protected]$$$$ > [email protected]; \
    rm -f [email protected]$$$$

Das ist, was ich glaube, passiert.
Sofort möchte "make" für jede Quelldatei eine ".d" -Datei enthalten.
Da anfänglich keine .d-Dateien vorhanden sind, wird der Codeabschnitt immer wieder ausgeführt, um alle fehlenden .d-Dateien zu erstellen.
Dies bedeutet, dass make immer wieder neu beginnt, bis jede .d-Datei erstellt und in das Makefile aufgenommen wird.
Jede ".d" -Datei ist das, was Beta sagte: ein Ziel mit einer Reihe von Abhängigkeiten und einem NEIN-Rezept. 

Wenn eine Headerdatei geändert wird, müssen die in den Regeln enthaltenen Regeln zuerst die Abhängigkeiten aktualisiert werden. Das ist es, was mich ein bisschen abwirft, wie kann man den Code-Block wieder aufrufen? Es wird zum Aktualisieren von .d-Dateien verwendet. Wenn sich also eine .h-Datei ändert, wie wird sie aufgerufen? Abgesehen davon ist mir klar, dass die Standardregel zum Kompilieren des Objekts verwendet wird. Alle Erklärungen/Missverständnisse zu dieser Erklärung werden gebeten.


Später in diesem Buch werden Probleme mit dieser Methode und Probleme aufgezeigt, die meiner Meinung nach auch in der Implementierung der Advanced Auto-Dependency Generation vorhanden sind.
Problem 1: Es ist ineffizient. "make" muss jedes Mal neu gestartet werden, wenn eine .d-Datei erstellt wird
Problem 2: make generiert Warnmeldungen für alle fehlenden .d-Dateien. Dies ist meist nur ein Ärgernis und kann durch Hinzufügen eines "-" vor der Include-Anweisung ausgeblendet werden.
Problem 3: Wenn Sie eine src-Datei löschen, weil sie nicht mehr benötigt wird, stürzt 'make' beim nächsten Kompilierungsversuch ab, da einige .d-Dateien die fehlende src als Abhängigkeit haben und da es keine Regel gibt, diese Src neu zu erstellen, weigert sich make, weiter zu gehen. 

Sie sagen, dass eine Lösung für diese Probleme die Methode von Tromey ist, der Code unterscheidet sich jedoch stark vom Code auf der Website. Vielleicht liegt es nur daran, dass sie einige Makros verwendet haben, es zu einem Funktionsaufruf gemacht und etwas anders geschrieben haben. Ich schaue immer noch nach, wollte aber einige Entdeckungen teilen, die ich bisher gemacht habe. Hoffentlich öffnet sich eine wenig weitere Diskussion und bringt mich näher an das Ganze heran. 

1
Trevor Hickey

Ich benutze lieber die $ (Shell ...) Funktion mit find. Hier ist ein Beispiel eines meiner Makefiles:

SRCDIR = src
OBJDIR = obj
LIBDIR = lib
DOCDIR = doc

# Get Only the Internal Structure of Directories from SRCDIR
STRUCTURE := $(Shell find $(SRCDIR) -type d)

#Filter-out hidden directories
STRUCTURE := $(filter-out $(Shell find $(SRCDIR)/.* -type d),$(STRUCTURE))

# Get All Files From STRUCTURE
CODEFILES := $(addsuffix /*,$(STRUCTURE))
CODEFILES := $(wildcard $(CODEFILES))


## Filter Only Specific Files
SRCFILES := $(filter %.c,$(CODEFILES))
HDRFILES := $(filter %.h,$(CODEFILES))
OBJFILES := $(subst $(SRCDIR),$(OBJDIR),$(SRCFILES:%.c=%.o))
DOCFILES := $(addprefix $(DOCDIR)/,             \
            $(addsuffix .md,                    \
            $(basename $(SRCFILES))))


# Filter Out Function main for Libraries
LIBDEPS := $(filter-out $(OBJDIR)/main.o,$(OBJFILES))

Bei diesem Ansatz erhalte ich zunächst die gesamte interne Verzeichnisstruktur mit beliebiger Tiefe. Dann bekomme ich alle Dateien in die Struktur. Zu diesem Zeitpunkt kann ich filter, filter-out, adduffix usw. verwenden, um genau das zu erhalten, was ich zu jedem Zeitpunkt benötige. 

In diesem Beispiel werden * .c-Dateien behandelt. Sie können es jedoch auch in * .cpp ändern.

0
user4713908