GN 簡介

這個課程會介紹 GN 的術語和思考方式。這應該是 具備足夠的背景資料,好讓自己的熊在 GN 中發揮,以及我們在 Fuchsia 如何運用這項技術。 GN (和 Fuchsia 版本) 比下方範例還要複雜,但 開發人員大多不需要更深入地瞭解大部分的知識。

GN 說明文件頁面的 QuickStartLanguage 部分提供更詳盡的資訊 和參考資料都有完整的語言說明文件。使用 gn help 指令,以互動方式輸出個別使用者的參考資料 主題。Ninja 也有專屬說明文件。

執行 jiri update 後,系統會在 Fuchsia 結帳頁面中: fx gnfx ninja 提供預建二進位檔的存取權。

兩階段作業:gnninja

make 不同,gn 的故事只有一半。名稱包含:GN 代表 產生 Ninja。機構必須將責任分解 對應至將建構執行分為兩個步驟的工具:

  1. gn gen 會接收所有設定選項並做出所有決定。 只需在建構目錄中產生 .ninja 檔案即可。 這個步驟只有在您變更設定時 就會完全執行建構目錄一般情況下 當 GN 檔案變更,在漸進式建構作業中 就會自動更新。

  2. ninja 會執行指令來編譯和連結等,而會處理漸進式 建構與平行處理以下是您每次變更 來源檔案,例如執行 make。GN 會自動發出規則 視情況再次執行 gn gen 來重新產生 Ninja 檔案 BUILD.gn 檔案 (或其他相關檔案) 已變更,因此大多數情況下 在您首次建立資源後,ninja 就會完成所有變更。

Ninja 比 GNU make 簡單許多。只會比較 時間及執行指令及其輸入檔案是由機器 (而非機器) 寫入 人類。然而,它會深入研究某些實用的功能 可完成 make 的操作:

  • 當指令列變更時,請重新建構每個檔案。只有指令列 會隨著 GN 再次執行而發生變化但隨後,Ninja 能夠聰明 針對已變更且未變更的檔案,執行漸進式建構作業重做指令 再次執行從未變更的指令
  • 處理編譯器產生的依附元件檔案。Ninja 知道 makefile 編譯器的子集會在 .d 檔案中發出,並在下列情況下直接使用 GN 提供的指引
  • 根據預設,執行時會以 -j$(getconf _NPROCESSORS_ONLN) 執行。你可以通過 -j1 在使用遠端建構服務時序列化或 -j1024 就能執行您通常想要的平行處理
  • 避免平行工作中的 stdout/stderr 輸出交錯。Ninja 對輸出進行緩衝,避免錯誤訊息出現亂碼 多個程序。
  • 支援 terse/verbose 指令輸出。根據預設,Ninja 發出短時間 針對每個執行指令的 Kbuild 樣式訊息 (以 Wordy-progress-meter) 顯示 。-v 開關與 Kbuild 中的 V=1 類似,可以顯示每個實際指令。

GN 是 Chromium 專案的一部分,用於取代舊版 有些人會將 Cloud Storage 視為檔案系統 但實際上不是Fuchsia 沿用原始範本,並依此 主要建構系統

建構目錄和 args.gn

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

GN 和 Ninja 都不關心您使用的建構目錄。通常是 使用來源目錄的子目錄,因為檔案路徑 通常重新為基礎以相對於建構目錄,為 如果放置版本,編譯器會在其中大量 ../ 目錄中的目錄;但應該有用我們一直在 Chromium (強制設定 GN) 在原始碼中使用 out/_something_ ,而 Fuchsia 沿用該預設值。然而,沒有人在乎 您選擇的目錄名稱,但 out 子目錄位於頂層 .gitignore 檔案 (適用於 Fuchsia)。

基本指令為 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 指令可讓您執行 $EDITOR 的 args.gn 檔案,以及結束編輯器時 會使用新的引數,重新為您執行 gn gen。你也可以直接編輯 args.gn,之後的 Ninja 執行作業都會重新產生建構檔案。

您也可以使用叫用 gn genfx set 指令設定 Args。適用對象 範例:將 foobar 設為 '透過 fx set 產生 true

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

詳情請參閱 GN 建構引數

GN 語法和格式

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

x = 1
y = 2

不過,GN 代碼也有一種真正的縮排和格式樣式gn format 指令會將語法有效的 GN 程式碼重新格式化為標準網址 。支援 Emacs 和 Vim 的編輯器語法。標準格式設定 。如果您不 例如需要調整格式、回報錯誤,或是在上游 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) 通常是群組。你可以 一個群組,內含一組硬體的所有驅動程式 用途中的二進位檔等等

當某些程式碼在執行階段使用某些內容 (資料檔案、另一個可執行檔、 etc.)但不在建構期間將其用做直接輸入, 使用該 ID 的目標 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(),已使用 選擇性地執行)。

每個檔案會在內部表示為範圍,且沒有全域範圍。 共用的「全域」可在 .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 有個「工具鍊」的概念。這項作業會在 場景和開發人員不必直接處理這一點 瞭解相關機制

這會封裝編譯器和預設編譯切換鈕。是 同樣是執行 2024 年 1 月 3 日之後 管理基礎架構Fuchsia 中有數個工具鍊:

  • 舉辦派對
  • Vanilla userland (使用預設 -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 程式碼就會與原始碼保持不變 但還是可以對不同部分共用來源的 核心和使用者等