Appearance
Make and Incremental Builds
Make answers one central question: which targets are out of date relative to their prerequisites, and which recipes can update them? It is a general build-graph executor, not a C++ project model or dependency package manager.
Rules describe a graph
make
CXX := c++
CPPFLAGS := -Iinclude
CXXFLAGS := -std=c++20 -Wall -Wextra -Wpedantic -g
objects := build/main.o build/report.o
.PHONY: all clean
all: build/report-cli
build/report-cli: $(objects)
$(CXX) $(objects) -o $@
build/%.o: src/%.cpp
@mkdir -p build
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -MMD -MP -c $< -o $@
-include $(objects:.o=.d)
clean:
rm -rf buildRecipe lines begin with a tab in traditional Make syntax. $@ is the target and $< the first prerequisite. Make expands variables and decides which recipes run; the shell executes each recipe line under Make's rules.
The cleanup recipe is intentionally scoped to the explicit build directory. Build scripts should never turn an unresolved or broad variable into a recursive deletion target.
Timestamps drive ordinary rebuilding
If a prerequisite is newer than its target, Make rebuilds the target. .d files emitted by the compiler add header dependencies so changing report.hpp rebuilds affected objects.
Phony targets represent actions rather than files. Without .PHONY, a file named clean could convince Make that cleanup is already satisfied.
Variables separate categories
Conventional variables distinguish preprocessor flags (CPPFLAGS), C flags (CFLAGS), C++ flags (CXXFLAGS), and linker/library settings (LDFLAGS, LDLIBS). Users and environments may override them:
sh
make CXX=clang++ CXXFLAGS='-std=c++20 -O2 -Wall -Wextra'Use ?= for defaults intended to yield to caller values. Avoid baking one developer's absolute paths into the Makefile.
Parallelism requires accurate dependencies
make -j can run independent recipes concurrently. Missing prerequisites become races: a generated header may not exist before compilation, or a linker may start before all objects finish. Parallel failures often expose an incorrect graph rather than a Make bug.
Each recipe line may run in a separate shell, so cd or variable assignments do not necessarily persist to the next line. Combine dependent shell actions carefully or express directory structure through target paths.
Configurations need separate outputs
Debug and release objects compiled with different flags should not overwrite one another. Use configuration-specific directories such as build/debug and build/release, or let a higher-level generator create separate build trees.
Make syntax can express large portable builds, but compiler detection, transitive target properties, generated exports, installation, and many platform conventions become substantial custom work. That is the point where CMake, Meson, Bazel, or a platform-native project model may add value.
Make variants differ
GNU Make is common but not the only implementation. BSD Make, Microsoft's NMake, and other variants have incompatible extensions. State which dialect a nontrivial Makefile requires. A CMake-generated Makefile is output; do not edit it.
Optional prompts
Debug: Source changes rebuild correctly, but header changes do not. What graph information is missing?
Answer: Object targets lack dependencies on included headers. Generate and include compiler dependency files or list those prerequisites accurately.
Explain: Why can make -j reveal failures absent from serial builds?
Answer: Serial order can accidentally hide missing graph edges. Parallel execution relies on declared prerequisites rather than incidental recipe order.