使用 GN 工具鍊的最佳做法

總覽

在 GN 中,工具鍊可讓您透過多種方式建立目標。您需要瞭解 GN 程式碼並進行偵錯。由於 GN 程式碼可在 current_toolchain 上進行條件式,因此在工具鍊 A 中執行特定動作的目標,在工具鍊 B 中可能執行的是完全不同的作業,而且可能完全不會出現在工具鍊 C 中。

本文件詳細說明使用工具鍊解決 GN 程式碼 (.gn.gni 檔案) 常見問題的最佳做法。除了《Fuchsia 建構系統政策》中所列的最佳做法以外,這些最佳做法也了。

請參閱 GN 工具鍊和 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_* 變數檢查工具鍊

在目前的工具鍊斷言或編寫conditional時,如果其中一個符合您的需求,請使用 BUILDCONFIG.gn 中定義的其中一個 is_* 變數:

is_android = 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_component_build = false
is_official_build = 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)",
  ]
}

這個方法不必費心處理範例/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 時,只有標籤的目錄很重要,而非目標名稱。如果沒有合適的特定目標,請使用名為「任何目標」的假目標。

建議:將假目標命名為「任何內容」。

codegen_dir = get_label_info(":anything($default_toolchain)", "target_gen_dir")

不建議的做法:將假目標命名為「任何項目」以外的名稱。

codegen_dir = get_label_info(":bogus($default_toolchain)", "target_gen_dir")

避免使用特定語言的工具鍊

請勿為特定程式設計語言建立工具鍊。我們早就進行這項作業,但發現是一個壞點例如,我們之前有 rust_toolchain,但後來將其移除。我們也打算移除 fidl_toolchain