Ein Schnellführer zu Makefiles und GNU Make für Anfänger
GNU Make ist ein Entwicklungswerkzeug, das bestimmt, welche Teile eines bestimmten Codebestands erneut kompiliert werden müssen, und Linux-Befehle ausführen kann, um diese Operationen durchzuführen.
Dieses Build-Automatisierungstool kann mit jeder Programmiersprache verwendet werden, deren Kompilierung über die Shell durch Ausführen von Befehlen erfolgen kann, was es für C, C++ und viele andere kompilierte Sprachen unverzichtbar macht.
Makefiles in Linux
Um GNU Make zu verwenden, benötigen wir eine Reihe von Regeln, die die Beziehung zwischen verschiedenen Dateien in unserem Programm definieren, sowie Befehle zum Aktualisieren jeder Datei. Diese werden in eine spezielle Datei geschrieben, die ‚Makefile‘ oder ‚makefile‘ genannt wird.
Der 'make'-Befehl verwendet die Makefile-Datenbank und die letzten Änderungszeiten der Dateien, um zu entscheiden, welche Dateien neu kompiliert werden müssen.
Inhalt einer Makefile
Im Allgemeinen enthalten Makefiles fünf Arten von Elementen: explizite Regeln, implizite Regeln, Variablendefinitionen, Direktiven und Kommentare.
- Explizite Regeln geben an, wie man eine oder mehrere Dateien (genannt
Targets) erstellt oder neu erstellt und wann dies zu tun ist. - Implizite Regeln geben vor, wie Dateien basierend auf ihren Namen erstellt oder neu erstellt werden, und beschreiben, wie eine Zieldatei mit einer anderen Datei mit einem ähnlichen Namen zusammenhängt.
- Variablendefinitionen sind Zeilen, die Zeichenfolgenwerte für Variablen angeben, die später im Makefile ersetzt werden sollen.
- Anweisungen sind Anweisungen für make, um beim Lesen der Makefile etwas Besonderes zu tun.
- Kommentare beginnen mit einem
'#'-Symbol. Jede Zeile, die mit'#'beginnt, wird von make ignoriert.
Struktur von Makefiles
Die Informationen, die make sagen, wie ein System neu kompiliert werden soll, stammen aus dem Lesen der Makefile.
Ein einfaches Makefile besteht aus Regeln mit der folgenden Syntax:
target ... : prerequisites ...
recipe
...
...
- Ein Target ist die Ausgabedatei, die vom Programm erzeugt wird, die auch ein fiktives Target sein kann (unten erklärt). Beispiele sind ausführbare Dateien, Objektdateien oder fiktive Targets wie
clean,install,testoderall. - Eine Voraussetzung (auch als Abhängigkeit bezeichnet) ist eine Datei, die als Eingabe zur Erstellung der Zieldateien verwendet wird.
- Ein Rezept ist die Aktion, die make ausführt, um die Zieldatei basierend auf den Voraussetzungen zu erstellen. Es ist notwendig, ein Tabulatorzeichen vor jede Rezeptzeile zu setzen, es sei denn, Sie geben die
.RECIPEPREFIX-Variable an, um ein anderes Zeichen als Präfix zu definieren.
Ein Beispiel-Makefile:
final: main.o end.o inter.o start.o
gcc -o final main.o end.o inter.o start.o
main.o: main.c global.h
gcc -c main.c
end.o: end.c local.h global.h
gcc -c end.c
inter.o: inter.c global.h
gcc -c inter.c
start.o: start.c global.h
gcc -c start.c
clean:
rm -f main.o end.o inter.o start.o final
In diesem Beispiel verwenden wir vier C-Quelldateien und zwei Header-Dateien, um die ausführbare Datei final zu erstellen. Jede ‘.o'-Datei ist sowohl ein Ziel als auch eine Voraussetzung innerhalb des Makefiles. Beachten Sie das letzte Ziel mit dem Namen clean – es ist eine Aktion und keine tatsächliche Datei.
Da wir normalerweise während der Kompilierung nicht reinigen müssen, wird es in keiner anderen Regel als Voraussetzung angegeben. Ziele, die sich nicht auf Dateien beziehen, sondern nur Aktionen darstellen, werden phony targets genannt. Sie haben typischerweise keine Voraussetzungen wie normale Ziel-Dateien.
Wie GNU Make eine Makefile verarbeitet
Standardmäßig startet make mit dem ersten Ziel im Makefile, das als Standardziel bezeichnet wird. In unserem Beispiel ist final das erste Ziel. Da seine Voraussetzungen Objektdateien enthalten, müssen diese aktualisiert werden, bevor final erstellt wird. Jede Voraussetzung wird entsprechend ihrer eigenen Regel verarbeitet.
Eine Neukompilierung erfolgt, wenn Änderungen an Quell- oder Header-Dateien vorgenommen wurden oder wenn die Objektdatei überhaupt nicht existiert. Nachdem die notwendigen Objektdateien neu kompiliert wurden, entscheidet make, ob final erneut verlinkt werden soll. Dies geschieht, wenn final nicht existiert oder wenn eine der Objektdateien neuer ist als sie.
Zum Beispiel, wenn wir inter.c ändern und make ausführen, wird diese Quelldatei neu kompiliert, um inter.o zu aktualisieren, und dann wird final gelinkt.
Verwendung von Variablen in Makefiles
In unserem Beispiel mussten wir alle Objektdateien zweimal in der Regel für final auflisten:
final: main.o end.o inter.o start.o
gcc -o final main.o end.o inter.o start.o
Um eine solche Duplizierung zu vermeiden, können wir Variablen einführen, um Listen von Dateien zu speichern. Die Verwendung von Variablen macht das Makefile auch leichter zu pflegen.
Hier ist eine verbesserte Version:
CC = gcc
CFLAGS = -Wall -Wextra -O2
OBJ = main.o end.o inter.o start.o
TARGET = final
$(TARGET): $(OBJ)
$(CC) -o $(TARGET) $(OBJ)
main.o: main.c global.h
$(CC) $(CFLAGS) -c main.c
end.o: end.c local.h global.h
$(CC) $(CFLAGS) -c end.c
inter.o: inter.c global.h
$(CC) $(CFLAGS) -c inter.c
start.o: start.c global.h
$(CC) $(CFLAGS) -c start.c
clean:
rm -f $(OBJ) $(TARGET)
Beachten Sie, wie wir auch CC für den Compiler und CFLAGS für die Kompilierungsoptionen definiert haben, was es einfach macht, Compiler-Optionen zu ändern oder sogar den Compiler für das gesamte Projekt zu wechseln.
Regeln zum Reinigen des Quellverzeichnisses
Wie im Beispiel zu sehen ist, können wir Regeln definieren, um das Quellverzeichnis aufzuräumen, indem wir unerwünschte Dateien nach der Kompilierung entfernen. Aber nehmen wir an, wir haben eine tatsächliche Datei namens clean – wie können wir zwischen der Datei und dem Ziel unterscheiden? Hier kommen phony-Ziele ins Spiel.
Ein falsches Ziel ist eigentlich nicht der Name einer Datei; es ist nur ein Name für ein Rezept, das ausgeführt wird, wenn es explizit angefordert wird. Die Hauptgründe für die Verwendung von falschen Zielen sind, Konflikte mit Dateien desselben Namens zu vermeiden und die Leistung zu verbessern.
Hier ist ein wichtiges Detail: Das Rezept für clean wird standardmäßig nicht ausgeführt, wenn man make ausführt. Stattdessen muss es explizit mit make clean aufgerufen werden.
Um ein falsches Ziel korrekt zu deklarieren, verwenden Sie die .PHONY-Direktive:
.PHONY: clean all install
clean:
rm -f $(OBJ) $(TARGET)
all: $(TARGET)
Moderne Makefile-Best Practices
Hier sind einige zeitgenössische Praktiken, die man in Betracht ziehen sollte:
Verwenden Sie Musterregeln, um Wiederholungen zu reduzieren:
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
Fügen Sie ein standardmäßiges ‘alle‘ Ziel hinzu:
.PHONY: all
all: $(TARGET)
Schließen Sie Abhängigkeitsgenerierung für die automatische Header-Verfolgung ein:
DEPS = $(OBJ:.o=.d)
-include $(DEPS)
Fügen Sie weitere falsche Ziele für gängige Operationen hinzu:
.PHONY: install test run
Verwenden Sie automatische Variablen wie $@ (Ziel), $> (erste Voraussetzung) und $^ (alle Voraussetzungen), um Regeln generischer zu machen.
Fazit
Versuchen Sie nun, Makefiles für Ihren eigenen Code zu erstellen. GNU Make bleibt ein leistungsfähiges und weit verbreitetes Build-Tool, insbesondere für C- und C++-Projekte.
Das Verständnis von Makefiles wird Ihnen helfen, mit zahllosen Open-Source-Projekten zu arbeiten und Ihnen eine detaillierte Kontrolle über Ihren Build-Prozess zu geben. Zögern Sie nicht, Ihre Fragen oder Erfahrungen in den Kommentaren zu teilen!