FFX 子工具是 ffx CLI 可執行的頂層指令。這些函式可直接編譯至 ffx,或建構為獨立指令,並位於建構輸出目錄或 SDK 中,然後使用 FHO 工具介面叫用。
放置位置
首先,請在 fuchsia.git 樹狀結構中的某處建立目錄,以存放子工具。目前子工具位於下列位置:
- ffx 外掛程式樹狀結構,僅適用於內建和混合外掛程式/子工具。一般來說,新的子工具不應放在這裡。
- 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 子工具為基礎,但為了簡潔起見,部分內容可能會移除或簡化。如果這裡有任何項目無法運作或不清楚,請查看該目錄中的檔案。
BUILD.gn
以下是簡單子工具的 BUILD.gn 檔案簡單範例。請注意,如果您習慣使用舊版外掛程式介面,ffx_tool 動作不會對您強制執行程式庫結構,也不會執行任何複雜作業。這是 rustc_binary 動作的簡單包裝函式,但會新增一些額外目標,用於產生中繼資料、主機工具和 SDK 原子。
# 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",
"//src/developer/ffx/lib/target/holders:lib",
"//src/developer/ffx/lib/writer: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
這是工具的主要程式碼所在位置。您將在此設定以 argh 為基礎的指令引數結構,並從結構衍生 FfxTool 和 FfxMain 實作項目,該結構會保留工具執行時所需的內容。
引數
#[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,
}
這是保存工具所需脈絡的結構。包括上述定義的引數結構、您可能需要的精靈或裝置的任何 Proxy,或您自行定義的其他項目。
這個結構體中必須有一個元素參照上述引數型別,且該元素應具有 #[command] 屬性,以便為 FfxTool 實作設定正確的相關聯型別。
這個結構中的任何項目都必須實作 TryFromEnv,或具有指向函式的 #[with()] 註解,該函式會傳回實作 TryFromEnvWith 的項目。fho 程式庫或其他 ffx 程式庫也內建這些實作項目。
此外,工具上方的 #[check()] 註解會使用 CheckEnv 的實作項目,驗證指令是否應在不為結構體本身產生項目的情況下執行。這裡的 AvailabilityFlag 會檢查實驗狀態,如果未啟用,就會提早結束。編寫新的子指令時,應加上這項宣告,避免使用者在子指令準備好廣泛使用前就依賴它。
直接連線
FFX 子工具預設會透過 FFX Daemon 通訊。如果子工具需要直接連線至目標,可以新增宣告: ```rust
[derive(FfxTool)]
[target(direct)]
pub struct MyTool { ... } ```
未連線至目標
如果子工具不需要目標連線,可以透過 #[target(None)] 宣告:
```rust
[derive(FfxTool)]
[target(None)]
pub struct MyTool { ... } ```
只有在指令支援 ffx --strict 時,才需要這個宣告,否則工具會驗證指令列上是否指定目標。不過,如果工具不會與目標裝置互動,建議您新增這項屬性。
FIDL 通訊協定
FFX 子工具可透過 Overnet,使用 FIDL 協定與目標裝置通訊。如要從子工具存取 FIDL 通訊協定,請按照下列步驟操作:
將 FIDL Rust 繫結新增為子工具
BUILD.gn檔案的依附元件。 以下範例會為fuchsia.deviceFIDL 程式庫新增繫結:deps = [ "//sdk/fidl/fuchsia.device:fuchsia.device_rust", ]將必要繫結匯入子工具實作項目。以下範例會從
fuchsia.device匯入NameProviderProxy:use fidl_fuchsia_device::NameProviderProxy;宣告工具結構的成員欄位。由於 FIDL 代理會實作
TryFromEnv特徵,因此 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(())
}
}
您可以在這裡實作實際的工具邏輯。您可以為相關聯的特徵指定類型,系統會根據 ffx 執行的環境,透過 TryFromEnv 為您初始化該類型。Writer大多數新子工具應使用 MachineWriter<> 型別,指定比上述範例 String 更不通用的型別,但適用的型別會因工具而異。未來,所有新工具可能都必須實作機器介面。
此外,這個函式的結果類型預設會使用 fho Error 型別,可用於區分因使用者互動而發生的錯誤,以及非預期的錯誤。詳情請參閱錯誤文件。
單元測試
如要對子工具進行單元測試,只要按照標準方法,在主機上測試 Rust 程式碼即可。如果 with_unit_tests 參數設為 true,ffx_plugin() GN 範本就會為單元測試產生 <target_name>_lib_test 程式庫目標。
如果 lib.rs 包含測試,可以使用 fx test 叫用測試:
fx test ffx_example_lib_test如果 fx 測試找不到您的測試,請檢查產品設定是否包含您的測試。您可以使用下列指令納入所有 ffx 測試:
fx set ... --with=//src/developer/ffx:tests在測試中使用假的 FIDL Proxy
測試子工具的常見模式是為 FIDL 通訊協定建立假的 Proxy。這樣一來,您就能從呼叫 Proxy 傳回各種結果,而不必實際處理整合測試的複雜性。
fn setup_fake_echo_proxy() -> ffx::EchoProxy {
let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<ffx::EchoMarker>();
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 檔案的主要清單中參照該工具,並將其新增至 tools 和 test 群組的 public_deps。
完成後,您應該就能在 ffx commands 的輸出內容中,看到 Workspace Commands 清單中的工具,並執行該工具。fx build ffx
實驗性子工具和子指令
建議子工具一開始不要在 BUILD.gn 中包含 sdk_category。這些未指定類別的子工具會視為「內部」工具,不會納入 SDK。如要使用二進位檔,使用者必須直接取得二進位檔。
不過,子指令的處理方式不同。
子指令需要在工具中新增 AvailabilityFlag 屬性 (如需範例,請參閱 ffx target update 的歷史記錄中的提交)。如要使用子指令,使用者必須設定相關聯的設定選項,才能叫用該子指令。
不過,這種做法存在一些問題,例如缺少對子指令 FIDL 依附元件的任何驗證。因此,自 2023 年 12 月起,處理子指令的機制將有所變更。
與子工具類似,子指令可以宣告其 SDK 類別,判斷子指令是否適用於 SDK。子工具只會使用子工具類別層級或以上的子指令建構。FIDL 依附元件檢查會正確驗證子指令的需求。
新增至 SDK
工具穩定後,即可將二進位檔新增至 SDK 建構作業。請注意,執行這項操作前,工具必須相對穩定且經過充分測試 (盡可能在未納入 SDK 的情況下進行),且您必須確保已考量相容性問題。
相容性
將子工具新增至 SDK 和 IDK 之前,請注意以下三個方面:
FIDL 程式庫 - 將子工具新增至 SDK 時,您必須將所有依附的 FIDL 程式庫新增至 SDK。(詳情請參閱將 API 升級至
host_tool。)指令列引數 - 為測試指令列選項變更造成的重大變更,ArgsInfo 衍生巨集會用於產生指令列的 JSON 表示法。
這項功能會用於黃金檔案測試,偵測差異。黃金檔案。最終,這項測試將會強化,以便偵測並警告會破壞回溯相容性的變更。
輸出內容適合機器處理 - 工具和子指令應盡可能提供適合機器處理的輸出內容,尤其是用於測試或建構指令碼的工具。MachineWriter 物件用於以 JSON 格式編碼輸出內容,並提供用於偵測輸出結構變更的結構定義。
在目前的相容性時間範圍內,機器輸出內容必須穩定。最終,機器輸出格式會顯示金色勾號。 機器撰寫的輸出內容可讓您在自由文字中獲得不穩定的輸出內容。
更新子工具
如要將子工具新增至 SDK,請在適當類別 (例如 partner) 的 BUILD.gn 中設定 sdk_category。如果子工具包含不再是實驗性質的子指令,請移除這些子指令的 AvailabilityFlag 屬性,這樣就不再需要特殊的設定選項來叫用這些子指令。
納入 SDK
您也需要在 SDK GN 檔案的 host_tools 分子中新增子工具,例如:
sdk_molecule("host_tools") {
visibility = [ ":*" ]
_host_tools = [
...
"//path/to/your/tool:sdk", # <-- insert this
...
]
]
使用者體驗和 SDK 審查
子工具必須遵守 CLI 指南。