When writing C++ code, you can compile small programs directly with a compiler such as g++
or clang++
. But as your project grows, managing multiple files, dependencies, and build configurations manually becomes error-prone. This is where build systems come in.
- CMake1 is a meta-build system: it generates build configuration files (for Make, Ninja, Visual Studio, etc.), making your project portable across platforms and compilers.
- Ninja2 is a small, fast build tool designed to replace Make. It’s optimized for speed and is widely used in large-scale C++ projects like LLVM and TensorFlow. Born on Chromium project which contains more than 30,000 source files3 which build system using Make build systems were taking 10 seconds, with Ninja it came down to under 1 second.
This guide will walk you through:
- Compiling a simple program with a compiler directly
- Automating build file creation with CMake
- Using Make vs Ninja backends with CMake
Step 1: A Simple C++ Program
Create a file named main.cpp
:
#include <iostream>
using namespace std;
int main() {
cout << "Hello world" << endl;
return 0;
}
Step 2: Compile and Run Directly with a Compiler
You can use either g++
or clang++
:
clang++ -o main main.cpp
./main
g++ -o main main.cpp
./main
Output:
Hello world
This works fine for small projects, but it doesn’t scale well. Let’s automate with a build system.
Step 3: Using CMake as a Build System
Create a CMakeLists.txt
file:
cmake_minimum_required(VERSION 3.10)
project(demoproject)
add_executable(main main.cpp)
CMake generates build files based on the chosen build system (e.g., Make or Ninja).
In this example, we’ll demonstrate how to use both generators with the same project.
Keep the source file (main.cpp
) and the CMakeLists.txt
in the project root, and create two separate
directories for the builds: one for Make (build) and one for Ninja (ninjabuild).
mkdir build
mkdir ninjabuild
Folder structure
.
├── build # build directory for Make
├── CMakeLists.txt
├── main.cpp
├── ninjabuild # build directory for Ninja
└── readme.md
Option A: Build with Make
cd build
cmake ..
make
./main
Here,
cmake ..
→ generates a Makefile from the top-levelCMakeLists.txt
.make
→ compiles the project../main
→ runs the compiled binary.
Folder structure (tree build -L 1
):
build
├── CMakeCache.txt # CMake configuration cache
├── CMakeFiles # internal CMake files
├── Makefile # build rules for make
├── cmake_install.cmake # install script
└── main # compiled binary
Option B: Build with Ninja
cd ninjabuild
cmake -G Ninja ..
ninja
./main
Here,
cmake -G Ninja ..
→ generates abuild.ninja
file fromCMakeLists.txt
.ninja
→ builds the project using Ninja../main
→ runs the compiled binary. Folder structure (tree ninjabuild -L 1
):
ninjabuild
├── build.ninja # build rules for Ninja
├── CMakeCache.txt # CMake configuration cache
├── CMakeFiles # internal CMake files
├── cmake_install.cmake # install script
└── main # compiled binary
Make vs. Ninja
- Make: Widely available by default on most systems; works well for small to medium projects.
- Ninja: Designed for speed; faster incremental builds and often preferred for large or complex projects.
Why Use CMake with Ninja?
- CMake: CMake1 abstracts away platform/compiler differences and makes your project portable.
- Ninja: Ninja2 is designed for speed, better parallelism, and more efficient incremental builds than Make.
- Together, they provide a modern, cross-platform build setup widely adopted in industry.
Conclusion
- For very small projects, compiling directly with
g++
/clang++
works. - For larger projects, use CMake to manage builds.
- Use Ninja when you want faster builds than Make.
References:
Advertisement