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.

make

make <target> — Führt ein bestimmtes Target aus.

make build

make -f <file> — Verwendet ein bestimmtes Makefile statt des Standards.

make -f Makefile.prod build

make -n — Probelauf: Zeigt die Kommandos an, die ausgeführt würden, ohne sie auszuführen.

make -n deploy

make -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 -j

make -B — Baut alle Targets bedingungslos neu (erzwungener Rebuild).

make -B

make -s — Stiller Modus: Gibt die Kommandos beim Ausführen nicht aus.

make -s build

make -k — Weitermachen: Baut andere Targets weiter, auch wenn eines fehlschlägt.

make -k test

make -C <directory> — Wechselt vor dem Lesen des Makefiles in das angegebene Verzeichnis.

make -C src/ build

make VAR=value — Überschreibt eine Variable auf der Kommandozeile.

make CC=clang build

Makefile-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.o

VAR = value — Rekursiv expandierte Variable (bei jeder Verwendung neu ausgewertet).

CC = gcc
CFLAGS = -Wall -O2

VAR := 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/local

VAR += value — Hängt an eine bestehende Variable an.

CFLAGS += -g

export 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
endif

ifneq ($(VAR),value) … endif — Bedingter Block: Wird ausgeführt, wenn die Variable NICHT dem Wert entspricht.

ifneq ($(DEBUG),)
CFLAGS += -g
endif

ifdef VAR … endif — Bedingter Block: Wird ausgeführt, wenn die Variable definiert ist.

ifdef VERBOSE
Q :=
else
Q := @
endif

ifndef VAR … endif — Bedingter Block: Wird ausgeführt, wenn die Variable NICHT definiert ist.

ifndef CC
CC := gcc
endif

Spezielle 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: help

include <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 app

Häufige Makefile-Muster

all: build — Konvention: Das Target „all" baut alles.

all: build docs

clean: — 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.

Verwandte Kommandos

  • artisan – Kommandozeilen-Werkzeug des Laravel-Frameworks
  • cargo – Build-System und Paketmanager für Rust
  • composer – Abhängigkeitsverwaltung für PHP-Projekte