开始开发 ffx 子工具

FFX 子工具是 ffx cli 运行。它们可以直接编译为 ffx 和/或作为单独的 命令,并在 build 输出目录或 SDK 中找到这些命令; 系统将使用 FHO 工具接口调用该方法。

放置位置

首先,在 fuchsia.git 树中的某个位置创建一个目录来存放 子工具目前,子工具位于以下位置:

  • ffx 插件树,其中仅内置和 混合插件/子工具 go。新的子工具通常不应放在此处。
  • ffx 工具树,其中包含仅限外部运行的子工具 。将它放在此处,可使 ffx 的维护者更轻松地协助解决任何问题或 使用 ffx 与该工具之间的接口更改来更新您的子工具。 如果您在此处输入名称,但 FFX 团队不是此工具的主要维护人员, 则必须OWNERS 文件放在添加该文件的目录中 和一些单独的负责人,这样我们知道如何对问题进行分类, 工具。
  • 项目自身树中的某处。如果 ffx 工具 只是一个现有程序的封装容器,但如果要这样做,您必须 设置您的OWNERS文件,以便 FFX 团队可以批准对 与 ffx 交互的部分。为此,您可以添加 file:/src/developer/ffx/OWNERS 附加到 OWNERS 文件(位于该工具所在子目录下)。

除了不在插件中放置新工具之外,另一个是特定位置的决策, 可能需要与工具团队进行讨论,以决定最佳位置。

哪些文件

确定好工具的存储位置后,即可创建源代码 文件。最佳做法是让 工具的代码拆分为一个实现各种内容的库和一个仅调用该库的 main.rs

以下文件集是一个正常的起点:

BUILD.gn
src/lib.rs
src/main.rs
OWNERS

当然,如果您愿意,也可以将内容拆分到更多库中。请注意, 这些示例均基于示例 echo subtool, 但为简洁起见,可能删除或简化部分内容。查看以下位置中的文件: 该目录下的任何项目。

BUILD.gn

以下是一个简单子工具的 BUILD.gn 文件的简单示例。注意事项 如果您习惯使用旧版插件接口,那么 ffx_tool 操作不会 强制实施库结构,或执行任何非常复杂的操作。这是一个相当大的 封装了 rustc_binary 操作的简单封装容器,但添加了一些额外的目标 用于生成元数据、生成宿主工具和生成 SDK Atom。

# Copyright 2022 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import("//build/host.gni")
import("//build/rust/rustc_library.gni")
import("//src/developer/ffx/build/ffx_tool.gni")
import("//src/developer/ffx/lib/version/build/ffx_apply_version.gni")

rustc_library("lib") {
  # This is named as such to avoid a conflict with an existing ffx echo command.
  name = "ffx_tool_echo"
  edition = "2021"
  with_unit_tests = true

  deps = [
    "//src/developer/ffx/fidl:fuchsia.developer.ffx_rust",
    "//src/developer/ffx/lib/fho:lib",
    "//third_party/rust_crates:argh",
    "//third_party/rust_crates:async-trait",
  ]

  test_deps = [
    "//src/lib/fidl/rust/fidl",
    "//src/lib/fuchsia",
    "//src/lib/fuchsia-async",
    "//third_party/rust_crates:futures-lite",
  ]

  sources = [ "src/lib.rs" ]
}

ffx_tool("ffx_echo") {
  edition = "2021"
  output_name = "ffx-echo"
  deps = [
    ":lib",
    "//src/developer/ffx/lib/fho:lib",
    "//src/lib/fuchsia-async",
  ]
  sources = [ "src/main.rs" ]
}

group("echo") {
  public_deps = [
    ":ffx_echo",
    ":ffx_echo_host_tool",
  ]
}

group("bin") {
  public_deps = [ ":ffx_echo_versioned" ]
}

group("tests") {
  testonly = true
  deps = [ ":lib_test($host_toolchain)" ]
}

main.rs

主要 Rust 文件通常相当简单,只需使用以下代码调用 FHO: 用作入口点的正确类型,ffx 知道如何传达 替换为:

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use ffx_tool_echo::EchoTool;
use fho::FfxTool;

#[fuchsia_async::run_singlethreaded]
async fn main() {
    EchoTool::execute_tool().await
}

lib.rs

这是工具的主要代码所在的位置。在这里,您可以设置 基于参数的结构体,用于命令参数,并派生 FfxToolFfxMain 通过一个结构来保存工具需要运行的上下文。

参数

#[derive(ArgsInfo, FromArgs, Debug, PartialEq)]
#[argh(subcommand, name = "echo", description = "run echo test against the daemon")]
pub struct EchoCommand {
    #[argh(positional)]
    /// text string to echo back and forth
    pub text: Option<String>,
}

这是定义子工具在其后需要的任何参数的结构体 子命令名称。

工具结构

#[derive(FfxTool)]
pub struct EchoTool {
    #[command]
    cmd: EchoCommand,
    #[with(daemon_protocol())]
    echo_proxy: ffx::EchoProxy,
}

这是包含工具所需上下文的结构。这包括 如上面定义的参数结构,守护程序或 可能需要使用的设备,或者其他您可以自行定义的内容。

此结构体中必须有一个引用参数类型的元素 并且应该具有 #[command] 属性,以便 可以为 FfxTool 实现设置正确的关联类型。

此结构中的所有内容都必须实现 TryFromEnv 或具有 #[with()] 该注解指向一个函数,该函数返回实现 TryFromEnvWith。还有一些内置于这些 API 中的实现 fho 库或其他 ffx 库。

此外,该工具上方的 #[check()] 注解使用 CheckEnv,用于验证是否应在不生成任何项的情况下运行该命令 结构体本身此处的 AvailabilityFlag 将检查 如果未启用,则会提前退出。在编写新的 子命令中,则应添加此声明,以防止人们依赖 然后再发布。

FIDL 协议

FFX 子工具可以通过 FIDL 协议与目标设备通信, Overnet。如需从子工具访问 FIDL 协议,请执行以下操作:

  1. 将 FIDL Rust 绑定作为依赖项添加到子工具的 BUILD.gn 文件中。 以下示例为 fuchsia.device FIDL 库添加了绑定:

      deps = [
        "//sdk/fidl/fuchsia.device:fuchsia.device_rust",
      ]
    
  2. 将必要的绑定导入您的子工具实现。以下 示例从 fuchsia.device 导入 NameProviderProxy

    use fidl_fuchsia_device::NameProviderProxy;
    
  3. 声明工具结构的成员字段。由于 FIDL 代理实现 TryFromEnv trait,FHO 框架将创建并初始化该字段 。

    #[derive(FfxTool)]
    pub struct EchoTool {
      #[command]
      cmd: EchoCommand,
      name_proxy: NameProviderProxy,
    }
    

FfxMain 实现

#[async_trait(?Send)]
impl FfxMain for EchoTool {
    type Writer = MachineWriter<String>;
    async fn main(self, mut writer: Self::Writer) -> Result<()> {
        let text = self.cmd.text.as_deref().unwrap_or("FFX");
        let echo_out = self
            .echo_proxy
            .echo_string(text)
            .await
            .user_message("Error returned from echo service")?;
        writer.item(&echo_out)?;
        Ok(())
    }
}

在这里,您可以实现实际的工具逻辑。您可以为 Writer 个关联的特征,该类型将(通过 TryFromEnv)为 ffx。大多数新的子工具应该 使用 MachineWriter<> 类型,指定一个比示例更宽泛的类型 String,但有意义的内容因工具而异。将来,它可能会 所有新工具都必须实现机器接口。

此外,此函数的结果类型默认使用 fho Error 类型, 可以用来区分 或意外错误。如需了解详情,请参阅 (位于错误文档中)。

单元测试

如果要对子工具进行单元测试,只需遵循 在主机上测试 Rust 代码ffx_plugin() GN 模板 在以下情况下,系统会为单元测试生成 <target_name>_lib_test 库目标: with_unit_tests 参数设置为 true

如果您的 lib.rs 包含测试,则可以使用 fx test 调用它们:

fx test ffx_example_lib_test

如果 fx 测试没有找到您的测试,请检查产品配置是否包含 您的测试。您可以使用以下命令包含所有 ffx 测试:

fx set ... --with=//src/developer/ffx:tests

在测试中使用虚构 FIDL 代理

测试子工具的常见模式是为 FIDL 创建虚构代理 协议。这样,您就可以从调用 而无需操心集成的复杂性问题 测试。

    fn setup_fake_echo_proxy() -> ffx::EchoProxy {
        let (proxy, mut stream) =
            fidl::endpoints::create_proxy_and_stream::<ffx::EchoMarker>().unwrap();
        fuchsia_async::Task::local(async move {
            while let Ok(Some(req)) = stream.try_next().await {
                match req {
                    ffx::EchoRequest::EchoString { value, responder } => {
                        responder.send(value.as_ref()).unwrap();
                    }
                }
            }
        })
        .detach();
        proxy
    }

然后在单元测试中使用此虚构代理

    #[fuchsia::test]
    async fn test_regular_run() {
        const ECHO: &'static str = "foo";
        let cmd = EchoCommand { text: Some(ECHO.to_owned()) };
        let echo_proxy = setup_fake_echo_proxy();
        let test_stdout = TestBuffer::default();
        let writer = MachineWriter::new_buffers(None, test_stdout.clone(), Vec::new());
        let tool = EchoTool { cmd, echo_proxy };
        tool.main(writer).await.unwrap();
        assert_eq!(format!("{ECHO}\n"), test_stdout.into_string());
    }

OWNERS

如果此子工具位于 ffx 树中,您将需要添加一个 OWNERS 文件, 告诉我们谁对该代码负责,以及如何将代码问题 分类。它应如下所示:

file:/path/to/authoritative/OWNERS

最好将其添加为引用(使用 file: 或可能的 include),而不是 作为直接的参与者名单,这样就不会因退出 。

添加到 build

要将该工具作为宿主工具添加到 GN build 图中,您需要引用该工具 在 ffx tools gn 文件的主列表中, 已添加到 tools 组和 test 组的 public_deps 中。

之后,如果您fx build ffx,应该能在列表中看到您的工具 即 ffx commands 输出中的 Workspace Commands,您应该能够 运行应用所需的资源

实验性子工具和子命令

建议子工具最初不在sdk_category BUILD.gn。系统会将这些未指定类别的子工具视为 它们不属于 SDK build 的一部分。如果用户想使用 必须直接为他们提供二进制文件

不过,子命令的处理方式不同。

子命令需要将 AvailabilityFlag 属性添加到工具中(请参阅提交内容) 来自ffx target update的历史 )。如果用户希望使用子命令,则需要设置 关联的配置选项,才能调用该子命令。

不过,这种方法存在一些问题,例如缺少任何验证 该子命令的 FIDL 依赖项。因此,处理 截至 2023 年 12 月,子命令目前正在更改。

与子工具类似,子命令将能够声明其 SDK 类别(使用 默认值为“experimental”),以确定子命令是否可用。 仅使用属于子工具类别或在其类别之上的子命令来构建子工具 。FIDL 依赖关系检查将正确验证子命令的要求。

向 SDK 添加

工具稳定下来并且您已准备好将其添加到 SDK 中后 将二进制文件添加到 SDK build 中。请注意,在执行此操作之前 必须被视为相对稳定且经过充分测试(在没有 已将其包含在 SDK 中),您需要确保您已考虑 兼容性问题。

兼容性

在将子工具添加到 SDK 和 IDK:

  1. FIDL 库 - 您需要添加所依赖的任何 FIDL 库 您向 SDK 添加子工具时,会应用到 SDK。(有关详情,请参阅 将 API 提升到 partner_internal。)

  2. 命令行参数 - 用于测试因命令行而导致的破坏性更改 ArgsInfo 派生宏用于生成 JSON 表示法 。

    这用于黄金文件测试,以检测 差异。黄金文件。最终,此测试将得到增强,以检测和警告 会破坏向后兼容性的更改

  3. 机器友好型输出 - 工具和子命令需要有机器输出 尤其是在测试或构建脚本中使用的工具。 MachineWriter 对象用于以 JSON 格式对输出进行编码 并提供用于检测输出结构更改的架构。

    机器输出必须在当前的兼容性范围内保持稳定 窗口。最终,系统将针对机器输出格式提供黄金检查。 使用机器写入程序输出的好处是 自由文本中的不稳定输出。

更新子工具

如需将子工具添加到 SDK,请在其 BUILD.gn 中将 sdk_category 设置为 适当的类别(例如 partner)。如果子工具包含子命令 不再处于实验阶段,请移除其 AvailabilityFlag 属性 它们不再需要调用特殊的配置选项。

包含在 SDK 中

您还需要将您的子工具添加到host_tools SDK GN 文件,例如:

sdk_molecule("host_tools") {
  visibility = [ ":*" ]

  _host_tools = [
    ...
    "//path/to/your/tool:sdk", # <-- insert this
    ...
  ]
]

用户体验和 SDK 审核

子工具必须遵循 CLI 指南