| # Developing an ffx plugin |
| |
| Note: This document describes the legacy plugin macro system and is here only |
| as a reference while we still have some plugins using it around. See |
| [the top level ffx development docs](/docs/development/tools/ffx/development/README.md) |
| for documentation on writing subtools. |
| |
| 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 {#gn-rule} |
| |
| Use the [`ffx_plugin()`](/src/developer/ffx/build/ffx_plugin.gni) 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: |
| |
| ```gn |
| import("//src/developer/ffx/build/ffx_plugin.gni") |
| |
| ffx_plugin("ffx_example") { |
| version = "0.1.0" |
| edition = "2021" |
| with_unit_tests = true |
| deps = [] |
| args_sources = [ |
| "src/args.rs", |
| ] |
| sources = [ |
| "src/lib.rs", |
| ] |
| } |
| ``` |
| |
| Note: `ffx_plugin()` wraps the `rustc_library()` build template, so the same set |
| of attributes are available. |
| |
| 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 {#args} |
| |
| Create the file `src/args.rs` containing the plugins supported arguments: |
| |
| ```rust |
| 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](https://docs.rs/argh/0.1.3/argh/) crate and more |
| documentation can be found [here](https://docs.rs/argh/0.1.3/argh/). This |
| struct has been decorated by the `ffx_command` attribute that signifies that |
| your plugin should run when someone types the following command: |
| |
| ```posix-terminal |
| 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: |
| |
| ```rust |
| 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](https://docs.rs/argh/0.1.3/argh/) |
| |
| ## Plugin {#plugin} |
| |
| Create the file `src/lib.rs` containing the plugin implementation: |
| |
| ```rust |
| 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. |
| |
| Note: The `ffx_example_args::ExampleCommand` in the above example is generated |
| automatically by the `ffx_plugin()` template. For more details, see |
| [plugin internals](plugin-internals.md). |
| |
| ## Integration {#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][ffx-build] to add your |
| `ffx_plugin()` target to the top level: |
| |
| ```gn |
| plugin_deps = [ |
| "//path/to/your/plugin/dir:ffx_example", |
| ... |
| ] |
| ``` |
| |
| Note: Alternatively, you can add your plugin to the dependency list of another |
| plugin to create a subcommand. |
| |
| To build and test your plugin, build `ffx`: |
| |
| ```posix-terminal |
| fx build ffx |
| ``` |
| |
| You should now see the output when you run your example command: |
| |
| ```none {:.devsite-disable-click-to-copy} |
| $ fx ffx example |
| Hello from the example plugin :) |
| ``` |
| |
| ## Unit tests {#unit-tests} |
| |
| If you want to unit test your plugin, just follow the standard method for |
| testing [rust code][rust-testing] 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`: |
| |
| ```posix-terminal |
| 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: |
| |
| ```posix-terminal |
| fx set ... --with=//src/developer/ffx:tests |
| ``` |
| |
| ## FIDL protocols {#fidl-proxy} |
| |
| FFX plugins can communicate with a target device using FIDL protocols through |
| [Overnet][overnet]. To access FIDL protocols from your plugin, follow the |
| instructions in this section. |
| |
| 1. Add the FIDL Rust bindings as a dependency to the plugin's `BUILD.gn` file. |
| The following example adds bindings for the `fuchsia.device` FIDL library: |
| |
| ```gn |
| 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", |
| ] |
| args_sources = [ |
| "src/args.rs", |
| ] |
| sources = [ |
| "src/lib.rs", |
| ] |
| } |
| ``` |
| |
| 1. Import the necessary bindings int your plugin implementation. The following |
| example imports `NameProviderProxy` from `fuchsia.device`: |
| |
| Note: The struct `NameProviderProxy` is generated as part of the rust bindings to the FIDL `fuchsia.device`, |
| [`NameProviderProxy](/sdk/fidl/fuchsia.device/name-provider.fidl) |
| |
| ```rust |
| use { |
| anyhow::Result, |
| ffx_core::ffx_plugin, |
| ffx_example_args::ExampleCommand, |
| fidl_fuchsia_device::NameProviderProxy, |
| }; |
| ``` |
| |
| 1. Include the FIDL proxy can be used in the plugin implementation. Plugins can |
| accept proxies in the parameters list: |
| |
| ```rust |
| pub async fn example( |
| name_proxy: NameProviderProxy, |
| _cmd: ExampleCommand, |
| ) -> Result<()> { } |
| ``` |
| |
| 1. Map the proxy type to a [component selector][component-select] representing |
| the component providing the FIDL protocol in the `ffx_plugin()` annotation: |
| |
| ```rust |
| #[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: |
| |
| ```rust |
| 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: |
| |
| - [DaemonProxy](/src/developer/ffx/fidl/daemon.fidl) |
| - [Remote Control Service (RCS)](/sdk/fidl/fuchsia.developer.remotecontrol/remote-control.fidl) |
| |
| You can simply add the above proxies to your plugin's parameter list to access |
| them in your implementation. |
| |
| ### Proxy moniker map {#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`][moniker-map] |
| in the following format: |
| |
| ```json {:.devsite-disable-click-to-copy} |
| { |
| ... |
| "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. |
| |
| [component-select]: /docs/development/tools/ffx/commands/component-select.md |
| [ffx-build]: /src/developer/ffx/BUILD.gn |
| [overnet]: /src/connectivity/overnet/ |
| [rust-testing]: /docs/development/languages/rust/testing.md |
| [moniker-map]: /src/developer/remote-control/data/moniker-map.json |