build 组件

本文档演示了如何构建和测试组件,重点介绍了定义软件包、组件及其测试的最佳实践。

概念

在构建组件之前,您应该了解以下概念:

软件包 是 Fuchsia 上的软件分发单位。软件包是一组文件,其中包含与软件包基本路径相关的关联路径。例如,一个软件包可能包含路径为 bin/hello_world 的 ELF 二进制文件和路径为 data/config.json 的 JSON 文件。必须将文件分组到软件包中,才能将这些文件推送到设备。

< Fuchsia 上的所有软件(内核映像和用户模式引导加载程序除外)都定义为组件。

组件由 组件清单 定义。组件通常包含其他文件,例如它们在运行时需要的可执行文件和数据资源。

无论是构建生产软件还是编写测试,开发者都必须以软件包和组件的形式定义其软件。

在运行时, 组件实例 将其包的内容视为路径 /pkg 下的只读文件。在同一软件包中定义两个或更多组件并不会授予每个组件访问其他组件的功能的权限。不过,它可以向一个组件保证另一个组件可用。因此,如果某个组件尝试启动另一个组件的实例(例如在集成测试中),将这两个组件打包在一起会很有益。

组件可通过多种方式实例化,但所有方式都以某种方式指定了其 <0x0通常,组件是通过指定其包名称和其组件清单在包中的路径来启动的,使用 fuchsia-pkg:// 方案

组件清单

组件清单是用于对组件声明进行编码的文件,通常作为软件包的一部分进行分发。二进制格式是包含组件声明的持久性 FIDL 文件。清单声明了有关组件的程序二进制文件和所需功能的信息。

以下是一个简单的“Hello, World”组件的清单文件示例:

{
    // Information about the program to run.
    program: {
        // Use the built-in ELF runner to run platform-specific binaries.
        runner: "elf",
        // The binary to run for this component.
        binary: "bin/hello",
        // Program arguments
        args: [
            "Hello",
            "World!",
        ],
    },

    // Capabilities used by this component.
    use: [
        { protocol: "fuchsia.logger.LogSink" },
    ],
}

清单分片

某些功能集合代表了系统中许多组件通用的使用情形要求,例如日志记录。为了简化在组件中包含这些功能,组件框架支持将这些功能抽象为可包含在主清单文件中的清单分片。从概念上讲,这类似于 C 编程语言中的 #include 指令。

以下清单与上一个示例中的清单等效,但将日志记录功能替换为清单分片 include

{
    // Include capabilities for the syslog library
    include: [ "syslog/client.shard.cml" ],

    // Information about the program to run.
    program: {
        // Use the built-in ELF runner to run platform-specific binaries.
        runner: "elf",
        // The binary to run for this component.
        binary: "bin/hello-world",
        // Program arguments
        args: [
            "Hello",
            "World!",
        ],
    },
}

相对路径

"//" 开头的包含路径相对于您正在处理的源树的根目录。对于不以 "//" 开头的 include 路径,构建系统将尝试从 Fuchsia SDK 中解析它们。

分片间依赖项

如果一个清单分片向清单添加了一个子项,而第二个清单分片添加了第二个依赖于第一个子项的子项,那么从第一个子项到第二个子项的 offer 声明将导致清单验证错误(如果第二个分片包含在清单中,而第一个分片未包含在清单中),因为该 offer 将引用一个不存在的子项。

// echo_server.shard.cml
{
    children: [ {
        name: "echo_server",
        url: "fuchsia-pkg://fuchsia.com/echo_server#meta/echo_server.cm",
    } ],
}
// echo_client.shard.cml
{
    children: [
        {
            name: "echo_client",
            url: "fuchsia-pkg://fuchsia.com/echo_client#meta/echo_client.cm",
        }
    ],
    offer: [ {
        // This offer will cause manifest validation to fail if
        // `echo_client.shard.cml` is included in a manifest without
        // `echo_server.shard.cml`.
        protocol: "fuchsia.examples.Echo",
        from: "echo_server",
        to: "echo_client",
    } ],
}

为解决此问题,可以设置优惠的 source_availability 字段,以告知清单编译可以接受优惠来源缺失。如果设置为 unknown,则系统会对商品声明执行以下操作:

  • 如果存在 from 来源:将库存状况设置为 required
  • 如果 from 来源不存在:则将库存状况设置为 optional,并将相应商品的来源重写为 void
// echo_client.shard.cml
{
    children: [
        {
            name: "echo_client",
            url: "fuchsia-pkg://fuchsia.com/echo_client#meta/echo_client.cm",
        }
    ],
    offer: [
        {
            // If `echo_server.shard.cml` is included in this manifest, then
            // `echo_client` can access the `fuchsia.examples.Echo` protocol from
            // it.
            //
            // If `echo_server.shard.cml` is not included in this manifest, then
            // `echo_client` will be offered the protocol with a source of
            // `void` and `availability == optional`. `echo_client` must consume
            // the capability optionally to not fail route validation.
            protocol: "fuchsia.examples.Echo",
            from: "echo_server",
            to: "echo_client",
            source_availability: "unknown",
        }
    ],
}

同样,也可以设置相应优惠的 target_availablility 字段,以告知清单编译,该优惠的目标可以缺失。 如果设置为 unknown,则系统会对商品声明执行以下操作:

  • 如果存在 to 目标:则按照之前的可用性(和来源可用性规则)发出 offer
  • 如果 to 目标不存在:系统会从已编译的清单中完全省略 offer

如需详细了解 availability 的运作方式,请参阅可用性

客户端库包含

如上所示,组件清单支持“include”语法,该语法允许引用一个或多个清单分片作为其他清单内容的来源。某些依赖项(例如库)会假定依赖组件在运行时具有某些可用功能。例如,C++ Syslog 库就做出了这样的假设。

如果您要构建客户端库,可以在 BUILD.gn 文件中使用 expect_includes 声明这些必需的依赖项。例如,请考虑以下假设文件 //sdk/lib/fonts/BUILD.gn

import("//tools/cmc/build/expect_includes.gni")

# Client library for components that want to use fonts
source_set("font_provider_client") {
  sources = [
    "font_provider_client.cc",
    ...
  ]
  deps = [
    ":font_provider_client_includes",
    ...
  ]
}

expect_includes("font_provider_client_includes") {
  includes = [
    "client.shard.cml",
  ]
}

这会设置一个 build-time 要求,即依赖清单必须包含预期的清单分片:

{
    include: [
        "//sdk/lib/fonts/client.shard.cml",
    ]
    ...
}

包含路径是相对于源根目录解析的。 允许传递性 include(include 的 include)。 不允许循环引用。

为分片命名时,请勿重复完整路径中的信息。在上面的示例中,如果将分片命名为 fonts.shard.cml,则完整路径将为 sdk/lib/fonts/fonts.shard.cml,这会造成重复。该文件名为 client.shard.cml,表示它将由字体 SDK 库的客户端使用。

组件软件包 GN 模板

GN 是 Fuchsia 使用的元构建系统。Fuchsia 通过定义模板来扩展 GN。模板提供了一种向 GN 的内置目标类型添加内容的方式。

Fuchsia 定义了以下 GN 模板来定义软件包和组件:

以下是一个假设的软件包,其中包含一个运行 C++ 程序的组件:

import("//build/components.gni")

executable("my_program") {
  sources = [ "my_program.cc" ]
}

fuchsia_component("my_component") {
  manifest = "meta/my_program.cml"
  deps = [ ":my_program" ]
}

fuchsia_package("my_package") {
  deps = [ ":my_component" ]
}

请注意以下细节:

  • 导入 "//build/components.gni" 以访问与软件包、组件和测试相关的所有模板。
  • fuchsia_component() 模板声明了组件。它依赖于程序二进制文件(在本例中为 executable()),并且需要指向组件清单文件的 manifest
  • 组件名称和软件包名称均派生自其目标名称。 在上面的示例中,这些名称组合在一起,形成了用于启动组件的网址:fuchsia-pkg://fuchsia.com/my_package#meta/my_component.cm

特定于语言的组件示例

下面是一些基本示例,展示了如何使用各种常用语言定义包含单个组件(用于启动程序)的软件包。系统会假定所引用的源文件和组件清单位于指定的路径中。

C++

import("//build/components.gni")

executable("bin") {
  output_name = "my_program"
  sources = [ "main.cc" ]
}

fuchsia_component("my_component") {
  manifest = "meta/my_component.cml"
  deps = [ ":bin" ]
}

fuchsia_package("my_package") {
  deps = [ ":my_component" ]
}

Rust

import("//build/rust/rustc_binary.gni")
import("//build/components.gni")

rustc_binary("bin") {
  output_name = "my_program"
  sources = [ "src/main.rs" ]
}

fuchsia_component("my_component") {
  manifest = "meta/my_component.cml"
  deps = [ ":bin" ]
}

fuchsia_package("my_package") {
  deps = [ ":my_component" ]
}

Go

import("//build/go/go_binary.gni")
import("//build/components.gni")

go_binary("bin") {
  output_name = "my_program"
  sources = [ "main.go" ]
}

fuchsia_component("my_component") {
  manifest = "meta/my_component.cml"
  deps = [ ":bin" ]
}

fuchsia_package("my_package") {
  deps = [ ":my_component" ]
}

包含单个组件的软件包

软件包是分发单元。如果您需要确保多个组件始终共存,或者希望能够通过更新单个软件包来同时更新多个组件,那么在同一软件包中定义多个组件会很有益。

此模式也常用于创建封闭式集成测试。例如,如果两个组件之间的集成测试中,一个组件是另一个组件中实现的服务客户端,则该测试将同时包含客户端和服务器组件。

不过,您可能经常会定义只需要单个组件的软件包。在这种情况下,您可以方便地使用 fuchsia_package_with_single_component() 模板。此模板将 fuchsia_package()fuchsia_component() 融合在一起。

C++

import("//build/components.gni")

executable("rot13_encoder_decoder") {
  sources = [ "rot13_encoder_decoder.cc" ]
}

fuchsia_package_with_single_component("rot13") {
  manifest = "meta/rot13.cml"
  deps = [ ":rot13_encoder_decoder" ]
}

Rust

import("//build/rust/rustc_binary.gni")
import("//build/components.gni")

rustc_binary("rot13_encoder_decoder") {
  sources = [ "src/rot13_encoder_decoder.rs" ]
}

fuchsia_package_with_single_component("rot13") {
  manifest = "meta/rot13.cml"
  deps = [ ":rot13_encoder_decoder" ]
}

Go

import("//build/go/go_binary.gni")
import("//build/components.gni")

go_binary("rot13_encoder_decoder") {
  sources = [ "rot13_encoder_decoder.go" ]
}

fuchsia_package_with_single_component("rot13") {
  manifest = "meta/rot13.cml"
  deps = [ ":rot13_encoder_decoder" ]
}

测试软件包 GN 模板

测试软件包是指包含至少一个作为测试启动的组件的软件包。测试软件包使用 fuchsia_test_package.gni 定义。此模板可用于定义各种测试,但最适合用于集成测试,即除了测试本身之外,其他组件也参与测试的测试。如需了解专门用于单元测试的模板,请参阅单元测试

import("//build/components.gni")

executable("my_test") {
  sources = [ "my_test.cc" ]
  testonly = true
  deps = [
    "//src/lib/fxl/test:gtest_main",
    "//third_party/googletest:gtest",
  ]
}

fuchsia_component("my_test_component") {
  testonly = true
  manifest = "meta/my_test.cml"
  deps = [ ":my_test" ]
}

executable("my_program_under_test") {
  sources = [ "my_program_under_test.cc" ]
}

fuchsia_component("my_other_component_under_test") {
  manifest = "meta/my_component_under_test.cml"
  deps = [ ":my_program_under_test" ]
}

fuchsia_test_package("my_integration_test") {
  test_components = [ ":my_test_component" ]
  deps = [ ":my_other_component_under_test" ]
  test_specs = {
    environments = [ vim3_env ]
  }
}

group("tests") {
  deps = [ ":my_integration_test" ]
  testonly = true
}

请注意以下细节:

  • 此示例定义了 "my_test_component",假设它实现了使用一些常见测试框架(例如 C++ Googletest、Rust Cargo 测试等)编写的测试。
  • 测试与相关组件 "my_other_component_under_test" 一起打包。这可以是测试组件所需的模拟服务提供方,也可以是测试需要调用的其他组件。将这些组件打包在一起可确保在测试运行时,依赖组件可用于启动,并且构建版本与测试版本相同。
  • environments 参数使 fuchsia_test_package() 可以选择性地采用 test_spec.gni 参数并替换默认测试行为。在此示例中,相应测试已配置为在 VIM3 设备上运行。
  • 最后,此示例定义了一个 group() 来包含所有测试(我们只有一个测试)。这是在整个源代码树中组织目标的推荐做法

由于 GN 存在限制fuchsia_test_package() 中的任何 test_component 目标都必须在与测试软件包目标相同的 BUILD.gn 文件中定义。您可以通过 fuchsia_test() 间接解决此行为。

在一个 BUILD.gn 文件中,定义以下内容:

# Let this be //foo/BUILD.gn
import("//build/components.gni")

executable("my_test") {
  sources = [ "my_test.cc" ]
  testonly = true
  deps = [
    "//src/lib/fxl/test:gtest_main",
    "//third_party/googletest:gtest",
  ]
}

fuchsia_component("my_test_component") {
  testonly = true
  manifest = "meta/my_test.cml"
  deps = [ ":my_test" ]
}

fuchsia_test("my_test_component_test") {
  package = "//bar:my_test_package"
  component = ":my_test_component"
}

group("tests") {
  testonly = true
  deps = [ ":my_test_component_test" ]
}

然后,您可以在其他位置将 fuchsia_component() 目标添加到 fuchsia_package() 目标的 deps 中。

# Let this be //bar/BUILD.gn
import("//build/components.gni")

fuchsia_package("my_test_package") {
  testonly = true
  deps = [ "//foo:my_test_component" ]
}

单元测试

由于单元测试非常常见,因此 build 系统提供了两个简化的 GN 模板:

  • fuchsia_unittest_component.gni 定义要作为测试运行的组件,并可以选择自动生成基本组件清单,该清单随后必须包含在软件包中。
  • fuchsia_unittest_package.gni 定义了一个包含单个组件的软件包,该软件包将作为测试运行,是单个 fuchsia_unittest_component() 目标与 fuchsia_test_package() 配对的简写形式。

使用清单的单元测试

以下示例演示了如何构建测试可执行文件,以及如何为测试定义软件包和组件。

C++

import("//build/components.gni")

executable("my_test") {
  sources = [ "test.cc" ]
  deps = [
    "//src/lib/fxl/test:gtest_main",
    "//third_party/googletest:gtest",
  ]
  testonly = true
}

fuchsia_unittest_package("my_test") {
  manifest = "meta/my_test.cml"
  deps = [ ":my_test" ]
}

Rust

import("//build/rust/rustc_test.gni")
import("//build/components.gni")

rustc_test("my_test") {
  sources = [ "test.rs" ]
  testonly = true
}

fuchsia_unittest_package("my_test") {
  manifest = "meta/my_test.cml"
  deps = [ ":my_test" ]
}

Go

import("//build/go/go_test.gni")
import("//build/components.gni")

go_test("my_test") {
  sources = [ "test.go" ]
  testonly = true
}

fuchsia_unittest_package("my_test") {
  manifest = "meta/my_test.cml"
  deps = [ ":my_test" ]
}

使用 fx test 通过 GN 目标名称或完整组件网址启动测试组件:

GN Target

fx test my_test

组件网址

fx test fuchsia-pkg://fuchsia.com/my_test#meta/my_test.cm

使用生成的清单进行单元测试

上述示例为测试指定了清单。不过,单元测试可能不需要任何特定功能。

以下是一个执行 ROT13 加密和解密的测试示例。受测算法是纯逻辑,可以完全隔离地进行测试。如果我们为这些测试编写清单,则该清单只会包含要执行的测试二进制文件。在这种情况下,我们只需指定测试可执行文件的路径,模板就会为我们生成简单的清单。

C++

import("//build/components.gni")

executable("rot13_test") {
  sources = [ "rot13_test.cc" ]
  deps = [
    "//src/lib/fxl/test:gtest_main",
    "//third_party/googletest:gtest",
  ]
  testonly = true
}

fuchsia_unittest_package("rot13_test") {
  deps = [ ":rot13_test" ]
}

Rust

import("//build/rust/rustc_test.gni")
import("//build/components.gni")

rustc_test("rot13_test") {
  sources = [ "rot13_test.rs" ]
  testonly = true
}

fuchsia_unittest_package("rot13_test") {
  deps = [ ":rot13_test" ]
}

Go

import("//build/go/go_test.gni")
import("//build/components.gni")

go_test("rot13_test") {
  sources = [ "rot13_test.go" ]
  testonly = true
}

fuchsia_unittest_package("rot13_test") {
  deps = [ ":rot13_test" ]
}

您可以使用以下命令找到生成的组件清单文件:

fx gn outputs $(fx get-build-dir) //some/path/to/build/file:unittest target_component_generated_manifest

如需直接打印,请执行以下操作:

fx build && cat $(fx get-build-dir)/$(fx gn outputs $(fx get-build-dir) //some/path/to/build/file:unittest target_component_generated_manifest)

使用 fx test 通过 GN 目标名称或完整组件网址启动测试组件:

GN Target

fx test rot13_test

组件网址

fx test fuchsia-pkg://fuchsia.com/rot13_test#meta/rot13_test.cm

单个软件包中的多个单元测试

如需将多个单元测试组件打包在一起,请使用 fuchsia_unittest_component() 规则,而不是 fuchsia_unittest_package(),并将它们收集到 fuchsia_test_package() 中。这样,您就可以使用 fx test <package_name> 在单个软件包中运行所有测试组件,而无需单独执行它们。

以下示例创建了一个包含两个单独测试组件 rot13_decoder_testrot13_encoder_test 的单个测试软件包 rot13_tests

C++

import("//build/components.gni")

executable("rot13_decoder_bin_test") {}

executable("rot13_encoder_bin_test") {}

fuchsia_unittest_component("rot13_decoder_test") {
  deps = [ ":rot13_decoder_bin_test" ]
}

fuchsia_unittest_component("rot13_encoder_test") {
  deps = [ ":rot13_encoder_bin_test" ]
}

fuchsia_test_package("rot13_tests") {
  test_components = [
    ":rot13_decoder_test",
    ":rot13_encoder_test",
  ]
}

Rust

import("//build/rust/rustc_test.gni")
import("//build/components.gni")

rustc_test("rot13_decoder_bin_test") {}

rustc_test("rot13_encoder_bin_test") {}

fuchsia_unittest_component("rot13_decoder_test") {
  deps = [ ":rot13_decoder_bin_test" ]
}

fuchsia_unittest_component("rot13_encoder_test") {
  deps = [ ":rot13_encoder_bin_test" ]
}

fuchsia_test_package("rot13_tests") {
  test_components = [
    ":rot13_decoder_test",
    ":rot13_encoder_test",
  ]
}

Go

import("//build/go/go_test.gni")
import("//build/components.gni")

go_test("rot13_decoder_test") {}

go_test("rot13_encoder_test") {}

fuchsia_unittest_component("rot13_decoder_test") {
  deps = [ ":rot13_decoder_bin_test" ]
}

fuchsia_unittest_component("rot13_encoder_test") {
  deps = [ ":rot13_encoder_bin_test" ]
}

fuchsia_test_package("rot13_tests") {
  test_components = [
    ":rot13_decoder_test",
    ":rot13_encoder_test",
  ]
}

使用 fx test 启动软件包中的所有测试组件,只需提供 GN 目标名称:

fx test rot13_tests

测试驱动型开发

fx smoke-test 命令会自动检测构建系统已知的所有因签出中的更改而受到影响的测试。请尝试以下操作:

fx -i smoke-test --verbose

在上述命令中,--verbose 会输出 fx smoke-test 认为哪些测试会受到您的更改的影响,而 -i 会在您每次保存更改时自动重复执行此命令。对于测试驱动的开发,请尝试在单独的 shell 中启动此命令,并在您处理代码时观看代码重建和重新测试。

fx smoke-test 最适合与密封测试软件包搭配使用。如果测试软件包包含其中任何测试的所有依赖项,则该软件包是密封的。 也就是说,任何会影响此测试结果的代码更改都应需要重新构建相应测试的软件包。

其他打包资源

在上面的示例中,我们展示了从软件包到生成可执行文件的目标的 deps 路径可确保可执行文件包含在软件包中。

有时,您需要添加其他文件。下面我们演示了如何使用两个 resource.gni 模板:resource()resource_group()resource_tree()

示例:字体

import("//build/components.gni")

resource("roboto_family") {
  sources = [
    "Roboto-Black.ttf",
    "Roboto-Bold.ttf",
    "Roboto-Light.ttf",
    "Roboto-Medium.ttf",
    "Roboto-Regular.ttf",
    "Roboto-Thin.ttf",
  ]
  outputs = [ "data/fonts/{{source_file_part}}" ]
}

fuchsia_component("text_viewer") {
  ...
  deps = [
    ":roboto_family",
    ...
  ]
}

在上面的示例中,提供了六个要打包在 data/fonts/ 下的文件,从而生成路径 data/fonts/Roboto-Black.ttfdata/fonts/Roboto-Bold.ttf 等。destination 的格式接受 GN 来源扩展占位符

然后,定义一个依赖于字体的文本查看器组件。在此示例中,文本查看器实现使用 Roboto 字体呈现文本。该组件可以在其沙盒中读取路径 /pkg/data/fonts/... 下的指定字体。

示例:使用黄金数据的集成测试

在此示例中,我们定义了一个假设的服务,用于缩小 JSON 文件。据说该服务会接收包含 JSON 文本的缓冲区,并返回包含相同 JSON 数据但空白字符较少的缓冲区。我们提供了一个集成测试,其中测试组件充当精简器组件的客户端,并将要精简的给定 JSON 文件的结果与已知的好结果(或“黄金文件”)进行比较。

import("//build/components.gni")

fuchsia_component("minifier_component") {
  ...
}

fuchsia_package("minifier_package") {
  ...
}

resource("testdata") {
  sources = [
    "testdata/input.json",
    "testdata/input_minified.json",
  ]
  outputs = [ "data/{{source_file_part}}" ]
}

fuchsia_component("minifier_test_client") {
  testonly = true
  deps = [
    ":testdata",
    ...
  ]
  ...
}

fuchsia_test_package("minifier_integration_test") {
  test_components = [ ":minifier_test_client" ]
  deps = [ ":minifier_component" ]
}

请注意,我们将 resource() 依赖项放在测试组件上。从 build 系统的角度来看,资源依赖关系可能已放置在测试软件包上,并且 build 会产生相同的结果。不过,更好的做法是将依赖项放在需要它们的 build 目标上。这样,我们就可以在不同的测试软件包中重复使用相同的测试组件目标,例如针对不同的精简器组件进行测试,而测试组件的工作方式保持不变。

示例:使用 resource_group()

在上述示例中,所有路径都符合某种结构,因此我们可以为多个文件指定单个输出模式,甚至可以利用 GN 源扩展占位符。在下一个示例中,我们需要将不同的文件重命名为不同的目标路径以进行打包。

import("//build/components.gni")

resource_group("favorite_recipes") {
  files = [
    {
      source = "//recipes/spaghetti_bolognese.txt"
      dest = "data/pasta/spaghetti_bolognese.txt"
    },
    {
      source = "//recipes/creamy_carbonara.txt"
      dest = "data/pasta/carbonara.txt"
    },
    {
      source = "//recipes/creme_brulee.txt"
      dest = "data/dessert/creme_brulee.txt"
    },
    ...
  ]
}

我们的所有来源都位于一个目录中,但要打包到不同的目录中,有些甚至要打包到名称不同的目录中。为了表达这种相同的关系,我们可能需要与文件数量一样多的 resource() 目标。在这种情况下,应改用 resource_group(),如上所示。

示例:使用 resource_tree()

对于较大的文件集,使用 resource_group() 将每个源文件映射到目标文件路径可能会很麻烦。resource_tree() 提供了一种将源文件的目录树映射到软件包中目标目录下的相同层次结构的方法。以下示例将子目录 default_repo_files/ 复制到软件包目录 repo/(使用 sources 列表确保仅包含明确列出的文件)。

import("//build/components.gni")

resource_tree("default-repo") {
  sources_root = "default_repo_files"
  sources = [
    "keys/root.json",
    "keys/snapshot.json",
    "keys/targets.json",
    "keys/timestamp.json",
    "repository/1.root.json",
    "repository/1.snapshot.json",
    "repository/1.targets.json",
    "repository/root.json",
    "repository/snapshot.json",
    "repository/targets.json",
    "repository/timestamp.json",
  ]
  dest_dir = "repo"
}

resource()resource_group()resource_tree() 的底层行为是相同的。您可以自由选择自己喜欢的选项。

受限的功能

当新的组件清单功能正在积极开发中,或者某个功能仅面向小部分受众群体时,组件框架团队可能希望限制谁可以使用该功能。CML 编译器 (cmc) 通过组件 build 规则中的选择启用属性来控制对这些受限功能的访问权限。

如需使用受限功能,请添加 restricted_features 属性:

fuchsia_component("my-component") {
  manifest = "meta/my-component.cml"
  # This component opts-in to the restricted "allow_long_names" feature.
  restricted_features = [ "allow_long_names" ]
  deps = [ ... ]
}

受限功能的使用仅限于许可名单中的用户。 您必须在 //tools/cmc/build/restricted_features/BUILD.gn 中将您的组件添加到相应功能的许可名单中。

问题排查

本部分包含您在构建组件时可能会遇到的常见问题。

缺失的分片包括

如果您的 check_includes 操作缺少必需的清单分片include,则 build 会失败并显示以下错误

Error at ../../examples/components/echo_server/meta/echo_server.cml:
"../../examples/components/echo_server/meta/echo_server.cml" must include "../../sdk/lib/inspect/client.shard.cml".

当组件的依赖链中的某个库具有 expect_includes 要求,但组件清单中未找到所需的 include 时,就会发生此错误。请参考以下使用 Inspect 的示例:

C++

  1. 您的组件依赖于 //sdk/lib/inspect/component/cpp

    executable("bin") {
      output_name = "echo_server_cpp"
      sources = [ "main.cc" ]
    
      deps = [
        "//examples/components/routing/fidl:echo",
        "//sdk/lib/sys/cpp",
        # This library requires "inspect/client.shard.cml" 
        "//sdk/lib/inspect/component/cpp", 
        "//sdk/lib/async-loop:async-loop-cpp",
        "//sdk/lib/async-loop:async-loop-default",
      ]
    }
    
  2. //sdk/lib/inspect/component/cpp 依赖于 //sdk/lib/inspect:client_includes,后者是一条 expect_includes() 规则。

Rust

  1. 您的组件依赖于 //src/lib/diagnostics/inspect/runtime/rust

    rustc_binary("echo_server") {
      edition = "2021"
      deps = [
        "//examples/components/routing/fidl:echo_rust",
        # This library requires "inspect/client.shard.cml" 
        "//src/lib/diagnostics/inspect/runtime/rust", 
        "//src/lib/diagnostics/inspect/rust",
        "//src/lib/fuchsia",
        "//src/lib/fuchsia-component",
        "//third_party/rust_crates:anyhow",
        "//third_party/rust_crates:futures",
      ]
    
      sources = [ "src/main.rs" ]
    }
    
  2. //src/lib/diagnostics/inspect/runtime/rust 依赖于 //sdk/lib/inspect:client_includes,后者是一条 expect_includes() 规则。

如需解决此问题,请在组件清单中添加缺少的 include。例如:

{
    include: [
        // Add this required include 
        "inspect/client.shard.cml", 

        // Enable logging
        "syslog/client.shard.cml",
    ],

    // ...
}

如需详细了解所需 include 的来源,您可以使用 gn path 命令来探索依赖项路径:

fx gn path $(fx get-build-dir) my-component expect_includes target --with-data

该命令会输出类似如下所示的内容,其中显示了需要包含的库的路径:

C++

$ fx gn path $(fx get-build-dir) //examples/components/routing/cpp/echo_server //sdk/lib/inspect:client_includes --with-data
//examples/components/echo_server:bin --[private]-->
//sdk/lib/inspect/component/cpp --[data]-->
//sdk/lib/inspect:client_includes

Rust

$ fx gn path $(fx get-build-dir) //examples/components/routing/rust/echo_server //sdk/lib/inspect:client_includes --with-data
//examples/components/routing/rust/echo_server:bin --[public]-->
//examples/components/routing/rust/echo_server:bin.actual --[private]-->
//src/lib/diagnostics/inspect/runtime/rust:rust --[public]-->
//src/lib/diagnostics/inspect/runtime/rust:lib --[public]-->
//src/lib/diagnostics/inspect/runtime/rust:lib.actual --[private]-->
//sdk/lib/inspect:client_includes

未能验证清单

如果您的 cmc_validate_references 操作因以下错误而导致 build 失败,则表示您的组件清单包含对组件软件包中找不到的资源的引用

Error found in: //examples/components/echo/rust:rust-component_cmc_validate_references(//build/toolchain/fuchsia:x64)
    Failed to validate manifest: "obj/examples/components/echo/rust/cml/rust-component_manifest_compile/echo_rust.cm"
program.binary=bin/echo_example_oops but bin/echo_example_oops is not provided by deps!

Did you mean bin/echo_example?

Try any of the following:
...

当组件清单 program 块中的 binary 字段引用了 fuchsia_package() 中不存在的文件路径时,就会发生这种情况。

如需解决此问题,请验证以下各项:

  1. 组件清单中的引用路径输入正确。

    {
        // ...
    
        // Information about the program to run.
        program: {
            // Use the built-in ELF runner.
            runner: "elf",
    
            // The binary to run for this component. 
            binary: "bin/echo_example_oops", 
        },
    }
    
  2. 组件可执行目标是与 fuchsia_package() 相连的 deps 链的一部分:

    C++

    executable("bin") {
      output_name = "echo_example"
      sources = [ "main.cc" ]
    
      deps = [ ... ]
    }
    
    # Neither the component or package depend on ":bin" 
    fuchsia_component("component") {
      manifest = "meta/echo_example.cml"
      deps = [] 
    }
    
    fuchsia_package("package") {
      package_name = "echo_example"
      deps = [ ":component" ] 
    }
    

    Rust

    rustc_binary("echo_example") {
      edition = "2021"
      sources = [ "src/main.rs" ]
    
      deps = [ ... ]
    }
    
    # Neither the component or package depend on ":echo_example" 
    fuchsia_component("component") {
      manifest = "meta/echo_example.cml"
      deps = [] 
    }
    
    fuchsia_package("package") {
      package_name = "echo_example"
      deps = [ ":component" ] 
    }
    

静态功能分析器

Scrutiny 静态分析器在无法验证 组件拓扑 中的每个 功能路由 时,构建失败并出现以下错误:

Static Capability Flow Analysis Error:
The route verifier failed to verify all capability routes in this build.
...

Verification Errors:
[
  {
    "capability_type": "directory",
    "results": { ... }
  },
  {
    "capability_type": "protocol",
    "results": { ... }
  },
]

如果分析无法通过有效的 exposeoffer 组件清单声明链成功跟踪从功能来源到请求功能的组件的功能路由,就会发生此错误。

在以下示例中,错误是由于组件 /core/echo 请求使用 fuchsia.logger.LogSink 协议 use,但父级没有针对该功能的相应 offer

"errors": [
  {
    "using_node": "/core/echo",
    "capability": "fuchsia.logger.LogSink",
    "error": {
      "error": {
        "analyzer_model_error": {
          "routing_error": {
            "use_from_parent_not_found": {
              "moniker": {
                "path": [
                  {
                    "name": "core",
                    "collection": null,
                    "rep": "core"
                  },
                  {
                    "name": "echo",
                    "collection": null,
                    "rep": "echo"
                  }
                ]
              },
              "capability_id": "fuchsia.logger.LogSink"
            }
          }
        }
      },
      "message": "A `use from parent` declaration was found at `/core/echo` for `fuchsia.logger.LogSink`, but no matching `offer` declaration was found in the parent"
    }
  }
]

如需解决此问题,请查看 build 失败时提供的错误详细信息,找出路由错误的来源,然后在路由链中添加或更正无效的声明。在之前的示例错误中,应在父组件的清单中添加 offer

{
    // ...

    children: [
        // ...
        {
            name: "echo",
            url: "echo#meta/default.cm",
        },
    ],
    offer: [
        // ...
        { 
            protocol: "fuchsia.logger.LogSink", 
            from: "parent", 
            to: "#echo", 
        }, 
    ],
}

如需详细了解如何构建功能路由,请参阅连接组件