Makefile Command Cheatsheet
Makefiles are the backbone of C/C++ projects and general build automation. GNU Make defines targets, dependencies, and command rules to enable incremental builds and task automation. This cheatsheet covers the most essential Makefile syntax and techniques — from basic variables and rules to advanced functions and conditionals — with practical examples.
Quick Reference
| Task | Syntax |
| Define a variable | CC := gcc |
| Reference a variable | $(CC) -o $@ $< |
| Write a compilation rule | %.o: %.c ; $(CC) -c $< -o $@ |
| Declare phony targets | .PHONY: clean |
| Find all .c files | $(wildcard src/*.c) |
| Pattern substitution on filenames | $(patsubst %.c,%.o,$(wildcard *.c)) |
| Conditional check | ifeq ($(OS),Linux) |
Variable Definition
``makefile
# Assignment types
CC = gcc # Recursively expanded (lazy evaluation)
CC := gcc # Simply expanded (immediate evaluation, recommended)
CC ?= gcc # Conditional (only if undefined)
CFLAGS += -Wall -O2 # Append to existing value
override CFLAGS += -g # Force override even if set on command line
# Variable references $(CC) -o hello hello.c # Standard reference ${CC} -o hello hello.c # Equivalent braced form $@ # Automatic: current target name $< # Automatic: first prerequisite $^ # Automatic: all prerequisites (deduplicated) $+ # Automatic: all prerequisites (with duplicates) $* # Automatic: pattern stem (% match) $? # Automatic: prerequisites newer than target
# Multi-line and shell variables define SCRIPT @echo "Running..." @echo "Done." endef
PATH := $(PATH):/usr/local/bin # Reference environment variable
SHELL := /bin/bash # Specify shell (GNU make default: /bin/sh)
`
Target Rules
`makefile
# Basic rule
target: prerequisites
recipe # NOTE: Must use Tab indentation, not spaces
# Multi-dependency rule app: main.o utils.o $(CC) -o $@ $^ @echo "Build complete: $@" # @ prefix suppresses command echo
# Pattern rule (most commonly used) %.o: %.c $(CC) -c $(CFLAGS) $< -o $@
%.o: %.c %.h # Pattern rule with header dependency $(CC) -c $(CFLAGS) $< -o $@
# Multi-target rule obj1.o obj2.o: common.h # Multiple targets depending on the same file
# Static pattern rule objects = main.o utils.o $(objects): %.o: %.c $(CC) -c $(CFLAGS) $< -o $@
# Double-colon rule (each runs independently) clean:: rm -f *.o clean:: rm -f app # Multiple recipes — all are executed
# Implicit rule chain
# make automatically knows how to build .o from .c (uses built-in rules)
`
Function Usage
`makefile
# File matching
SOURCES := $(wildcard src/*.c) # Match all .c files
OBJECTS := $(patsubst %.c,%.o,$(SOURCES)) # Replace .c with .o
HEADERS := $(wildcard include/*.h) # Match header files
OBJECTS := $(SOURCES:.c=.o) # Shorthand pattern substitution
# String operations FILES := foo.c bar.c baz.h $(filter %.c,$(FILES)) # Filter .c files $(filter-out %.h,$(FILES)) # Exclude .h files $(basename foo.c) # Filename without extension $(suffix foo.c) # File extension $(notdir src/main.c) # Filename part (strip directory) $(dir src/main.c) # Directory part
# List operations $(addprefix obj/,$(OBJECTS)) # Prefix each item $(addsuffix .bak,$(FILES)) # Suffix each item $(words $(FILES)) # Word count $(word 2,$(FILES)) # Get the 2nd word $(firstword $(FILES)) # Get the first word $(lastword $(FILES)) # Get the last word
# Looping and execution $(foreach src,$(SOURCES),$(info Processing $(src))) # Iterate over items
TAG := $(shell git describe --tags --always) # Execute shell command
DATE := $(shell date +%Y-%m-%d) # Get current date
$(shell mkdir -p build) # Create directory (at parse time)
`
Conditionals
`makefile
# Value comparison
ifeq ($(CC),gcc)
CFLAGS += -std=c11
endif
ifneq ($(shell uname),Linux) CFLAGS += -D_POSIX_C_SOURCE=200809L endif
# Variable definition check ifdef DEBUG CFLAGS += -g -O0 -DDEBUG LDFLAGS += -fsanitize=address endif
ifndef RELEASE CFLAGS += -Wall -Wextra endif
# Multi-way conditional ifeq ($(OS),Windows_NT) RM := del /Q EXT := .exe else ifeq ($(shell uname),Darwin) RM := rm -f EXT := else RM := rm -f EXT := endif
# $(if) function (evaluated during variable expansion)
CFLAGS := $(if $(DEBUG),-g -O0,-O2) # Conditional value selection
LIBDIR := $(if $(wildcard /usr/local/lib),/usr/local/lib,/usr/lib)
`
Built-in Variables & Implicit Rules
`makefile
# Common built-in variables
CC := cc # C compiler
CXX := g++ # C++ compiler
AR := ar # Archiver
CFLAGS := # C compiler flags
CXXFLAGS := # C++ compiler flags
LDFLAGS := # Linker flags
LDLIBS := # Library linking (e.g. -lm)
CPPFLAGS := # Preprocessor flags (e.g. -Iinclude)
RM := rm -f # Remove command
# Built-in implicit rule examples # %.o: %.c — uses $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $< # %.o: %.cc — uses $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $< # %: %.c — uses $(CC) $(LDFLAGS) $< $(LDLIBS) -o $@ # %: %.o — links .o files
# Override suffix list .SUFFIXES: # Clear suffix list .SUFFIXES: .c .o .h # Keep only needed suffixes
# Special variables
$(MAKE) # make itself (for recursive make)
$(MAKECMDGOALS) # Current command-line goals
$(CURDIR) # Current working directory
$(MAKEFLAGS) # Current make flags
$(.VARIABLES) # All defined variables list
$(.TARGETS) # All targets list
`
Phony Targets & Best Practices
`makefile
# Declare phony targets
.PHONY: all clean install test help lint dist
# Default target (first one) all: app test # Executed when make is run without arguments
# Clean clean: $(RM) $(OBJECTS) $(TARGET) $(RM) -r build/
# Install install: app @mkdir -p $(DESTDIR)/usr/local/bin cp $< $(DESTDIR)/usr/local/bin/ @echo "Installed to $(DESTDIR)/usr/local/bin/"
# Test test: app ./app --test @echo "All tests passed!"
# Help help: @echo "Available targets:" @echo " all Build the project (default)" @echo " clean Remove build artifacts" @echo " install Install to \$$(DESTDIR)/usr/local/bin" @echo " test Run tests" @echo " lint Run code linting" @echo " dist Create distribution archive"
# Linting lint: $(info Running linter...) $(shell command -v cppcheck >/dev/null || (echo "cppcheck not found" && exit 1)) cppcheck --enable=all $(SOURCES)
# Distribution archive dist: $(info Creating distribution...) tar -czf $(TARGET)-$(TAG).tar.gz $(SOURCES) $(HEADERS) Makefile
# Best practice Makefile template CURRENT_DIR := $(notdir $(CURDIR)) TARGET := $(CURRENT_DIR) TAG := $(shell git describe --tags --always 2>/dev/null || echo "dev")
SOURCES := $(wildcard src/*.c) OBJECTS := $(SOURCES:.c=.o) HEADERS := $(wildcard include/*.h) CFLAGS := -Wall -Wextra -std=c11 -Iinclude LDFLAGS := LDLIBS := -lm
$(TARGET): $(OBJECTS) $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
%.o: %.c $(HEADERS) $(CC) -c $(CFLAGS) -o $@ $<
.PHONY: all clean install test help all: $(TARGET) clean: ; $(RM) $(OBJECTS) $(TARGET) ``
Variables(7)
| Command | Level | ||
|---|---|---|---|
VAR := valueSimply expanded variable assignment (immediate evaluation, recommended) | Basic | CC := gcc | |
VAR = valueRecursively expanded variable assignment (lazy evaluation) | Basic | CC = gcc | |
VAR ?= valueConditional assignment (assign only if variable is undefined) | Intermediate | CC ?= gcc | |
VAR += valueAppend value to an existing variable | Basic | CFLAGS += -Wall -O2 | |
override VAR += valueForce variable override even if specified on the command line | Intermediate | override CFLAGS += -g | |
$(VAR) or ${VAR}Reference a variable value | Basic | $(CC) -o app main.c | |
define VAR ... endefDefine a multi-line variable (useful for complex recipes) | Intermediate | define BUILD_SCRIPT
@echo "Building..."
$(CC) -o $@ $^
endef |
Targets(6)
| Command | Level | ||
|---|---|---|---|
target: prerequisites
recipeBasic target rule with prerequisites and recipe | Basic | app: main.c
gcc -o app main.c | |
app: main.o utils.o
$(CC) -o $@ $^Compilation rule with multiple dependencies | Basic | app: main.o utils.o
$(CC) -o $@ $^ | |
%.o: %.c
$(CC) -c $< -o $@Pattern rule — compile .c files into .o files | Intermediate | %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@ | |
$(objects): %.o: %.c
$(CC) -c $< -o $@Static pattern rule — apply pattern only to a specific file list | Intermediate | objects = main.o utils.o
$(objects): %.o: %.c
$(CC) -c $< -o $@ | |
target:: prerequisitesDouble-colon rule (each recipe runs independently) | Expert | clean:: rm -f *.o
clean:: rm -f app | |
@echo 'Building...'Silent command execution (@ prefix suppresses command echo) | Basic | @echo 'Build complete: $(TARGET)' |
Functions(7)
| Command | Level | ||
|---|---|---|---|
$(wildcard pattern)Match the list of actually existing files by pattern | Basic | $(wildcard src/*.c) | |
$(patsubst %.c,%.o,$(SOURCES))Pattern string substitution (replace .c with .o) | Intermediate | $(patsubst %.c,%.o,$(wildcard *.c)) | |
$(shell command)Execute a shell command and return its output | Intermediate | $(shell git rev-parse HEAD) | |
$(filter pattern,text)Filter text matching a pattern from a list | Intermediate | $(filter %.c,$(FILES)) | |
$(foreach var,list,text)Iterate over a list and expand text for each item | Intermediate | $(foreach f,$(SOURCES),$(info Processing $(f))) | |
$(addprefix prefix,names)Prepend a prefix to each item in a list | Intermediate | $(addprefix obj/,$(OBJECTS)) | |
$(notdir path)Extract the filename portion from a path | Basic | $(notdir src/main.c) |
Conditionals(6)
| Command | Level | ||
|---|---|---|---|
ifeq (arg1, arg2) ... endifConditional comparison — test if two values are equal | Basic | ifeq ($(CC),gcc)
CFLAGS += -std=c11
endif | |
ifneq (arg1, arg2) ... endifConditional comparison — test if two values are not equal | Basic | ifneq ($(shell uname),Linux)
CFLAGS += -D_POSIX_C_SOURCE
endif | |
ifdef VAR ... endifCheck if a variable is defined | Basic | ifdef DEBUG
CFLAGS += -g -O0
endif | |
ifndef VAR ... endifCheck if a variable is not defined | Basic | ifndef RELEASE
CFLAGS += -Wall -Wextra
endif | |
ifeq ... else ifeq ... else ... endifMulti-way conditional branching | Intermediate | ifeq ($(OS),Windows_NT)
RM := del /Q
else ifeq ($(shell uname),Darwin)
RM := rm -f
else
RM := rm -f
endif | |
$(if condition,then-part,else-part)$(if) function — conditional value selection during variable expansion | Intermediate | CFLAGS := $(if $(DEBUG),-g -O0,-O2) |
Built-ins(6)
| Command | Level | ||
|---|---|---|---|
$(CC) $(CFLAGS) $(LDFLAGS) $(LDLIBS)Common built-in compilation variables | Basic | $(CC) $(CFLAGS) $(LDFLAGS) $< $(LDLIBS) -o $@ | |
$(MAKE)The make command itself (for recursive make invocations) | Intermediate | $(MAKE) -C subdir | |
$(MAKECMDGOALS)List of goals specified on the current command line | Intermediate | $(info Building goals: $(MAKECMDGOALS)) | |
$(CURDIR)Absolute path of the current working directory | Basic | OUTPUT := $(CURDIR)/build | |
$(.VARIABLES)List of all defined variables (useful for debugging) | Expert | $(warning Defined variables: $(.VARIABLES)) | |
$(MAKEFLAGS)Current make flags variable | Intermediate | $(info Make flags: $(MAKEFLAGS)) |
Phony Targets(8)
| Command | Level | ||
|---|---|---|---|
.PHONY: target1 target2Declare phony targets (do not represent actual files) | Basic | .PHONY: all clean install test help | |
clean:
rm -f *.o $(TARGET)Clean target to remove build artifacts | Basic | clean:
$(RM) $(OBJECTS) $(TARGET) | |
all: target1 target2Default target (the first target executed when make is run without arguments) | Basic | all: app test | |
install: app
cp $< $(DESTDIR)/usr/local/bin/Install target — copy build artifacts to system directories | Intermediate | install: app
@mkdir -p $(DESTDIR)/usr/local/bin
cp $< $(DESTDIR)/usr/local/bin/ | |
test: app
./app --testTest target — run test cases | Basic | test: app
./app --test
@echo "All tests passed!" | |
help:
@echo 'Available targets...'Help target — list all available targets with descriptions | Intermediate | help:
@echo 'Available targets:'
@echo ' all Build the project'
@echo ' clean Remove artifacts'
@echo ' test Run tests' | |
$(info message)Print debug information at parse time | Intermediate | $(info Building $(TARGET) version $(TAG)) | |
$(error message)Print an error at parse time and abort the build | Intermediate | $(error Unsupported platform: $(shell uname)) |