Makefile Patterns Guide
This guide covers pattern rules, static pattern rules, implicit rules, dependency generation, and common Makefile patterns for various project types.
Claude Code Knowledge Pack7/10/2026
Makefile Patterns Guide
Overview
This guide covers pattern rules, static pattern rules, implicit rules, dependency generation, and common Makefile patterns for various project types.
Pattern Rules
Pattern rules use % to match filenames and create generic build rules.
Basic Pattern Rules
# Compile .c files to .o files
%.o: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
# Generate assembly from C
%.s: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -S $< -o $@
# Preprocess C files
%.i: %.c
$(CC) $(CPPFLAGS) -E $< -o $@
Pattern Rules with Directories
# Source in src/, objects in build/obj/
build/obj/%.o: src/%.c
@mkdir -p $(@D)
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
# Multiple source directories
build/obj/%.o: src/%.c
@mkdir -p $(@D)
$(CC) -c $< -o $@
build/obj/%.o: lib/%.c
@mkdir -p $(@D)
$(CC) -c $< -o $@
Pattern Rule Variables
# Use stem ($*) in pattern rules
%.pdf: %.tex
pdflatex $*
# Runs: pdflatex report (for report.tex -> report.pdf)
# Multiple transformations
%.html: %.md
pandoc $< -o $@
%.pdf: %.md
pandoc $< -o $@
Static Pattern Rules
More efficient and explicit than pattern rules for known file lists.
Syntax
$(targets): target-pattern: prereq-pattern
recipe
Basic Example
OBJECTS := main.o utils.o helper.o
# Static pattern rule
$(OBJECTS): %.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# Equivalent to:
# main.o: main.c
# $(CC) $(CFLAGS) -c main.c -o main.o
# utils.o: utils.c
# $(CC) $(CFLAGS) -c utils.c -o utils.o
# helper.o: helper.c
# $(CC) $(CFLAGS) -c helper.c -o helper.o
With Directories
SOURCES := $(wildcard src/*.c)
OBJECTS := $(SOURCES:src/%.c=build/obj/%.o)
$(OBJECTS): build/obj/%.o: src/%.c
@mkdir -p $(@D)
$(CC) $(CFLAGS) -c $< -o $@
Multiple Dependencies
# All objects depend on config.h
$(OBJECTS): %.o: %.c config.h
$(CC) $(CFLAGS) -c $< -o $@
# Specific objects depend on additional headers
$(NETWORK_OBJS): %.o: %.c network.h common.h
$(CC) $(CFLAGS) -c $< -o $@
Implicit Rules
GNU Make has built-in implicit rules. You can use or override them.
Common Built-in Rules
# These rules are built into make:
# %.o: %.c
# $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
# %.o: %.cpp
# $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
# %: %.o
# $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
Disabling Implicit Rules
# Disable all built-in rules (recommended for explicit Makefiles)
.SUFFIXES:
# Re-enable specific patterns
.SUFFIXES: .c .o .h
# Or disable specific rules
%.o: %.c
# Empty recipe disables the built-in rule
Custom Implicit Rules
# Add your own implicit rules
%.o: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@
# Language-specific rules
%.o: %.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -MMD -MP -c $< -o $@
%.o: %.s
$(AS) $(ASFLAGS) -c $< -o $@
Dependency Generation
Manual Dependencies (Avoid)
# Tedious and error-prone
main.o: main.c common.h utils.h
utils.o: utils.c utils.h common.h
helper.o: helper.c helper.h common.h
Automatic Dependency Generation
Method 1: Embedded in compilation:
SOURCES := $(wildcard src/*.c)
OBJECTS := $(SOURCES:src/%.c=build/obj/%.o)
DEPENDS := $(OBJECTS:.o=.d)
# Generate dependencies during compilation
build/obj/%.o: src/%.c
@mkdir -p $(@D)
$(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@
# Include generated dependency files
-include $(DEPENDS)
Flags explained:
-MMD: Generate dependency file (.d)-MP: Add phony targets for headers (prevents errors if header deleted)-MF file: Specify dependency file name (optional)
Method 2: Separate dependency generation:
# Generate dependencies separately
%.d: %.c
@$(CC) $(CPPFLAGS) -MM $< | sed 's,\\($*\\)\\.o[ :]*,\\1.o $@ : ,g' > $@
-include $(DEPENDS)
Generated .d file example:
# main.d (generated from main.c)
build/obj/main.o build/obj/main.d: src/main.c include/common.h \\
include/utils.h
include/common.h:
include/utils.h:
Common Project Patterns
Pattern 1: Simple Single-Directory C Project
CC ?= gcc
CFLAGS ?= -Wall -Wextra -O2
TARGET := myapp
SOURCES := $(wildcard *.c)
OBJECTS := $(SOURCES:.c=.o)
DEPENDS := $(OBJECTS:.o=.d)
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJECTS)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
%.o: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@
-include $(DEPENDS)
clean:
$(RM) $(TARGET) $(OBJECTS) $(DEPENDS)
Pattern 2: Multi-Directory C Project
PROJECT := myapp
CC ?= gcc
CFLAGS ?= -Wall -Wextra -O2 -Iinclude
SRCDIR := src
INCDIR := include
BUILDDIR := build
OBJDIR := $(BUILDDIR)/obj
SOURCES := $(wildcard $(SRCDIR)/*.c)
OBJECTS := $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
DEPENDS := $(OBJECTS:.o=.d)
TARGET := $(BUILDDIR)/$(PROJECT)
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJECTS)
@mkdir -p $(@D)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
$(OBJDIR)/%.o: $(SRCDIR)/%.c
@mkdir -p $(@D)
$(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@
-include $(DEPENDS)
clean:
$(RM) -r $(BUILDDIR)
Pattern 3: C++ Project with Libraries
PROJECT := myapp
CXX ?= g++
CXXFLAGS ?= -Wall -Wextra -std=c++17 -O2
SRCDIR := src
BUILDDIR := build
OBJDIR := $(BUILDDIR)/obj
# Source files
SOURCES := $(wildcard $(SRCDIR)/*.cpp)
OBJECTS := $(SOURCES:$(SRCDIR)/%.cpp=$(OBJDIR)/%.o)
DEPENDS := $(OBJECTS:.o=.d)
# Library
LIBNAME := $(PROJECT)
STATIC_LIB := $(BUILDDIR)/lib$(LIBNAME).a
SHARED_LIB := $(BUILDDIR)/lib$(LIBNAME).so
# Executable
TARGET := $(BUILDDIR)/$(PROJECT)
.PHONY: all static shared executable clean
all: executable
executable: $(TARGET)
static: $(STATIC_LIB)
shared: $(SHARED_LIB)
# Link executable
$(TARGET): $(OBJECTS)
@mkdir -p $(@D)
$(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@
# Create static library
$(STATIC_LIB): $(OBJECTS)
@mkdir -p $(@D)
$(AR) rcs $@ $^
# Create shared library
$(SHARED_LIB): $(OBJECTS)
@mkdir -p $(@D)
$(CXX) -shared $^ -o $@
# Compile with -fPIC for libraries
$(OBJDIR)/%.o: $(SRCDIR)/%.cpp
@mkdir -p $(@D)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -fPIC -MMD -MP -c $< -o $@
-include $(DEPENDS)
clean:
$(RM) -r $(BUILDDIR)
Pattern 4: Mixed C/C++ Project
PROJECT := mixed
CC ?= gcc
CXX ?= g++
CFLAGS ?= -Wall -O2
CXXFLAGS ?= -Wall -O2 -std=c++17
SRCDIR := src
BUILDDIR := build
OBJDIR := $(BUILDDIR)/obj
# Separate C and C++ sources
C_SOURCES := $(wildcard $(SRCDIR)/*.c)
CXX_SOURCES := $(wildcard $(SRCDIR)/*.cpp)
# Separate object files
C_OBJECTS := $(C_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
CXX_OBJECTS := $(CXX_SOURCES:$(SRCDIR)/%.cpp=$(OBJDIR)/%.o)
ALL_OBJECTS := $(C_OBJECTS) $(CXX_OBJECTS)
DEPENDS := $(ALL_OBJECTS:.o=.d)
TARGET := $(BUILDDIR)/$(PROJECT)
.PHONY: all clean
all: $(TARGET)
# Link with C++ compiler (for C++ standard library)
$(TARGET): $(ALL_OBJECTS)
@mkdir -p $(@D)
$(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@
# Compile C sources
$(OBJDIR)/%.o: $(SRCDIR)/%.c
@mkdir -p $(@D)
$(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@
# Compile C++ sources
$(OBJDIR)/%.o: $(SRCDIR)/%.cpp
@mkdir -p $(@D)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -MMD -MP -c $< -o $@
-include $(DEPENDS)
clean:
$(RM) -r $(BUILDDIR)
Pattern 5: Go Project
PROJECT := myapp
GO ?= go
GOFLAGS ?=
PREFIX ?= /usr/local
# Version from git
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
LDFLAGS := -ldflags "-X main.version=$(VERSION)"
# Source files
SOURCES := $(shell find . -name '*.go' -not -path './vendor/*')
# go.sum may not exist in modules with no external dependencies
GO_SUM := $(wildcard go.sum)
TARGET := $(PROJECT)
.PHONY: all build install test clean fmt lint mod-tidy
all: build
build: $(TARGET)
$(TARGET): $(SOURCES) go.mod $(GO_SUM)
$(GO) build $(GOFLAGS) $(LDFLAGS) -o $@ ./cmd/$(PROJECT)
install: $(TARGET)
install -d $(DESTDIR)$(PREFIX)/bin
install -m 755 $(TARGET) $(DESTDIR)$(PREFIX)/bin/
test:
$(GO) test -v ./...
clean:
$(RM) $(TARGET)
$(GO) clean
fmt:
$(GO) fmt ./...
lint:
golangci-lint run
mod-tidy:
$(GO) mod tidy
Pattern 6: Python Project
PROJECT := mypackage
PYTHON ?= python3
PIP ?= $(PYTHON) -m pip
.PHONY: all build install develop test lint format clean
all: build
build:
$(PYTHON) -m build
install:
$(PIP) install .
develop:
$(PIP) install -e .[dev]
test:
$(PYTHON) -m pytest tests/ -v
lint:
$(PYTHON) -m flake8 src/ tests/
$(PYTHON) -m pylint src/
format:
$(PYTHON) -m black src/ tests/
$(PYTHON) -m isort src/ tests/
clean:
$(RM) -r build/ dist/ *.egg-info/
$(RM) -r .pytest_cache/ .coverage htmlcov/
find . -type d -name '__pycache__' -exec rm -r {} +
find . -type f -name '*.pyc' -delete
Pattern 7: Multi-Binary Project
PROJECT := tools
CC ?= gcc
CFLAGS ?= -Wall -Wextra -O2
SRCDIR := src
BUILDDIR := build
OBJDIR := $(BUILDDIR)/obj
# Multiple programs
PROGRAMS := tool1 tool2 tool3
TARGETS := $(addprefix $(BUILDDIR)/,$(PROGRAMS))
# Common library
LIBSRC := $(wildcard $(SRCDIR)/common/*.c)
LIBOBJ := $(LIBSRC:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
.PHONY: all clean $(PROGRAMS)
all: $(TARGETS)
# Individual program targets
tool1: $(BUILDDIR)/tool1
tool2: $(BUILDDIR)/tool2
tool3: $(BUILDDIR)/tool3
# Build each program
$(BUILDDIR)/tool1: $(OBJDIR)/tool1.o $(LIBOBJ)
@mkdir -p $(@D)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
$(BUILDDIR)/tool2: $(OBJDIR)/tool2.o $(LIBOBJ)
@mkdir -p $(@D)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
$(BUILDDIR)/tool3: $(OBJDIR)/tool3.o $(LIBOBJ)
@mkdir -p $(@D)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
# Compile pattern
$(OBJDIR)/%.o: $(SRCDIR)/%.c
@mkdir -p $(@D)
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
clean:
$(RM) -r $(BUILDDIR)
Pattern 8: Docker Integration
PROJECT := myapp
VERSION := 1.0.0
REGISTRY := docker.io
IMAGE := $(REGISTRY)/$(PROJECT):$(VERSION)
IMAGE_LATEST := $(REGISTRY)/$(PROJECT):latest
.PHONY: all build docker-build docker-push docker-run docker-clean
all: build
build:
$(MAKE) -f Makefile.app
docker-build:
docker build -t $(IMAGE) -t $(IMAGE_LATEST) .
docker-push: docker-build
docker push $(IMAGE)
docker push $(IMAGE_LATEST)
docker-run: docker-build
docker run --rm -it $(IMAGE)
docker-clean:
docker rmi $(IMAGE) $(IMAGE_LATEST) 2>/dev/null || true
Advanced Patterns
Pattern: Recursive Directory Processing
# Find all .c files recursively
SOURCES := $(shell find src -name '*.c')
# Mirror directory structure in build/
OBJECTS := $(SOURCES:src/%.c=build/obj/%.o)
# Create all necessary directories
OBJDIRS := $(sort $(dir $(OBJECTS)))
$(OBJDIRS):
@mkdir -p $@
# Order-only prerequisite: directories must exist
$(OBJECTS): | $(OBJDIRS)
build/obj/%.o: src/%.c
$(CC) $(CFLAGS) -c $< -o $@
Pattern: Multiple Build Configurations
BUILD_TYPES := debug release profile
.PHONY: all $(BUILD_TYPES) clean
all: release
# Target-specific variables
debug: CFLAGS += -g -O0 -DDEBUG
debug: TARGET := build/debug/$(PROJECT)
debug: $(TARGET)
release: CFLAGS += -O3 -DNDEBUG
release: TARGET := build/release/$(PROJECT)
release: $(TARGET)
profile: CFLAGS += -pg -O2
profile: TARGET := build/profile/$(PROJECT)
profile: $(TARGET)
# Generic build rule
$(TARGET): $(OBJECTS)
@mkdir -p $(@D)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
Pattern: Parallel Sub-Builds
SUBDIRS := lib1 lib2 lib3
.PHONY: all $(SUBDIRS)
# Build subdirs in parallel: make -j4
all: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $@
# Dependencies between subdirs
lib2: lib1
lib3: lib1 lib2
Best Practices
- Use static pattern rules for known file lists (more efficient)
- Generate dependencies automatically (-MMD -MP)
- Create directories with order-only prerequisites
- Disable built-in rules (.SUFFIXES:) for explicit Makefiles
- Use pattern rules for generic transformations
- Mirror source structure in build directory
- Separate C and C++ compilation in mixed projects
- Use -fPIC when building shared libraries
- Include generated dependencies with -include
- Test patterns with make -n (dry run)