Build Systems: Make, CMake, Ninja, Meson
This chapter covers the tools that automate compiling and linking: Make, CMake, Ninja, and Meson. They solve the same problem — incremental builds — at different levels of abstraction.
Why This Matters
Hand-typing compiler commands does not scale. Build systems track dependencies and rebuild only what changed, and meta-build systems (CMake, Meson) generate builds for multiple generators and platforms.
Prerequisites
c.build.6— compilers.
Core Concept
Make
make reads a Makefile of targets, prerequisites, and recipes:
app: main.o util.o
cc main.o util.o -o app
main.o: main.c util.h
cc -c main.c
util.o: util.c util.h
cc -c util.c
It rebuilds a target only when a prerequisite is newer.
CMake
CMake is a meta-build system. You write CMakeLists.txt; CMake generates Makefiles, Ninja files, or IDE projects.
cmake_minimum_required(VERSION 3.16)
project(app C)
add_executable(app main.c util.c)
Ninja
Ninja is a fast, low-level build tool. You usually do not write Ninja files by hand; CMake/Meson generate them.
Meson
Meson is a modern meta-build system with a Python-like language and Ninja as its default backend.
project('app', 'c')
executable('app', 'main.c', 'util.c')
Examples
Make build
make # build
make clean # clean
CMake build
cmake -S . -B build -G Ninja
cmake --build build
Meson build
meson setup build
meson compile -C build
How It Works
Make uses timestamps and a dependency graph. Ninja does the same but is optimized for speed and parallelism. CMake/Meson generate low-level build files, so the *generated* system does the actual tracking.
Variations
Dependency generation
Compilers can emit dependency info (-MMD -MP), which build systems consume to track header changes automatically.
Out-of-source builds
CMake/Meson encourage building in a separate build/ directory, keeping the source tree clean.
Common Mistakes
- Forgetting to list header dependencies in a Makefile (stale builds).
- Not re-running CMake after changing
CMakeLists.txt. - Mixing tabs and spaces in Makefiles (Make requires tabs in recipes).
Undefined Behavior
- None; build systems are tooling.
Portability
- Make is nearly universal; CMake/Meson are portable and generate
platform-appropriate builds.
Under the Hood
Build systems construct a directed acyclic graph of build steps and execute the minimal set needed to bring outputs up to date. Ninja is designed for incremental speed; Make is the classic interpreter.
Practical Usage
- Use Make for small, simple projects.
- Use CMake or Meson for cross-platform and larger projects.
- Generate Ninja files from CMake/Meson for fast builds.
Exercises
1. Write a Makefile for a two-file project with a header dependency. 2. Convert the project to CMake and build it out-of-source. 3. Use -MMD -MP and confirm header changes trigger rebuilds. 4. Build the same project with Meson + Ninja.
Deep Challenge
Set up a CMake project with a static library and an executable that links it, plus a compile definition that toggles a feature. Explain how CMake's dependency tracking handles header changes and rebuilds.
Related Concepts
c.build.8— compiler/linker flags.c.build.9— cross compilation.c.build.10— debug/release builds.
References
- GNU Make manual, CMake documentation, Ninja manual, Meson documentation.
Verification
- Build system behaviors are documented tool behavior.
VERIFIED - No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Make and Makefiles
- [ ] CMake
- [ ] Ninja
- [ ] Meson
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.build.make | 0 | 5 |
| c.build.cmake | 0 | 5 |
| c.build.ninja | 0 | 5 |
| c.build.meson | 0 | 4 |