本文將介紹 GN 的詞彙和思考方式,這個時間長度必須足以在 GN 中取得軸承,並說明如何在福奇亞使用這些資料。GN (和 Fuchsia 版本) 比下方的範例更複雜,但一般開發人員不需要更進一步瞭解大部分的層面。
GN 說明文件頁面 QuickStart 和語言頁面提供有關 GN 的詳細說明,參考資料則提供完整的語言說明文件。請使用 gn help
指令,以互動方式列印個別主題的參考資料。Ninja 也有專屬的說明文件。
執行 jiri update
後,在 Fuchsia 結帳程序中,fx gn
和 fx ninja
指令會提供預先建構的二進位檔的存取權。
兩階段作業:gn
和 ninja
和 make
不同,gn
這個故事只有一半。在名稱中,GN 代表 Generate Ninja。這類工具之間的責任劃分為將執行建構作業分為兩個步驟:
gn gen
會採用所有設定選項並做出所有決定。其實只需要在建構目錄中產生.ninja
檔案。只有在您變更設定或完全微調建構目錄時,才需要手動執行這個步驟。一般來說,只有在 GN 檔案變更時才需要執行這項操作,而在漸進式建構作業中,當 GN 檔案或設定有所變更時,系統就會自動執行這項作業。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 gen
的 fx set
指令設定 Args。例如透過 fx set
將 foxtrot
設為 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 值可採用以下其中一種類型:
- 布林值,
true
或false
- 以正常十進位語法表示的整數;未使用太多
- 字串,一律以「雙引號」括住 (下方說明
$
的展開方式相關資訊) - 範圍 (以大括號表示):
{ ... }
;請見下方。 - 以方括號括住的值清單:
[ 1, true, "foo", { x=1 y=2 } ]
是包含四個元素的清單。
值有動態類型,沒有隱含類型強制轉換,但絕不會進行類型檢查。不同類型的值絕對不會相等,但比較類型並非錯誤。
字串常值會展開雙引號內的簡易 $var
或 ${var}
運算式。這是立即展開:如果 var 為字串,x${var}y
與 x +
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 程式碼就會與所描述的來源保持不變,但仍可為核心和使用者執行不同的共用來源子集等。