Fuchsia build 中的 build 变体

摘要

本文档介绍了“build 变体”的实现。“build 变体”是 Fuchsia 构建系统的一项功能,用于构建主机和设备二进制文件的插桩版本或特别优化版本。

读者必须熟悉 GN toolchain() 实例并已阅读以下文档:

build 变体概览

Fuchsia build 定义了多种类型的 build 变体,例如:

  • asanubsan 变体分别用于通过 Clang 的 Address Sanitizer未定义的行为排错程序构建机器代码。甚至有一种结合了这两种方式的 asan-ubsan 变体。

  • coverage 变体用于在启用了 Clang 基于插桩的性能剖析的情况下构建机器代码,以支持代码覆盖率收集。

  • profile 变体也用于构建插桩代码,但用于支持配置文件引导的优化。

  • thinltolto 变体用于构建启用了链接时优化的二进制文件。

  • gcc 变体用于使用 GCC 编译器(而不是 Clang)构建 Zircon 内核的某些部分(这对于消除可能会以非常重要的方式影响内核的细微机器代码生成问题非常有用)。

  • releasedebug 变体,用于替换默认编译模式,默认编译模式由 args.gn 中的 is_debug build 配置变量的值决定。

  • 满足特殊需求的几个其他变体,这些变体全部在 //build/config/BUILDCONFIG.gn 文件中定义,遵循本文档其余部分所述的惯例。

一般来说,单个 build 变体模型:

  • 一组额外配置,用于定义在构建变体二进制文件及其依赖项时要应用的编译器、编译器或链接器标志。

  • 一组将添加到 build 图中的最终变体二进制文件目标(例如可执行文件、可加载模块,甚至有时是共享库)的可选隐式依赖项。

例如,让我们以“asan”build 变体为例,它用于启用 Clang 的 Address Sanitizer 支持。在实际使用中,构建启用 Address Sanitizer 的 Fuchsia 可执行程序至少需要:

  • 在构建可执行文件及其所有依赖项(在 Fuchsia 的情况下,包括 C 库)时,将 -fsanitize=address 标记同时传递给 Clang 编译器和链接器。

  • 在运行时可用 Asan 运行时 (libclang_rt.asan.so) 以及它自己的依赖项(即 libc++.solibc++abi.solibunwind.so 二进制文件的特殊预构建版本)。

基础工具链和变体工具链

build 变体始终应用于特定的“基本”工具链,该工具链会提供由变体本身增强的默认设置。这会创建一个新的 GN toolchain() 实例(称为“变体工具链”),该实例有自己的 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 工具链,那么它的所有变体工具链也会有 shlib 工具链。最后,可以将一个变体应用于多个基本工具链。

例如,//build/toolchain:host_x64-asan//build/toolchain/fuchsia:x64-asan 是通过将同一 asan 变体应用于用于构建主机和 Fuchsia 设备二进制文件的基本工具链创建的变体工具链。

后者还会将 //build/toolchain/fuchsia:x64-asan-shared 作为 shlib 工具链,以生成基于 ELF 的共享库。

必须在 build 中使用 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 变体的属性。这些范围通过 //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 中使用。如果省略 name,则 configs 不得为空,并将用于派生名称(通过用短划线连接名称)。

  • tags:自由格式字符串的可选列表,用于描述变体的属性(请参阅工具链和变体标记

  • toolchain_args:可选范围,此范围中定义的每个变量都会替换此变体的工具链上下文中的 build 参数。

  • host_onlytarget_only:可选范围,可包含上述任何字段。这些值分别仅用于主机或目标(即设备)工具链。此处包含的任何字段也不应也在外部范围内。

示例如下:

具有单个配置的变体描述符示例

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

上面的范围定义了一个名为 "lto" 的变体描述符(由于范围内没有 name 键,因此这个名称是从 configs 中的值推导而来,而这里的值只包含一个项)。

应用此变体将添加 //build/config/lto:lto 配置(在 //build/config/lto/BUILD.gn 中定义),并且该文件还应包含一个 //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
  }
}

此变体描述符已明确命名,不会添加任何配置或依赖项。另一方面,它可确保将全局 build 配置变量 is_debug 设置为 false,从而更改在相应的变体工具链上下文中定义的默认配置的数量。

通用变体

构建系统的一个鲜为人知的功能称为“通用变体”。这些是与其他已知变体组合的其他变体描述符,其工作原理如下:

  • 如果在 args.gn 中设置了 is_debug=false,则意味着所有二进制文件都应在进行最大优化的情况下构建,那么 "debug" 变体描述符由该 build 定义。这样,您就可以根据需要在调试模式下构建特定目标。

  • 同样,如果 is_debug=true(默认值),则 "release" 变体描述符由 build 定义。这样就可以构建特定目标,并在必要时进行全面优化。

  • 此外,上述通用变体会通过 build 自动与所有其他已知的变体描述符组合。例如,如果为 is_debug=false,则 build 还将创建 "asan-debug""ubsan-debug""thinlto-debug" 等。如果为 is_debug=true,则将改为定义 "asan-release""ubsan-release""thinlto-release" 等。

请注意,这些变体描述符由 build 根据 is_debug 的值有条件地定义。也就是说,当 is_debug=false 时,不存在 "release" 变体及其组合;当 is_debug=true 时,不存在 "debug" 变体及其组合!

toolchain_variant 全局变量

当位于 BUILD.gn*.gni 文件中时,全局 toolchain_variant 变量可用于检索 current_toolchain 的变体相关信息。此范围具有以下架构:

  • name:build 变体描述符的名称。在基本工具链的上下文中,这是一个空字符串,否则为用于创建当前 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:一个布尔值,如果当前工具链具有用于构建 ELF 共享库的 shlib 工具链(例如 //build/toolchain/fuchsia:x64),或者位于此类工具链(例如 //build/toolchain/fuchsia:x64-shared)中,则该值为 true。

  • configsremove_common_configsremove_shared_configs:指向 config() 项的 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:一个布尔值,true如果此工具链是由basic_toolchain()模板创建,因此未使用 GN 内对 C/C++ 和 Rust 的任何内置支持。它只有 copyaction 目标。

目标定义很少使用此全局变量的内容根据当前工具链上下文更改其配置。这主要发生在低级别目标中,例如 C 库、内核工件或并非以预构建形式提供的插桩运行时支持。

工具链变体 libprefix

为了能够在单个 Fuchsia 软件包中混合使用经过插桩和非插桩的二进制文件,构建系统必须执行特殊步骤:

  • 使用插桩变体工具链构建的共享库必须安装到 "lib/<variant>/"(而不是默认的 "lib/" 位置)。

  • 可执行二进制文件必须使用链接器参数(例如 "-Wl,-dynamic-linker=<variant>/ld.so.1")进行编译,该参数会替换默认值("ld.so.1",已在 Fuchsia clang 预构建工具链二进制文件中硬编码)。

  • 一种特殊情况是,模糊 build 变体会对库子目录使用非模糊 build 变体名称。

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 构建系统支持选择要启用的 build 变体,以及要将这些变体应用于哪些具体目标或目标组。可以通过在 build 配置文件 (args.gn) 中定义 select_variant 变量来完成此操作。请参考以下示例:

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

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

列表中的每个值都是一个表达式,称为变体选择器,该选择器可以是范围或字符串,用于配置 build 如何将变体应用于不同的目标集。

如果 select_variant 已定义且不是空列表,其值将用于确定如何构建可链接的目标,例如可执行文件、可加载模块和共享库(位于基本工具链上下文的构建图中),及其所有依赖项。

系统会按顺序比较 select_variant 中显示的变体选择器,并选择与当前目标匹配的第一个选择器。因此,上面的示例意味着:

  • //src/sys/component/manager:bin 程序二进制文件及其依赖项应始终使用 release 变体进行构建(注意:此示例会在 gn gen 时导致错误,即 is_debug=falseargs.gn 文件中,因为在这种情况下 "release" 变体不存在,请参阅通用变体以了解原因)。

  • 主机二进制文件应在 "asan" 变体中构建。请注意,"host_asan" 不是变体描述符名称,而是变体快捷方式

  • blobfs 程序设备二进制文件应始终使用 "thinlto" 变体进行构建,该变体会执行链接时优化。

  • 所有其他设备二进制文件都应使用 "ubsan" 变体构建。

变体选择器

变体选择器是一个可以出现在全局 select_variant build 配置变量中的值。在基础工具链的上下文中定义可链接的目标时,构建系统使用它们来控制变体选择。

支持三种类型的值:

  • 为一组目标定义一组匹配条件的范围。该范围的格式如下:

    • 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 列表中选择器的顺序很重要:第一个与当前目标匹配的选择器胜出,并确定如何构建上述目标。

变体快捷方式

除了变体描述符之外,build 还设置了许多“快捷方式”,这些快捷方式是一些硬编码变体选择器范围值的命名别名。build 会添加一些硬编码变体,然后根据已知变体列表创建其他变体:

  • "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 变体选择机制。

不应直接从 BUILD.gn 文件中调用此模板,相反,它会由 Fuchsia build 为 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 的 Fuchsia 程序二进制文件。由于构建的工作原理,这会生成 ${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 的限制,这是必要的。

启用 build 变体后,二进制文件目标的实际输出位置将根据 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,
      ]

使用此设置时,构建始终会成功,并且操作命令始终处理正确的二进制文件。

所有这些操作都是在 build 中自动完成的。最终结果是,依赖项就不需要关心其依赖项是否使用特定变体进行构建,它们可以依赖于输出位置是稳定的,至少对于未剥离的二进制文件路径而言是如此。

ELF 共享库的输出位置

待定

特殊的 novariant 描述符

待定

特殊全局变量

host_toolchainhost_out_dir 全局变量

待定

zircon_toolchain 变量

待定

variant() 模板

待定