The new FHO subtool library provides some helpful macros for migrating an existing plugin over to the new subtool interface. By migrating your plugin to a subtool, you have these benefits:
ffx
environment (see the FHO docs linked above).ffx
itself.Please note that if you are starting a new subtool, you can skip a lot of the steps that were involved in the legacy plugin system, so you should follow the getting started instructions instead.
Plugins that don't have sub-plugins, with no plugin_deps
section in their ffx_plugin()
action, are relatively simple to migrate. Migrating plugins that have sub-plugins is treated as a separate task.
The following instructions are largely based on the differences between the ffx daemon echo
plugin as originally written and the ffx echo
subtool used as a proof of concept in developing the new subtool interface.
lib.rs
:Given a plugin lib.rs
file that looks like this:
use anyhow::Result; use ffx_core::ffx_plugin; use ffx_echo_args::EchoCommand; use ffx_writer::Writer; use fidl_fuchsia_developer_ffx::EchoProxy; #[ffx_plugin(EchoProxy = "daemon::protocol")] pub async fn echo(echo_proxy: EchoProxy, cmd: EchoCommand, #[ffx(machine = String)] mut writer: Writer) -> Result<()> { // implementation here Ok(()) }
The simplest way to migrate to the new plugin system is to remove the #[ffx_plugin]
macro and add the following derive-macro-based code:
use fho::{FfxTool, FfxMain, MachineWriter, Result}; use ffx_echo_args::EchoCommand; use fidl_fuchsia_developer_ffx::EchoProxy; #[derive(FfxTool)] pub struct EchoTool { #[command] cmd: EchoCommand, #[with(fho::daemon_protocol())] echo_proxy: ffx::EchoProxy, } #[async_trait(?Send)] impl FfxMain for EchoTool { type Writer = MachineWriter<String>; async fn main(self, writer: MachineWriter<String>) -> Result<()> { // implementation here Ok(()) } }
Unlike the previous macro, there are no restrictions on naming the members of the EchoTool
struct, and you can even implement your own loaders by deriving a trait from FHO if you want to do something more complicated.
The main()
function is given this struct and the writer as moved objects, so you can feel free to deconstruct them however you like.
Also note that the Result
type comes from FHO and not anyhow
. At this boundary, there is now a specific error type that holds information about whether or not the error is user-surfaceable and how to present it. See errors for more information on how to work with this error type and its interactions with the ffx_error
and ffx_bail
macros.
For now, most migrated plugins still need to be included into the main ffx
binary, and that means you'll need to also add the following macro invocation to generate the legacy plugin entry points:
fho::embedded_plugin!(EchoTool);
main.rs
Since your subtool can now be run independently, you will need to add a simple main.rs
that will call into FHO and your plugin lib to properly run when ffx
invokes it:
use ffx_tool_echo::EchoTool; use fho::FfxTool; #[fuchsia_async::run_singlethreaded] async fn main() { EchoTool::execute_tool().await }
In general, you won't ever have a main.rs
any more complicated than this.
For existing plugins that still need to integrate with the existing ffx
plugin system, these plugins will continue using the ffx_plugin
GN action since it sets all the dependencies up properly for inclusion in the ffx
binary.
If the plugin is a top level subcommand of ffx
, then we'll also add an ffx_tool()
action as well to build the separately compiled plugin:
# Existing import for the plugin action import("//src/developer/ffx/build/ffx_plugin.gni") # New import for the ffx_tool action. import("//src/developer/ffx/build/ffx_tool.gni") ffx_plugin("ffx_echo") { version = "0.1.0" edition = "2021" with_unit_tests = true args_sources = [ "src/args.rs" ] sources = [ "src/lib.rs" ] deps = [ "//src/developer/ffx/fidl:fuchsia.developer.ffx_rust" ] test_deps = [ "//src/lib/fuchsia-async" ] } # This will generate the executable file for your plugin. ffx_tool("ffx_echo_tool") { edition = "2021" output_name = "ffx-echo" deps = [ ":ffx_echo", "//src/developer/ffx/lib/fho:lib", "//src/lib/fuchsia-async", ] sources = [ "src/main.rs" ] # To be included in the sdk in the future, add this: sdk_category = "partner" sdk_target_name = "ffx_echo_tool_sdk" }
At this point, it will be possible to build the subtool but it won‘t be actively built. In order to do that, you’ll need to add it to the list of “dual-mode plugins” in the ffx
config.gni:
# ...snip... dual_mode_plugins = [ # ... "//path/to/your/plugin:ffx_echo_tool", # ... ] # ...snip...
Now, running fx build ffx
should cause both ffx
and your plugin to be rebuilt, and if you run ffx commands
you should be able to see your command in the “Workspace Commands” list.