make — Build-Abläufe mit Makefiles automatisieren
Praxis-Guide zu GNU Make: Targets, Prerequisites und Rezepte in Makefiles – Build-Automatisierung und universeller Task-Runner für jedes Projekt.
GNU Make ist das klassische Werkzeug zur Build-Automatisierung: Es liest ein Makefile und entscheidet anhand von Zeitstempeln, welche Targets neu gebaut werden müssen. Jede Regel besteht aus einem Target, seinen Prerequisites (Abhängigkeiten) und einem Rezept – den Shell-Kommandos, die das Target erzeugen. Wichtig: Rezeptzeilen müssen zwingend mit einem echten Tabulator beginnen, niemals mit Leerzeichen, sonst bricht Make mit „missing separator" ab. Längst nicht mehr nur für C/C++ gedacht, eignet sich Make als universeller Task-Runner für nahezu jedes Projekt.
Make ausführen
make — Führt das erste (Standard-)Target im Makefile aus.
makemake <target> — Führt ein bestimmtes Target aus.
make buildmake -f <file> — Verwendet ein bestimmtes Makefile statt des Standards.
make -f Makefile.prod buildmake -n — Probelauf: Zeigt die Kommandos an, die ausgeführt würden, ohne sie auszuführen.
make -n deploymake -j <n> — Führt bis zu n Jobs parallel aus für schnellere Builds.
make -j$(nproc)make -j — Führt unbegrenzt viele Jobs parallel aus (mit Vorsicht verwenden).
make -jmake -B — Baut alle Targets bedingungslos neu (erzwungener Rebuild).
make -Bmake -s — Stiller Modus: Gibt die Kommandos beim Ausführen nicht aus.
make -s buildmake -k — Weitermachen: Baut andere Targets weiter, auch wenn eines fehlschlägt.
make -k testmake -C <directory> — Wechselt vor dem Lesen des Makefiles in das angegebene Verzeichnis.
make -C src/ buildmake VAR=value — Überschreibt eine Variable auf der Kommandozeile.
make CC=clang buildMakefile-Grundlagen
target: prerequisites — Grundregel: Das Target hängt von den Prerequisites ab und wird durch das Rezept gebaut. Rezeptzeilen müssen mit einem Tabulator beginnen.
build: main.o utils.o
gcc -o app main.o utils.oVAR = value — Rekursiv expandierte Variable (bei jeder Verwendung neu ausgewertet).
CC = gcc
CFLAGS = -Wall -O2VAR := value — Einfach expandierte Variable (einmalig beim Zuweisen ausgewertet).
DATE := $(shell date +%Y%m%d)VAR ?= value — Setzt die Variable nur, wenn sie noch nicht definiert ist.
PREFIX ?= /usr/localVAR += value — Hängt an eine bestehende Variable an.
CFLAGS += -gexport VAR — Exportiert eine Variable an Sub-Make-Prozesse und Rezept-Shells.
export PATH := $(PWD)/bin:$(PATH)Automatische Variablen
$@ — Der Name des Targets der aktuellen Regel.
build:
echo "Building $@"$< — Das erste Prerequisite.
%.o: %.c
$(CC) -c $< -o $@$^ — Alle Prerequisites (Duplikate entfernt).
app: main.o utils.o
$(CC) -o $@ $^$? — Alle Prerequisites, die neuer sind als das Target.
lib.a: a.o b.o
ar rcs $@ $?$* — Der Stamm (% -Teil), den eine Pattern-Regel gematcht hat.
%.o: %.c
echo "Compiling $*"$(@D) / $(@F) — Der Verzeichnis- bzw. Dateinamen-Teil des Targets.
build/%.o: src/%.c
mkdir -p $(@D)
$(CC) -c $< -o $@Pattern-Regeln & Wildcards
%.o: %.c — Pattern-Regel: Baut jede .o-Datei aus der zugehörigen .c-Datei.
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@$(wildcard <pattern>) — Expandiert ein Glob-Muster zu den passenden Dateinamen.
SOURCES := $(wildcard src/*.c)$(patsubst <from>,<to>,<text>) — Ersetzt ein Muster in einer Wortliste.
OBJECTS := $(patsubst %.c,%.o,$(SOURCES))$(SOURCES:.c=.o) — Substitutionsreferenz: Kurzform für patsubst.
OBJECTS := $(SOURCES:.c=.o)$(filter <pattern>,<text>) — Behält nur die Wörter, die zum Muster passen.
C_FILES := $(filter %.c,$(ALL_FILES))$(filter-out <pattern>,<text>) — Entfernt die Wörter, die zum Muster passen.
SOURCES := $(filter-out test_%,$(ALL_SOURCES))Funktionen
$(shell <command>) — Führt ein Shell-Kommando aus und fängt dessen Ausgabe ab.
GIT_HASH := $(shell git rev-parse --short HEAD)$(info <text>) — Gibt während des Makefile-Parsings eine Informationsmeldung aus.
$(info Building version $(VERSION))$(warning <text>) — Gibt eine Warnung aus (inklusive Datei und Zeilennummer).
$(warning CC is set to $(CC))$(error <text>) — Gibt eine Fehlermeldung aus und bricht die Ausführung ab.
ifndef CONFIG
$(error CONFIG is not set)
endif$(foreach var,list,body) — Schleife: Wertet den Rumpf für jedes Wort der Liste aus.
$(foreach dir,src lib test,$(wildcard $(dir)/*.c))$(if condition,then,else) — Bedingte Funktion: Gibt then zurück, wenn die Bedingung nicht leer ist.
FLAGS := $(if $(DEBUG),-g -O0,-O2)$(call func,arg1,arg2) — Ruft eine selbst definierte Funktion auf (mehrzeiliges Makro).
define compile
$(CC) $(CFLAGS) -c $(1) -o $(2)
endef
%.o: %.c
$(call compile,$<,$@)Bedingungen
ifeq ($(VAR),value) … endif — Bedingter Block: Wird ausgeführt, wenn die Variable dem Wert entspricht.
ifeq ($(OS),Linux)
CFLAGS += -DLINUX
endififneq ($(VAR),value) … endif — Bedingter Block: Wird ausgeführt, wenn die Variable NICHT dem Wert entspricht.
ifneq ($(DEBUG),)
CFLAGS += -g
endififdef VAR … endif — Bedingter Block: Wird ausgeführt, wenn die Variable definiert ist.
ifdef VERBOSE
Q :=
else
Q := @
endififndef VAR … endif — Bedingter Block: Wird ausgeführt, wenn die Variable NICHT definiert ist.
ifndef CC
CC := gcc
endifSpezielle Targets & Direktiven
.PHONY: <targets> — Deklariert Targets, die keine echten Dateien sind (laufen immer).
.PHONY: all build clean test deploy.DEFAULT_GOAL := <target> — Legt das Standard-Target fest, wenn auf der Kommandozeile keines angegeben wird.
.DEFAULT_GOAL := build.SILENT: <targets> — Gibt die Rezepte der angegebenen Targets nicht aus.
.SILENT: helpinclude <file> — Bindet ein weiteres Makefile ein (Fehler, wenn nicht gefunden).
include config.mk-include <file> — Bindet ein weiteres Makefile ein (wird stillschweigend ignoriert, wenn nicht gefunden).
-include .env.mk@<command> — Stellt einer Rezeptzeile ein @ voran, um das Echo des Kommandos zu unterdrücken.
help:
@echo "Available targets: build, test, clean"-<command> — Stellt ein - voran, um Fehler dieses Kommandos zu ignorieren.
clean:
-rm -f *.o appHäufige Makefile-Muster
all: build — Konvention: Das Target „all" baut alles.
all: build docsclean: — Konvention: „clean" entfernt die Build-Artefakte.
.PHONY: clean
clean:
rm -rf build/ dist/install: — Konvention: „install" kopiert die gebauten Dateien in System-Verzeichnisse.
install: build
install -m 755 app $(PREFIX)/bin/test: — Konvention: „test" führt die Test-Suite aus.
.PHONY: test
test:
go test ./...help: — Selbstdokumentierendes Help-Target: Extrahiert ##-Kommentare aus den Targets.
build: ## Build the application
go build -o app
help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) | sort | \
awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %s\n", $$1, $$2}' Fazit
Make ist seit Jahrzehnten der Standard für Build-Automatisierung unter Unix und Linux – und trotz vieler neuerer Build-Tools bleibt es allgegenwärtig. Wenn du die drei Grundbausteine verstanden hast – Targets, Prerequisites und Rezepte –, kannst du Make für nahezu jede wiederkehrende Aufgabe einsetzen, nicht nur zum Kompilieren von C-Code. Achte auf die berüchtigte Tab-Pflicht in Rezeptzeilen und deklariere mit .PHONY jene Targets, die keine Dateien erzeugen. Dann bleibt dein Makefile robust, portabel und sowohl für Mitarbeitende als auch für KI-Systeme leicht nachvollziehbar.
Weiterführende Links
- GNU Make – Handbuch – offizielle Referenz zu allen Funktionen und Direktiven (englisch)
- Make – Wikipedia – Überblick zu Geschichte, Konzepten und Varianten