1. 介紹
開篇先介紹、先甩資料給大家看,之后再自己演示一下基本使用。Ninja 是Google的一名程序員推出的注重速度的構建工具,一般在Unix/Linux上的程序通過make/makefile來構建編譯,而Ninja通過將編譯任務並行組織,大大提高了構建速度。
Github:github.com/ninja-build/ninja
2. 參考資料
《The Performance Of Open Source Application》第三章
3. 使用
3.1. cmake生成
一般是通過cmake來生成ninja的配置,進而進行編譯。先從cmake-examples入門:github.com/ttroy50/cmake-examples
比如01-basic\B-hello-headers項目,運行指令:cmake -Bbuild -GNinja 即可生成ninja工程。
運行ninja編譯:
3.2. 手動寫ninja配置文件
本文重點演示一下手寫ninja配置文件方法,Demo工程結構:
./build.ninja
./src/jfz.cpp
其中jfz.cpp:
#include "Hello.h"
int main(int argc, char *argv[])
{
printf("sandeepin poi!");
return 0;
}
build.ninja(注意結尾要有空行):
# 指定ninja最小需要版本
ninja_required_version = 1.5
# 變量
GCC = D:\Library\MinGW\bin\g++.exe
cflags = -Wall
# 編譯規則,指定depfile,可以用於生成ninja_deps文件
rule compile_jfz
command = $GCC -c $cflags -MD -MF $out.d $in -o $out
description = 編譯 $in 成為 $out
depfile = $out.d
deps = gcc
build jfz.o : compile_jfz src/jfz.c
# 鏈接規則
rule link_jfz
command = $GCC $DEFINES $INCLUDES $cflags $in -o $out
description = 鏈接 $in 成為 $out
build jfz.exe : link_jfz jfz.o
# 編譯all,就是做任務build jfz.exe
build all: phony jfz.exe
# 默認編譯什么(單獨運行ninja)
default all
運行效果:
第一次運行按任務先編譯,再鏈接,最終產生了可執行文件,第二次運行由於沒改文件,ninja不處理。ninja支持如下參數:
--version # 打印版本信息
-v # 顯示構建中的所有命令行(這個對實際構建的命令核對非常有用)
-C DIR # 在執行操作之前,切換到`DIR`目錄
-f FILE # 制定`FILE`為構建輸入文件。默認文件為當前目錄下的`build.ninja`。如 ./ninja -f demo.ninja
-j N # 並行執行 N 個作業。默認N=3(需要對應的CPU支持)。如 ./ninja -j 2 all
-k N # 持續構建直到N個作業失敗為止。默認N=1
-l N # 如果平均負載大於N,不啟動新的作業
-n # 排練(dry run)(不執行命令,視其成功執行。如 ./ninja -n -t clean)
-d MODE # 開啟調試模式 (用 -d list 羅列所有的模式)
-t TOOL # 執行一個子工具(用 -t list 羅列所有子命令工具)。如 ./ninja -t query all
-w FLAG # 控制告警級別
ninja -d list相關:
debugging modes:
stats print operation counts/timing info 打印統計信息
explain explain what caused a command to execute 解釋導致命令執行的原因
keepdepfile don't delete depfiles after they're read by ninja 讀取depfile后,不刪除它
keeprsp don't delete @response files on success 讀取@response后,不刪除它
nostatcache don't batch stat() calls per directory and cache them 不對每個目錄批量處理stat()調用和緩存它們
multiple modes can be enabled via -d FOO -d BAR 多模式調用可以接着幾個-d
ninja -w list相關,主要指定幾種情況下告警級別是多少:
warning flags:
dupbuild={err,warn} multiple build lines for one target
phonycycle={err,warn} phony build statement references itself
depfilemulti={err,warn} depfile has multiple output paths on separate lines
ninja -t list相關,主要集成了graphviz等一些對開發非常有用的工具。
ninja subtools:
browse # 在瀏覽器中瀏覽依賴關系圖。(默認會在8080端口啟動一個基於python的http服務)
clean # 清除構建生成的文件
commands # 羅列重新構建制定目標所需的所有命令
deps # 顯示存儲在deps日志中的依賴關系
graph # 為指定目標生成 graphviz dot 文件。如 ninja -t graph all |dot -Tpng -o graph.png
query # 顯示一個路徑的inputs/outputs
targets # 通過DAG中rule或depth羅列target
compdb # dump JSON兼容的數據庫到標准輸出
recompact # 重新緊湊化ninja內部數據結構
這里主要列舉幾種參數執行效果:
-n是假執行,實際未產生文件,由於假執行,keepdepfile沒起到效果,這個受限於編譯器分析依賴,下面的統計信息就是stats效果,explain解釋了為什么執行這些任務。
這里-v打印每個任務執行了哪些指令,可見到keepdepfile生效了,保存了依賴.d文件。
ninja工具舉例:
1、顯示依賴
2、顯示執行指令
3、顯示目標
4、繪依賴圖(要安裝graphviz,直接打印出dot文本)
轉圖片(支持png、svg等,大圖推薦svg渲染,相關dot參數見graphviz文檔):
ninja -t graph | dot -Tpng -o jfz.png
這個demo比較簡單,實際上依賴分析功能需要編譯器提供,或者任務自己輸出依賴文件,ninja只做一個任務編排和執行功能。
4. 信息補充
4.1. 環境變量
通過環境變量NINJA_STATUS可以控制ninja打印進度狀態的樣式,有幾個占位符:
%s 起始edges的數量。
%t 完成構建必須運行的edges總數。
%p 起始edges的百分比。
%r 當前運行的edges數。
%u 要開始的剩余edges數。
%f 完成的edges數。
%o 每秒完成edges的總速率
%c 當前每秒完成edges的速率(由-j或其默認值指定的構建的平均值)
%e 經過的時間(以秒為單位)。(自Ninja 1.2起可用。)
%% 一個普通的%字符。
默認進度狀態為"[%f/%t] "(請注意尾隨空格以與構建規則分開)。可能的進度狀態的另一個示例可能是"[%u/%r/%f] "。
嘗試改為export NINJA_STATUS="[%p/%f/%t %e] "(Windows下set NINJA_STATUS="[%p/%f/%t %e] ")的效果如下:
4.2. ninja_log每項含義
依次為:開始時間、結束時間、mtime、output文件路徑名、命令行hash。
其中mtime是輸入文件們的最后修改時間的時間戳算出來的值,經測試,開始時間、結束時間、命令行hash均不會影響增量的判定。
4.3. mtime檢查文件測試
假設現在時間是2019-12-31 15:35:55,將輸入.c文件修改時間改為之前的,不會觸發重新編譯;將輸入.c文件修改時間改為未來的,每次都觸發編譯。
4.4. frontend_file參數
特別的,AOSP定制版的ninja有frontend_file參數,可以將控制台輸出信息轉為流存儲,通過其它的工具如tail -f xxx查看信息。soong源碼中就是這樣讀取ninja日志的,效率更高,之前應該是靠cat捕獲日志的吧。見這個提交。
4.5. ninja檢測的是任務名的文件是否生成
如果我讓輸出文件和任務名不一樣,ninja每次都會重新編譯:
這點要注意,為了利用ninja的增量特性,除非迫不得已,不要讓輸出文件和任務名不同。
源碼編譯
本文僅嘗試官方版的編譯,AOSP版本可以依賴不同,GCC版本要求不同,需要注意。