Using Ninja Build to Build Projects Faster

10 minute read     Updated:

Antreas Antoniou %
Antreas Antoniou

Ninja is a compact build system with a focus on fast incremental builds. It was originally developed by Evan Martin, a Google dev, partly in response to the needs of building large projects such as Google Chrome.

If you’re developing a software system and you require a rebuild every few minutes to test your latest feature or code block, then Ninja will only rebuild what you have just modified or added and nothing else—as opposed to Make, which would rebuild the whole project every single time.

This article will start by explaining build systems in a little more detail. It’ll then introduce Ninja and teach you how to use Ninja to build a simple C++ project.

What Is a Build System?

Software projects are usually composed of many files. And the process of compiling, linking, copying, structuring, testing, or more generally, processing these files into an executable program is called a build.

Build systems fall into two broad categories:

  1. Build generator: Software that takes as input a spec file written in a specially designed language (in many cases, Turing Complete programming languages) and generates a build file that tells build tools how to go about building a software package.
  2. Build tool: Software that takes in a build file previously generated by a build generator and then builds a software package. Ninja is an example of a build tool.

Next, let’s take a closer look at Ninja and when you may want to use it.

What Is Ninja?

What Is Ninja?

Ninja is a fast build tool that can also be used as a build tool for other build generators. As mentioned, it was originally developed by Evan Martin, a Google dev, as a resource to speed up the building of projects such as Chrome. Since its inception, some notable projects built using Ninja include Chrome, Android, all Meson projects, Swift, and LLVM.

For a very interesting review and tech analysis of the Ninja build system, Ninja’s creator wrote a critical review article eight years after its original release.

Ninja differs from other build systems in two major ways:

  • It’s designed to have its input files generated by a higher-level build system such as CMake or Meson, and
  • It’s designed to run builds as fast as possible.

This philosophy even goes into the default arguments, which are designed to provide the best performance with little to no tinkering. For example, it builds things in parallel by default. This motivates developers to ensure that their code can be built in parallel, and any problems can be detected early in the development process.

Next, we’ll take a closer look at the strengths and weaknesses of Ninja as a build system.

Advantages and Limitations of Ninja

The main advantage of Ninja is its speed in incremental builds. It incentivizes developers to write code that can be built in parallel, using defaults that utilize the -jN flag, which causes Ninja to build in parallel. Furthermore, Ninja doesn’t use a background daemon to constantly keep track of things in memory; it always starts its own binary from scratch and works without relying on any state. So developers will always have a realistic idea of build time without fancy background optimizations done by daemons, and it also makes Ninja very portable and simple.

According to benchmarks, Ninja performs as well as Make in a fresh build but outperforms it in an incremental build by what appears to be an exponentially increasing factor.

On the downside, Ninja can’t build projects without a build file, so it must always work with a build generator, such as CMake or Meson, the most popular build generators that work with Ninja. Problems can be introduced depending on the build generator used. For example, Make requires each file to be specified in the build file, making the process of writing these files extremely complicated, time consuming, and prone to errors. So, the key is to choose the right build generator that works well with Ninja.

When Should You Use Ninja?

Ninja works well for large projects with many files that need many incremental builds over a short time. If you’re already using Make, Meson, or CMake to generate build files and using Make to build them, Ninja is a plug-and-play replacement that will, at worst, keep the performance the same or, at best, improve it in an exponential manner in case of incremental builds.

Conversely, Ninja might not be a good choice if you want an end-to-end tool (build generator and tool in one) that has a high-level language to describe relationships between files and is also a build tool. In that case, something like Bazel might be better, but it’s often slower than Ninja and not as portable.

Implementing a Ninja Build

Implementing a Ninja Build

The following sections explain the different ways to install Ninja before going through step-by-step instructions for implementing a Ninja build.

How to Install Ninja

This section explains how to install Ninja on Linux, Mac, and Windows, and how to build it from source.

For a more thorough set of instructions for any specialized installation cases, please see the Ninja GitHub page, a very useful resource. For day-to-day use, the wiki page also includes a list of standard build patterns and build generators that work well with Ninja.

Installing Ninja on Linux

Depending on the Linux flavor, the installation process differs a bit:

  • Arch: pacman -S ninja
  • Debian/Ubuntu: apt-get install ninja-build
  • Fedora: dnf install ninja-build
  • Gentoo: emerge dev-util/ninja
  • OpenSUSE: zypper in ninja
  • Alpine: apk add ninja

Installing Ninja on MacOS

Ninja can be installed using either Homebrew or MacPorts with the following one-liners:

  • Homebrew: brew install ninja
  • MacPorts: port install ninja

Installing Ninja on Windows

Chocolatey or Scoop can be used to install Ninja with a one-liner on Windows:

  • Chocolatey: choco install ninja
  • Scoop: scoop install ninja

Installing Ninja via Package Managers

Ninja can also be installed via package managers, which generally provide more convenience when managing multiple other packages in addition to it:

  • Conda: conda install -c conda-forge ninja
  • Pip: python -m pip install ninja
  • Spack: spack install ninja

Building Ninja From Source

Users who don’t want to build Ninja with specialized flags can build it from source with the following instructions.

First, clone and checkout the Ninja repo:

git clone git://github.com/ninja-build/ninja.git && cd ninja
git checkout release

Then, build a basic Ninja binary and a set of files needed to build Ninja:

./configure.py --bootstrap

This will generate the Ninja binary and a build.ninja file that can be used to build Ninja with itself. That is, the basic Ninja binary generated in the previous step can be used to build the particular configuration of Ninja required. A sort of “ninjaception” if you will.

Use the following code to build Ninja:

cmake -Bbuild-cmake
cmake --build build-cmake

The Ninja binary will now be inside the build-cmake directory (though the user can name this directory anything).

The following code will run the unit tests:

./build-cmake/ninja_test

Creating a Project With Ninja

Creating a Project with Ninja

To demonstrate how to use Ninja as well as showcase some of its strengths, this tutorial uses a sample project, which can be found in this GitHub repo.

This tutorial will show you how to create a simple from-scratch project and an incremental project to demonstrate the time-saving features of Ninja.

Prerequisites

The following are prerequisites to follow along:

  • CMake is required to build the project. Instructions for CMake installation can be found on their official website.
  • For Linux users, CMake can be installed with the single terminal command sudo snap install cmake --classic. It can be installed on macOS with brew install cmake.
  • Python is required to generate the sample files used in this tutorial. Ensure Python is installed.

Creating a Project From Scratch

To create a project from scratch, do the following:

  1. Clone the repository with git clone https://github.com/AntreasAntoniou/ninja-tutorial.git
  2. Navigate to the project directory using cd ninja-tutorial
  3. Navigate to the from-scratch project using cd scratch
  4. Notice the two files inside this folder: hello_world.cpp and CMakeLists.txt

hello_world.cpp is a simple C++ program that prints “Hello World” to the console:

// C++ program to display "Hello World"

// Header file for input/output functions
#include <iostream>
using namespace std;

// main() function: where the execution of program begins
int main()
{
    // Print "Hello World"
    cout << "Hello World";

    return 0;
}

CMakeLists.txt is a CMake file that describes the project and how to build it:

cmake_minimum_required (VERSION 3.8)

project(HelloWorld CXX)
set(CMAKE_CXX_STANDARD 14)

add_executable(HelloWorld hello_world.cpp)

Now use CMake to generate a build file for Ninja:

cmake -G Ninja

This should generate a build.ninja file in the current directory, along with related configuration files.

The project can now be built with Ninja using the following command:

ninja

The following output should be generated:

[2/2] Linking CXX executable HelloWorld

With that, the Ninja project should be successfully built.

Creating and Building an Incremental Project

Now, go back to the root of the repository and navigate to the incremental project by running:

cd ..
cd incremental

There are three files here: hello_world-template.cpp, CMakeLists-template.txt, and generate_project_files.py.

The generate_project_files.py file is a Python script that generates the C++ project files and the CMake file from the template files.

This script needs to be run twice: once to generate a 1000-file project and a second time to generate a 1001-file project. So the second project will be an incremental build of the first.

Generate the 1000-file project first:

python3 generate_project_files.py --num_files 1000

Now, use CMake to generate a build file for Ninja:

cmake -S sample_project -G Ninja

This should generate a build.ninja file in the current directory, along with related configuration files.

Build the project with Ninja:

ninja

Next, emulate an incremental build by adding one more file to your sample project:

python3 generate_project_files.py --num_files 1001

Use CMake to generate a build file for Ninja:

cmake -S sample_project -G Ninja

This should generate a build.ninja file in the current directory, along with related configuration files.

As before, build the project with Ninja:

ninja

In the second build, Ninja only builds the new file and not the entire project. This can be seen in the terminal output that shows how many files had to be processed, as well as the time taken for the build to complete.

On the local setup (Apple M1 Max, 16-inch) used in this tutorial, the first build took 35 seconds, and the second build took three seconds.

The following is a copy of the terminal output for the second build:

cmake -S sample_project/ -G Ninja
-- The CXX compiler identification is AppleClang 14.0.0.14000029
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: \
  /Library/Developer/CommandLineTools/usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: \
  /Users/helloworld/ninja-tutorial/incremental

ninja-tutorial/incremental on  main [!+?] via △ v3.24.2 via 🐍 v3.9.13 \
on ☁️  took 4s
ninja
[2000/2000] Linking CXX executable HelloWorld998

ninja-tutorial/incremental on  main [!+?] via 🐍 v3.9.13 on ☁️ took 5m54s
python generate_project_files.py --num_files 1001
Done

ninja-tutorial/incremental on  main [!+?] via 🐍 v3.9.13 on ☁️
cmake -S sample_project/ -G Ninja
-- Configuring done
-- Generating done
-- Build files have been written to: \
  /Users/helloworld/ninja-tutorial/incremental

ninja-tutorial/incremental on  main [!+?] via 🐍 v3.9.13 on ☁️ took 3s
ninja
[2/2] Linking CXX executable HelloWorld1000

As you can see, Ninja is able to build the updated 1001-file project in only three seconds, compared to the 35 seconds it took to build its predecessor project consisting of 1000 files. Because Ninja had already built the 1000-file variant of the same project, it only had to add one more file to the build.

Conclusion

This article introduced you to build systems and Ninja before explaining how to install Ninja and use it to build your C++ projects.

Tools that help automate the building, testing, and deployment of software, such as the build systems discussed in this article, are essential to the success of any software project. Ninja is a great tool that can save time and effort when building C++ projects. You can as well explore other build tools like Bazel.

While you’re here:

Earthly is the effortless CI/CD framework.
Develop CI/CD pipelines locally and run them anywhere!

Antreas Antoniou %
Antreas Antoniou

I have spent the last 6 years doing research and teaching in machine learning, and in particular deep learning methods. My teaching experience in particular has been exceptionally useful in learning how to communicate very complex information efficiently to a diverse audience. As a researcher I got to tackle some of the most exciting new directions in the deep learning field, namely meta-learning. I was even part of a massive effort to write a survey paper that helps ease newcomers into meta-learning, and helps direct experts into the latest and greatest. This paper can be found at https://arxiv.org/abs/2004.05439 I have also spend the last 10 years working with software engineering, and the last 6 in particular in building state of the art deep learning systems. I have worked at Google and Amazon building such systems, as well as during my time as a PhD student.

Published:

Get notified about new articles!

We won't send you spam. Unsubscribe at any time.