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 通过定义模板来扩展 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" ]
}

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

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)

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

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_tests,其中包含两个单独的测试组件:rot13_decoder_testrot13_encoder_test

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 也会产生相同的结果。不过,最好将依赖项放在需要它们的目标上。这样一来,我们就可以在不同的测试软件包中重复使用同一测试组件目标,例如,针对不同的缩减器组件进行测试,测试组件将具有相同的工作方式。

示例:使用 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) 通过组件构建规则中的选择启用属性来控制对这些受限功能的访问。

若要使用受限功能,请添加 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_includesinclude

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", 
        "//zircon/system/ulib/async-loop:async-loop-cpp",
        "//zircon/system/ulib/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

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

静态功能分析器

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/echouse 请求 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"
    }
  }
]

如需解决此问题,请浏览构建失败中提供的错误详情,以发现路由错误的来源,然后在路由链中添加或更正无效声明。在前面的示例错误中,应在父组件的清单中添加 offer

{
    // ...

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

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