總覽
在 GN 中,工具鍊提供多種建構目標的方式。如要瞭解及偵錯 GN 程式碼,您有知情必要知道自己使用的工具鍊。由於 GN 程式碼可能取決於 current_toolchain,因此在工具鍊 A 中執行某項作業的目標,在工具鍊 B 中可能完全不同,甚至可能不存在於工具鍊 C 中。
本文詳細說明使用工具鍊解決 GN 程式碼 (.gn 和 .gni 檔案) 常見問題的最佳做法。除了 Fuchsia 建構系統政策中列出的最佳做法外,也請遵循這些最佳做法。
如要進一步瞭解工具鍊的運作方式,請參閱「GN toolchains and the Fuchsia Build」,或執行 fx gn help toolchain 查看 GN 的內建文件。
目標
本文中的最佳做法是根據下列目標制定:
- 一致性:偏好以一種方式做事。
- 清楚:使用斷言清楚傳達意圖。
- 效能:避免在建構作業中執行不必要的工作。
最佳做法
對預期工具鍊進行判斷
如果檔案只會在一個或特定工具鍊中使用,請在頂端加入斷言。
建議做法:在只建構主機可執行檔的 BUILD.gn 檔案中,判斷 is_host。
assert(is_host)
# ...
建議做法:在僅適用於預設工具鍊的範本中,判斷 current_toolchain ==
default_toolchain。
template("foo") {
assert(current_toolchain == default_toolchain,
"The foo template can only be used in the default toolchain")
}
將目標納入條件式中
如果檔案需要使用多個工具鍊,您就無法對預期的工具鍊進行判斷,因此請將目標包裝在條件區塊中,避免不必要的擴展。這樣就能更輕鬆地瞭解目標的使用位置,也有助於縮短 GN 生成時間。
建議做法:將目標包裝在 is_host
和 is_fuchsia 檢查中。
# example/BUILD.gn
executable("built_everywhere") {
# ...
}
if (is_host) {
executable("only_on_host") {
# ...
}
}
if (is_fuchsia) {
executable("only_on_fuchsia") {
# ...
}
}
不建議的做法:無條件定義所有目標。
# example/BUILD.gn
executable("built_everywhere") {
# ...
}
executable("only_on_host") {
# ...
}
executable("only_on_fuchsia") {
# ...
}
這種做法會增加目標數量,導致 GN 和 ninja 速度變慢。舉例來說,當 GN 在預設工具鍊中看到對 example:only_on_fuchsia 的參照時,會評估預設工具鍊中的所有 example/BUILD.gn,包括 only_on_host 目標。由於這會透過目標的依附元件遞移級聯,因此在特定位置發生錯誤可能會導致數以萬計的不必要目標。
使用 is_* 變數檢查工具鍊
在目前的工具鍊上判斷或編寫條件時,請使用 BUILDCONFIG.gn 中定義的 is_* 變數 (如有符合需求的變數):
is_android = false
is_apple = false
is_chromeos = false
is_fuchsia = false
is_fuchsia_host = false
is_host = false
is_ios = false
is_linux = false
is_mac = false
is_win = false
is_uefi = false
is_component_build = false
is_official_build = false
is_elf = false
is_pecoff = false
is_dwarf = false
建議:使用 is_host 檢查主機工具鍊。
if (is_host) {
# ...
}
不建議:使用 current_toolchain ==
host_toolchain 檢查主機工具鍊。
if (current_toolchain == host_toolchain) {
# ...
}
檢查 current_toolchain == host_toolchain 通常是錯誤的,因為涉及變數時,會有許多主機工具鍊。
只有在有理由的情況下,才檢查 current_toolchain 的值。舉例來說,其中一個有效用途是檢查 current_toolchain ==
default_toolchain,以定義與工具鍊無關的動作。
偏好較少且較早的工具鍊重新導向
如要使用非預設工具鍊,您必須在某個時間點重新導向至該工具鍊。請盡可能將這些重新導向作業推送到建構圖表上方,這樣一來,重新導向次數就會減少,且您能夠對預期工具鍊進行判斷。
建議:在建構作業中較早的時間點重新導向至 host_toolchain。
# example/BUILD.gn
group("tests") {
testonly = true
deps = [ "foo:tests($host_toolchain)" ]
}
# examples/foo/BUILD.gn
assert(is_host)
test("foo_unit_tests") {
# ...
}
test("foo_integration_tests") {
# ...
}
group("tests") {
testonly = true
deps = [
":foo_unit_tests",
":foo_integration_tests",
]
}
不建議:在建構過程中稍後多次重新導向至 host_toolchain。
# example/BUILD.gn
group("tests") {
testonly = true
deps = [ "foo:tests" ]
}
# examples/foo/BUILD.gn
if (is_host) {
test("foo_unit_tests") {
# ...
}
test("foo_integration_tests") {
# ...
}
}
group("tests") {
testonly = true
deps = [
":foo_unit_tests($host_toolchain)",
":foo_integration_tests($host_toolchain)",
]
}
這種做法會不必要地處理 examples/foo/BUILD.gn 兩次,一次是在預設工具鍊中,另一次是在主機工具鍊中。
避免自動轉送工具鍊
如果目標只適用於特定工具鍊,只要在預期工具鍊上進行判斷即可。
建議:針對預期的工具鍊進行判斷,並定義目標一次。
assert(current_toolchain == desired_toolchain)
action(target_name) {
# ...
}
不建議:使用 GN 群組隱藏工具鍊需求,自動重新導向所有其他工具鍊。
if (current_toolchain == desired_toolchain) {
action(target_name) {
# ...
}
} else {
group(target_name) {
public_deps = [ ":$target_name($desired_toolchain)" ]
}
}
雖然讓目標在任何工具鍊中運作似乎很方便,但這種做法會導致難以瞭解實際情況。
將與工具鍊無關的動作放入預設工具鍊
無論工具鍊為何,部分動作的行為都相同,因此在多個工具鍊中重複這些動作會浪費資源。最常見的例子是程式碼產生:雖然我們可能會在多個工具鍊中建構產生的程式碼,但每次都不應重新產生程式碼。如要解決這個問題,請確保動作只在 default_toolchain 中定義。
建議:在預設工具鍊中執行一次程式碼生成。
if (current_toolchain == default_toolchain) {
action("codegen") {
visibility = [ ":*" ]
outputs = [ "$target_gen_dir/main.cc" ]
# ...
}
}
executable("program") {
deps = [ ":codegen($default_toolchain)" ]
sources = get_target_outputs(deps[0])
# ...
}
不建議:在每個工具鍊中重新生成程式碼。
action("codegen") {
visibility = [ ":*" ]
outputs = [ "$target_gen_dir/main.cc" ]
# ...
}
executable("program") {
deps = [ ":codegen" ]
sources = get_target_outputs(deps[0])
# ...
}
使用 :anything 標籤取得輸出目錄
使用「target_gen_dir」或「target_out_dir」呼叫 get_label_info 時,只有標籤的目錄重要,目標名稱則不重要。如果沒有合適的特定目標,請使用名為「anything」的虛假目標。
建議:將虛假目標命名為「anything」。
codegen_dir = get_label_info(":anything($default_toolchain)", "target_gen_dir")
不建議:將虛假目標命名為「anything」以外的名稱。
codegen_dir = get_label_info(":bogus($default_toolchain)", "target_gen_dir")
避免使用特定語言的工具鍊
請勿為特定程式設計語言建立工具鍊。我們在早期就這麼做,結果發現這是個壞主意。舉例來說,我們過去有 rust_toolchain,但後來移除了。我們也計畫移除 fidl_toolchain。