GN 簡介

本文將介紹 GN 的詞彙和思考方式,這個時間長度必須足以在 GN 中取得軸承,並說明如何在福奇亞使用這些資料。GN (和 Fuchsia 版本) 比下方的範例更複雜,但一般開發人員不需要更進一步瞭解大部分的層面。

GN 說明文件頁面 QuickStart語言頁面提供有關 GN 的詳細說明,參考資料則提供完整的語言說明文件。請使用 gn help 指令,以互動方式列印個別主題的參考資料。Ninja 也有專屬的說明文件。

執行 jiri update 後,在 Fuchsia 結帳程序中,fx gnfx ninja 指令會提供預先建構的二進位檔的存取權。

兩階段作業:gnninja

make 不同,gn 這個故事只有一半。在名稱中,GN 代表 Generate Ninja。這類工具之間的責任劃分為將執行建構作業分為兩個步驟:

  1. gn gen 會採用所有設定選項並做出所有決定。其實只需要在建構目錄中產生 .ninja 檔案。只有在您變更設定或完全微調建構目錄時,才需要手動執行這個步驟。一般來說,只有在 GN 檔案變更時才需要執行這項操作,而在漸進式建構作業中,當 GN 檔案或設定有所變更時,系統就會自動執行這項作業。

  2. ninja 會執行指令進行編譯和連結等作業,並會處理漸進式建構作業和平行處理任務。這是您每次變更來源檔案 (例如執行 make) 時需要執行的步驟。GN 會在相關 BUILD.gn 檔案 (或其他相關檔案) 有所變更時再次執行 gn gen,藉此自動發送規則來重新產生 Ninja 檔案。因此,對在您首次建構後的大多數變更中,ninja 會執行所有作業。

與 GNU make 這類項目相比,Ninja 非常簡單。只會比較時間和執行指令,而指令的輸入檔案是由機器寫入,不是人類。不過,此工具建構了一些我們反向操作的實用內容,以便在 make 中完成:

  • 指令列發生變更時,重新建立每個檔案。只有在再次執行 GN 時,指令列才會大幅改變。但之後,Ninja 就會巧妙地為已變更的檔案重新執行指令,且不會重新執行未變更的指令。
  • 處理編譯器產生的依附元件檔案。Ninja 知道編譯器會發出 .d 檔案中的 makefile 子集,並在 GN 導向時直接取用。
  • 預設使用 -j$(getconf _NPROCESSORS_ONLN) 執行。您可以在使用 Goma 時傳遞 -j1 以序列化或 -j1024,但前者會自動執行您所需的平行處理。
  • 避免平行工作產生交錯 stdout/stderr 輸出。Ninja 會緩衝處理輸出,避免多個程序中的中斷訊息出現亂碼。
  • 支援 tere/verbose 指令輸出。根據預設,Ninja 會在每個執行指令時,以 Wordy-progress-Meter 樣式的形式發送簡短的 Kbuild 樣式訊息。-v 開關與 Kbuild 中的 V=1 類似,可用來顯示每個實際指令。

GN 是 Chromium 專案的一部分,取代舊版建構系統。它沿用自以下架構,現在可在整個樹狀結構中使用,做為主要建構系統。

建構目錄和 args.gn

Ninja 一律會在建構目錄中執行。Ninja 的所有指令都會從建構目錄的根目錄執行。常見狀況是 ninja -C build-dir

GN 和 Ninja 都不關心您使用的建構目錄。通常會使用來源目錄的子目錄。由於檔案路徑通常與建構目錄相對不同,因此如果您將建構目錄放在其他位置,給編譯器的檔案名稱將會包含大量 ../,但應該可以正常運作。一直以來,Chromium (預先使用 GN 本身) 經常在來源目錄中使用 out/_something_,因此 Fuchsia 沿用了該預設值。不過,您不必煩惱您選擇的建構目錄名稱,不過 out 子目錄是 Fuchsia 頂層 .gitignore 檔案中。

基本指令為 gn gen build-dir。這項操作會視需要建立 build-dir/,並在其中填入目前設定的 Ninja 檔案。如果 build-dir/args.gn 存在,gn gen 會讀取該檔案以設定 GN 建構引數 (請參閱下方說明)。args.gn 是 GN 語法中的檔案,可將值指派給 GN 建構引數,以覆寫任何硬式編碼預設值。這表示只要重複 gn gen build-dir,即可保留上次的操作。

您也可以將 --args=... 新增至 gn Gen,或使用 gn args 指令設定建構引數。gn args 指令可讓您在 args.gn 檔案上執行 $EDITOR,關閉編輯器時,這個指令會使用新的引數重新執行 gn gen。也可以隨時編輯 args.gn,下次 Ninja 執行作業將會重新產生建構檔案。

您也可以使用會叫用 gn genfx set 指令設定 Args。例如透過 fx setfoxtrot 設為 true

$ fx set <your configuration> --args 'foxtrot=true'

詳情請參閱 GN 建構引數

GN 語法和格式

GN 語法不區分大小寫。x=1 y=2 與:

x = 1
y = 2

不過,GN 代碼有一個真正的縮排和格式設定樣式gn format 指令會將語法有效的 GN 程式碼重新格式化為標準樣式。此外,系統支援 Emacs 和 Vim 的編輯器語法。標準格式將由 Tricium 強制執行,並且大量重新格式化將完成。如果您不喜歡格式、回報錯誤或在上游 GN 進行變更,只要程式碼導向到新的真值,我們就會大規模重新格式化所有內容,以符合新的真值。

來源路徑和 GN 標籤

不論是檔案,GN 都會使用 POSIX 樣式路徑 (一律以字串表示),同時參照 GN 定義的實體。路徑可以是相對路徑,也就是與包含路徑字串的 BUILD.gn 檔案的目錄相對。也可以是「來源 絕對」(相對於來源樹狀結構根)。來源絕對路徑在 GN 中以 // 開頭。

最終在指令中使用來源路徑時,系統會將這些路徑轉譯為適用於 OS 的適用路徑,該路徑可以是絕對路徑或相對於建構目錄 (執行指令的位置)。

預先定義的變數會在來源路徑結構定義中使用,以找出建構目錄的某些部分:

  • $root_build_dir 是建構目錄本身
  • $root_out_dir 是目前工具鍊的子目錄 (請見下方說明)
    • 這就是所有「頂層」目標的位置。在許多 GN 版本中,所有執行檔和程式庫都會顯示在這裡。
  • $target_out_dir$root_out_dir 的子目錄,適用於目前 BUILD.gn 檔案中目標所建構的檔案。這是物件檔案的位置。
  • $target_gen_dir 是建議您加入產生的程式碼的對應位置
  • $root_gen_dir 是在子目錄之外產生所需程式碼的位置

GN 標籤會參照 BUILD.gn 檔案中定義的內容。這些路徑以來源路徑為基礎,且一律會出現在 GN 字串中。GN 標籤的完整語法為 "dir:name",其中 dir 部分是命名特定 BUILD.gn 檔案的來源路徑。name 是指該檔案中帶有 target_type("name") { ... } 的目標。簡而言之,您可以定義目標時 使用與目錄相同的名稱。沒有 : 部分的 "//path/to/dir" 標籤是 "//path/to/dir:dir" 的簡寫。這是最常見的情況。

依附元件圖表和 BUILD.gn 個檔案

依附元件圖中的所有 GN 都是根層級。有一個根 BUILD.gn 檔案。假如該目錄中的標籤有依附元件,則只能讀取其他 BUILD.gn 檔案的方式。

沒有萬用字元。每個目標都必須命名為其他目標的依附元件才能建構。您可以在 ninja 指令列上提供個別目標,以便明確建構目標。否則,它們必須位於 //:default 目標圖表中 (在根 BUILD.gn 檔案中名為 default)。

有一個名為 group() 的一般中繼資料目標類型不會對應至建構產生的檔案,而是妥善建構依附元件圖表。default 等頂層目標通常是群組。例如為某個硬體的所有驅動程式建立群組,群組用於特定用途中的所有二進位檔群組等等。

當某些程式碼在執行階段使用某些項目 (資料檔案、另一個執行檔等)但不會在建構期間將其當做直接輸入使用,而該檔案屬於使用該檔案的目標 data_deps 清單內。這也會足以將成品進入 BOOTFS 映像檔。

也可以為目標加上 testonly = true 標籤,表示目標包含測試。GN 會根據目標,禁止非 testonly 的目標達成,以便控管測試二進位檔的最終位置。

建構圖片檔是由一或多個 zbi() 目標驅動。系統會建構並使用 ZBI 主機工具來建立 ZBI。目標可透過現有依附元件圖表中的現有,放置於此映像檔中,方便您將其依附於核心及映像檔中的任何驅動程式或執行檔。

請注意,在 Ninja 檔案中定義的目標時,BUILD.gn 檔案的精細程度是,但預設或其他目標的依附元件圖表以個別目標的精細程度為準。因此,圖表中的 BUILD.gn 檔案預設目標會讓該檔案中的所有目標 (以及工具鍊,請見下方) 成為 Ninja 指令列上的目標,即使目標並非系統預設建構目標。

更多進階概念

GN 運算式語言和 GN 範圍

GN 是一種簡易、動態類型的命令式語言,其唯一用途就是產生宣告式 Ninja 規則。所有範圍都有環狀,也就是語言和資料類型的詞法繫結結構。

GN 值可採用以下其中一種類型:

  • 布林值,truefalse
  • 以正常十進位語法表示的整數;未使用太多
  • 字串,一律以「雙引號」括住 (下方說明 $ 的展開方式相關資訊)
  • 範圍 (以大括號表示):{ ... };請見下方。
  • 以方括號括住的值清單:[ 1, true, "foo", { x=1 y=2 } ] 是包含四個元素的清單。

值有動態類型,沒有隱含類型強制轉換,但絕不會進行類型檢查。不同類型的值絕對不會相等,但比較類型並非錯誤。

字串常值會展開雙引號內的簡易 $var${var} 運算式。這是立即展開:如果 var 為字串,x${var}yx + var + y 相同。透過這種方式,任何值都能算繪為美觀的字串。

由英數字元和底線組成的 ID,可透過指派運算子填入範圍。透過 = 進行命令和透過 += 修改的命令式指派其實是所有 GN 語言的功能 (還有一些具有副作用的特殊方法,例如用於偵錯的 print()write_file(),並以差異化方式使用)。

每個檔案在內部都代表一個範圍,沒有全域範圍。共用的「globals」可在 .gni 檔案中定義,並匯入使用時所在的位置 (import("//path/to/something.gni"))。每個工具鍊都會處理每個 .gni 檔案 (請參閱下方有關工具鍊的資訊),而產生的範圍則複製到匯入檔案範圍。

目標宣告推出了子範圍:

foo = true
executable("target") {
  foo = 12
}
# Outside the target, foo == true

如有已定義變數,但從未在範圍內使用,GN 對診斷錯誤非常嚴格。目標的範圍就像目標的關鍵字引數清單一樣,並會檢查引數名稱的拼字是否正確。如果省略必要引數,目標定義程式碼也可以使用 assert() 診斷錯誤。

值也可以是範圍。當您使用函式時,它的運作方式就像結構:value.member。但範圍一律是一組 GN 程式碼區塊,會執行這組程式碼和值:

foo = {
  x = global_tuning + 42
  if (some_global && other_thing == "foobar") {
    y = 2
  }
}

這會一律定義 foo.x,但有時只會定義 foo.y

GN 工具鍊

GN 有個稱為「工具鍊」的概念,這些作業都是在幕後進行,開發人員不必直接處理,但可以幫助您瞭解機制。

這會封裝編譯器和預設編譯切換鈕。這是唯一能夠以不同方式編譯相同的內容兩次。Fuchsia 提供了多個工具鍊:

  • 主機
  • 香草使用者地 (以預設的 -fPIE 編譯)
  • 使用者環境中的共用資料庫 (已使用 -fPIC 編譯)
  • userboot
  • 核心
  • ARM64 的核心實體位址模式 (以 -mstrict-align 編譯)
  • x86 的多重啟動 (已使用 -m32 編譯)
  • Gigaboot 的 UEFI
  • 工具鍊也會用於「變化版本」配置,這是我們允許選擇性啟用 ASan 的架構,或針對某些使用者區域進行類似操作。

每個工具鍊都是透過 GN 標籤識別。目標標籤的完整語法實際上是 //path/to/dir:name(//path/to/toolchain/label)。通常系統會省略工具鍊,而這會擴展至 label($current_toolchain);也就是說,標籤參照通常位於相同的工具鍊中。

在每個工具鍊中,所有 GN 檔案都會分別執行個體化。每個工具鍊會以不同的方式設定全域變數,因此 GN 程式碼可使用 if (is_kernel)if (current_toolchain == some_toolchain) 等測試,在不同情況下以不同方式行為。這樣一來,GN 程式碼就會與所描述的來源保持不變,但仍可為核心和使用者執行不同的共用來源子集等。