注:本文來自網絡資料整理,主要是彙總學習資料,方便完成FlutterEngine編譯提供一個完整的學習資料路線
node
Ninja 是一個構建系統,與 Make 相似。做爲輸入,你須要描述將源文件處理爲目標文件這一過程所需的命令
。 Ninja 使用這些命令保持目標處於最新狀態。與其它一些構建系統不一樣,Ninja 的主要設計目標是速度。 Ninja 也有其它一些設計目標:Ninja 必須易於嵌入大型構建系統。 Ninja 得到了至關的成功,逐漸取代了所使用的構建系統。Ninja 公開後,一些人貢獻了代碼,使得流行的 CMake 構建系統可以生成 Ninja 文件。如今,Ninja 也被用來開發基於 CMake 的系統,如 LLVM 和 ReactOS。其它一些擁有定製構建系統的項目,如 TextMate,直接將 Ninja 做爲其構建目標。python
在高層視角下,任何構建系統主要執行三項任務。linux
Ninjagit
將絕大部分工做推給了構建文件生成器,那本身還有什麼事呢?上述想法理論看上很不錯,但真實世界的須要永遠更復雜。Ninja 在開發過程當中添加(也失去)了不少特性。不論什麼時候,重要的問題都是「咱們能作得更少嗎?」此處概述這如何運做。 在構建規則出錯時須要人去調試(構建)文件,因此 .ninja 構建文件是普通文本,與 Makefiles 相似。爲了加強可讀性.ninja 也支持一些抽象。算法
第一種抽象是「rule」shell
能夠表明單個命令行調用,rule 定義後在不一樣的構建步驟間共享。這是 Ninja 語法的一個例子,聲明瞭一條名爲「compile」的 rule——這條 rule 會調用 gcc 編譯器,此外還有兩條 build 語句對特定文件使用了 compile。數據庫
rule compile
command = gcc -Wall -c $in -o $out
build out/foo.o: compile src/foo.c
build out/bar.o: compile src/bar.c
複製代碼
第二種抽象是變量。編程
在上面的例子中,那些以」"爲前綴的標識符就是變量("爲前綴的標識符就是變量(in 和 $out)。變量便可以表示命令的輸入,也能夠表示命令的輸出,也能夠給長字符串起一個短點的名字。這裏有一個compile定義,用一個變量表示編譯器的標誌:數組
cflags = -Wall
rule compile
command = gcc $cflags -c $in -o $out
複製代碼
一條規則中使用的 變量 能夠在單個 build 塊中被縮進表述的新定義覆蓋。繼續上面的例子,cflags 的值能夠在單個文件處調整:瀏覽器
build out/file_with_extra_flags.o: compile src/baz.c
cflags = -Wall -Wextra
複製代碼
rule 與函數很像,並且變量又酷似參數。這兩個簡單的功能使 Ninja 的語法與編程語言過於類似,這很危險——是「不作多餘事」的對立面。但他們能夠減小重複字符串,這不只對人很是有用,也有利於電腦計算,由於減小了須要分析的文本量。
構建文件,一旦完成分析,就能夠描繪出一幅依賴圖:最終的二進制輸出依賴於一組對象文件,這組對象文件中的每一個都是編譯源文件的結果。特別地,這是一幅二分圖(bipartite graph),「結點」(輸入文件)指向「邊」(構建指令),構建指令再指向「結點」(輸出文件)[6]。構建過程就是遍歷這幅圖。
給定一個構建目標,Ninja 首先遍歷這幅圖以肯定每條「邊」上輸入文件的狀態:即,輸入文件是否存在,以及被修改的時間。Ninja 隨即計算出一份計劃。計劃便是爲了保證最終目標處於最新狀態而必須執行的「邊」的集合,依據中間文件的修改時間判斷。最後,執行計劃,遍歷圖並將「邊」標記爲已執行,至此順利結束。
優化 Ninja
Ninja 最初的實現仔細地組織了數據結構,爲快速構建創造條件。但從優化的角度來講這並非個聰明的想法。在程序完成之際,我想到,一個分析器(profiler)能夠揭示哪些代碼對性能產生重要影響。
這些年來,分析(profiling)的結果指向過程序的不一樣的區域。有時是單個熱點程序,能夠微優化(micro-optimized)。更多時候,分析會指向一些更普遍的問題,如,除非必要,不要分配或複製內存。也存在某些情型,採用更好的表示方法或數據結構能夠得到最好的效果。接下來是對 Ninja 的實現的簡單表述,以及圍繞 Ninja 性能的有趣故事。
解析(Parsing)
起初,Ninja 使用的是手寫的詞法分析器和遞歸降低分析器。我覺得語法足夠簡單了。事實證實,對於像 這樣夠大的項目僅僅是解析構建文件(拓展名以 .ninja 結尾)所消耗的時間都十分使人吃。
很快,最初用來解析單個字符的函數很快出如今分析結果中:
static bool IsIdentifierCharacter(char c) {
return
('a' <= c && c <= 'z') ||
('A' <= c && c <= 'Z') ||
// and so on...
}
``
一個簡單的調整就能夠節省 200 毫秒——用一個有 256 個條目、以輸入字符爲索引的查找表替換這個函數。這樣一張表用 Python 代碼很好生成,像這樣:
```shell
cs = set()
for c in string.ascii_letters + string.digits + r'+,-./\_$':
cs.add(ord(c))
for i in range(256):
print '%d,' % (i in cs),
複製代碼
這個技巧使 Ninja 在至關一段時間裏保證了運行速度。最終咱們轉移到了更正式的工具:re2c,PHP所用的詞法分析器(lexer)生成工具。它能夠生成更復雜的查找表,和人沒法理解的代碼。例如:
if (yych <= 'b') {
if (yych == '`') goto yy24;
if (yych <= 'a') goto yy21;
// and so on...
複製代碼
當初採用文本格式做爲輸入格式是不是一個好主意?這一點依然不明確。或許最終咱們會採用某種機器友好的格式做爲 Ninja 輸入文件的格式,這也許能夠避免絕大部分解析工做。
規範化(Canonicalization)
Ninja 避免用字符串識別路徑。取而代之的是,Ninja 將它遇到的每一個路徑映射到惟一的 Node 對象,在後續代碼中以 Node 對象表示這一路徑。複用 Node 對象保證了一個給定的路徑只在硬盤上檢查一次,檢查的結果(例如,修改時間)可在其它代碼中複用。
指向 Node 對象的指針如同這一路徑的惟一標識。若是想要測試兩個 Node 是否指向同一路徑,比較指針就足夠了,不須要進行昂貴的字符串比較。例如,在 Ninja 遍歷輸入文件構成的圖時,會保存一個 Node 依賴棧,以檢查依賴是否有環:若是 A 依賴 B,B依賴 C,而 C 又依賴 A。構建就沒法進行。這個棧,表明一組文件,能夠經過一個指針數組實現,可使用指針相等性判斷檢查是否有重複。
爲了保證指向一個文件的路徑老是指向同一 Node,Ninja 必須可靠地將一個文件全部可能的名字映射到同一 Node 對象。這須要對輸入文件中提到的全部文件進行規範化(canonicalization),將像 foo/../bar.h 這樣的路徑轉換爲 bar.h。最初,Ninja 只是簡單地要求全部路徑以規範的形式給出,但因爲幾個緣由,這最後仍是不行。一個緣由是用戶指定的路徑(例如,在命令行輸入 ninja ./bar.h )應該能正確工做。另外一個緣由是變量的組合可能產生出不規範的路徑。最後,gcc 給出的依賴信息可能不規範。
因而,最終,Ninja 對路徑進處理。這也致使路徑功能成能成爲分析結果中的另外一處熱點。原來的實現是以清晰而不是以性能爲重點編寫的,因此標準的優化技術,如移除雙循環(removing a double loop)或避免內存分配,做用顯著。
設計目標
!!! info "這裏是Ninja的設計目標"
* 很是快(即瞬間)增量構建,即便是很是大的項目。
* 代碼如何構建的策略很是少。關於代碼如何構建不一樣的項目和高級構建系統有不一樣的意見;例如,構建目標是應該與代碼放在一塊兒仍是放在單獨的目錄裏?是否有爲項目構建一個可分發的「包」的規則?規避這些策略,而不是選擇,不然只會變得複雜。
* 獲取正確的依賴關係,在某些特定的狀況下Makefiles很難獲取正確的依賴關係(例如:輸出隱式依賴於命令行生成,構建c代碼你須要使用gcc的-M標誌去生成其頭文件依賴)
* 當目標和遍歷衝突時咱們選擇速度.
複製代碼
一些明確的 non-goals:
方便人工編寫構建文件的語法。你應該使用其它程序來生成你的構建文件,這是咱們能夠迴避許多策略決定。
built-in rules. _Out of the box, Ninja has no rules for
e.g. compiling C code._
build-time customization of the build. _Options belong in
the program that generates the ninja files_.
build-time decision-making ability such as conditionals or search
paths. Making decisions is slow.
複製代碼
重申一下,Ninja比其它構建系統要快,由於它是異常的簡單。在你爲你的項目編寫.ninja文件時你必須告訴Ninja要它作什麼。
與Make比較
Ninja的定位很是清晰,就是達到更快的構建速度。
ninja的設計是對於make的缺陷的考慮,認爲make有下面幾點形成編譯速度過慢:
隱式規則,make包含不少默認 變量計算,好比編譯參與應該如何計算出來 依賴對象計算 ninja認爲描述文件應該是這樣的:
依賴必須顯式寫明(爲了方即可以產生依賴描述文件) 沒有任何變量計算 沒有默認規則,沒有任何默認值 針對這點因此基本上能夠認爲ninja就是make的最最精簡版。 ninja相對於make增長了下面這些功能:
若是構建命令發生變化,那麼這個構建也會從新執行。 所依賴的目錄在構建以前都已經建立了,若是不是這樣的話,咱們執行命令以前都要去生成目錄。 每條構建規則,除了執行命令以外,還容許有一個描述,真正執行打印這個描述而不是實際執行命令。 每條規則的輸出都是buffered的,也就是說並行編譯,輸入內容不會被攪和在一塊兒。 構建工具太多了,我我的以爲make主要偏大衆化一點,能夠進行各類隱式推導,比較靈活,每一條命令執行都有輸出。 而Ninja主要的設計目的是爲了像chromium這種大型項目,可以顯著的提升編譯速度,一方面它去掉了各類計算和推導,把一些耗時的須要計算的東西去掉了,只留下簡單重要的部分,因此若是本身去寫build.ninja文件的話比較繁瑣,因此都是依賴於其它構建工具生成的,另外一方面它每次輸出只輸出一個描述,而不是真正的命令執行輸出,真正的命令執行再後臺運行,只有警告和報錯信息纔會顯示出來,這也提升了它的速度。
Make vs Ninja Performance Comparison這篇文章對Make接Ninja進行測試對比。
Ninja 目前在Windows和類Unix系統都支持,雖然大部分測試都是在linux上完成的(而且在Linux上性能也最好),不過在MAC OS X和FreeBSD 上也都能很好的工做。 若是你的項目很小,Ninja的速度優點可能不那麼明顯(然而,即便是小項目,Ninja的極其簡潔的語法和極其簡單的構建規則,也使你的項目可以更加快速的構建)。換一句話說,若是你對你的項目編輯-編譯的循環時間感到滿意,那麼Ninja可能對你不會有太多的幫助。 還有許多其它的構建系統,比Ninja使用更加友好,功能也更增強大。做者以爲Ninja的設計受到了tup構建系統設計的影響,並認爲重作的設計很是聰明。 Ninja的好處是能夠將它與智能的元構建系統結合起來使用。 gyp用於生成Google 和相關項目(v8,node.js)的元構建文件創建系統,已被GN取代。 gyp能夠爲支持的全部平臺生成Ninja文件。 有關詳細信息,請參閱Chromium Ninja文檔。 CMake一個普遍使用的元構建系統,在Linux上CMake 2.8.8版本能夠生成Ninja文件。 較新版本的CMake支持在Windows和Mac OS X上生成Ninja文件。 其餘:Ninja應該完善其餘元構建軟件的支持,例如premake。 若是你在作這項工做,請讓咱們知道!
運行ninja。 默認狀況下,它在當前目錄中查找名爲build.ninja的文件 並構建全部過時目標。 您能夠在命令行參數中指定要構建的目標(文件)。
還有一個特殊的語法,目標^指定的目標做爲第一個輸出(若是存在)。 例如,若是您指定目標 foo.c ^, 那麼foo.o將首先被構建(假設你的構建文件中存在這些目標)。
許多Ninja的flags與Make是匹配的; 例如ninja -C build -j 20改爲構建目錄並並行運行20個構建命令。 (注意 Ninja默認狀況下以並行方式運行命令,因此一般你不須要傳遞-j。) Ninja 默認基於系統中可用的 CPU 數量以併發方式執行指令。由於同時運行的命令們的輸出可能混淆,Ninja 會在一個命令完成前緩存其輸出。從結果看,如同命令是串行的。 這種對命令輸出的控制使得 Ninja 能夠當心控制總的輸出。在構建過程當中 Ninja 顯示一行表示狀態;若是構建順利完成,Ninja 的所有輸出就只有一行。這不會使 Ninja 運行得更快,但可使人感受 Ninja 很快,這幾乎與在真實速度上的目標同樣重要。
Ninja支持用一個環境變量來控制其行爲: NINJA STATUS,在運行規則以前會打印進度狀態。 下面是幾個可用的佔位符:
%s:: The number of started edges.
%t:: The total number of edges that must be run to complete the build.
%p:: The percentage of started edges.
%r:: The number of currently running edges.
%u:: The number of remaining edges to start.
%f:: The number of finished edges.
%o:: Overall rate of finished edges per second
%c:: Current rate of finished edges per second (average over builds
specified by -j or its default)
%e:: Elapsed time in seconds. (Available since Ninja 1.2.)
%%:: A plain % character.
複製代碼
默認進度狀態爲 "[%f/%t] " ( 注意結尾空格以與構建規則分開). 另外一個可能的進度狀態的例子以下: "[%u/%r/%f] ".
在Ninja的開發過程當中,命令行裏使用-t能夠運行一些很是有用的工具,目前有如下一些工具可使用:
query:: dump指定target的輸入和輸出.
browse:: 在Web瀏覽器中瀏覽依賴關係圖。 單擊文件將焦點切到該文件上,會顯示輸入和輸出。 這個 功能須要Python安裝。 默認使用端口8000並打開Web瀏覽器。 能夠按照以下方式修改:
ninja -t browse --port=8000 --no-browser mytarget
複製代碼
graph::以自動圖形佈局工具graphviz的語法輸出一個文件。 使用方式以下:
ninja -t graph mytarget | dot -Tpng -ograph.png
複製代碼
在Ninja源代碼樹中,運行「ninja graph.png」命令將爲Ninja自己生成一張圖。 若是沒有指定目標則將爲 all目標生成。
Ninja和Make很是類似。他執行一個文件之間的依賴圖,經過檢測文件修改時間,運行必要的命令來更新你的構建目標。 一個構建文件(默認文件名爲:build.ninja)提供一個rule(規則)表——長命令的簡短名稱,和運行編譯器的方式。同時,附帶提供build(構建)語句列表,代表經過rule如何構建文件——哪條規則應用於哪一個輸入產生哪個輸出。
從概念上講,build語句描述項目的依賴圖;而rule語句描述當給定一個圖的一條邊時,如何生成文件。
這是一個用於驗證絕大部分語法的.ninja文件,將做爲後續描述相關的示例。具體內容,以下:
cflags = -Wall
rule cc
command = gcc $cflags -c $in -o $out
build foo.o: cc foo.c
複製代碼
ninja支持爲字符串聲明簡短可讀的名字。一個聲明的語法,以下:
cflags = -g
能夠在=右邊使用,並經過$進行引用(相似shell和perl的語法)。具體形式,以下:
rule cc
command = gcc $cflags -c $in -o $out
複製代碼
變量還能夠用和成對的大括號)來引用。 當給定變量的值不能被修改,只能覆蓋(shadowed)時,變量更恰當的叫法是綁定(」bindings」)。
規則爲命令行聲明一個簡短的名稱。他們由關鍵字rule和一個規則名稱打頭的行開始,而後緊跟着一組帶縮進格式的 variable = value行組成。 以上示例中聲明瞭一個名爲cc的rule,連同一個待運行的命令。在rule(規則)上下文中,command變量用於定義待執行的命令,out爲命令的輸出文件列表(foo.o)。 參考手冊中羅列了全部特殊的變量。
build語句聲明輸入和輸出文件之間的一個關係。構建語句由關鍵字build開頭,格式爲
build outputs: rulename inputs 1 這樣的一個聲明,全部的輸出文件來源於輸入文件。當缺輸出文件或輸入文件變動時,Ninja將會運行此規則來從新生成輸出。 以上的簡單示例,描述了使用cc規則如何構建foo.o文件。 在build block範圍內(包括相關規則的執行),變量out表示輸出列表。 一個構建語句,能夠和rule同樣,緊跟一組帶縮進格式的key = value對。當在命令中變量執行時,這些變量將覆蓋(shadow)任何變量。好比:
cflags = -Wall -Werror
rule cc
command = gcc $cflags -c $in -o $out
# 若是沒有指定,build的輸出將是$cflags
build foo.o: cc foo.c
# 可是,你能夠在特殊的build中覆蓋cflags這樣的變量
build special.o: cc special.c
cflags = -Wall
# cflags變量僅僅覆蓋了special.o的範圍
# 如下的子序列build行獲得的是外部的(原始的)cflags
build bar.o: cc bar.c
複製代碼
若是你要從build語句傳遞更多的信息到rule規則(例如,若是規則須要知道」第一輸入文件的擴展名」),那麼請經過擴展變量傳遞,就像cflags同樣。
若是頂級Ninja文件使用build指定了任何輸出,而且它又過時了,那麼再爲構建用戶目標以前會先構建頂級文件裏的目標。
根據代碼生成Ninja文件
Ninja發行包中的misc/ninja_syntax.py是一個很小的python模塊,用於生成Ninja文件。你可使用python,執行如
ninja.rule(name='foo', command='bar',depfile='$out.d')
的調用,生成合適的語法。若是這樣還不錯,能夠將其整合到你的項目中。
phony 規則 可使用phony建立其它target(編譯構建目標)的別名。好比:
build foo: phony some/file/in/a/faraway/subdir/foo
這樣使得ninja foo構建更長的路徑。從語義上講,phony規則等同於一個沒有作任何操做的普通規則,可是phony規則經過特殊的方式進行處理,這樣當其運行時不會被打印,記日誌,也不做爲構建過程當中打印出來的命令計數。 還能夠用phony爲構建時可能還不存在的文件建立dummy目標。
默認目標 默認狀況下,若是沒有在命令行中指定target,那麼Ninja將構建任何地方沒有做爲輸入命名的每個輸出。能夠經過default目標語句來重寫這個行爲。一個default語句,讓Ninja構建一個給定的輸出文件子集,若是命令行中沒有指定構建目標。 默認目標語句,由關鍵字default打頭,而且採用default targets的格式。一個default目標語句必須出如今,聲明這個目標做爲一個輸出文件的構建語句以後。他們是累積的(cumulative),因此可使用多個default語句來擴展默認目標列表。好比:
default foo bar default baz
This causes Ninja to build the foo, bar and baz targets by default.
Ninja構建日誌保存在構建過程的根目錄或.ninja文件中builddir變量對應的目錄的.ninja_log文件中。 通常而言,像上面這樣的微優化不如改變算法或處理方式的結構性優化有效。Ninja 的構建日誌就是這樣一個例子。 Linux kernel 構建系統的一部分會追蹤用於生成輸出的命令。考慮一個啓發性的例子:你將輸入文件 foo.c 編譯爲輸出文件 foo.o,隨後修改了構建文件致使應該用不一樣的編譯選項從新編譯 foo.c。從構建系統的角度看,爲了感知須要構建,必需要麼注意到 foo.o 依賴於構建文件(構建文件依賴於項目的組織,這也許意味着對構建文件的修改將致使整個項目的從新構建),或記錄生成每一個輸出的命令,在每次構建時進行比較。 kernel(以及 Makefiles 和 Ninja)採用後一種方法。在構建時,Ninja 寫下一份構建日誌,記錄生成每一個輸出的完整命令。[9]在後續構建中,Ninja 載入以前的構建日誌,經過比較當前命令與構建日誌中的命令來發現變動。就像加載構建文件或路徑規範化,這成爲了分析結果中的又一處熱點。 在進行了一些小優化後,Nico Weber,一個對 Ninja 貢獻了不少代碼的人,實現一種新的構建日誌格式。比起一般很長且須要大量時間進行解析的記錄命令,Ninja 取而代之以命令的哈希(hash)。在後續構建中,Ninja 比較將要執行的明令的哈希與記錄中的哈希。若是二者不一樣,則能夠肯定輸出已過時。這一方法很成功。使用哈希急劇下降了構建日誌的大小——在 Mac OX X 上,從 200MB 降到 2MB——並使加載速度快了 20 倍。
Available since Ninja 1.2.
Ninja version labels follow the standard major.minor.patch format,
where the major version is increased on backwards-incompatible
syntax/behavioral changes and the minor version is increased on new
behaviors. Your build.ninja may declare a 變量 named
ninja_required_version that asserts the minimum Ninja version
required to use the generated file. For example,
ninja_required_version = 1.1
1
declares that the build file relies on some feature that was
introduced in Ninja 1.1 (perhaps the pool syntax), and that
Ninja 1.1 or greater must be used to build. Unlike other Ninja
變量s, this version requirement is checked immediately when
the 變量 is encountered in parsing, so it’s best to put it
at the top of the build file.
Ninja always warns if the major versions of Ninja and the
ninja_required_version don’t match; a major version change hasn’t
come up yet so it’s difficult to predict what behavior might be
required.
複製代碼
還有另外一種元數據(metadata)必需跨構建保存用。爲了正確構建 C/C++ 代碼,一個構建系統必需能感知頭文件間的依賴。假定 foo.c 包含一行 #inclue 「bar.h」 。而 bar.h 自身又包含一行 #include 「bar.h」。全部的三個文件都會影響後續編譯。例如,baz.h 的改變也會觸發 foo.o 的從新構建。
一些構建系統使用一個「頭文件掃描器」在構建時提取這部分依賴信息。但這個方法太慢,並且很難精確處理有 #ifdef 指令出現的情形。另外一種選擇是要求構建文件正確地報告全部依賴,包括頭文件的依賴,但這對開發人員來講十分笨重:每次你添加或刪除 #include 語句時,都須要修改或從新生成構建文件。
一個有用的方法依賴於這樣的事實:在編譯時,gcc (以及微軟的 Visual Studio)能夠給出在構建輸出時用到了哪些頭文件。這份信息,如同用於生成輸出的信息,能夠被構建系統記錄和加載。由此,依賴能夠被精確追蹤。在第一次編譯時,由於還未有輸出,全部文件都會被編譯,故不需頭文件依賴。第一次編譯後,對於被某個輸出用到的任何文件若是發生更改(包括增長或刪除額外的依賴),就會致使從新構建。這保證了依賴信息的更新。
在編譯時,gcc 以 Makefile 的格式記下頭文件依賴。Ninja 包括一個解析器處理這一Makefile 語法(的簡化子集),並在下一次構建時載入這份依賴信息。在 的最近一次構建,gcc 產生了共 90MB 的 Makefile,所有帶有必須規範化的引用路徑。
就像其它解析過程,經過使用 re2c 及儘量地避免複製可使性能有所提高,但就像 GYP 項目,這一解析工做能夠不在關鍵時間路徑上完成。近期,咱們在 Ninja 上的工做(在寫做本文時,這一工能已經完成,但還未發佈)是讓這一過程發生的早一些。
一旦 Ninja 開始執行構建指令,全部影響性能的工做都已完成,Ninja 在等待它啓動的命令完成的過程當中近乎閒置。在處理頭文件依賴的新方法中,Ninja 利用這段時間處理 gcc 給出的 Makefile ,規範化路徑,將依賴處理爲一種能夠快速識別的二進制格式。在下一次構建中,Ninja 只須要加載這一文件。改進很是劇烈,特別是在 Windows 上。(本章稍後討論這個)
「依賴日誌」須要儲存上千條路徑及路徑間的依賴。載入日誌和追加日誌都必須迅速。追加日誌操做應該是安全的,即便被打斷,好比構建被取消。
在考慮了一些相似於數據庫的方案後,我最終想到了一個簡單的實現:文件由記錄的序列組成,而記錄要麼是一個路徑,要麼是一個依賴列表。每一個寫入文件的路徑都被賦於了一個整數序列號。故而依賴就是一列整數。爲了向文件添加依賴,Ninja 首先記錄下尚未序列號的路徑,而後用這些序列號記錄依賴。在後續的構建載入這一文件時,Ninja 能夠簡單地使用一個數組將序列號映射到對應的 Node 指針。
Ninja目前支持depfile和deps模式的C/C++頭文件依賴生成。 如
rule cc
depfile = $out.d
command = gcc -MMD -MF $out.d [other gcc flags here]
複製代碼
-MMD標識告訴gcc要生成頭文件依賴,-MF則說明要寫到哪裏。 deps按照編譯器的名詞來管理。具體以下:(針對微軟的VC:msvc)
rule cc
deps = msvc
command = cl /showIncludes -c $in /Fo$out
複製代碼
爲了支持併發做業,Ninja還支持pool的機制(和用-j並行模式同樣)。此處不詳細描述了。具體示例,以下:
# No more than 4 links at a time.
pool link_pool
depth = 4
# No more than 1 heavy object at a time.
pool heavy_object_pool
depth = 1
rule link
...
pool = link_pool
rule cc
...
# The link_pool is used here. Only 4 links will run concurrently.
build foo.exe: link input.obj
# A build statement can be exempted from its rule's pool by setting an
# empty pool. This effectively puts the build statement back into the default
# pool, which has infinite depth.
build other.exe: link input.obj
pool =
# A build statement can specify a pool directly.
# Only one of these builds will run at a time.
build heavy_object1.obj: cc heavy_obj1.cc
pool = heavy_object_pool
build heavy_object2.obj: cc heavy_obj2.cc
pool = heavy_object_pool
複製代碼
這裏有一個名爲console深度爲1的預約義池,池中的任何任務均可以直接訪問標準輸入、輸出和錯誤流並提供給Ninja,一般是鏈接到用戶的控制檯。這對於交互式任務或運行時間較長的任務比較有用。能夠在控制檯上更新狀態(例如測試套件)。 當’console’池中的任務正在運行時,Ninja的正常輸出(如進度狀態和併發任務的輸出)將被緩衝起來直到控制檯任務運行完成。
一個Ninja文件是一系列聲明,聲明能夠是下列之一:
規則聲明,以rulename開頭,而後是一些列的變量的定義;
A build edge, which looks like +build output1 output2:
rulename input1 input2+. +
Implicit dependencies may be tacked on the end with +|
dependency1 dependency2+. +
Order-only dependencies may be tacked on the end with +||
dependency1 dependency2+. (See <
複製代碼
Ninja僅支持ASCII字符集。 註釋覺得#開始一直到行末。
新行是很重要的。像build foo bar的語句,是一堆空格分割分詞(token),到換行結束。一個分詞中的新行和空格必須進行轉譯。目前只有一個轉譯字符,$,其具備如下行爲:
$ followed by a newline
轉譯換行,讓當前行一直擴展到下一行。
$ followed by text
這是, 變量引用。
${varname}
這是,另$varname的另外一種語法。
$ followed by space
這表示一個空格。(僅在path列表中,須要用空格分割文件名)
這表示一個冒號。(僅在build行中須要。此時冒號終止輸出列表)
這個表示,字面值的$。
一個build或default語句,最早被解析,做爲一個空格分割的文件名列表,而後每個name都被展開。也就是說,變量中的一個空格將做爲被展開後文件名中的一個空格。
spaced = foo bar
build $spaced/baz other$ file: ...
在一個name = value語句中,value前的空白都會被去掉。出現跨行時,後續行起始的空白也會被去掉。
two_words_with_one_space = foo $
bar
one_word_with_no_space = foo$
bar
複製代碼
其餘的空白,僅位於行開始處的很重要。若是一行的縮進比前一行多,那麼被人爲是其父邊界的一部分。若是縮進比前一行少,那他就關閉前一個邊界。
Ninja支持的頂層變量有builddir和ninja_required_version。具體說明,以下:
builddir: 構建的一些輸出文件的存放目錄。 ninja_required_version:指定知足構建需求的最小Ninja版本。 Rule 變量 一個rule塊包含一個key = value的列表聲明,這直接影響規則的處理。如下是一些特殊的key:
command (required):
待執行的命令。這個字符串 $variables被展開以後,被直接傳遞給sh -c,不通過Ninja翻譯。每個規則只能包含一條command聲明。若是有多條命令,須要使用&&符號進行連接。
depfile: 指向一個可選的Makefile,其中包含額外的隱式依賴。這個明確的爲了支持C/C++的頭文件依賴。
deps: (1.3版本開始支持)若是存在,必須是gcc或msvc,來指定特殊的依賴。產生的數據庫保存在builddir指定目錄.ninja_deps文件中。
msvc_deps_prefix: (1.5版本開始支持)定義必須從msvc的/showIncludes輸出中去掉的字符串。僅在deps = msvc並且使用非英語的Visual Studio版本時使用。
description: 命令的簡短描述,做爲命令運行時更好的打印輸出。打印整行仍是對應的描述,由-v標記控制。若是一個命令執行失敗,整個命令行老是在命令輸出以前打印。
generator: 若是存在,指明這條規則是用來重複調用生成器程序。經過兩種特殊的方式,處理使用生成器規則構建文件:首先,若是命令行修改了,他們不會從新構建;其次,默認不會被清除。
in: 空格分割的文件列表被做爲一個輸入傳遞給引用此rule的構建行,若是出如今命令中須要使用${in}(shell-quoted)。(提供$in僅僅爲了圖個方便,若是你須要文件列表的子集或變種,請構建一個新的變量,而後傳遞新的變量。)
in_newline: 和$in同樣,只是分割符爲換行而不是空格。(僅爲了和$rspfile_content一塊兒使用,解決MSVC - linker使用固定大小的緩衝區處理輸入,而形成的一個bug。)
out: 空格分割的文件列表被做爲一個輸出傳遞給引用此rule的構建行,若是出如今命令中須要使用${out};
restat: 若是存在,引起Ninja在命令行執行完以後,從新統計命令的輸出。
rspfile, rspfile_content: 若是存在(兩個同時),Ninja將爲給定命令提供一個響應文件,好比,在調用命令以前將選定的字符串(rspfile_content)寫到給定的文件(rspfile),命令執行成功以後闡述文件。
這個在Windows系統很是有用,由於此時命令行的最大長度很是受限,必須使用響應文件替代。具體使用方式,以下:
rule link
command = link.exe /OUT$out [usual link flags here] @$out.rsp
rspfile = $out.rsp
rspfile_content = $in
build myapp.exe: link a.obj b.obj [possibly many other .obj files]
複製代碼
在Unixes和Windows上命令行的行爲是不一樣的。
在Unixes上,命令是參數數組。 Ninja命令變量直接傳遞給sh -c,而後負責 將該字符串解釋爲argv數組。 所以引用規則由shell決定,你可使用全部正常的shell 運算符,如連接多個命令,或VAR = value cmd 來設置環境變量。
在Windows上,命令是字符串,所以Ninja直接將command字符串 傳遞給CreateProcess。 (在常見狀況下編譯器簡單執行這意味着有更少的開銷。)所以引用規則由被調用的程序肯定,在Windows上一般由C庫提供。 若是你須要shell解釋命令(如使用&&來連接多個命令),使命令執行Windows shell前綴命令與cmd / c。
有兩種稍微有點區別的輸出:
顯示輸出, 在build行會列出來,在rule規則中能夠經過$out變量訪問。 這是標準的輸出使用形式,例如一個編譯命令的目標文件。
隱式輸出, 在build行其語法格式以下,在build行的:前out1 out2(在Ninja1.7版本開始支持).語義與顯式輸出相同,惟一的區別是隱式輸出不會出如今$out變量裏。這是爲了表示在命令行中沒有指定輸出的命令。
Ninja目前支持3種類型的構建依賴。分別是:
羅列在build行中的顯式的依賴。他們能夠做爲規則中的in中使用(不可見)。 經過構建行末|| dep1 dep2語法表示的次序惟一(Order-only)依賴。他們過時的時候,輸出不會被從新構建,直到他們被重建,但僅修改這種依賴不會引起輸出重建。 變量展開 變量在路徑(在build或default語句)和name = value右邊被展開。 當name = value語句被執行,右手邊的被當即展開(根據如下的規則),今後
foo"。
rule demo
command = echo "this is a demo of $foo"
build out: demo
foo = bar
複製代碼
頂層(Top-level)變量聲明的邊界,是相關的文件。 subninja關鍵自,用於包含另外一個.ninja文件,其表示新的邊界。被包含的subninja文件可使用父文件中的變量,在文件邊界中覆蓋他們的值,可是這不影響父文件中變量的值。 同時,能夠用#include語句在當前邊界內,引入另外一個.ninja文件。這個有點像C中的#include語句。 構建塊中聲明的變量的邊界,就是其所屬的塊。一個構建塊中展開的變量的全部查詢次序爲:
特殊內建變量(out); build/rule塊中構建層的變量; 構建行所在文件中的文件層變量(File-level); 使用subninja關鍵字引入那個文件的(父)文件中的變量。
在上一篇GN
構建系統中主要介紹了GN
是一個構建編譯文件相關依賴和配置參數的工具,主要是靜態的生產Ninja
須要的構建文件 Ninja
主要是分析文件的內部內容,和分析文件內容的異常狀況和依賴,更加關注系統和編譯環境的配置和出來
GN
:一個靜態文件依賴關係和系統
,CPU
,工具鏈
相關的依賴關係(靜態關係
) Ninja
:在GN生成的文件做爲基礎,出來開發過程當中文件的變更,源代碼進行檢查,優化,最終生成可執行文件
note: ·