This page describes the basic steps for creating a plugin for ffx
.
The plugin system employs a combination of GN build rules and Rust attributes
to decouple plugin code from ffx
internals.
GN Build Rule
Use the ffx_plugin()
build rule
template in your project's BUILD.gn
file to create a build target for your
plugin.
Your BUILD.gn
file should look similar to the following example:
import("//src/developer/ffx/build/ffx_plugin.gni")
ffx_plugin("ffx_example") {
version = "0.1.0"
edition = "2021"
with_unit_tests = true
deps = []
sources = [
"src/args.rs",
"src/lib.rs",
]
}
Inside the src/
directory, the project should contain two source files:
src/args.rs
: Defines the CLI parameters for your plugin.src/lib.rs
: Contains the main plugin source code implementation.
Arguments
Create the file src/args.rs
containing the plugins supported arguments:
use {argh::FromArgs, ffx_core::ffx_command};
#[ffx_command()]
#[derive(FromArgs, Debug, PartialEq)]
#[argh(subcommand, name = "example", description = "an example")]
pub struct ExampleCommand {}
This uses the argh crate and more
documentation can be found here. This
struct has been decorated by the ffx_command
attribute that signifies that
your plugin should run when someone types the following command:
fx ffx example
If you want to add more parameters for your plugins, you add them to this struct.
An example parameter would look like this:
use {argh::FromArgs, ffx_core::ffx_command};
#[ffx_command()]
#[derive(FromArgs, Debug, PartialEq)]
#[argh(subcommand, name = "example", description = "an example")]
pub struct ExampleCommand {
#[argh(positional)]
/// example optional positional string parameter
pub example: Option<String>,
}
See more documentation: - Argh
Plugin
Create the file src/lib.rs
containing the plugin implementation:
use {
anyhow::Result,
ffx_core::ffx_plugin,
ffx_example_args::ExampleCommand,
};
#[ffx_plugin()]
pub async fn example(_cmd: ExampleCommand) -> Result<()> {
println!("Hello from the example plugin :)");
Ok(())
}
Plugin methods need to accept the argh command created in the src/args.rs
file as a parameter even if they do not use them.
Integration
Add your plugin library as a dependency to ffx
to include it in the build.
Edit the plugin_deps
array in the ffx
build target to add your
ffx_plugin()
target to the top level:
plugin_deps = [
"//path/to/your/plugin/dir:ffx_example",
...
]
To build and test your plugin, build ffx
:
fx build ffx
You should now see the output when you run your example command:
$ fx ffx example
Hello from the example plugin :)
Unit tests
If you want to unit test your plugin, just follow the standard method for
testing rust code on a host. The ffx_plugin()
GN template
generates a <target_name>_lib_test
library target for unit tests when the
with_unit_tests
parameter is set to true
.
If your lib.rs
contains tests, they can be invoked using fx test
:
fx test ffx_example_lib_test
If fx test doesn't find your test, check that the product configuration includes your test. You can include all the ffx tests with this command:
fx set ... --with=//src/developer/ffx:tests
FIDL protocols
FFX plugins can communicate with a target device using FIDL protocols through Overnet. To access FIDL protocols from your plugin, follow the instructions in this section.
Add the FIDL Rust bindings as a dependency to the plugin's
BUILD.gn
file. The following example adds bindings for thefuchsia.device
FIDL library:import("//src/developer/ffx/build/ffx_plugin.gni") ffx_plugin("ffx_example") { version = "0.1.0" edition = "2021" with_unit_tests = true deps = [ "//sdk/fidl/fuchsia.device:fuchsia.device_rust", ] sources = [ "src/args.rs", "src/lib.rs", ] }
Import the necessary bindings int your plugin implementation. The following example imports
NameProviderProxy
fromfuchsia.device
:use { anyhow::Result, ffx_core::ffx_plugin, ffx_example_args::ExampleCommand, fidl_fuchsia_device::NameProviderProxy, };
Include the FIDL proxy can be used in the plugin implementation. Plugins can accept proxies in the parameters list:
pub async fn example( name_proxy: NameProviderProxy, _cmd: ExampleCommand, ) -> Result<()> { }
Map the proxy type to a component selector representing the component providing the FIDL protocol in the
ffx_plugin()
annotation:#[ffx_plugin( NameProviderProxy = "bootstrap/device_name_provider:out:fuchsia.device.NameProvider" )]
The example plugin implementation in src/lib.rs
should now look like the
following:
use {
anyhow::Result,
ffx_core::ffx_plugin,
ffx_example_args::ExampleCommand,
fidl_fuchsia_device::NameProviderProxy,
};
#[ffx_plugin(
NameProviderProxy = "bootstrap/device_name_provider:out:fuchsia.device.NameProvider"
)]
pub async fn example(
name_proxy: NameProviderProxy,
_cmd: ExampleCommand,
) -> Result<()> {
if let Ok(name) = name_proxy.get_device_name().await? {
println!("Hello, {}", name);
}
Ok(())
}
Repeat these steps to include additional FIDL proxies to your ffx
plugin.
The following FIDL proxies are built into ffx
, and do not require additional
dependencies or mappings:
You can simply add the above proxies to your plugin's parameter list to access them in your implementation.
Proxy moniker map
ffx
and the Remote Control Service (RCS) provide a mechanism for maintaining
compatibility with existing monikers used by ffx
plugins if the moniker
representing a given FIDL proxy changes. For example:
- The FIDL proxy is provided by a new component
- The FIDL protocol name changes
- The proxy moniker varies across product builds
RCS supports this using moniker maps that override the monikers defined in
an ffx
plugin's source and map it to a different value. To override a given
moniker, add an entry to
//src/developer/remote-control/data/moniker-map.json
in the following format:
{
...
"original/moniker:out:fuchsia.MyService": "some/new/moniker:expose:fuchsia.MyOtherService"
}
This example enables RCS to override references to
original/moniker:out:fuchsia.MyService
in ffx
plugins and route them to
some/new/moniker:expose:fuchsia.MyOtherService
in any build which contains
the mapping.