FFX 子工具是 ffx cli 可執行的頂層指令。這些指令可以直接編譯至 ffx
和/或建構為可在建構輸出目錄或 SDK 中找到的個別指令,然後使用 FHO 工具介面叫用。
ffx
放置位置
首先,請在 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
會檢查實驗狀態,並在未啟用實驗功能時提早退出。編寫新的子命令時,應附上這項宣告,以免使用者在子命令可供大眾使用前就依賴它。
FIDL 通訊協定
FFX 子工具可透過 Overnet 使用 FIDL 通訊協定與目標裝置通訊。如要從子工具存取 FIDL 通訊協定,請按照下列步驟操作:
將 FIDL Rust 繫結新增為子工具的
BUILD.gn
檔案依附元件。以下範例會為fuchsia.device
FIDL 程式庫新增繫結: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(())
}
}
您可以在此實作實際的工具邏輯。您可以為 Writer
關聯特徵指定類型,系統會根據執行 ffx
的內容,透過 TryFromEnv
為該類型進行初始化。大多數的新子工具應使用 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
。
完成後,如果您 fx build ffx
,應該會在 ffx commands
的輸出內容中,在 Workspace Commands
的清單中看到您的工具,並且可以執行該工具。
實驗性子工具和子指令
建議子工具在 BUILD.gn
中一開始不要包含 sdk_category
。這些未指定類別的子工具會視為「內部」工具,不會納入 SDK。如果使用者想使用二進位檔,就必須直接提供二進位檔。
不過,子指令的處理方式有所不同。
子指令需要在工具中新增 AvailabilityFlag
屬性 (請參閱 ffx target update
記錄中的提交內容,瞭解相關範例)。如果使用者想使用子指令,就必須設定相關的設定選項,才能叫用該子指令。
不過,這種做法存在問題,例如缺少對子指令的 FIDL 依附元件進行驗證。因此,自 2023 年 12 月起,我們將變更處理子指令的機制。
與子工具類似,子指令也可以宣告其 SDK 類別,以便判斷 SDK 中是否可使用子指令。子工具只會使用位於子工具類別層級或以上的子指令來建構。FIDL 依附元件檢查會正確驗證子指令的要求。
新增至 SDK
工具穩定後,您就可以將其納入 SDK,並將二進位檔新增至 SDK 建構。請注意,在執行這項操作前,工具必須相對穩定且經過充分測試 (盡可能不要已將工具納入 SDK),且您必須確實考量相容性問題。
相容性
將子工具新增至 SDK 和 IDK 前,請注意以下三個事項:
FIDL 程式庫:將子工具新增至 SDK 時,您必須將依附於 SDK 的任何 FIDL 程式庫新增至 SDK。(詳情請參閱「將 API 提升至
host_tool
」)。指令列引數:為了測試指令列選項變更所造成的重大變更,我們使用 ArgsInfo 衍生巨集來產生指令列的 JSON 表示法。
這項功能會用於黃金檔案測試,用於偵測差異。黃金檔案。這項測試最終會強化,以便偵測並發出回溯相容性受損的變更警告。
機器友善的輸出內容:工具和子命令必須盡可能提供機器輸出內容,特別是用於測試或建構指令碼的工具。MachineWriter 物件可用於以 JSON 格式編碼輸出內容,並提供用於偵測輸出結構變更的結構定義。
機器輸出內容必須在目前的相容性時間窗格內保持穩定。最後,系統會檢查機器輸出格式是否符合標準。使用機器寫入器輸出的優點是,您可以自由使用自由文字輸出不穩定的輸出內容。
更新子工具
如要將子工具新增至 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
...
]
]
UX 和 SDK 審查
子工具必須遵循 CLI 指南。