build 组件

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

概念

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

软件包是一组文件,这些文件具有相对于软件包基准的相关路径。例如,一个软件包可能包含路径 bin/hello_world 下的 ELF 二进制文件,以及路径 data/config.json 下的 JSON 文件。必须将文件分组到软件包中,才能将这些文件推送到设备。

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

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

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

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

组件可通过多种方式进行实例化,所有这些方式都以某种方式指定了其网址。通常,组件通过使用 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!",
        ],
    },
}

相对路径

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

分片间依赖项

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

// 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",
        }
    ],
}

如需详细了解 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",
  ]
}

这会设置一个构建时要求,要求依赖项清单包含预期的清单分片:

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

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

为分片命名时,请勿重复使用完整路径中的字符。在上面的示例中,如果将分片命名为 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" ]
}

单元测试

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

  • fuchsia_unittest_component.gni 用于定义要作为测试运行的组件,并提供自动生成基本组件清单的选项,该清单必须包含在软件包中。
  • fuchsia_unittest_package.gni 用于定义包含一个要作为测试运行的组件的软件包,是与 fuchsia_test_package() 搭配使用的单个 fuchsia_unittest_component() 目标的简写形式。

包含清单的单元测试

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

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 目标

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 目标

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",
  ]
}

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

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",
    ...
  ]
}

在上面的示例中,提供了 6 个文件以打包到 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() 依赖项放置在测试组件上。从构建系统的角度来看,资源依赖项可以放置在测试软件包中,构建过程也会产生相同的结果。不过,最好将依赖项放在需要它们的目标上。这样,我们就可以在其他测试软件包中重复使用相同的测试组件目标,例如针对其他缩减器组件进行测试,而测试组件将会以相同的方式运行。

示例:使用 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() 提供了一种将源文件的目录树映射到软件包中 destation 目录下的相同层次结构的方法。以下示例将子目录 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 中相应功能的许可名单中。

问题排查

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

缺少的片段包括

缺少所需清单分片includecheck_includes

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,就会出现此问题。请参考以下使用检查的示例:

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",
    ],

    // ...
}

如需详细了解所需包含文件的来源,您可以使用 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 操作会失败并显示以下错误,原因是组件清单中包含无法在组件软件包中找到的资源引用

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" ] 
    }
    

静态 capability 分析器

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 组件清单声明链成功跟踪从其来源到请求该 capability 的组件的 capability 路径,就会发生这种情况。

在以下示例中,由于组件 /core/echo 请求 use fuchsia.logger.LogSink 协议,但没有父级对该功能的相应 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", 
        }, 
    ],
}

如需详细了解如何构建 capability 路由,请参阅 Connect 组件