Make verstehen
August 1, 2026 · – Besucher
make taucht in fast jedem eingebetteten und systemnahen Projekt auf, mit dem ich zu tun hatte — Yocto-Rezepte, Kernel-Builds, U-Boot, kleine Firmware-Repositories. Es ist auch eines dieser Tools, die Leute jahrelang durch Nachahmung benutzen, indem sie ein Makefile aus dem letzten Projekt kopieren, ohne je das dahinterliegende Modell wirklich zu durchdringen. Hier ist dieses Modell, direkt dargelegt.
Was ist Make?
Make ist ein Build-Automatisierungswerkzeug: Es liest eine Datei (ein Makefile), die eine Menge von Zielen (targets), die Voraussetzungen (prerequisites), von denen jedes Ziel abhängt, und das Rezept (recipe, Shell-Befehle) beschreibt, das nötig ist, um dieses Ziel aus seinen Voraussetzungen zu erzeugen. Anhand dieser Beschreibung ermittelt make, welche Ziele veraltet sind, und führt nur die Rezepte aus, die nötig sind, um sie auf den aktuellen Stand zu bringen — nicht mehr.
main: main.o utils.o
gcc -o main main.o utils.o
main.o: main.c
gcc -c main.c
Führt man make aus, wird main.o nur gebaut, wenn main.c neuer ist als main.o (oder main.o noch nicht existiert), und main wird nur gelinkt, wenn sich main.o oder utils.o geändert hat. Nichts wird unnötig neu gebaut.
Für wen ist es gedacht?
Make wurde im April 1976 von Stuart Feldman in den Bell Labs geschrieben. Nach seiner eigenen Schilderung war der Auslöser ein Kollege — Steve Johnson, Autor von yacc — der einen ganzen Vormittag damit verbrachte, ein Programm zu debuggen, das tatsächlich korrekt war; die Binärdatei war nach einer Änderung am Quellcode schlicht nicht neu gelinkt worden. Feldman erhielt dafür 2003 den ACM Software System Award. Das Werkzeug, das an diesem Nachmittag entstand — Abhängigkeiten einmal erfassen und die Maschine herausfinden lassen, was neu gemacht werden muss — wurde zu einer der am häufigsten kopierten Ideen im Software-Tooling. GNU Make, die Version, die heute fast jeder benutzt, ist eine freie Neuimplementierung des GNU-Projekts (1) (5) (6).
In der Praxis ist es für jeden gedacht, der ein nicht-triviales Artefakt — eine kompilierte Binärdatei, ein Dokument, eine Menge generierter Dateien — aus kleineren Teilen zusammensetzt, bei dem es verschwenderisch oder langsam wäre, bei jeder Änderung alles von Grund auf neu zu bauen. Das umfasst C/C++-Projekte, Kernel- und Bootloader-Builds, LaTeX-Dokumente und jede Menge Ad-hoc-Automatisierung, die überhaupt nichts mit dem Kompilieren von Code zu tun hat.
Wann sollte man darauf zurückgreifen?
Make lohnt sich, wenn drei Dinge gleichzeitig zutreffen:
- Es gibt einen echten Abhängigkeitsgraphen — manche Ausgaben hängen von anderen ab, und ein Rebuild ist nicht einfach „alles neu erzeugen".
- Rebuilds sind teuer genug, dass es wichtig ist, unnötige zu vermeiden (ein zweisekündiger Compile braucht das nicht; ein zwanzigminütiger Kernel-Build schon).
- Die Regeln sind einigermaßen stabil — Makes zeitstempelbasiertes Modell handhabt sehr dynamische Pipelines nicht so elegant wie ein Task-Runner mit expliziter Invalidierung das könnte.
Für eine Handvoll Dateien ohne gegenseitige Abhängigkeiten ist ein Shell-Skript oft einfacher und ehrlicher. Make beginnt sich auszuzahlen, sobald „musste das wirklich neu gebaut werden?" zu einer Frage wird, die es wert ist, automatisch statt von Hand gestellt zu werden.
Wo sitzt es in der Toolchain?
Make sitzt üblicherweise eine Ebene über dem Compiler und eine Ebene unter dem, was ein Mensch ausführt. In einem typischen C-Projekt: Quelldateien → (Compiler, gesteuert von Make) → Objektdateien → (Linker, gesteuert von Make) → fertige Binärdatei. Make selbst kompiliert oder linkt nichts — es ruft die Werkzeuge auf, die das tun, als Shell-Befehle in jedem Rezept.
Es tendiert auch dazu, unterhalb höherstufiger Build-Konfigurationswerkzeuge zu sitzen. CMake, Autotools und Yoctos BitBake etwa ersetzen Make weniger, als dass sie Makefiles generieren (oder etwas Gleichwertiges ansteuern) als eigentliches Ausführungs-Backend. Selbst Projekte, die einem nie direkt ein Makefile zeigen, lassen also oft irgendwo in der Pipeline eines laufen.
Warum funktioniert es so, wie es funktioniert?
Die zentrale Designentscheidung — Dateiänderungszeitstempel zu vergleichen, um zu entscheiden, was veraltet ist — ist es, was Make auf großen Bäumen schnell macht: Einen Zeitstempel zu prüfen ist billig, sodass ein Rebuild eines riesigen Projekts nach einer Ein-Zeilen-Änderung fast alles überspringen und trotzdem korrekt sein kann — vorausgesetzt, der ihm mitgeteilte Abhängigkeitsgraph ist korrekt.
Dieser letzte Nebensatz ist die Quelle fast jeder „Make ist kaputt"-Beschwerde, auf die man stößt, und es lohnt sich, dabei zu verweilen: Make ist immer nur so korrekt wie der Graph, über den es informiert wurde. Wenn eine .c-Datei per #include einen Header einbindet, der nicht als Voraussetzung der zugehörigen .o-Datei aufgeführt ist, löst das Bearbeiten dieses Headers keinen Rebuild aus — nicht weil Make verwirrt ist, sondern weil ihm nie jemand mitgeteilt hat, dass diese Abhängigkeit existiert. Deshalb werden reale Makefiles fast immer mit einem vom Compiler generierten Abhängigkeitsschritt kombiniert (-MMD -MP bei GCC/Clang) statt mit handgeschriebenen Header-Listen — der Compiler kennt den wahren Include-Graphen bereits und ist damit die einzig zuverlässige Quelle dafür (4).
Wie funktioniert es tatsächlich?
Ziele, Voraussetzungen, Rezepte. Die Grundeinheit ist:
target: prerequisite1 prerequisite2
recipe line 1
recipe line 2
Rezeptzeilen müssen mit einem Tabulator eingerückt werden, nicht mit Leerzeichen — eine berühmte historische Macke, über die auch heute noch Leute stolpern.
Automatische Variablen reduzieren Wiederholungen:
%.o: %.c
gcc -c $< -o $@
$@ ist das Ziel, $< die erste Voraussetzung — allein diese Musterregel kann jede .o-Datei aus der passenden .c-Datei bauen, ohne eine Zeile pro Datei.
.PHONY-Ziele markieren Namen, die keinen echten Dateien entsprechen — clean, all, test —, damit Make nicht durcheinanderkommt, falls zufällig eine Datei mit diesem Namen existiert, und immer das Rezept ausführt, statt Zeitstempel zu prüfen: (2)
.PHONY: clean
clean:
rm -f *.o main
Variablen vermeiden es, Flags überall zu wiederholen, und erleichtern die projektübergreifende Wiederverwendung:
CC = gcc
CFLAGS = -Wall -O2
main.o: main.c
$(CC) $(CFLAGS) -c main.c
Die automatische Abhängigkeitsgenerierung schließt die oben beschriebene Lücke:
CFLAGS = -Wall -O2 -MMD -MP
-include $(wildcard *.d)
%.o: %.c
$(CC) $(CFLAGS) -c $<
-MMD lässt den Compiler pro Objektdatei eine .d-Datei ausgeben, die jeden Header auflistet, der bei diesem Compile-Vorgang tatsächlich eingebunden wurde. -include faltet diese Dateien zurück in das Makefile, sodass der Abhängigkeitsgraph korrekt bleibt, während sich Header ändern — ohne dass ihn jemand von Hand pflegen muss (3).
Schlussgedanke
Das meiste, was wie Eigenheiten von Make aussieht — die Zeitstempelvergleiche, das Bestehen auf Tabulatoren, die Art, wie eine fehlende Abhängigkeit stillschweigend statt lautstark fehlschlägt — ergibt sich aus einer einzigen Designentscheidung von 1976: das Modell einfach und die Prüfung billig halten und darauf vertrauen, dass das Makefile die Realität korrekt beschreibt. Sobald man das als Perspektive einnimmt, stellen sich die meisten „Make verhält sich seltsam"-Momente als „der Graph, den ich ihm gegeben habe, war unvollständig" heraus — was sich sehr viel leichter beheben lässt.
Weiterführende Artikel
- Git: Branches löschen, mergen und einen schlechten Push rückgängig machen — die alltäglichen Branch- und Push-Befehle
Referenzen
- Feldman, S. I. (1979). Make — A Program for Maintaining Computer Programs. Software: Practice and Experience, 9(4), 255–265. — Feldmans eigenes Paper, das Make vorstellt, einschließlich der ursprünglichen Motivation.
- GNU Make Manual — die vollständige, maßgebliche Referenz für Syntax und Semantik von GNU Make.
- GNU Make Manual: Automatic Prerequisites — das
-MMD/-include-Muster in den eigenen Worten der Maintainer. - GCC: Preprocessor Options (
-Mfamily) — was-MMDund-MPunter der Haube tun. - Make (software) — Wikipedia — allgemeine Geschichte und Abstammung der Make-Implementierungen (BSD Make, GNU Make, NMAKE usw.).
- Stuart Feldman — Wikipedia — Hintergrund zu Feldmans Arbeit in den Bell Labs und dem ACM Software System Award 2003.
Kommentare