Fuchsia 建構系統使用自訂 Ninja 二進位檔,可為開發人員體驗帶來多項改善。本頁面將說明這些功能。
提振精神
如要瞭解自訂 Fuchsia 專用 Ninja 的動機,請參閱 RFC-0153。
簡而言之,上游版本難以提供多項功能,但這些功能對 Fuchsia 開發人員來說非常實用。
所有 Fuchsia 專屬變更都會在fuchsia-rfc-0153本機 Ninja git 鏡像的分支版本上執行,並定期重新設定基準,方便以 GitHub 提取要求的形式傳送至上游專案,如 RFC 的「策略」一節所述。
功能:正在執行的指令狀態
在環境中將 NINJA_STATUS_MAX_COMMANDS 設為嚴格正整數,讓 Ninja 在智慧終端機中執行時,在狀態列下方列印執行時間最長的指令表格,以及這些指令的經過時間。舉例來說,使用 export NINJA_STATUS_MAX_COMMANDS=4 時,狀態可能如下所示:
[0/28477](260) STAMP host_x64/obj/tools/configc/configc_sdk_meta_generated_file.stamp
0.4s | STAMP obj/sdk/zircon_sysroot_meta_verify.stamp
0.4s | CXX obj/BUILD_DIR/fidling/gen/sdk/fidl/fuchsia.me...chsia.media/cpp/fuchsia.media_cpp_common.common_types.cc.o
0.4s | CXX obj/BUILD_DIR/fidling/gen/sdk/fidl/fuchsia.me...fuchsia.media/cpp/fuchsia.media_cpp.natural_messaging.cc.o
0.4s | CXX obj/BUILD_DIR/fidling/gen/sdk/fidl/fuchsia.me...dia/cpp/fuchsia.media_cpp_natural_types.natural_types.cc.o
以下動畫圖片顯示實際情況:

請注意:
在 Ninja 的試執行或詳細叫用 (即使用
-n或--verbose旗標) 中,這項功能會自動停用。如果 Ninja 未在互動式 / 智慧終端機中執行,這項功能就會自動停用。
執行控制台指令時,這項功能也會暫停 (如上例所示,執行 Bazel 動作時會顯示)。
這項功能可讓您輕鬆找出建構作業的瓶頸,也就是長時間執行的指令,導致其他指令無法平行執行。
預設情況下,指令表每秒會更新 10 次,這有助於瞭解哪些長時間執行的指令會拖慢建構速度。您可以將 NINJA_STATUS_REFRESH_MILLIS 設為毫秒十進位值,變更重新整理週期 (請注意,系統會忽略低於 100 的值,因為經過的時間只會列印到單一十進位分數位數)。
功能:支援 GNU Make Jobserver
GNU Make Jobserver 通訊協定可讓建構系統在任何時間點限制並行工作 (即執行緒或程序) 的總數,即使存在遞迴建構工具叫用也是如此。
這需要頂層 server 設定 job slots 集區,供參與的用戶端 (例如編譯器、連結器,甚至是建構工具) 共用。
Fuchsia 專屬的 Ninja 二進位檔可做為通訊協定的用戶端或伺服器。
啟動 Ninja 時,您可以透過 --jobserver 指令列旗標啟用伺服器模式,或在環境中設定 NINJA_JOBSERVER=1。
Ninja 啟動時,系統會查看 MAKEFLAGS 環境變數的值,自動啟用用戶端模式。如果 Ninja 是從做為伺服器的其他建構作業呼叫,這項功能就相當實用。
在 Fuchsia 建構作業中,於 args.gn 中設定 enable_jobserver = true,即可在伺服器模式中啟動頂層 Ninja 呼叫。
舉例來說,這項設定適用於核心 IDK 和核心 SDK 建構工具設定,可節省 6 到 12 分鐘的建構時間。因為這些作業需要從頂層建構作業啟動 24 個以上的 Ninja 子建構作業,而這些作業可運用通訊協定,更妥善地協調各自產生多個平行指令的方式。
功能:以 Chrome Trace JSON 陣列格式產生建構追蹤記錄
--chrome_trace FILENAME 選項可用於告知 Ninja 在建構作業完成後 (即使失敗也一樣),產生建構事件的追蹤記錄。
建議在輸出 FILENAME 中使用 .gz 後置字元,直接產生經過 gzip 壓縮的追蹤記錄檔,因為這類檔案通常會小 20 倍。
這個檔案採用 Chrome 追蹤 JSON 陣列格式,可直接載入任何 Chromium 架構瀏覽器的「chrome://tracing」分頁,更有趣的是,https://ui.perfetto.dev 也支援讀取壓縮追蹤記錄做為輸入內容。
請注意,產生的追蹤記錄檔也包含流程事件,有助於顯示建構作業的重要路徑。對應的建構事件會在 cat 欄位的值中包含 critical_path。
功能:使用者啟動的安全關機
使用者按下 Ctrl-C 停止建構時,Ninja 會將 SIGINT 傳送至子程序,然後等待子程序完成,在某些罕見情況下,這可能需要很長時間。
如果使用者在等待時第二次按下 Ctrl-C,Ninja 現在會列印訊息,並顯示仍在執行的指令表格,同時也會傳送 SIGTERM 信號,要求指令正常停止。
如果這樣還不夠,使用者第三次按下 Ctrl-C 鍵時,系統會將 SIGKILL 傳送至所有子程序,強制終止這些程序,並將控制權交還給使用者。
以下動畫圖片顯示實際情況:

功能:以結構化記錄失敗的指令
建構失敗後,建構目錄中的 .ninja_errors.json 檔案會包含每個失敗動作的詳細資料,例如指令、狀態碼和緩衝輸出 (Bazel 建構動作除外)。
這個檔案的 JSON 結構定義請參閱這裡,工具和開發人員可使用這個檔案回報及重現失敗的動作,以進行偵錯。
功能:持續模式可縮短啟動時間
在環境中設定 NINJA_PERSISTENT_MODE=1,加快後續 Ninja 呼叫速度。這項功能會讓 Ninja 啟動背景伺服器程序,讀取建構資訊清單一次,然後在連續建構之間將建構圖保留在記憶體中。
請注意:
這項功能應完全透明,且不應影響 Ninja 的其他行為。如果發現任何問題或差異,請透過
fuchsia-build-team@google.com告訴我們!系統會自動偵測輸入
.ninja檔案的任何變更。在這種情況下,現有伺服器會關閉,並自動啟動新伺服器。變更 GN 建構檔案或執行jiri update後,不需要額外與使用者互動。伺服器程序閒置 5 分鐘後,就會正常關閉。 在環境中設定
NINJA_PERSISTENT_TIMEOUT_SECONDS=<count>即可變更這項延遲。使用
fx build -t server status擷取目前建構目錄的伺服器狀態。使用
fx build -t server stop明確停止任何正在執行的伺服器執行個體。伺服器會將記錄訊息寫入建構目錄中的
.ninja_persistent_log檔案。不過,您可以在啟動伺服器前,在環境中設定NINJA_PERSISTENT_LOG_FILE=<path>,藉此變更位置。目前,每個伺服器程序會為
core.x64建構設定佔用約 1 GiB 的 RAM。確切數字取決於 Ninja 圖表的大小,而這又取決於您的args.gn設定。每個 Ninja 建構目錄最多只能由一個伺服器程序提供服務。 但使用多個建構目錄時,可能會有多個程序。
Ninja 工具 (例如
ninja -C <dir> -t commands <target>) 尚未在伺服器上執行,因此仍會使用緩慢的啟動程序。我們將在日後修正這個問題,以加快查詢速度。
已知錯誤 / 注意事項,將會解決:
在同一目錄中混合使用持續性和非持續性建構作業,目前可能會造成伺服器混淆,因為系統無法正確偵測 Ninja 建構作業和依附元件記錄的獨立變更。我們會修正這個問題。
解決方法:在環境中取消設定
NINJA_PERSISTENT_MODE前,請先使用-t server stop停止伺服器。「快速啟動」只需要幾秒鐘。目前,每個累加建構作業仍需為建構圖中的所有檔案呼叫 stat(),這需要幾秒鐘的時間。我們會在日後修正這個問題,屆時會使用主機作業系統的 inotify / kqueue 檔案系統監控功能,在只有少數檔案經過修改時立即啟動。
目前不適用於 Windows。這是因為 Win32 的技術限制,導致無法將控制台控制代碼複製到其他程序。這主要是上游 Ninja 團隊的問題,因為 Fuchsia 開發作業並非在 Windows 上進行。
功能:延遲 Bazel 動作的指令
這項功能可讓 Ninja 延後 (「延遲」) 準備好的 Bazel 建構動作,然後將這些動作批次處理成單一 bazel build 呼叫。更多 Bazel 目標會並行建構,減少建構期間的 Ninja/Bazel 轉換次數。在實務上,視建構設定而定,這最多可減少基礎架構建構工具的建構時間 10 分鐘。
對動作排程的影響
請考慮採用混合使用原生 GN 動作和 Bazel 動作的建構計畫:
bazel1 gn1
| |
+-------+
|
[mixed] bazel2 gn2
| | |
+---------+--------+
|
[test1]
在這張圖表中:
gn1和gn2是原生 Ninja / GN 動作 (例如 C++ 編譯)。bazel1和bazel2是 Bazel 動作。[mixed]和[test1]是虛假目標。
如果沒有延遲指令,Ninja 會在滿足每個個別動作的依附元件後,立即啟動 Bazel 指令。假設 Ninja 僅使用兩個平行工作位置 (-j2),為了簡化起見,這看起來會像這樣:
Time -------------------------------------------------------------------->
Ninja: [gn1 ] [gn2 ]
Bazel: [bazel1 ] [bazel2 ]
(startup overhead) (startup overhead)
由於每個 Bazel 動作都會做為獨立的子程序執行:
Ninja 會啟動 2 個不同的
bazel build指令。每次叫用都會支付 Bazel 啟動和分析的固定費用。
bazel1和bazel2無法在 Bazel 中平行建構。
啟用延遲指令後,Ninja 會優先處理非延遲動作 (GN/C++ 工作),同時將準備就緒的 Bazel 動作收集到批次中。如果沒有其他非延遲作業可以進展,Ninja 會在單一叫用中建構所有累積的 Bazel 動作:
Time -------------------------------------------------------------------->
Ninja: [gn1 ]
[gn2 ]
Bazel: [bazel1 + bazel2]
Ninja 會先平行執行
gn1和gn2。bazel1和bazel2準備就緒後,Ninja 會攔截並將其排入佇列,而不是立即啟動子程序。所有正在執行的 GN 工作完成後,Ninja 會叫用 Bazel 一次,一起建構
{bazel1, bazel2}。Bazel 現在可以使用自己的內部依附關係圖和工作站集區,同時建構這兩個目標。最後,系統會啟動
test1的最後一個 GN 動作。
對正確性的影響
這項功能只會變更 Ninja 排定建構動作的時間,不會變更 Ninja 建構的內容。所有輸出內容都相同,且系統仍會遵守工作類型之間的依附元件。舉例來說,如果 GN 目標依附於 Bazel 建構的主機工具,Ninja 只會在主機工具可用時建構該目標。
開發人員唯一可見的差異是:
在完整建構作業中,會先建構更多 GN 工作,再叫用 Bazel 工作。
Bazel 的進度輸出內容會反映出多個目標同時建構。