Make & Makefiles

What is make?

make is a build tool. You tell it targets (things to build), their dependencies (what they need), and recipes (how to build them). It only rebuilds what changed—so multi-file C++ projects compile fast.

A rule looks like:

target: prerequisites
<TAB>recipe command(s)
Code language: HTML, XML (xml)

(Yes, the leading character before each recipe line must be a TAB.)


Single-file example

Files: main.cpp, Makefile

Makefile

# compiler & flags
CXX := g++
CXXFLAGS := -std=c++17 -Wall -Wextra -pedantic -O2

# default target
all: app

app: main.o
	$(CXX) $(CXXFLAGS) -o $@ $^

main.o: main.cpp
	$(CXX) $(CXXFLAGS) -c $<

.PHONY: clean run
clean:
	$(RM) app app.exe *.o    # $(RM) is a built-in "rm -f"
run: app
	./app || .\app.exe
Code language: PHP (php)

Use it

make        # build
make run    # run (Linux/mac: ./app, Windows PowerShell: .\app.exe)
make clean  # delete binaries/objects
Code language: PHP (php)

Multi-file C++ with classes

Files: main.cpp, List.h, List.cpp, Makefile

Makefile

# --- config ---------------------------------------------------
CXX := g++
CXXFLAGS := -std=c++17 -Wall -Wextra -pedantic -O2 -MMD -MP
TARGET := ds-app

# list your source files here
SRCS := main.cpp List.cpp
OBJS := $(SRCS:.cpp=.o)
DEPS := $(OBJS:.o=.d)

# --- rules ----------------------------------------------------
all: $(TARGET)

$(TARGET): $(OBJS)
	$(CXX) $(CXXFLAGS) -o $@ $^

# pattern rule: how to compile any .cpp -> .o
%.o: %.cpp
	$(CXX) $(CXXFLAGS) -c $< -o $@

# include auto-generated header dependencies
-include $(DEPS)

.PHONY: clean run debug release
run: $(TARGET)
	./$(TARGET) || .\$(TARGET).exe

clean:
	$(RM) $(TARGET) $(TARGET).exe $(OBJS) $(DEPS)

# quick build modes
debug: CXXFLAGS := -std=c++17 -Wall -Wextra -pedantic -O0 -g -MMD -MP
debug: clean all

release: CXXFLAGS := -std=c++17 -Wall -Wextra -pedantic -O2 -DNDEBUG -MMD -MP
release: clean all
Code language: PHP (php)

Why the extras?

  • -MMD -MP + -include $(DEPS) = auto header dependency tracking.
    If you change List.h, only the right files rebuild.
  • Pattern rule %.o: %.cpp avoids writing a separate rule for every file.
  • debug/release targets flip optimization & debug symbols.

Typical project layout

.
├─ include/        # headers (e.g., List.h)
├─ src/            # sources (e.g., List.cpp, main.cpp)
└─ Makefile
Code language: PHP (php)

Adjust the template like this:

INCDIR := include
SRCDIR := src
SRCS := $(SRCDIR)/main.cpp $(SRCDIR)/List.cpp
CPPFLAGS := -I$(INCDIR)      # add include path
Code language: PHP (php)

Everyday commands

make            # build default target
make -j         # build in parallel (faster on multi-core)
make clean      # remove build artifacts
make debug      # rebuild with -g -O0 (useful for gdb)
make run        # build and run
Code language: PHP (php)

Common pitfalls (and fixes)

  • “missing separator. Stop.” → Your recipe line starts with spaces. Use a TAB.
  • “No rule to make target …” → File path wrong or missing from SRCS.
  • Linker errors (undefined reference) → You compiled a class but forgot to include its .o in $(OBJS) / $(SRCS).
  • Headers change but nothing rebuilds → Add -MMD -MP and -include $(DEPS) (already in the template).
  • Windows notes
    • With w64devkit or MSYS you have rm. If not, replace the clean recipe with: clean: del /q $(TARGET).exe *.o *.d 2> NUL || true
    • Run programs in PowerShell with .\app.exe.

Tiny starter template (copy/paste)

CXX := g++
CXXFLAGS := -std=c++17 -Wall -Wextra -pedantic -O2 -MMD -MP
TARGET := program
SRCS := main.cpp Foo.cpp Bar.cpp
OBJS := $(SRCS:.cpp=.o)
DEPS := $(OBJS:.o=.d)

all: $(TARGET)
$(TARGET): $(OBJS)
	$(CXX) $(CXXFLAGS) -o $@ $^
%.o: %.cpp
	$(CXX) $(CXXFLAGS) -c $< -o $@
-include $(DEPS)

.PHONY: clean run
clean: ; $(RM) $(TARGET) $(TARGET).exe $(OBJS) $(DEPS)
run: $(TARGET) ; ./$(TARGET) || .\$(TARGET).exeCode language: JavaScript (javascript)

Make syntax cheat sheet (variables, assignment, automatic vars, and %)

$(NAME) — variable expansion

  • $(NAME) inserts the value of variable NAME where it appears.
  • Same as ${NAME}; prefer $(...) in Makefiles.
  • Example: CXX := g++ CXXFLAGS := -std=c++17 all: $(CXX) $(CXXFLAGS) main.cpp -o app
  • Substitution ref: $(SRCS:.cpp=.o) replaces suffix .cpp with .o across the words in $(SRCS).

Tip: In recipe lines (the shell commands), use $$ to pass a literal $ to the shell:

show:
	echo $$PATH    # make expands $$ → $ so the shell sees $PATH
Code language: PHP (php)

:=, =, ?=, += — assignment operators

  • := simple (immediate) expansion: right side is expanded now and stored. SRC_DIR := src SRCS := $(SRC_DIR)/a.cpp $(SRC_DIR)/b.cpp # uses current SRC_DIR immediately
  • = recursive (deferred) expansion: right side is expanded when used. SRC_DIR = src SRCS = $(SRC_DIR)/a.cpp SRC_DIR = other # Later, SRCS expands to "other/a.cpp" because it’s deferred.
  • ?= set if not already set: CXX ?= g++ # only sets if CXX wasn’t provided (e.g., from env or command line)
  • += append: CXXFLAGS := -std=c++17 CXXFLAGS += -Wall -Wextra

Rule of thumb: use := for predictable “compute once” values (paths, lists); use = only if you want late binding.


Automatic variables: $@, $^, $< (and friends)

These only have meaning inside a rule’s recipe:

  • $@ → the target name
    e.g., in app: main.o, $@ is app.
  • $^all prerequisites (deduplicated)
    e.g., in app: a.o b.o a.o, $^ is a.o b.o.
  • $< → the first prerequisite (most useful in single-dependency compile rules)
    e.g., in %.o: %.cpp, $< is the current .cpp file.
  • (Nice to know) $* → the stem matched by % in a pattern rule.
  • (Nice to know) $(basename $@), $(@D), $(@F) → directory/file parts.

Example putting them together:

$(TARGET): $(OBJS)
	$(CXX) $(CXXFLAGS) -o $@ $^

%.o: %.cpp
	$(CXX) $(CXXFLAGS) -c $< -o $@
Code language: JavaScript (javascript)

% — pattern wildcard (and $* the stem)

% matches an arbitrary “stem” in pattern rules and implicit rules.

  • Compile-anything pattern: %.o: %.cpp $(CXX) $(CXXFLAGS) -c $< -o $@ If Make needs List.o, it substitutes the stem List and uses List.cpp as the prerequisite.
  • % also appears in file lists (rarely needed for beginners), and in advanced functions. The key idea: % is the placeholder that binds to $* (the stem).

Mini example (annotated)

CXX := g++
CXXFLAGS := -std=c++17 -Wall -Wextra -MMD -MP
TARGET := ds-app

SRCS := main.cpp List.cpp
OBJS := $(SRCS:.cpp=.o)  # substitution ref
DEPS := $(OBJS:.o=.d)

all: $(TARGET)

$(TARGET): $(OBJS)       # target has prerequisites
	$(CXX) $(CXXFLAGS) -o $@ $^   # $@=ds-app, $^=all .o files

%.o: %.cpp               # % is the pattern; stem binds to $*
	$(CXX) $(CXXFLAGS) -c $< -o $@  # $< = first prereq (the .cpp)

-include $(DEPS)

.PHONY: clean
clean: ; $(RM) $(TARGET) $(TARGET).exe $(OBJS) $(DEPS)Code language: PHP (php)
Scroll to Top