大規模なC++プロジェクトをビルドしていて、「コンパイルに時間がかかりすぎる…」「Makefileが遅い」「もっと速くビルドできないの?」と困ったことはありませんか?
「Ninjaって何?」「Makeとどう違うの?」「どうやって使うの?」と疑問に思っている方も多いはずです。
実は、Ninja(ニンジャ)は、ビルド速度に特化した小規模で高速なビルドシステムで、Makeの代替として開発されたツールなんです。まるで、素早く動く忍者のように、従来のMakeよりも圧倒的に速くプロジェクトをビルドできるんですよ。
この記事では、Ninjaの基本から仕組み、インストール方法、CMakeとの連携、実践的な使い方まで、初心者の方にも分かりやすく丁寧に解説していきます。
具体的なコード例をたくさん使いながら、高速ビルドの世界をマスターしていきましょう!
Ninjaとは?その基本を知ろう
基本的な説明
Ninjaは、ビルド速度に特化した小規模で高速なビルドシステムです。
正式名称:
Ninja build system
読み方:
ニンジャ
開発者:
Evan Martin(元Google)
初版リリース:
2010年
主な特徴:
- 非常に高速
- シンプルな設計
- CMakeなどから生成されることを想定
- 手書きは想定していない
身近な例で理解しよう
料理の準備:
従来の方法(Make):
レシピを読む(Makefile解析)
↓
材料を確認(依存関係チェック)
↓
調理手順を考える(ビルドプラン作成)
↓
料理を作る(コンパイル実行)
毎回、レシピを最初から読み直す...
Ninja方式:
事前に準備された調理カード(build.ninja)
↓
カードを見て即座に調理開始
↓
無駄な確認なし
必要な作業だけを高速実行!
Ninjaは、「事前に最適化された作業指示書」を使って、無駄を極限まで削ぎ落としているんです。
Makeとの比較
Make:
利点:
- 歴史が長い
- 広く普及
- 柔軟
欠点:
- 大規模プロジェクトで遅い
- Makefile解析に時間がかかる
- 依存関係チェックが非効率
Ninja:
利点:
- 圧倒的に高速
- シンプルな設計
- 並列ビルドに最適化
欠点:
- build.ninjaを手書きするのは困難
- 柔軟性は低い(意図的)
速度比較(大規模プロジェクト):
Make: 100秒
Ninja: 30秒
約3倍の高速化!
Ninjaが速い理由
1. シンプルな設計思想
Makeの問題点:
# Makefileは毎回解析される
%.o: %.c
gcc -c $< -o $@
# 複雑なルール展開
# 条件分岐、関数呼び出し...
毎回、Makefile全体を解析・評価する必要があります。
Ninjaのアプローチ:
# build.ninjaは事前に最適化済み
build foo.o: cc foo.c
# 直接的なルール
# 解析が最小限
ビルドに必要な情報だけが、シンプルな形で記述されています。
2. 効率的な依存関係管理
ファイルのタイムスタンプチェック:
Ninjaは、ファイルの変更を高速にチェックします。
並列処理の最適化:
依存関係グラフを最適化して、最大限の並列実行を実現します。
伝統的なビルド:
A → B → C → D → E
(順次実行)
Ninja:
A → B → D
↘ C ↗ ↘ E
(並列実行)
3. 最小限の再ビルド
必要なファイルだけをビルド:
変更されたファイルとその依存ファイルだけを再ビルドします。
例:
1つのソースファイルを変更
↓
Ninja: 1つのオブジェクトファイルを再コンパイル
Make: 依存関係を再確認してから再コンパイル
4. メモリ効率
Makeの問題:
- Makefile全体をメモリに読み込む
- 複雑なルール展開
Ninjaの利点:
- 必要な部分だけを読み込む
- メモリ使用量が少ない
インストール方法
macOS
Homebrewを使用:
brew install ninja
確認:
ninja --version
出力例:
1.11.1
Linux(Ubuntu/Debian)
aptを使用:
sudo apt update
sudo apt install ninja-build
確認:
ninja --version
Linux(Fedora/RHEL)
dnf/yumを使用:
sudo dnf install ninja-build
# または
sudo yum install ninja-build
Windows
方法1:Chocolateyを使用
choco install ninja
方法2:Scoopを使用
scoop install ninja
方法3:手動インストール
1. GitHubリリースページからダウンロード
2. ninja.exeをダウンロード
3. PATHに追加
# 環境変数PATHに追加
setx PATH "%PATH%;C:\path\to\ninja"
ソースからビルド
すべてのOS:
# リポジトリをクローン
git clone https://github.com/ninja-build/ninja.git
cd ninja
# ビルド(Ninjaを使ってNinjaをビルド!)
./configure.py --bootstrap
# インストール
sudo cp ninja /usr/local/bin/
基本的な使い方
シンプルな例
build.ninjaファイル:
# ルールの定義
rule cc
command = gcc -c $in -o $out
description = Compiling $in
rule link
command = gcc $in -o $out
description = Linking $out
# ビルドターゲット
build hello.o: cc hello.c
build main.o: cc main.c
build hello: link hello.o main.o
ビルド実行:
ninja
出力:
[1/3] Compiling hello.c
[2/3] Compiling main.c
[3/3] Linking hello
特定のターゲットをビルド:
ninja hello.o
クリーン:
ninja -t clean
build.ninjaの基本構文
ルール定義:
rule <ルール名>
command = <実行コマンド>
description = <説明文>
depfile = <依存ファイル>
deps = <依存関係タイプ>
ビルドステートメント:
build <出力ファイル>: <ルール名> <入力ファイル>
<変数> = <値>
変数:
# 変数の定義
cflags = -Wall -O2
# 使用
rule cc
command = gcc $cflags -c $in -o $out
主な組み込み変数:
$in:入力ファイル$out:出力ファイル$in_newline:改行区切りの入力ファイル$out_newline:改行区切りの出力ファイル
CMakeとの連携(推奨)
なぜCMakeと組み合わせるのか
Ninjaの設計思想:
Ninjaのbuild.ninjaファイルは、手書きではなく、ツールによって生成されることを想定しています。
CMakeの役割:
- プロジェクトの構造を定義
- プラットフォーム固有の設定を処理
- build.ninjaファイルを生成
組み合わせのメリット:
CMake(柔軟性) + Ninja(速度) = 最強の組み合わせ
CMakeでNinjaを使う
CMakeLists.txt:
cmake_minimum_required(VERSION 3.15)
project(MyProject)
# C++標準の設定
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# ソースファイル
add_executable(myapp
main.cpp
utils.cpp
config.cpp
)
# インクルードディレクトリ
target_include_directories(myapp PRIVATE include)
# リンクするライブラリ
target_link_libraries(myapp PRIVATE pthread)
Ninjaジェネレーターを使用してビルド:
# ビルドディレクトリを作成
mkdir build
cd build
# CMakeでNinjaジェネレーターを指定
cmake -G Ninja ..
# ビルド
ninja
# または
cmake --build .
オプション付き:
# Releaseビルド
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release ..
ninja
# Debugビルド
cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ..
ninja
# 並列ジョブ数を指定
ninja -j 8
生成されたbuild.ninjaの確認
生成されたファイル:
ls build/
build.ninja
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
rules.ninja
build.ninjaの中身(抜粋):
# CMakeが生成したビルドファイル
rule CXX_COMPILER__myapp
command = /usr/bin/c++ $DEFINES $INCLUDES $FLAGS -MD -MT $out -MF $DEP_FILE -o $out -c $in
description = Building CXX object $out
depfile = $DEP_FILE
deps = gcc
build CMakeFiles/myapp.dir/main.cpp.o: CXX_COMPILER__myapp ../main.cpp
DEP_FILE = CMakeFiles/myapp.dir/main.cpp.o.d
FLAGS = -std=gnu++17
OBJECT_DIR = CMakeFiles/myapp.dir
OBJECT_FILE_DIR = CMakeFiles/myapp.dir
# ... 他のファイル ...
build myapp: CXX_EXECUTABLE_LINKER__myapp CMakeFiles/myapp.dir/main.cpp.o CMakeFiles/myapp.dir/utils.cpp.o
FLAGS = ...
LINK_LIBRARIES = -lpthread
自動生成されているので、手で編集する必要はありません!
実践例1:C++プロジェクト
プロジェクト構造
myproject/
├── CMakeLists.txt
├── include/
│ ├── utils.h
│ └── config.h
├── src/
│ ├── main.cpp
│ ├── utils.cpp
│ └── config.cpp
└── tests/
└── test_utils.cpp
CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(MyProject VERSION 1.0.0)
# C++標準
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# コンパイルオプション
add_compile_options(-Wall -Wextra -pedantic)
# インクルードディレクトリ
include_directories(include)
# ライブラリの作成
add_library(mylib STATIC
src/utils.cpp
src/config.cpp
)
# 実行ファイルの作成
add_executable(myapp
src/main.cpp
)
# リンク
target_link_libraries(myapp PRIVATE mylib)
# テストの有効化
enable_testing()
# テスト実行ファイル
add_executable(test_utils tests/test_utils.cpp)
target_link_libraries(test_utils PRIVATE mylib)
# テストの追加
add_test(NAME UtilsTest COMMAND test_utils)
ビルドとテスト
# プロジェクトルートで
mkdir build
cd build
# CMake設定(Ninjaを使用)
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release ..
# ビルド
ninja
# 特定のターゲットだけビルド
ninja myapp
# テスト実行
ninja test
# または
ctest
出力:
[1/5] Building CXX object CMakeFiles/mylib.dir/src/utils.cpp.o
[2/5] Building CXX object CMakeFiles/mylib.dir/src/config.cpp.o
[3/5] Linking CXX static library libmylib.a
[4/5] Building CXX object CMakeFiles/myapp.dir/src/main.cpp.o
[5/5] Linking CXX executable myapp
実践例2:クロスプラットフォームビルド
ツールチェーンファイル
arm-toolchain.cmake:
# ARMクロスコンパイル用ツールチェーン
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
# コンパイラの指定
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
# ルートファイルシステム
set(CMAKE_FIND_ROOT_PATH /usr/arm-linux-gnueabihf)
# 検索パスの設定
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
クロスコンパイルビルド
# ARM向けビルド
mkdir build-arm
cd build-arm
cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=../arm-toolchain.cmake ..
ninja
# 生成されたバイナリを確認
file myapp
出力:
myapp: ELF 32-bit LSB executable, ARM, EABI5 version 1
実践例3:大規模プロジェクトでの並列ビルド
Chromiumスタイルのビルド
大規模プロジェクトでのNinja活用:
# すべてのCPUコアを使用
ninja -j $(nproc)
# または特定のジョブ数
ninja -j 16
# 負荷を考慮(CPUコア数 - 1)
ninja -j $(($(nproc) - 1))
ビルド時間の測定
# 時間測定
time ninja
# または
ninja -v # 詳細出力(どのコマンドが実行されているか表示)
出力例:
[1/1247] Compiling src/main.cpp
[2/1247] Compiling src/utils.cpp
...
[1247/1247] Linking myapp
real 0m45.123s
user 5m32.456s
sys 0m12.345s
増分ビルド
1つのファイルを変更:
# ファイルを編集
vim src/utils.cpp
# 再ビルド
ninja
出力:
[1/3] Compiling src/utils.cpp
[2/3] Linking libmylib.a
[3/3] Linking myapp
変更されたファイルとその依存ファイルだけが再ビルドされます!
Ninjaのツールとコマンド
主なコマンド
ビルド:
ninja # すべてをビルド
ninja target # 特定のターゲットをビルド
ninja -j N # N個のジョブで並列ビルド
ninja -v # 詳細出力
ninja -n # ドライラン(実際にはビルドしない)
クリーン:
ninja -t clean # ビルド成果物を削除
ninja -t clean target # 特定のターゲットをクリーン
依存関係の表示:
# 依存関係グラフを生成(Graphviz形式)
ninja -t graph > graph.dot
dot -Tpng graph.dot -o graph.png
# または特定のターゲット
ninja -t graph myapp > graph.dot
ビルド済みターゲットの表示:
ninja -t targets
出力例:
hello.o: CXX_COMPILER__myapp
main.o: CXX_COMPILER__myapp
myapp: CXX_EXECUTABLE_LINKER__myapp
all: phony
依存関係ツリーの表示:
ninja -t deps myapp
コンパイルデータベースの生成:
ninja -t compdb > compile_commands.json
これは、IDEやリンター(clang-tidy等)で使用できます。
デバッグツール
なぜ再ビルドされたか確認:
ninja -d explain
出力例:
ninja explain: output myapp.o older than most recent input src/myapp.cpp (1234567890 vs 1234567891)
ビルドログの確認:
ninja -t log
ルールの一覧:
ninja -t rules
build.ninjaの詳細
基本構造
完全な例:
# 変数定義
builddir = build
cflags = -Wall -O2
cxx = g++
# ルール定義
rule cxx
command = $cxx $cflags -c $in -o $out -MMD -MF $out.d
description = CXX $out
depfile = $out.d
deps = gcc
rule link
command = $cxx $in -o $out
description = LINK $out
# ビルドターゲット
build $builddir/main.o: cxx src/main.cpp
build $builddir/utils.o: cxx src/utils.cpp
build $builddir/myapp: link $builddir/main.o $builddir/utils.o
# デフォルトターゲット
default $builddir/myapp
高度な機能
フォニーターゲット:
# 常に実行されるターゲット
build clean: phony
command = rm -rf build
build all: phony myapp tests
# デフォルト
default all
変数のスコープ:
# グローバル変数
cflags = -Wall
rule cxx
command = g++ $cflags $extra_cflags -c $in -o $out
# ビルドステートメント固有の変数
build foo.o: cxx foo.cpp
extra_cflags = -DFOO_ENABLED
build bar.o: cxx bar.cpp
extra_cflags = -DBAR_ENABLED
サブビルドファイル:
# build.ninja
subninja sub/build.ninja
Makeからの移行
Makefile vs build.ninja
Makefile:
CC = gcc
CFLAGS = -Wall -O2
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
myapp: main.o utils.o
$(CC) $^ -o $@
clean:
rm -f *.o myapp
build.ninja(等価):
cc = gcc
cflags = -Wall -O2
rule cc
command = $cc $cflags -c $in -o $out
rule link
command = $cc $in -o $out
build main.o: cc main.c
build utils.o: cc utils.c
build myapp: link main.o utils.o
rule clean
command = rm -f *.o myapp
build clean: phony
違い:
Makefile:
- パターンルール(
%.o: %.c) - 自動変数(
$<,$^) - 条件分岐、関数
build.ninja:
- 明示的なビルドステートメント
- シンプルな変数展開のみ
- 柔軟性は低いが高速
移行の推奨アプローチ
直接移行は推奨されません:
build.ninjaを手書きするのは大変です。
推奨:CMakeを導入
# 1. CMakeLists.txtを作成
# 2. CMakeでbuild.ninjaを生成
cmake -G Ninja .
# 3. Ninjaでビルド
ninja
トラブルシューティング
問題1:Ninjaが見つからない
症状:
bash: ninja: command not found
原因:
Ninjaがインストールされていない、またはPATHに入っていない。
解決策:
# macOS
brew install ninja
# Ubuntu/Debian
sudo apt install ninja-build
# 確認
which ninja
ninja --version
問題2:CMakeがNinjaを見つけられない
症状:
CMake Error: CMake was unable to find a build program corresponding to "Ninja".
原因:
NinjaがPATHに入っていない。
解決策:
# Ninjaのパスを確認
which ninja
# CMakeに明示的に指定
cmake -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja ..
問題3:ビルドが失敗する
症状:
[1/10] Compiling src/main.cpp
FAILED: src/main.cpp
...
ninja: build stopped: subcommand failed.
原因:
コンパイルエラー。
解決策:
詳細を確認:
ninja -v
実際に実行されているコマンドが表示されます。
クリーンビルド:
ninja -t clean
ninja
問題4:依存関係が正しく検出されない
症状:
ヘッダーファイルを変更しても再ビルドされない。
原因:
依存関係ファイル(.d)が古いまたは破損。
解決策:
# クリーンビルド
ninja -t clean
ninja
# またはCMakeの再設定
rm -rf build
mkdir build
cd build
cmake -G Ninja ..
ninja
問題5:並列ビルドでメモリ不足
症状:
c++: fatal error: Killed signal terminated program cc1plus
原因:
並列ジョブ数が多すぎてメモリ不足。
解決策:
# ジョブ数を減らす
ninja -j 4
# またはメモリを考慮した自動設定
ninja -j $(($(nproc) / 2))
パフォーマンスチューニング
最適な並列度
CPU コア数を考慮:
# すべてのコアを使用
ninja -j $(nproc)
# CPUコア数 + 2(I/O待ち時間を考慮)
ninja -j $(($(nproc) + 2))
# 負荷を抑える
ninja -j $(($(nproc) - 1))
コンパイラキャッシュ
ccacheの使用:
# ccacheをインストール
sudo apt install ccache
# CMakeで有効化
cmake -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache ..
# ビルド
ninja
2回目以降のビルドが劇的に速くなります!
分散ビルド
distccの使用:
複数のマシンでビルドを分散できます。
# distccをセットアップ
# CMakeで有効化
cmake -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=distcc ..
ninja
他のビルドシステムとの比較
Make vs Ninja vs Bazel
| 項目 | Make | Ninja | Bazel |
|---|---|---|---|
| 速度 | 遅い | 非常に速い | 速い |
| 学習曲線 | 緩やか | 中程度 | 急 |
| 柔軟性 | 高い | 低い | 中 |
| 手書き | 可能 | 非推奨 | 非推奨 |
| 並列性 | 中 | 優秀 | 優秀 |
| キャッシュ | 基本的 | 高度 | 非常に高度 |
| 規模 | 小〜中 | 小〜大 | 中〜超大 |
選択のガイドライン
Makeが良い場合:
- 小規模プロジェクト
- 既存のMakefile資産が大量
- シンプルなビルドプロセス
Ninjaが良い場合:
- 中〜大規模プロジェクト
- ビルド速度が重要
- CMakeと組み合わせる
Bazelが良い場合:
- 超大規模プロジェクト(Google Chrome級)
- モノレポ(複数プロジェクトを1つのリポジトリで管理)
- 再現性が最重要
よくある質問
Q1: build.ninjaは手書きすべき?
A: いいえ、CMakeなどのツールで生成すべきです。
理由:
- Ninjaは生成されることを前提に設計されている
- 手書きは複雑で保守が困難
- CMakeの方が柔軟で読みやすい
例外:
非常にシンプルなプロジェクトや、学習目的なら手書きもOK。
Q2: MakeからNinjaに移行すべき?
A: プロジェクトの規模による。
移行を推奨:
- 大規模プロジェクト(ビルドに数分以上)
- ビルド時間がボトルネック
- CMakeを使っている(簡単)
移行不要:
- 小規模プロジェクト(数秒でビルド)
- 既存のMakefileで十分
- チームがMakeに慣れている
Q3: Ninjaだけで開発できる?
A: できますが、CMakeとの併用を推奨します。
Ninjaのみ:
- build.ninjaを手書き
- 柔軟性が低い
CMake + Ninja:
- CMakeLists.txtを書く
- CMakeがbuild.ninjaを生成
- 柔軟で高速
Q4: Windows/macOS/Linuxすべてで使える?
A: はい、クロスプラットフォームです。
すべてのOSで:
- 同じCMakeLists.txt
- 同じNinjaコマンド
- プラットフォーム差異はCMakeが吸収
Q5: 既存のIDEと統合できる?
A: はい、多くのIDEがサポートしています。
対応IDE:
- Visual Studio Code(CMake Tools拡張)
- CLion(JetBrains)
- Qt Creator
- Visual Studio 2019以降
設定例(VS Code):
{
"cmake.generator": "Ninja",
"cmake.buildDirectory": "${workspaceFolder}/build"
}
まとめ
Ninjaは、ビルド速度に特化した小規模で高速なビルドシステムで、特にCMakeと組み合わせることで、大規模プロジェクトのビルド時間を劇的に短縮できる強力なツールです。
この記事のポイント:
- Ninjaはビルド速度に特化したMake代替ツール
- 大規模プロジェクトで約3倍の高速化が可能
- シンプルな設計で効率的な並列ビルド
- build.ninjaは手書きではなく生成するべき
- CMakeとの組み合わせが最強
- すべてのOSで利用可能
- 並列ビルドの最適化が容易
- ccacheと組み合わせてさらに高速化
- 既存のIDEと統合可能
- 学習コストは中程度
Ninjaは、特に大規模なC/C++プロジェクトで威力を発揮します。従来のMakeと比べて、ビルド時間を大幅に短縮できるため、開発効率が劇的に向上します。
CMakeを使っている場合は、-G Ninjaオプションを追加するだけで簡単にNinjaに移行できます。まずは試しに使ってみて、ビルド時間の違いを体感してみてください。
特に、増分ビルドの速さには驚くはずです。1つのファイルを変更して再ビルドする際、Makeでは数秒かかっていたものが、Ninjaでは1秒以下になることも珍しくありません。
Ninjaをマスターして、快適で高速な開発環境を実現していきましょう!

コメント