在 Fuchsia 版本中建構變數

摘要

本文件說明如何實作「建構變數」,這是 Fuchsia 建構系統的實作功能,可讓您建構主機和裝置二進位檔的檢測特別最佳化版本。

讀取器必須熟悉 GN toolchain() 執行個體,且應已閱讀下列文件:

建構變數總覽

Fuchsia 版本定義了多種建構變數,例如:

  • asanubsan 變數分別用於使用 Clang 的 Address SanitizerUndefined Behaviour Sanitizer 來建構機器程式碼。甚至還有一個結合這兩種做法的 asan-ubsan 變化版本。

  • coverage 變化版本用於建構已啟用 Clang 檢測式剖析功能的機器程式碼,以支援程式碼涵蓋率收集功能。

  • profile 變化版本也可以用來建構檢測程式碼,但也能支援設定檔引導最佳化功能。

  • thinltolto 變數是用來建構啟用連結時間最佳化功能的二進位檔。

  • gcc 變化版本是用來使用 GCC 編譯器 (而不是 Clang) 建構 Zircon 核心的某些部分 (這在整理出可能有效影響核心的細微機器程式碼產生問題時,已經很實用)。

  • releasedebug 變化版本提供用於覆寫預設編譯模式,此模式取決於 args.gn 中的 is_debug 建構設定變數值。

  • 用於特殊需求的幾個其他變數,這些變數全都在 //build/config/BUILDCONFIG.gn 檔案中定義,詳情請參閱本文其餘部分。

一般來說,單一建構變數模型:

  • 一組額外設定,可定義在建構變化版本二進位檔及其依附元件時,要套用的編譯器、組合器或連結器旗標。

  • 要新增至建構圖表最終變化版本二進位檔目標的一組選用隱含依附元件 (即執行檔、可載入的模組,甚至是共用程式庫)。

舉例來說,假設「asan」建構變數可用於啟用 Clang 的 Address Sanitizer。實際上,建構 Fuchsia 執行程式時,至少需啟用 Address Sanitizer (至少應如此):

  • 建構執行檔及其所有依附元件時 (包括 Fuchsia 的 C 程式庫),將 -fsanitize=address 標記同時傳遞至 Clang 編譯器和連接器。

  • 執行階段會提供 Asan 執行階段 (libclang_rt.asan.so),以及本身的依附元件 (即 libc++.solibc++abi.solibunwind.so 二進位檔的預先建構版本)。

基礎和變化版本工具鍊

建構變數一律套用至特定「基本」工具鍊,可提供由變化版本本身擴增的預設設定。這項操作會建立新的 GN 工具鍊執行個體,稱為「變化版本工具鍊」,其中包含自己的 root_out_dir。例如:

  • //build/toolchain:host_x64 是用於建構主機二進位檔的基礎工具鍊,root_out_dir${root_build_dir}/host_x64

    //build/toolchain:host_x64-ubsan 是套用 ubsan 變數所建立的變數工具鍊,而其 root_out_dir${root_build_dir}/host_x64-ubsan

  • //build/toolchain/fuchsia:x64 是預設工具鍊 (指定 x64 型裝置時),用於建構 Fuchsia 使用者層級二進位檔。由於這是預設值,因此其 root_out_dirroot_build_dir 相同。

    //build/toolchain/fuchsia:x64-asan 是將 asan 變化版本套用至預設工具鍊而建立的變化版本工具鍊。root_out_dir 會是 ${root_build_dir}/x64-asan

一般來說,//${tc_dir}:${tc_name}-${variant} 會是透過將 ${variant} 變化版本套用至名為 //${tc_dir}:${tc_name} 的基本工具鍊而建立的變化版本工具鍊,而其 root_out_dir 一律為 ${root_build_dir}/${tc_name}-${variant}

如果基本工具鍊有shlib 工具鍊,其所有變化版本工具鍊也會都有。最後,單一變數可套用至多個基本工具鍊。

舉例來說,//build/toolchain:host_x64-asan//build/toolchain/fuchsia:x64-asan 是變化版本工具鍊,透過將相同的 asan 變化版本套用至用於建構主機和 Fuchsia 裝置二進位檔的基礎工具鍊。

後者也會將 //build/toolchain/fuchsia:x64-asan-shared 做為 shlib 工具鍊,以產生以 ELF 為基礎的共用程式庫。

基本工具鍊必須在建構作業中使用 clang_toolchain_suite()zircon_toolchain_suite() 定義。這兩個範本最後都會呼叫 variant_toolchain_suite(),以實作這項神奇的功能,視需要自動建立變化版本工具鍊。

工具鍊和變化版本標記

Fuchsia 版本中的每個基本工具鍊都可以有多個標記,這些標記是說明工具鍊屬性的任意格式字串。舉例來說,"kernel" 標記是用來表示工具鍊用於建構核心構件 (這點非常重要,因為沒有 C 程式庫、沒有標準 C++ 程式庫,以及某些對特定目標定義而言重要的限制)。

如需所有有效工具鍊標記的清單,請參閱 //build/toolchain/toolchain_tags.gni

同樣地,每個變化版本定義都有許多標記,用來描述變化版本的屬性。舉例來說,"instrumentation" 標記是用來表示這個變化版本會建立執行執行階段檢測作業的機器程式碼 (例如消毒器或分析器)

如需所有有效變數標記及其說明文件的清單,請參閱 //build/toolchain/variant_tags.gni

建立變化版本工具鍊時,全域 toolchain_variant.tags 值會同時包含繼承自基本工具鍊的標記,以及沿用自變化版本的標記。

工具鍊變化版本例項化

建構系統只會在需要時建立變化版本工具鍊。可用的工具鍊 + 變化版本組合非常大量,一次建立所有變化版本會使 gn gen 速度大幅降低。

建構系統會依據以下條件決定要建立哪些工具鍊變化版本,而不會立即建立所有變數:

  • select_variant 全域變數中顯示的變化版本選取器清單。

  • 變化版本描述元名稱清單,會以 variant_toolchain_suite() 範本的 enable_variants 引數顯示。即使 select_variant 為空白,您仍可強制啟用少數變化版本。

    舉例來說,用於建構 C 程式庫的工具鍊的 ASan 和 UBSan 變化版本一律會啟用,因為建構 Core Fuchsia IDK 時需用到這些項目 (請參閱 //zircon/system/ulib/c/BUILD.gn)。

  • variant_toolchain_suite()exclude_variant_tags 引數中顯示的變化版本標記清單。系統很少用於排除特定變化版本,避免將特定變化版本套用至特定基本工具鍊。

    舉例來說,系統啟動載入程式會排除含有 "instrumented" 標記的變化版本,因為您無法在啟動裝置時執行消毒器或剖析執行階段 (請參閱 `//

變化版本描述元

變化版本描述元是一個 GN 範圍,用於向建構系統說明特定建構變數的屬性。這些屬性是透過 //build/config/BUILDCONFIG.gn 中的 known_variants 變數定義,且每個範圍都應遵循下列嚴格的結構定義:

  • configs:選用 GN 設定標籤清單,會自動新增至具有此變數的所有目標。

    請注意,這份清單中的每個設定 ${label} 都必須有一個目標 ${label}_deps,而此變數中建構的每個目標都會自動依附。在多數情況下,這會是空白的 group()

  • remove_common_configs:選用 GN 設定標籤清單,其中應在以此變化版本建構的任何目標中移除 (如有)。如果建構系統為二進位檔設定的部分預設設定不應用於特定變數,有時就會需要這個做法。

  • remove_shared_configs:選用 GN 設定標籤清單,與 remove_common_configs 類似,但僅適用於建構 shared_library() 目標及其依附元件。

  • deps:選用 GN 目標標籤清單,以隱含依附元件的形式,加進用此變化版本建構的任何可連結目標。

  • name:為變化版本描述元命名的專屬字串,通常用於 select_variant。如果省略 nameconfigs 不得為空,將用來產生名稱 (方法是將名稱與破折號合併)。

  • tags:選用形式的任意格式字串清單,說明變化版本的屬性 (請參閱工具鍊和變化版本標記)。

  • toolchain_args:選用範圍,此範圍定義的每個變數都會覆寫此變數工具鍊內容中的建構引數。

  • host_onlytarget_only:選用範圍,可包含上述任何欄位。這些值分別用於主機或目標 (即裝置) 工具鍊。這裡加入的任何欄位也都不應位於外部範圍。

以下是一些例子:

只有單一設定的變化版本描述元範例

{
  configs = [ "//build/config/lto" ]
  tags = [ "lto" ]
}

上述範圍定義了名為 "lto" 的變化版本描述元 (由於範圍中沒有 name 鍵,因此名稱是從 configs 中的值取得,而此處只會包含一個項目)。

套用這個變化版本後,系統會新增 //build/config/lto/BUILD.gn 中定義的 //build/config/lto:lto 設定,如果這類設定不含隱含依附元件,則該檔案也會包含 //build/config/lto:lto_deps 空白群組。例如:

# //build/config/lto/BUILD.gn
config("lto") {
  cflags = [ "-flto" ]
  asmflags = cflags
  ldflags = cflags
  rustflags = [ "-Clto=fat" ]
}

group("lto_deps") {
  # Implicit dependencies for "lto" config.
  # This is an empty group since there are none.
}

這個描述元使用 "lto" 標記來表示此變化版本已執行連結時間最佳化。這個標記也可以由 "thinlto" 描述元使用,該描述元將使用不同的設定。

含有多項設定的變化版本描述元範例

{
  configs = [
    "//build/config/sanitizers:ubsan",
    "//build/config/sanitizers:sancov",
  ]
  remove_common_configs = [ "//build/config:no_rtti" ]
  tags = [
    "instrumented",
    "instrumentation-runtime",
    "kernel-excluded",
    "sancov",
    "ubsan",
  ]
}

這會定義名為 "ubsan-sancov" 的變化版本描述元 (名稱是藉由彙整設定名稱與破折號,從 configs 清單衍生而來)、用於建構機器程式碼,藉此在執行階段偵測未定義的行為,並同時收集程式碼涵蓋率資訊。

請注意,您也必須定義 //build/config/sanitizers:ubsan_deps//build/config/sanitizers:sancov_deps,才能列出這些設定中的隱含依附元件。

這會使用 remove_common_config,因為 //build/config:no_rtti 是許多基本工具鍊的預設設定的一部分,但 RTTI 必須啟用,UBSan 檢測作業才能正常運作。

此處使用的代碼清單亦包含其他詳盡的資訊。請注意,"kernel-excluded" 標記是用來避免將這個變數套用至任何核心機器碼。

含有 toolchain_args 的變化版本描述元範例

{
  name = "release"
  toolchain_args = {
    is_debug = false
  }
}

這個變化版本描述元會明確命名,且不會新增任何設定或依附元件。另一方面,它可確保全域建構設定變數 is_debug 會設為 false,這樣會變更對應變化版本工具鍊結構定義中定義的預設設定數量。

通用變化版本

其中較不知名的建構系統功能稱為「通用變化版本」。這些是額外的變化版本描述元,可與其他已知變化版本「結合」,運作方式如下:

  • 如果在 args.gn 中設定 is_debug=false,表示所有二進位檔都應以最大程度最佳化來建構,而 "debug" 變化版本描述元則是由建構定義。視需要在偵錯模式中建構特定目標。

  • 同樣地,如果 is_debug=true (預設值),則是由建構定義 "release" 變化版本描述元。如此一來,您就可以視需要建構特定的目標,並全面最佳化。

  • 此外,上述的通用變化版本會由版本自動與所有其他已知的變化版本描述元合併。例如,如果 is_debug=false,則建構作業也會建立 "asan-debug""ubsan-debug""thinlto-debug" 等。如果是 is_debug=true,則其會改為定義 "asan-release""ubsan-release""thinlto-release" 等。

請注意,這些變數描述元是由建構根據 is_debug 值「有條件」定義。也就是說,當 is_debug=false 時沒有 "release" 變化版本及其組合,且 is_debug=true 時沒有 "debug" 變化版本及其組合!

toolchain_variant 全域變數

BUILD.gn*.gni 檔案中,全域 toolchain_variant 變數可用來擷取 current_toolchain 的變化版本相關資訊。這個範圍採用下列結構定義:

  • name:建構變數描述元的名稱。在基本工具鍊結構定義中,這是一個空白字串,或原本用於建立目前 GN toolchain() 執行個體的變化版本描述元名稱。

    各種工具鍊內容的範例名稱:

    //build/toolchain/fuchsia:x64                ""
    //build/toolchain/fuchsia:x64-shared         ""
    //build/toolchain/fuchsia:x64-asan           "asan"
    //build/toolchain/fuchsia:x64-asan-shared    "asan"
    
  • base:目前工具鍊的完整 GN 標籤。請注意,對於工具鍊變化版本的 shlib 工具鍊,這會指向最終的基本工具鍊。例如:

    //build/toolchain/fuchsia:x64              //build/toolchain/fuchsia:x64
    //build/toolchain/fuchsia:x64-asan         //build/toolchain/fuchsia:x64
    //build/toolchain/fuchsia:x64-shared       //build/toolchain/fuchsia:x64
    //build/toolchain/fuchsia:x64-asan-shared  //build/toolchain/fuchsia:x64
    
  • tags:任意形式字串清單,每個字串皆描述目前工具鍊執行個體及其變化版本的屬性。這只是工具鍊與變化版本標記的聯集。

  • instrumented:如果 tags 清單包含 "instrumentation" 標記值,這個布林值標記就會設為 true,以方便您取代 GN 中複雜的測試指示,例如:

    if (toolchain_variant.tags + [ "instrumentation" ]
        - [ "instrumentation" ] != toolchain_variant.tags) {
      # toolchain is instrumented
      ...
    }
    

    改為:

    if (toolchain_variant.instrumented) {
      # toolchain is instrumented
      ...
    }
    
  • is_pic_default:在可建構 ELF 位置獨立程式碼 (PIC) 的工具鍊中為 true 的布林值。這意味著可能是 shlib 工具鍊 (例如 //build/toolchain/fuchsia:x64-shared),或直接產生此類程式碼 (例如 //zircon/kernel/lib/userabi/userboot:userboot_arm64) 的基礎工具鍊。

  • with_shared:如果目前的工具鍊可透過 shlib 工具鍊建構 ELF 共用程式庫 (例如 //build/toolchain/fuchsia:x64),「或」在這類工具鍊 (例如 //build/toolchain/fuchsia:x64-shared) 中,則為 true 的布林值。

  • configsremove_common_configsremove_shared_configsconfig() 項目的 GN 標籤清單,清單會直接來自目前的變化版本描述元 (若為空白清單) 或空白清單。

  • deps:在目標中加入 GN 標籤清單,這些標籤應以依附元件的形式新增至任何可連結目標,並從變化版本描述元本身 (如有) 繼承。

  • libprefix:如果是檢測變數,這是共用程式庫的安裝前置字串字串,否則為空白字串。詳情請參閱工具鍊變化版本 libprefix 一節。

  • exclude_variant_tags:供變化版本選取邏輯在內部使用。繼承自 clang_toolchain_suite()zircon_toolchain_suite() 呼叫,或直接從目標定義沿用。這是一份標記清單,用於排除要套用至基本工具鍊或目標的變化版本,但在某些情況下需要。

  • suffix:此為 "-${toolchain_variant.name}",如果名稱空白,則為 ""。可在內部使用,簡化展開而無需條件。

  • supports_cpp:如果這個工具鍊支援 C/C++,則為 true 的布林值。

  • supports_rust:如果這個工具鍊支援 Rust,則為 true 的布林值。

  • is_basic:如果這個工具鍊是由 basic_toolchain() 範本建立,因此不具 C/C++ 和 Rust GN 內任何內建支援,則為 true 的布林值。只有一個 copyaction 目標。

這個全域變數的內容很少由目標定義使用,以便根據目前的工具鍊內容修改設定。這種情況主要發生在不是以預先建構的低階目標 (例如 C 程式庫、核心構件或檢測執行階段支援)。

工具鍊變化版本 libprefix

為了在單一 Fuchsia 套件中混合檢測及未檢測的二進位檔,建構系統必須執行特殊步驟:

  • 使用檢測變化版本工具鍊建構的共用程式庫必須安裝在 "lib/<variant>/",而非預設的 "lib/" 位置。

  • 可執行的二進位檔必須使用 "-Wl,-dynamic-linker=<variant>/ld.so.1" 等連結器引數編譯,這會覆寫預設值 ("ld.so.1",這是 Fuchsia clang 預先建構的工具鍊二進位檔中以硬式編碼方式寫入)。

  • 由於模糊建構變數會使用程式庫子目錄的建構變數名稱,因此運作方式模糊不清。

toolchain_variant.libprefix 變數會以下列方式定義,方便您輕鬆支援上述所有操作:

  variant name        libdir               libprefix        note

  no variant    --->  lib/                 ""               (default target toolchain)
  thinlto       --->  lib/                 ""               (uninstrumented)
  asan-ubsan    --->  lib/asan-ubsan/      "asan-ubsan/"    (instrumented)
  asan-fuzzer   --->  lib/asan/            "asan/"          (instrumented + fuzzing)

這可用來判斷安裝位置為 "lib/${toolchain_variant.libprefix}",並將連結器標記判定為 "-Wl,-dynamic-linker=${toolchain_variant.libprefix}ld.so.1"

變化版本選項

Fuchsia 建構系統支援選取要啟用的建構變數,並套用到個別目標或目標群組。方法是在建構設定檔 (args.gn) 中定義 select_variant 變數。請參考以下範例:

# From out/default/args.gn
...

select_variant = [
  {
    label = [ "//src/sys/component_manager:bin" ]
    variant = "release"
  },
  "host_asan",
  "thinlto/blobfs",
  "ubsan",
]

清單中的每個值都有一個運算式,稱為「變數選取器」,可以是範圍或字串,用於設定建構將變化版本套用至不同目標組合的方式。

如已定義 select_variant 且不是空白清單,系統將利用其值決定如何建構可連結目標,例如建構圖中建構圖中的執行檔、可載入的模組、共用程式庫,以及所有依附元件。

系統會依序比較 select_variant 中顯示的變化版本選取器,並選擇第一個符合目前目標的變數選取器。因此,上述範例表示:

  • //src/sys/component/manager:bin 程式二進位檔及其依附元件應一律使用 release 變化版本建構 (注意:這個範例會在 gn gen 時間導致 is_debug=false 位於 args.gn 檔案中,因為本例中不會有 "release" 變數,請參閱通用變化版本瞭解原因)。

  • 主機二進位檔應在 "asan" 變數中建構。請注意,"host_asan" 不是變化版本描述元名稱,而是變數捷徑

  • blobfs 程式裝置二進位檔應一律使用 "thinlto" 變數建構,以便執行連結時間最佳化作業。

  • 所有其他裝置二進位檔都必須使用 "ubsan" 變數建構。

變化版本選取器

變化版本選取器是可以顯示在全域 select_variant 建構設定變數中的值。在基礎工具鍊中定義可連結的目標時,建構系統會使用這些參數來控制變化版本選取項目。

支援的值類型有三種:

  • 定義一組目標比對條件的範圍。該範圍的格式則如下所示:

    • variant:指定的變化版本描述元名稱,只有在目前的目標符合其餘範圍中定義的所有條件時,系統才會使用這個名稱。

    • label:如果定義,這必須是合格的 GN 標籤清單 (包含 :,但沒有工具鍊標籤,例如 //src/sys/foo:foo)。

    • name:如果已定義,則會列出 GN 標籤目標名稱 (例如 //src/sys/foo:bar 目標的名稱為「bar」)。

    • dir:如果已定義,則會列出 GN 標籤目錄路徑 (例如 //src/sys/foo:bar 目標的路徑為 "//src/sys/foo")。

    • output_name:如果定義,則會提供目標 output_name 值的清單 (預設為 target_name)。

    • target_type:如已定義,系統會顯示與目標類型相符的字串清單。有效值包括:"executable""test""loadable_module""shared_library" 等等。

    • testonly:如果定義,則會使用布林值。如果為 true,選取器會比對目標為 testonly=true。如果為 false,選取器會比對不含 testonly=true 的目標。

    • host:如果定義,則會使用布林值。如果為 true,選取器會比對主機工具鍊中的目標。如果為 false,選取器會在目標工具鍊中進行比對。

  • 包含簡單名稱 (例如 "asan") 的字串,可指向變化版本捷徑,這是現有選取器範圍值的別名。

    舉例來說,"coverage" 值等於下列範圍:

    {
      variant = "coverage"
      host = false
    }
    
  • 包含變化版本捷徑名稱和以目錄路徑 (例如 "thintlo/blobfs") 分隔的輸出名稱的字串。這是一種便利的格式,可避免寫入對等的範圍,如上述範例所示:

    {
      variant = "thinlto"
      host = false
      output_name = [ "blobfs" ]
    }
    

select_variant 清單中的選取器順序相當重要:第一個符合目前目標的選取器會勝出,並決定上述目標的建構方式。

變化版本捷徑

除了變化版本描述元之外,建構還設定了多個「捷徑」,這些快速鍵為幾個硬式編碼變數選取器範圍值。此版本新增了幾個硬式編碼項目,然後從已知變數清單中建立其他項目:

  • "host_asan" 捷徑定義為使用 "asan" 變數描述元建構主機二進位檔,技術上等同於下列選取器範圍值清單:

    # Definition for the `host_asan` variant shortcut
    
    [
      {
        variant = "asan"
        host = true
      }
    ]
    

    同樣地,也有 host_asan-ubsanhost_coveragehost_profile 和多個。

  • 每個變化版本描述元名稱都有對應的捷徑,專門用於裝置二進位檔。舉例來說,"ubsan" 捷徑等同於這個選取器範圍值清單:

    [
      {
        variant = "ubsan"
        host = false
      }
    ]
    

    這就是在 select_variant 中使用變化版本描述元名稱的原因,只會套用至裝置二進位檔,如下所示:

    # Applies the `ubsan` variant to device binaries, not host ones!
    select_variant = [
      "ubsan",
    ]
    
  • 同樣地,每個通用變化版本及其組合都會定義捷徑,而這個捷徑只會再次套用到裝置二進位檔。

    也就是說,假設 args.gn 中的 is_debug=true,以下規則會強制所有裝置二進位檔在發布模式下建構,但主機仍會在偵錯模式下建構。

    is_debug = true
    select_variant = [ "release" ]
    

    這相當於:

    is_debug = true
    select_variant = [
      {
        variant = "release"
        host = false
      }
    ]
    

    如要在發布模式下強制編譯主機二進位檔,可以使用明確的範圍值,因為此用途沒有捷徑,如下所示:

    is_debug = true
    select_variant = [
      {
        variant = "release"
        host = true
      }
    ]
    

變化版本目標重新導向

variant_target() 範本

//build/config/BUILDCONFIG.gn 中定義的 variant_target() 範本會實作核心建構變數選擇機制。

請勿直接從 BUILD.gn 檔案呼叫此範本,而是由 Fuchsia 版本針對 executable()loadable_module()shared_library() 和幾個對應至可連結目標 (也就是使用靜態連結器建立的目標) 定義的包裝函式範本叫用。

在建構圖表的每個工具鍊結構定義中,每個目標的用途都會將 select_variant 的內容與目標的屬性 (例如目標類型和幾個額外引數) 進行比對:

1) 針對目標計算「建構工具工具鍊」,這是將用於建構實際二進位檔及其依附元件的 GN 工具鍊執行個體。

2) 如果目前的工具鍊是建構工具工具鍊,只要照常建構目標即可。

3) 否則,請建立 group()copy() 目標,以重新導向 (即公開依附) 在建構工具鍊中的目標。區分為群組或副本,取決於 variant_target() 實作中完整記錄的細微條件,但請參閱以下子章節瞭解相關說明。

必須提供 copy() 目標以保留某些可連結目標的輸出位置,而在不需要時使用,系統會使用 group()

在大多數情況下,executable()loadable_module() 目標需使用 copy()shared_library() 目標則需要 group()

可連結變化版本二進位檔的輸出位置

GN 設定語言的一項重大設計限制,在少數例外情況下,特定的目標定義除了標籤外,對其依附元件無法得知任何依附元件。這會造成問題,因為在許多情況下,特定目標必須知道依附元件的輸出內容的位置,或這些依附元件實際上屬於哪種類型的目標。

我們以以下範例詳細說明:

  • 名為 //src/my/program:binexecutable() 目標,會產生名為 my_program 的 Fuuchsia 程式二進位檔。基於建構的運作方式,這會產生 ${root_build_dir}/exe.unstripped/my_program${root_build_dir}/my_program,以及一些子檔案 (在此忽略)。

  • 名為 //src/my/program:verify_binaryaction() 目標,用於剖析程式二進位檔,以便檢查或擷取其中的資訊 (假設這個目標會驗證匯入符號參照)。這個目標必須依附第一個,但還要找出二進位檔的輸出位置,例如:

    action("//src/my/program:verify_imports")
      script = "check-my-imports.py"
      deps = [ "//src/my/program:bin" ]
      inputs = [ get_label_info(deps[0], "root_out_dir") + "/my_program" ]
      ...
      |
      |  deps
      |
      v

    executable("//src/my/program:bin")
      output_name = "my_program"
      # outputs: [
        ${root_build_dir}/exe.unstripped/my_program,
        ${root_build_dir}/my_program,
      ]

在這裡,action() 可以使用 get_label_info("<label>", "root_out_dir") 做為目錄的目錄,並在動作本身中使用硬式編碼的 output_name 值,以猜測程式二進位檔的位置。這違反抽象層,但基於 GN 的限制,這是必要的。

啟用建構變數後,二進位檔目標的實際輸出位置會根據 select_variant 而改變。如果使用簡單的 group() 實作變化版本重新導向,圖表就會變成:

    action("//src/my/program:verify_imports")
      script = "check-my-imports.py"
      deps = [ "//src/my/program:bin" ]
      inputs = [ get_label_info(deps[0], "root_out_dir") + "/my_program" ]
      ...
      |
      |  deps
      |
      v

    group("//src/my/program:bin")
      |
      |  public_deps
      |
      v

    executable("//src/my/program:bin(//build/toolchain/fuchsia:x64-asan")
      output_name = "my_program"
      # outputs: [
      #   ${root_build_dir}/x64-asan/exe.unstripped/my_program,
      #   ${root_build_dir}/x64-asan/my_program,
      # ]

問題在於頂層動作中的 inputs 值未變更,因此其指令會嘗試在舊位置 (${root_build_dir}/my_program) 尋找程式二進位檔,而不是新位置 (${root_build_dir}/x64-asan/my-program)。兩個版本都會使用過時的構件,或是因缺少檔案而失敗。

所以在動作本身中剖析 select_variant 的成本太高,所以如為可執行和可載入的模組目標,就需要 copy() 目標 (而非 group()),才能確保未去除的二進位檔複製到原始位置。圖表會變為:


    action("//src/my/program:verify_imports")
      script = "check-my-imports.py"
      deps = [ "//src/my/program:bin" ]
      inputs = [ get_label_info(deps[0], "root_out_dir") + "/my_program" ]
      ...
      |
      |  deps
      |
      v

    copy("//src/my/program:bin")
      outputs = [ "${root_build_dir}/my_program" ]
      sources = [ "${root_build_dir}/x64-asan/my_program" ]
      |
      |  public_deps
      |
      v

    executable("//src/my/program:bin(//build/toolchain/fuchsia:x64-asan")
      output_name = "my_program"
      # outputs: [
        ${root_build_dir}/x64-asan/exe.unstripped/my_program,
        ${root_build_dir}/x64-asan/my_program,
      ]

透過這項設定,建構作業會一直成功,且動作指令一律會處理正確的二進位檔。

以上所有操作都會在建構中自動完成。最終的效果是,依附於依附元件是否由特定變數建構而成,至少可以依賴輸出位置保持穩定,至少適用於未去除的二進位檔路徑。

ELF 共用程式庫的輸出位置

待定

特殊的 novariant 描述元

待定

特殊全域變數

host_toolchainhost_out_dir 全域變數

待定

zircon_toolchain 變數

待定

variant() 範本

待定