346 Stimmen

Wie man ein SIMPLE C++ Makefile erstellt

Wir müssen ein Makefile verwenden, um alles für unser Projekt zusammenzustellen, aber unser Professor hat uns nie gezeigt, wie das geht.

Ich habe nur eine Datei, a3driver.cpp . Der Treiber importiert eine Klasse von einem Ort, "/user/cse232/Examples/example32.sequence.cpp" .

Das war's. Alles andere ist in der .cpp .

Wie würde ich vorgehen, um ein einfaches Makefile zu erstellen, das eine ausführbare Datei namens a3a.exe ?

14 Stimmen

.EXE, also ist es definitiv Windows. Wenn ich es mir recht überlege... der Pfad ist im Unix-Stil. Wahrscheinlich verwendet er Mingw-32.

3 Stimmen

Seufz. Ich nehme an, man muss die Grundlagen eines jeden Berufs erlernen, auch wenn man sie nie anwenden wird. Man muss einfach verstehen, wie die Dinge funktionieren. Die Chancen stehen allerdings gut, dass Sie immer in einer IDE wie Eclipse entwickeln werden. Sie werden hier eine Antwort auf Ihren einfachen Ein-Zeilen-Fall bekommen, und es gibt jede Menge Web-Tutorials, aber wenn Sie in die Tiefe gehen wollen, kommen Sie um das O'Reilly-Buch nicht herum (das gilt auch für die meisten SW-Themen). amazon.com/Managing-Projects-Make-Nutshell-Handbooks/dp/ Kaufen Sie ein Exemplar aus 2. Hand bei amazon, half.com, betterworldbooks eBay

3 Stimmen

Der von @Dennis gepostete Link ist nicht mehr verfügbar, aber das gleiche Material findet sich in diesem archive.org-Seite .

641voto

Da es sich um ein Unix-Programm handelt, haben die ausführbaren Dateien keine Erweiterungen.

Es ist zu beachten, dass root-config ist ein Dienstprogramm, das die richtigen Kompilierungs- und Linking-Flags und die richtigen Bibliotheken für die Erstellung von Anwendungen gegen Root bereitstellt. Das ist nur ein Detail, das sich auf die ursprüngliche Zielgruppe dieses Dokuments bezieht.

Mach mich zum Baby

oder Du vergisst nie das erste Mal, dass du gemacht wurdest

Eine einführende Diskussion über make und wie man ein einfaches makefile schreibt

Was ist Make? Und warum sollte mich das interessieren?

Das Werkzeug namens Machen Sie ist ein Manager für Build-Abhängigkeiten. Das heißt, er kümmert sich darum, zu wissen, welche Befehle in welcher Reihenfolge ausgeführt werden müssen, um Ihr Softwareprojekt aus einer Sammlung von Quelldateien, Objektdateien, Bibliotheken, Headern usw. usw. - von denen sich einige möglicherweise kürzlich geändert haben - in eine korrekte, aktuelle Version des Programms zu verwandeln.

Eigentlich kann man Make auch für andere Dinge verwenden, aber darüber werde ich nicht sprechen.

Ein triviales Makefile

Angenommen, Sie haben ein Verzeichnis, das Folgendes enthält: tool tool.cc tool.o support.cc support.hh und support.o die abhängen von root und sollen in ein Programm namens tool und nehmen Sie an, dass Sie an den Quelldateien herumgeschraubt haben (d. h. die vorhandenen tool ist jetzt veraltet) und möchte das Programm kompilieren.

Um dies selbst zu tun, könnten Sie

  1. Prüfen Sie, ob entweder support.cc o support.hh ist neuer als support.o und wenn ja, führen Sie einen Befehl wie

    g++ -g -c -pthread -I/sw/include/root support.cc
  2. Prüfen Sie, ob entweder support.hh o tool.cc sind neuer als tool.o und wenn ja, führen Sie einen Befehl wie

    g++ -g  -c -pthread -I/sw/include/root tool.cc
  3. Prüfen Sie, ob tool.o ist neuer als tool und wenn ja, führen Sie einen Befehl wie

    g++ -g tool.o support.o -L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \
    -lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz -Wl,-framework,CoreServices \
    -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root -lm -ldl

Puh! Was für eine Anstrengung! Man muss sich eine Menge merken und es gibt viele Möglichkeiten, Fehler zu machen. (BTW-- die Einzelheiten der hier gezeigten Befehlszeilen hängen von unserer Softwareumgebung ab. Diese hier funktionieren auf meinem Computer).

Sie können natürlich auch einfach alle drei Befehle jedes Mal ausführen. Das würde funktionieren, ist aber für ein umfangreiches Programm (wie DOGS, dessen Kompilierung auf meinem MacBook mehr als 15 Minuten in Anspruch nimmt) nicht gut geeignet.

Stattdessen könnten Sie eine Datei namens makefile wie diese:

tool: tool.o support.o
    g++ -g -o tool tool.o support.o -L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \
        -lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz -Wl,-framework,CoreServices \
        -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root -lm -ldl

tool.o: tool.cc support.hh
    g++ -g  -c -pthread -I/sw/include/root tool.cc

support.o: support.hh support.cc
    g++ -g -c -pthread -I/sw/include/root support.cc

und tippen Sie einfach make in der Befehlszeile. Dadurch werden die drei oben beschriebenen Schritte automatisch ausgeführt.

Die Zeilen ohne Einrückungen haben hier die Form "Ziel: Abhängigkeiten" und sagen Sie Make, dass die zugehörigen Befehle (eingerückte Zeilen) ausgeführt werden sollen, wenn eine der Abhängigkeiten neuer ist als das Ziel. Das heißt, die Zeilen mit den Abhängigkeiten beschreiben die Logik dessen, was neu erstellt werden muss, um Änderungen in verschiedenen Dateien zu berücksichtigen. Wenn support.cc Änderungen, die bedeuten, dass support.o wiederaufgebaut werden muss, aber tool.o kann in Ruhe gelassen werden. Wenn support.o Änderungen tool muss wieder aufgebaut werden.

Die Befehle, die mit jeder Abhängigkeitslinie verbunden sind, sind mit einem Tabulator (siehe unten) versehen und sollten das Ziel ändern (oder zumindest berühren, um die Änderungszeit zu aktualisieren).

Variablen, eingebaute Regeln und andere Goodies

An diesem Punkt erinnert sich unser Makefile einfach an die Arbeit, die getan werden muss, aber wir mussten immer noch jeden einzelnen benötigten Befehl in seiner Gesamtheit herausfinden und eingeben. Das muss nicht so sein: Make ist eine mächtige Sprache mit Variablen, Funktionen zur Textmanipulation und einer ganzen Reihe von eingebauten Regeln, die uns diese Arbeit sehr erleichtern können.

Variablen erstellen

Die Syntax für den Zugriff auf eine make-Variable lautet $(VAR) .

Die Syntax für die Zuweisung an eine Make-Variable lautet: VAR = A text value of some kind (oder VAR := A different text value but ignore this for the moment ).

Sie können Variablen in Regeln wie dieser verbesserten Version unseres Makefiles verwenden:

CPPFLAGS=-g -pthread -I/sw/include/root
LDFLAGS=-g
LDLIBS=-L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \
       -lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz \
       -Wl,-framework,CoreServices -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root \
       -lm -ldl

tool: tool.o support.o
    g++ $(LDFLAGS) -o tool tool.o support.o $(LDLIBS)

tool.o: tool.cc support.hh
    g++ $(CPPFLAGS) -c tool.cc

support.o: support.hh support.cc
    g++ $(CPPFLAGS) -c support.cc

die etwas besser lesbar ist, aber immer noch viel Tipparbeit erfordert

Make-Funktionen

GNU make unterstützt eine Vielzahl von Funktionen, um auf Informationen aus dem Dateisystem oder andere Befehle auf dem System zuzugreifen. In diesem Fall sind wir interessiert an $(shell ...) das zur Ausgabe des/der Argumente(s) expandiert, und $(subst opat,npat,text) die alle Instanzen von opat avec npat im Text.

Dies können wir ausnutzen:

CPPFLAGS=-g $(shell root-config --cflags)
LDFLAGS=-g $(shell root-config --ldflags)
LDLIBS=$(shell root-config --libs)

SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))

tool: $(OBJS)
    g++ $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)

tool.o: tool.cc support.hh
    g++ $(CPPFLAGS) -c tool.cc

support.o: support.hh support.cc
    g++ $(CPPFLAGS) -c support.cc

was einfacher zu tippen und viel besser lesbar ist.

Beachten Sie, dass

  1. Wir geben immer noch ausdrücklich die Abhängigkeiten für jede Objektdatei und die endgültige ausführbare Datei an
  2. Wir mussten die Kompilierungsregel für beide Quelldateien explizit eingeben

Implizite und Musterregeln

Wir würden im Allgemeinen erwarten, dass alle C++-Quelldateien gleich behandelt werden, und Make bietet drei Möglichkeiten, dies anzugeben:

  1. Suffix-Regeln (in GNU make als veraltet angesehen, aber aus Gründen der Abwärtskompatibilität beibehalten)
  2. implizite Regeln
  3. Musterregeln

Es sind implizite Regeln eingebaut, von denen einige im Folgenden erläutert werden. Musterregeln werden in der folgenden Form angegeben

%.o: %.c
    $(CC) $(CFLAGS) $(CPPFLAGS) -c $<

was bedeutet, dass Objektdateien aus C-Quelldateien erzeugt werden, indem der gezeigte Befehl ausgeführt wird, wobei die Variable "automatic" $< expandiert zum Namen der ersten Abhängigkeit.

Eingebaute Regeln

Make hat eine ganze Reihe von eingebauten Regeln, die bedeuten, dass ein Projekt sehr oft durch ein sehr einfaches Makefile kompiliert werden kann, in der Tat.

Die in GNU make eingebaute Regel für C-Quelldateien ist die oben gezeigte. Ähnlich erzeugen wir Objektdateien aus C++-Quelldateien mit einer Regel wie $(CXX) -c $(CPPFLAGS) $(CFLAGS) .

Einzelne Objektdateien werden mit $(LD) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS) aber das wird in unserem Fall nicht funktionieren, weil wir mehrere Objektdateien verknüpfen wollen.

Von eingebauten Regeln verwendete Variablen

Die eingebauten Regeln verwenden einen Satz von Standardvariablen, die es Ihnen ermöglichen, lokale Umgebungsinformationen anzugeben (z. B. wo die Root-Include-Dateien zu finden sind), ohne alle Regeln neu schreiben zu müssen. Diejenigen, die für uns am ehesten interessant sind, sind:

  • CC -- den zu verwendenden C-Compiler
  • CXX -- den zu verwendenden C++-Compiler
  • LD -- den zu verwendenden Linker
  • CFLAGS -- Kompilierungsflag für C-Quelldateien
  • CXXFLAGS -- Kompilierungsflags für C++-Quelldateien
  • CPPFLAGS -- Flags für den c-Präprozessor (enthalten typischerweise Dateipfade und auf der Befehlszeile definierte Symbole), die von C und C++ verwendet werden
  • LDFLAGS -- Linker-Flags
  • LDLIBS -- zu verknüpfende Bibliotheken

Ein grundlegendes Makefile

Indem wir die Vorteile der eingebauten Regeln nutzen, können wir unser Makefile vereinfachen:

CC=gcc
CXX=g++
RM=rm -f
CPPFLAGS=-g $(shell root-config --cflags)
LDFLAGS=-g $(shell root-config --ldflags)
LDLIBS=$(shell root-config --libs)

SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))

all: tool

tool: $(OBJS)
    $(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)

tool.o: tool.cc support.hh

support.o: support.hh support.cc

clean:
    $(RM) $(OBJS)

distclean: clean
    $(RM) tool

Wir haben auch mehrere Standardziele hinzugefügt, die spezielle Aktionen durchführen (wie das Bereinigen des Quellverzeichnisses).

Beachten Sie, dass make, wenn es ohne ein Argument aufgerufen wird, das erste in der Datei gefundene Ziel verwendet (in diesem Fall all), aber Sie können das zu erhaltende Ziel auch benennen, wodurch make clean entfernen Sie in diesem Fall die Objektdateien.

Wir haben immer noch alle Abhängigkeiten fest kodiert.

Einige mysteriöse Verbesserungen

CC=gcc
CXX=g++
RM=rm -f
CPPFLAGS=-g $(shell root-config --cflags)
LDFLAGS=-g $(shell root-config --ldflags)
LDLIBS=$(shell root-config --libs)

SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))

all: tool

tool: $(OBJS)
    $(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)

depend: .depend

.depend: $(SRCS)
    $(RM) ./.depend
    $(CXX) $(CPPFLAGS) -MM $^>>./.depend;

clean:
    $(RM) $(OBJS)

distclean: clean
    $(RM) *~ .depend

include .depend

Beachten Sie, dass

  1. Es gibt keine Abhängigkeitslinien mehr für die Quelldateien!?!
  2. Es gibt eine seltsame Magie im Zusammenhang mit .depend und depend
  3. Wenn Sie das tun make entonces ls -A sehen Sie eine Datei namens .depend die Dinge enthält, die wie Zeilen mit Make-Abhängigkeiten aussehen

Weitere Lektüre

Bugs und historische Hinweise kennen

Die Eingabesprache für Make ist weißraumabhängig. Insbesondere, die auf die Abhängigkeiten folgenden Aktionszeilen müssen mit einem Tabulator beginnen . Aber eine Reihe von Leerzeichen kann genauso aussehen (und es gibt tatsächlich Editoren, die Tabulatoren stillschweigend in Leerzeichen umwandeln oder umgekehrt), was dazu führt, dass eine Make-Datei zwar richtig aussieht, aber trotzdem nicht funktioniert. Dies wurde schon früh als Fehler erkannt, aber ( Die Geschichte lautet ) wurde es nicht behoben, weil es bereits 10 Benutzer gab.

(Dies wurde aus einem Wiki-Beitrag kopiert, den ich für Physik-Diplomstudenten geschrieben habe).

11 Stimmen

Diese Methode zur Erzeugung von Abhängigkeiten ist veraltet und sogar schädlich. Siehe Erweiterte automatische Abhängigkeitsgenerierung .

5 Stimmen

-pthread Flaggenursachen gcc um die erforderlichen Makros zu definieren, -D_REENTRANT ist unnötig.

1 Stimmen

@MaximYegorushkin: Warum ist diese Methode schädlich?

68voto

Brendan Long Punkte 51048

Ich war schon immer der Meinung, dass es einfacher ist, dies anhand eines detaillierten Beispiels zu lernen, daher stelle ich mir Makefiles folgendermaßen vor. Für jeden Abschnitt gibt es eine Zeile, die nicht eingerückt ist und in der der Name des Abschnitts gefolgt von den Abhängigkeiten steht. Die Abhängigkeiten können entweder andere Sektionen sein (die vor der aktuellen Sektion ausgeführt werden) oder Dateien (die, wenn sie aktualisiert werden, dazu führen, dass die aktuelle Sektion beim nächsten Mal erneut ausgeführt wird make ).

Hier ist ein schnelles Beispiel (beachten Sie, dass ich 4 Leerzeichen verwende, wo ich einen Tabulator verwenden sollte, Stack Overflow lässt mich keine Tabulatoren verwenden):

a3driver: a3driver.o
    g++ -o a3driver a3driver.o

a3driver.o: a3driver.cpp
    g++ -c a3driver.cpp

Wenn Sie eingeben make wählt es den ersten Abschnitt (a3driver). a3driver hängt von a3driver.o ab, also geht es zu diesem Abschnitt. a3driver.o hängt von a3driver.cpp ab, also wird es nur ausgeführt, wenn a3driver.cpp seit der letzten Ausführung geändert wurde. Wenn dies der Fall ist (oder es noch nie ausgeführt wurde), wird a3driver.cpp zu einer .o-Datei kompiliert, dann geht es zurück zu a3driver und kompiliert die endgültige ausführbare Datei.

Da es nur eine Datei gibt, könnte es sogar auf eine Datei reduziert werden:

a3driver: a3driver.cpp
    g++ -o a3driver a3driver.cpp

Der Grund, warum ich das erste Beispiel gezeigt habe, ist, dass es die Leistungsfähigkeit von makefiles zeigt. Wenn Sie eine weitere Datei kompilieren müssen, können Sie einfach einen weiteren Abschnitt hinzufügen. Hier ist ein Beispiel mit einer secondFile.cpp (die einen Header namens secondFile.h lädt):

a3driver: a3driver.o secondFile.o
    g++ -o a3driver a3driver.o secondFile.o

a3driver.o: a3driver.cpp
    g++ -c a3driver.cpp

secondFile.o: secondFile.cpp secondFile.h
    g++ -c secondFile.cpp

Wenn Sie auf diese Weise etwas in secondFile.cpp oder secondFile.h ändern und neu kompilieren, wird nur secondFile.cpp neu kompiliert (nicht a3driver.cpp). Oder alternativ: Wenn Sie etwas in a3driver.cpp ändern, wird secondFile.cpp nicht neu kompiliert.

Lassen Sie mich wissen, wenn Sie Fragen dazu haben.

Es ist auch üblich, einen Abschnitt mit dem Namen "all" und einen Abschnitt mit dem Namen "clean" einzufügen. Mit "all" werden in der Regel alle ausführbaren Dateien erstellt, und mit "clean" werden "Build-Artefakte" wie .o-Dateien und die ausführbaren Dateien entfernt:

all: a3driver ;

clean:
    # -f so this will succeed even if the files don't exist
    rm -f a3driver a3driver.o

EDIT: Ich habe nicht bemerkt, dass du Windows verwendest. Ich denke, der einzige Unterschied ist die Änderung der -o a3driver à -o a3driver.exe .

0 Stimmen

Der absolute Code, den ich zu verwenden versuche, lautet: p4a.exe: p4driver.cpp g++ -o p4a p4driver.cpp ABER, er meldet mir "fehlendes Trennzeichen". Ich verwende TAB, aber es meldet mir trotzdem. Irgendeine Idee?

2 Stimmen

Soweit ich weiß, wird diese Fehlermeldung nur angezeigt, wenn Sie Leerzeichen haben. Stellen Sie sicher, dass Sie keine Zeilen haben, die mit Leerzeichen beginnen (Leerzeichen + Tabulator führt zu diesem Fehler). Das ist das einzige, was mir einfällt

0 Stimmen

Hinweis für künftige Redakteure: StackOverflow kann Tabs nicht darstellen, auch wenn Sie sie in die Antwort einfügen. Versuchen Sie also bitte nicht, meinen Hinweis zu diesem Thema zu "korrigieren".

40voto

friedmud Punkte 609

Warum listet jeder gerne die Quelldateien auf? Ein einfacher find-Befehl kann das leicht erledigen.

Hier ist ein Beispiel für ein schmutzig einfaches C++ Makefile. Legen Sie es einfach in einem Verzeichnis ab, das .C Dateien und geben Sie dann make ...

appname := myapp

CXX := clang++
CXXFLAGS := -std=c++11

srcfiles := $(shell find . -name "*.C")
objects  := $(patsubst %.C, %.o, $(srcfiles))

all: $(appname)

$(appname): $(objects)
    $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(appname) $(objects) $(LDLIBS)

depend: .depend

.depend: $(srcfiles)
    rm -f ./.depend
    $(CXX) $(CXXFLAGS) -MM $^>>./.depend;

clean:
    rm -f $(objects)

dist-clean: clean
    rm -f *~ .depend

include .depend

5 Stimmen

Ein Grund, die Quelldateien nicht automatisch zu finden, ist, dass man verschiedene Build-Ziele haben kann, die verschiedene Dateien benötigen.

0 Stimmen

Stimmt @hmijail, sowie Submodule, die eine Tonne von Quellen / Header, die Sie nicht wollen, kompiliert / gelinkt... und zweifellos viele andere Umstände, in denen erschöpfende Suche / Verwendung ist ungeeignet.

0 Stimmen

Warum verwenden Sie "shell find" und nicht "wildcard"?

15voto

No one Punkte 141

Sie hatten zwei Möglichkeiten.

Option 1: einfachstes Makefile = NO MAKEFILE.

Benennen Sie "a3driver.cpp" in "a3a.cpp" um, und schreiben Sie dann in die Befehlszeile:

nmake a3a.exe

Und das war's. Wenn Sie GNU Make verwenden, benutzen Sie "make" oder "gmake" oder was auch immer.

Option 2: ein 2-zeiliges Makefile.

a3a.exe: a3driver.obj
    link /out:a3a.exe a3driver.obj

3 Stimmen

Dies wäre eine ausgezeichnete Antwort, wenn sie nicht so viele Details über das Umfeld des Auftraggebers voraussetzen würde. Ja, sie arbeiten mit Windows, aber das bedeutet nicht, dass sie nmake . Die link Befehlszeile sieht auch sehr spezifisch für einen bestimmten Compiler aus, und sollte zumindest dokumentieren, welcher es ist.

12voto

VectorVortec Punkte 627

Ich habe friedmuds Antwort . Ich habe mich eine Weile damit beschäftigt, und es scheint ein guter Weg für den Anfang zu sein. Diese Lösung hat auch eine gut definierte Methode zum Hinzufügen von Compiler-Flags. Ich habe wieder geantwortet, weil ich Änderungen vorgenommen habe, damit es in meiner Umgebung, Ubuntu und g++, funktioniert. Mehr funktionierende Beispiele sind manchmal der beste Lehrer.

appname := myapp

CXX := g++
CXXFLAGS := -Wall -g

srcfiles := $(shell find . -maxdepth 1 -name "*.cpp")
objects  := $(patsubst %.cpp, %.o, $(srcfiles))

all: $(appname)

$(appname): $(objects)
    $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(appname) $(objects) $(LDLIBS)

depend: .depend

.depend: $(srcfiles)
    rm -f ./.depend
    $(CXX) $(CXXFLAGS) -MM $^>>./.depend;

clean:
    rm -f $(objects)

dist-clean: clean
    rm -f *~ .depend

include .depend

Makefiles scheinen sehr komplex zu sein. Ich benutzte eines, aber es erzeugte einen Fehler im Zusammenhang mit nicht in g++ Bibliotheken verknüpfen. Diese Konfiguration löste dieses Problem.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X