使用 GN 工具鍊的最佳做法

總覽

在 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_hostis_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