開始開發 ffx 子工具

FFX Subtools 是 ffx cli 可執行的頂層指令。這些程式庫可以直接編譯到 ffx 和/或做為獨立指令進行建構,可在建構輸出目錄或 SDK 中找到,然後使用 FHO 工具介面叫用這些指令。

這份文件說明如何開始為 ffx 編寫新的子工具。 如果您已有撰寫於新介面之前的外掛程式,並想要將其遷移至新的子工具介面,請參閱遷移文件以取得更多資訊。

放置位置

首先,在 fuchsia.git 樹狀結構的某個位置中建立目錄來存放子工具。目前子工具位於下列位置:

  • ffx 外掛程式樹狀結構,只有內建和混合型外掛程式/子工具會前往。一般而言,新的子工具不應放在這裡。
  • 「ffx 工具樹狀結構」,也就是僅供外部執行的子工具使用。在這裡插入之後,ffx 的維護人員可更輕鬆地協助解決問題或更新子工具,因為 ffx 與工具之間的介面變更。如果把這個檔案放在這裡,但 FFX 團隊不是這項工具的主要維護者,則必須OWNERS 檔案放入放置的目錄中,以便新增團隊的元件和一些個別擁有者,以便我們瞭解如何將問題分類。
  • 專案專屬樹狀結構中的某個位置。如果 ffx 工具純粹是現有程式的包裝函式,這就很合理,但如果您這麼做,就「必須」設定 OWNERS 檔案,才能讓 FFX 團隊核准與 ffx 互動的部分更新。只要在工具所在的子目錄上新增 file:/src/developer/ffx/OWNERSOWNERS 檔案即可。

除了不要將新工具導入外掛程式之外,特定位置的決定可能還是需要與工具團隊討論,以決定最佳位置。

哪些檔案

決定工具目的地後,請建立來源檔案。有別於舊版外掛程式系統,子工具不必拆分為三個不同的 Rust 程式庫。不過,最佳做法是將工具的程式碼細分為實作項目的程式庫,以及只會呼叫該程式庫的 main.rs

下列檔案集為正常的起點:

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

當然,如有需要,也可以將資源分割成更多的程式庫。請注意,這些範例均以 echo 子工具為例,但為求精簡,可能會移除或簡化部分。如果這裡沒有任何功能無法運作或不清楚,請查看該目錄中的檔案。

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

此結構保留了工具所需的背景資訊。這包括上述定義的引數結構、可能需要的任何 Daemon 或裝置 Proxy,或是您自行定義的其他項目。

此結構中必須有一個參照上述引數類型的元素,而且該元素應包含 #[command] 屬性,這樣才能為 FfxTool 實作設定正確的相關類型。

這個結構中的任何項目都必須實作 TryFromEnv,或有一個 #[with()] 註解,指向會回傳實作 TryFromEnvWith 的內容的函式。fho 程式庫或其他 ffx 程式庫也內建了這些方法的幾種實作。

此外,工具上方的 #[check()] 註解會使用 CheckEnv 的實作項目來驗證指令是否應該在不產生結構項目的情況下執行。這裡的 AvailabilityFlag 會檢查實驗狀態,如果未啟用,會提早結束。編寫新的子指令時,應在子指令中加入這個宣告,避免使用者等到該指令供更廣泛使用前,避免依賴。

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 類型,此類型可用來區分因使用者互動和非預期的錯誤。這與舊版外掛程式介面區分為一般 anyhow 錯誤,以及 ffx_errorffx_bail 產生錯誤之間的差異。詳情請參閱錯誤文件。

測試

測試子工具的常見專利為 FIDL 通訊協定建立假 Proxy。這可讓您從呼叫 Proxy 傳回完整的各種結果,而不必實際處理整合測試的複雜問題。

    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
    }

接著在單元測試中使用這個假 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),而不要當做直接成員清單,這樣才不會因為離開程序而過時。

新增至版本

如要將工具新增至 GN 建構圖做為主機工具,您必須在 ffx 工具 gn 檔案的主要清單中參照該工具,並新增至 toolstest 群組的 public_deps

之後,如果 fx build ffx 應該可以在 ffx commands 輸出內容的 Workspace Commands 清單中看到該工具,應該就能執行該工具。

實驗性子工具和子指令

建議子工具一開始不要在 BUILD.gn 中加入 sdk_category。這些沒有指定類別的子工具會視為「實驗」性質,因此不屬於 SDK 建構作業的一部分。如果使用者想使用二進位檔,就必須直接提供二進位檔。

不過,子指令的處理方式不同。

子指令需要在工具中新增 AvailabilityFlag 屬性 (如需範例,請參閱 ffx target update)。如果使用者想要使用子指令,就必須設定相關聯的設定選項才能叫用該子指令。

但這個方法有問題,例如缺少任何子指令的 FIDL 依附元件驗證。因此,處理子指令的機制已於 2023 年 12 月變更。

與子工具類似,子指令將可宣告其 SDK 類別 (預設為「實驗」),以判斷子指令是否可用。子工具只會透過子工具類別層級或上層的子指令建構。FIDL 依附元件檢查會正確驗證子指令的需求。

新增至 SDK

當工具變得穩定且準備好在 SDK 中納入後,建議您將二進位檔新增至 SDK 版本。請注意,在進行這項操作之前,工具必須視為相對穩定、進行完善測試 (如果不先在 SDK 中納入,可以盡可能多使用),而且務必確認已考慮到相容性問題。

相容性

將子工具新增至 SDK 和 IDK 之前,請先瞭解以下三個方面:

  1. FIDL 程式庫 - 在 SDK 中新增子工具時,您必須將自己依附的所有 FIDL 程式庫新增至 SDK。(詳情請參閱「將 API 升級為 partner_internal」)。

  2. 指令列引數 - 為了測試因指令列選項變更而導致的破壞性變更,系統會使用 ArgsInfo 衍生巨集來產生指令列的 JSON 表示法。

    這將用於黃金檔案測試,以偵測差異。黃金檔案。最後,這項測試會經過強化,以偵測並警告出會破壞回溯相容性的變更。

  3. 適合機器的輸出:工具和子指令盡可能具備機器輸出內容,特別是用於測試或建構指令碼的工具。MachineWriter 物件可用來將輸出編碼為 JSON 格式,並提供結構定義,以用於偵測輸出結構的變更。

    機器輸出內容必須在目前的相容性期間內保持穩定。最終,機器輸出格式將進入黃金級檢查。使用機器寫入器輸出的好處是,您可以釋出自由文字的不穩定輸出。

更新子工具

如要將子工具新增至 SDK,請在 SDK 的 BUILD.gn 中將 sdk_category 設為適當的類別 (例如 partner)。如果子工具包含不再實驗性的子指令,請移除其 AvailabilityFlag 屬性,這樣就不再需要叫用特殊設定選項。

納入 SDK

您也需要將子工具新增至 SDK GN 檔案host_tools 分子,例如:

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

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

使用者體驗回顧

Fuchsia 目前未在子工具中加入正式的「使用者體驗審查」後,再將子工具加入 SDK 和 IDK。這份設計準則發布後,這份說明文件就會更新。