Ninja 的運作方式

本頁概述 Ninja 的運作方式 紫紅色。

一般總覽

Fuchsia 建構系統使用 Ninja 平行啟動建構指令。 下列步驟說明 Ninja 的行為:

  1. 從頂層 build.ninja 載入 Ninja 建構計畫 檔案,其本身可包含其他數個 .ninja 檔案。

    在 Fuchsia 版本中,這些由 GN 版本建立的。 如果偏好在終端機視窗中工作 可使用 Google Cloud CLI gcloud 指令列工具這項作業會在記憶體中建構依附元件圖表

  2. 載入 Ninja 版本記錄檔和 deps 記錄 (如果有的話)。

    這項作業會新增前一個發現的依附元件邊緣 成功叫用 Ninja 建構作業。這種做法可快速漸進式建構作業 才算修正作業的成本

  3. 決定要產生哪些建構輸出內容 (也稱為「目標」)。

    從指令列指定的目標開始,以遞迴方式逐步引導 判斷哪些最終和中繼輸出內容過時的依附元件 因此需要重新建構需要的指令 重新執行皆會以有向非循環圖正確排序。

  4. 依據 CPU 數量平行啟動必要的建構指令 主機系統上 (或明確的 -j<count> 參數) 上。

    另一種控管平行處理的方式是使用 -l<max_load> 來限制 並在系統上載入值每當輸入的輸入內容 已更新「ninja」的相關知識。

,瞭解如何調查及移除這項存取權。

狀態顯示

在建構期間,Ninja 會平行啟動多個指令,而且根據預設, 會緩衝輸出內容 (stdout 和 stderr),直到完成為止

Ninja 也會顯示狀態行 (例如使用 fx build 時) 說明以下內容:

  • 已完成的指令數量。
  • 完成建構作業必須執行的指令總數2
  • 目前執行中的指令數量。
  • last-completed 指令的說明。通常包含 一個小型的助詞 (例如 ACTIONCXX),後面接著一組 輸出目標。
[102/345](36) ACTION path/to/some/build/artifact

上述範例代表截至目前為止,102 指令已完成 345,以目前 Ninja 啟動的 36 個平行指令, path/to/some/build/artifact 是要產生的最新建構構件。

您能夠透過設定 NINJA_STATUS 環境變數

如有任何指令產生某些輸出內容,或是輸出失敗,Ninja 會更新 然後輸出該指令的輸出內容 錯誤訊息。然後繼續輸出狀態行, 範例:

[102/345](24) ACTION path/to/some/build/artifact
<output of the command which generated 'path/to/some/build/artifact'>
[101/345](23) ACTION path/to/another/build/artifact

在實務上,這就是大多數編譯器警告的輸出方式。

一般的例外狀況,如果命令位於特殊的特殊 console 集區可以列印 立即連線至終端機這對於需要長時間執行的指令非常有用 列印自己的狀態更新。

Ninja 可確保同時只能啟動一個主控台指令。 也會暫停自己的狀態線更新,直到完成更新為止。不過 請注意,其他非主控台指令仍會在 而且這些輸出內容會經過緩衝處理Fuchsia 版本將這項功能 所有指令都會叫用 Bazel,因為這類指令往往很長 而是自己的狀態更新到終端機

Fuchsia 專屬狀態顯示

「Ninja」是 Fuchsia 相關特殊改善項目之一,會顯示一份表格 最古老的長時間執行指令 (含執行時間) 對於建構項目發生的情況這項功能只能在智慧終端機上啟用。

在環境中設定 NINJA_STATUS_MAX_COMMANDS=<count>,即可變更 指令數量fx build 將其預設值設為 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

詳情請參閱 Fuchsia 功能:待處理指令的狀態

Ninja 建構依附元件圖表

Ninja 從建構計畫建構的圖表僅包含兩種類型 節點數量3

  • 目標節點:目標節點會直接對應到已知的檔案路徑 Ninja 的指導。該路徑一律相對於建構目錄。

  • 動作節點:「動作」節點會模擬要執行的單一指令 從指定的輸入檔案組產生輸出檔案。

請注意下列資訊:

  • 非任何動作節點輸出內容的目標節點稱為來源 檔案。

  • 非動作節點輸入的目標節點須為輸出內容 所謂最終輸出內容,稱為最終輸出內容。

  • 「Target」節點同時是動作的輸出內容和輸入內容 稱為中繼目標或中繼輸出

  • 每個動作可以指向圖中的零或多個輸入目標節點。

  • 每個動作在圖表中可以有一或多個輸出目標節點。 動作不得有 0 個輸出,否則 Ninja 無法確定 來執行指令

  • 動作節點沒有名稱,因此無法直接參照 當叫用 Ninja 時只有檔案路徑,也就是目標。

Ninja 建構計畫

Ninja 建置方案是由 build.ninja位於 建構目錄,其中包含其他具有 include*.ninja 檔案,或 subninja 陳述式。以下是這項工具最重要的摘要 功能 (如需詳細資訊,請參閱 Ninja 手冊)。

.ninja 檔案中,動作節點是透過 build 陳述式定義:

build <outputs>: <rule_name> <inputs>

<outputs> 是輸出路徑清單,<inputs> 是輸入路徑清單。 而 <rule_name> 是 Ninja 規則的名稱,可做為食譜使用 撰寫最終指令來執行規則是由特殊 rule 所定義 陳述式:

rule <rule_name>
   command = <command expression>

<command expression> 可包含特殊的 $in$out 關鍵字 會展開為相應專案的輸入和輸出內容清單 建構規則

rule copy_file
  command = cp -f $in $out

build output.txt: copy_file input.txt

上方範例是一個小型的建構計畫,會指示 Ninja 執行 output.txt,則 cp -f input.txt output.txt 指令必須執行。

隱式輸出

即使指令包含不得出現的額外輸出內容 $out。可用以 使用 | 分隔符。

rule copy_file
  command = cp -f $in $out && touch $out.stamp

build output.txt | output.txt.stamp: copy_file input.txt

上述範例會向 Ninja 告知建構 output.txt 的指令 將 input.txt 複製到該檔案中,並建立 output.txt.stamp 檔案。

隱含輸入

同樣地,您也可以告訴 Ninja 有些輸入的內容 從 $in 運算式展開 (方法是使用右側的 |) 建構陳述式

rule cxx_compile
  command = c++ -c $in -o $out

build foo.o: cxx_compile foo.cc | foo.h

上述範例會告知 Ninja 編譯 foo.cc 將使用 foo.h 做為 輸入。

訂單專用輸入資料

可以向 Ninja 告知,有些檔案路徑是 一些輸出內容,因此應該「利用」具體做法是指示 Kubernetes 建立並維護 一或多個代表這些 Pod 的物件這會使用 || 分隔符位於 build 陳述式的右側,且必須一律顯示 晚於任何可能的 | 分隔符號 (如果有的話)。

rule cxx_binary
  command = c++ -o $out $in -ldl

rule cxx_shared_library
  command = c++ -shared -o $out $in

build foo.so: cxx_shared_library:

build program: cxx_binary main.cc || libfoo.so

以上範例會告知 Ninja 每次需要建構 program 時, 您也必須建立 foo.so,但這樣的順序並不重要。在其他 可以在發出 program 可產生 foo.so。在本例中,如果二進位檔僅載入程式庫,此做法有效 執行 dlopen() 程式碼。

利用休息最佳化減少重新建構

某些指令可能無法變更輸出檔案的時間戳記 (如果包含內容的話) 並沒有改變Ninja 可以用來減少指令總數 會在建構叫用期間執行。

如要支援這項功能,規則定義必須將特殊的 restat 變數設為 非空白值這會導致 Ninja 在 而不是執行該映像檔任何修改時間未變更的輸出內容都會視為 就像完全不必建構),而且 Ninja 會移除所有指令 來做為待處理指令清單的輸入內容

# A rule to invoke the create_manifest.py script that processes some input
# and generates a manifest as output. `restat` is set to indicate that the
# script will not update $out's timestamp if the file exists and its content
# is already correct.

rule create_manifest
  command = ../../create_manifest.py --input $in --output $out
  restat = 1

build package_manifest.json: create_manifest package_list.txt

build package_archive.zip: create_archive package_manifest.json

在上述範例中,如果開發人員變更 package_list.txt 不會變更 package_manifest.json 輸出檔案,然後 package_archive.zip 不需要重新產生。為了支援這項功能 Ninja 執行指令後,就會為每個輸出檔案記錄摘要 會包含指令雜湊和最新輸入內容的時間戳記 $BUILD_DIR/.ninja_build 中的特殊檔案,稱為「Ninja 建構記錄」

在下次 Ninja 叫用時,系統會使用建構記錄時間戳記,而非 更新檔案系統,藉此判斷檔案是否需要更新 重新產生。因此,在上述範例中, 雖然 package_list.txt 會與 package_manifest.json 建立關聯 因為檔案系統的時間戳記較舊如果沒有這項功能,Ninja 在每次建構叫用時重新建構資訊清單檔案。

在建構期間使用 depfile 探索隱含輸入

Ninja 啟動的指令可以產生特殊的依附性檔案 (縮寫為 例如:depfile),列出額外 隱含 輸入,也就是 您就會在建構計畫中沒有看到這個指令這項資訊會由 Ninja 讀取, 而該指令會在名為 $BUILD_DIR/.ninja_deps 的二進位檔案中記錄 「Ninja deps log」下次叫用 Ninja 時,系統會載入 deps 記錄檔 並將所有記錄的隱含輸入內容加到依附元件圖表中。

舉例來說,如果 C++ 編譯指令列出了 標題,包括未在對應的 .ninja 中明確列出的標頭 檔案。如果開發人員修改這類標頭,下一個 Ninja 叫用就會 會看到變更,並導致對應的 C++ 來源和其 才能重新編譯依附元件

方法是在規則定義中加入 depfile 變數宣告,如下所示: 於:

rule cc
    depfile = $out.d
    command = gcc -MD -MF $out.d [other gcc flags here]

請注意,根據預設,Ninja 會將 depfile 移除 擷取到二進位 deps 記錄檔檢查哪些 depfile 依附元件 後,請採取下列任一行動:

  • 執行 ninja -C <build_dir> -t deps <target>,其中 <build_dir> 是 建構目錄,而 <target> 是輸出檔案的路徑, 至 <build_dir>-t deps 選項會叫用 Ninja 工具, 這個輸出檔案的 deps 記錄檔內容。

    不過請注意,deps 記錄是二進位的附加檔案,因此會將 累積多個 Ninja 建構叫用中的 depfile 依附元件,因此 會列出更多隱含依附性,比上次產生依附元件時 指令

  • 移除建構構件,然後使用 -d keepdepfile 叫用 ninja 選項,強制 Ninja 將所有依附元件檔案保留在建構中 (將其內容複製到二進位 deps 記錄檔之後)。這個 以人工方式檢查內容,例如:

    $ rm $BUILD_DIR/foo.o
    $ ninja -C $BUILD_DIR -d keepdepfile foo.o
    $ cat $BUILD_DIR/foo.o.d
    

    請注意,實際的 depfile 路徑取決於規則定義。身為 大多數指令都會在第一個輸出路徑後方加上 .d 後置字串, 但不是由 Ninja 強制執行,

depfile 的正確性問題

建構計畫沒有變更時,Deps 記錄非常有效。 因為 Ninja 會偵測下一個包含 depfile 的漸進式建構作業。 隱含輸入的變化,並重新建構依附於該變數的任何項目。

不過,當建構計畫發生變更時,Ninja deps 記錄中的項目可以 「過時」,並會在應用程式的依附元件圖表中加入錯誤的邊緣 取得下一個 Ninja 叫用。有時候,這些物件會在下一個建構作業中中斷 呼叫。這種情況,尤其是將依附元件從 建構計畫,但仍會記錄在 deps 記錄檔中。

在實務上,這會導致隨機漸進式建構失敗 或使用基礎架構建構工具 (無論是在 CQ 或 CI 中皆是)。經濟學人 現在可以解決這個問題,因為 deps 記錄是 Ninja 管理 請注意,由於「開發人員」無法偵測到「過時」使用方式 使用時,舊版建構計畫的部分功能會失效)。

一般的解決方法是執行簡潔的建構作業。Fuchsia 甚至導入 「清理打造圍欄」,設法解決 有問題的情況


  1. 更具體來說,Ninja 依附元件圖表與 Bazel 動作圖表非常相似,而 Ninja 目標則對應至 Bazel File 物件。

  2. 如果 Ninja 認為某些指令的輸出內容視為最新狀態,則這個數字可能會在建構期間減少。

  3. 基於歷史因素,Ninja 原始碼使用名為 Node 的 C++ 類別建立目標模型,並使用名為 Edge 的 C++ 類別建立動作模型。不過,由於在讀取程式碼時經常會造成「令人混淆」,因此本文不會遵循這種誤導性慣例。