| # Migrate system components |
| |
| To migrate your system component, follow these steps: |
| |
| 1. [Migrate the component manifest](#create-component-manifest) |
| 1. [Add to the component topology](#add-component-to-topology) |
| 1. [Migrate component features](#features) |
| 1. [Verify the migrated component](#verify-component) |
| |
| ## Migrate the component manifest {#create-component-manifest} |
| |
| Create a minimal [CML file][glossary.component-manifest] and configure it with |
| GN so that it gets compiled and installed in your package. |
| |
| Note: Unlike CMX, CML is [JSON5][json5-external]{: .external}, which allows |
| comments and trailing commas. Take advantage of this when writing your CML file! |
| |
| 1. Determine where your CMX file is located in the source tree (for example, |
| [`fonts.cmx`][example-fonts]). Create a file in the same directory that has |
| the same filename but with a `.cml` extension, with the following contents: |
| |
| ```json5 |
| // fonts.cml |
| { |
| include: [ |
| // Enable system logging |
| "syslog/client.shard.cml", |
| ], |
| } |
| ``` |
| |
| Note: Your CML file will live side-by-side with the CMX file for now. Do not |
| delete the CMX file yet. |
| |
| 1. Find the build rule that defines your component. Normally, this is a |
| `fuchsia_component` rule. For example, see the fonts |
| [`BUILD.gn`][example-package-rule]. |
| |
| ```gn |
| fuchsia_component("fonts") { |
| manifest = "meta/fonts.cmx" |
| deps = [ ":font_provider" ] |
| } |
| ``` |
| |
| 1. Update the `manifest` element of the associated `fuchsia_component` rule to |
| point to your new `.cml` file instead: |
| |
| ```gn |
| fuchsia_component("fonts") { |
| {{ '<strong>' }}manifest = "meta/fonts.cml"{{ '</strong>' }} |
| deps = [ ":font_provider" ] |
| } |
| ``` |
| |
| ### Adding the executable {#component-executable} |
| |
| Add the [`program`][manifests-program] section of your CML file along with the |
| appropriate runner declaration. |
| |
| Note: The [runner][glossary.runner] declaration is necessary even if your |
| component is launched using the ELF runner. This is the default in CMX but must |
| be explicitly specified in CML. |
| |
| ```json |
| // fonts.cmx |
| { |
| "include": [ |
| "syslog/client.shard.cmx" |
| ], |
| {{ '<strong>' }}"program": { |
| "binary": "bin/font_provider" |
| },{{ '</strong>' }} |
| ... |
| } |
| ``` |
| |
| ```json5 |
| // fonts.cml |
| { |
| include: [ |
| // Enable system logging |
| "syslog/client.shard.cml", |
| ], |
| {{ '<strong>' }}program: { |
| runner: "elf", |
| binary: "bin/font_provider", |
| },{{ '</strong>' }} |
| } |
| ``` |
| |
| ### Declaring required services {#required-services} |
| |
| Add [`use`][manifests-use] declarations to your CML file. These are the |
| approximate equivalent of the [`services`][cmx-services] list in CMX. |
| |
| ```json |
| // fonts.cmx |
| { |
| "include": [ |
| "syslog/client.shard.cmx" |
| ], |
| "program": { |
| "binary": "bin/font_provider" |
| }, |
| {{ '<strong>' }}"sandbox": { |
| "services": [ |
| "fuchsia.logger.LogSink", |
| "fuchsia.pkg.FontResolver" |
| ] |
| ... |
| }{{ '</strong>' }} |
| } |
| ``` |
| |
| Convert each element of the `services` list to a `use` declaration for the |
| corresponding service `protocol`. |
| |
| ```json5 |
| // fonts.cml |
| { |
| include: [ |
| // Enable system logging |
| "syslog/client.shard.cml", |
| ], |
| program: { |
| runner: "elf", |
| binary: "bin/font_provider", |
| }, |
| {{ '<strong>' }}use: [ |
| { |
| protocol: [ "fuchsia.pkg.FontResolver" ], |
| }, |
| ],{{ '</strong>' }} |
| } |
| ``` |
| |
| ### Exposing available services {#available-services} |
| |
| In [Components v1][glossary.components-v1], you typically declare information |
| about services exposed by a component in a |
| [sysmgr configuration file][sysmgr-config]. These files are referenced by |
| `config_data` targets in the build, and specify mappings of services to |
| components in the `sys` [environment][glossary.environment]. |
| |
| Note: The most common location of this service mapping is |
| [`services.config`][example-services-config], which defines service mappings |
| that apply to every product configuration. |
| |
| 1. Identify all service mappings, if any, for your component. You can use |
| [CodeSearch][code-search] to find service mappings. Here is a |
| [sample search][sysmgr-config-search]. |
| |
| ```json |
| // services.config |
| { |
| "services": { |
| ... |
| "fuchsia.fonts.Provider": "fuchsia-pkg://fuchsia.com/fonts#meta/fonts.cmx", |
| ... |
| } |
| } |
| ``` |
| |
| 1. For each service mapping, add an [`expose`][manifests-expose] declaration |
| and a corresponding [`capabilities`][manifests-capabilities] entry with the |
| service `protocol`. |
| |
| ```json5 |
| // fonts.cml |
| { |
| include: [ |
| // Enable system logging |
| "syslog/client.shard.cml", |
| ], |
| program: { |
| runner: "elf", |
| binary: "bin/font_provider", |
| }, |
| {{ '<strong>' }}capabilities: [ |
| { |
| protocol: [ "fuchsia.fonts.Provider" ], |
| }, |
| ],{{ '</strong>' }} |
| use: [ |
| { |
| protocol: [ "fuchsia.pkg.FontResolver" ], |
| }, |
| ], |
| {{ '<strong>' }}expose: [ |
| { |
| protocol: "fuchsia.fonts.Provider", |
| from: "self", |
| }, |
| ],{{ '</strong>' }} |
| } |
| ``` |
| |
| 1. Build your updated package: |
| |
| ```posix-terminal |
| fx build |
| ``` |
| |
| 1. Verify that your package includes the compiled v2 component manifest (with a |
| `.cm` extension). |
| |
| ```posix-terminal |
| ffx scrutiny shell "search.components --url {{ '<var label="component">my_component.cm</var>' }}$" |
| ``` |
| |
| Note: it is valid to `use` from `self` in the unusual case that your component |
| both consumes and publishes the same protocol. You'll know this is the case |
| when the "services" section in your `.cmx` references a protocol that is mapped |
| to the same component's URL in a `services.config` file. |
| |
| ## Add the new component {#add-component-to-topology} |
| |
| Now you're ready to add your new component to the |
| [v2 component topology][components-topology]. This defines the relationship |
| between your component and the rest of the system. |
| |
| Take another look at any sysmgr configuration file(s) that defines service |
| mappings to your component, which you identified while |
| [migrating the component manifest](#create-component-manifest). The steps below |
| refer to the collection of all these services as your component’s "exposed |
| services". |
| |
| ```json |
| // services.config |
| { |
| "services": { |
| ... |
| "fuchsia.fonts.Provider": "fuchsia-pkg://fuchsia.com/fonts#meta/fonts.cmx", |
| ... |
| } |
| } |
| ``` |
| |
| ### Add the component to core {#add-component-to-core} |
| |
| - [Add a core realm shard](#add-core-shard): Your component is **not** present |
| in *all* products (eg. maybe it is present on workstation, but not |
| terminal). Using a [core realm shard][core-realm-rfc] allows the component |
| to be safely excluded where it isn't available. |
| - [Add directly to core](#add-core-direct): Your component is present on all |
| product and test build configurations. In this case you can add the |
| component directly to `core.cml`. |
| |
| #### Add a core realm shard {#add-core-shard} |
| |
| 1. Create a [manifest shard][manifests-shard]. A manifest shard uses generally |
| the syntax as a manifest, but *may* reference objects that don't exist |
| within the manifest itself. In this case we reference the `session-manager` child |
| which is not defined here, but we know is defined in `core`'s manifest. |
| |
| ```json5 |
| // component.core_shard.cml |
| { |
| children: [ |
| { |
| name: "font_provider", |
| url: "fuchsia-pkg://fuchsia.com/fonts#meta/fonts.cm", |
| }, |
| ], |
| offer: [ |
| { |
| protocol: "fuchsia.fonts.Provider", |
| from: "#font_provider", |
| to: [ "#session-manager" ], |
| }, |
| ], |
| } |
| ``` |
| |
| 1. Create a target in your `BUILD.gn` that defines the `core_shard`. |
| |
| ```gn |
| # font_provider/BUILD.gn |
| |
| import("//src/sys/core/build/core_shard.gni") |
| |
| core_shard("font_provider_shard") { |
| shard_file = "component.core_shard.cml" |
| } |
| ``` |
| |
| 1. Add the core realm shard to the appropriate products. For example, you can |
| add the component to the workstation product by modifying |
| `//products/workstation.gni` by adding the build target path to the |
| `core_realm_shards` array. If your component is present on all products that |
| derive from core and you are adding it via a shard, modify |
| `//products/core.gni`. |
| |
| ```gn |
| # //products/workstation.gni |
| ... |
| core_realm_shards += [ |
| ... |
| "//path/to/font_provider:font_provider_shard", |
| ] |
| ... |
| ``` |
| |
| #### Add directly to core {#add-core-direct} |
| |
| Add your component as a child instance of the [`core.cml`][cs-core-cml] |
| component, and offer its exposed services to dependent components. You need to choose a name |
| for your component instance and identify its component URL (you should be able |
| to get this from the config mapping). |
| |
| ```json5 |
| // core.cml |
| { |
| children: [ |
| ... |
| { |
| name: "font_provider", |
| url: "fuchsia-pkg://fuchsia.com/fonts#meta/fonts.cm", |
| }, |
| ], |
| offer: [ |
| ... |
| { |
| protocol: "fuchsia.fonts.Provider", |
| from: "#font_provider", |
| to: [ "#session-manager" ], |
| }, |
| ], |
| } |
| ``` |
| |
| ### Make your component's services available to v1 components {#route-to-v1} |
| |
| It is very common for there to be components in the v1 `sys` realm that depend |
| on your component's exposed services. Make your component's services available |
| to the v1 `sys` realm by adding a declaration like the following to your core |
| realm shard or `core.cml`. |
| |
| ```json5 |
| // core.cml / component.core_shard.cml |
| { |
| use: [ |
| { |
| protocol: "fuchsia.fonts.FontProvider", |
| from: "#font_provider", |
| }, |
| ], |
| } |
| ``` |
| |
| Note: You do _not_ need to offer the service to `appmgr`. `core` itself proxies |
| to and from the v1 `sys` realm, which is why we have `core` `use` the service. |
| |
| ### Offer services to your component {#offer-services} |
| |
| To work properly, your component must be offered all services that appear in its |
| [`use`][manifests-use] declarations. These services may be provided by v1 or v2 |
| components. Look in the sysmgr config files and `core.cml` to find the |
| originating components ([example search][sysmgr-config-search]). |
| |
| There are three possible cases: |
| |
| - [v1 component provides service](#v1-component-provides-service): The |
| provider of the service is a v1 component. |
| - [v2 component in `core.cml` provides service](#v2-core-cml-provides-service): |
| The provider of the service is a v2 component that's a child of `core.cml`. |
| - The provider of the service is a v2 component that's not child of |
| `core.cml`. If this is the case, reach out to |
| [component-framework-dev][cf-dev-list] for assistance. |
| |
| Note: You must also route all services requested by any manifest shards listed |
| in your manifest's [`include`][manifests-include]. |
| |
| #### v1 component provides service {#v1-component-provides-service} |
| |
| You’ll reach this case if a mapping for the service exists in a sysmgr config |
| file. To make the service available to your component, do the following. |
| |
| 1. Make sure a declaration like the following is present in |
| `appmgr.core_shard.cml` (if the service is configured in fuchsia.git) or |
| your [core realm shard](#add-core-shard) (if it's configured outside |
| fuchsia.git): |
| |
| ```json5 |
| // appmgr.core_shard.cml / component.core_shard.cml |
| { |
| capabilities: [ |
| ... |
| { protocol: "fuchsia.pkg.FontResolver" }, |
| ... |
| ], |
| } |
| ``` |
| 1. Add the following to your component's [core realm shard](#add-core-shard) or |
| `core.cml`: |
| |
| ```json5 |
| // core.cml / component.core_shard.cml |
| { |
| offer: [ |
| ... |
| { |
| protocol: "fuchsia.pkg.FontResolver", |
| from: "self", |
| to: [ "#font_provider" ], |
| }, |
| ... |
| ], |
| } |
| ``` |
| |
| Note: You do _not_ offer the service from `appmgr`. `core` itself proxies |
| to and from the v1 `sys` realm, which is why we have `core` offer the |
| service. |
| |
| #### v2 component in core.cml provides service {#v2-core-cml-provides-service} |
| |
| Route the service from the component in `core` that exposes it to your component |
| in `core.cml`: |
| |
| ```json5 |
| // core.cml |
| { |
| offer: [ |
| ... |
| { |
| protocol: [ "fuchsia.pkg.FontResolver" ], |
| from: "#font_resolver", |
| to: [ "#font_provider" ], |
| }, |
| ... |
| ], |
| } |
| ``` |
| |
| ### Resolve dependency cycles {#dependency-cycles} |
| |
| In Components v1, `appmgr` represents a collection of multiple components with |
| many capabilities. This increases the chance that a v2 component routes multiple |
| capabilities into and out of `appmgr` for a given component. Components that |
| both offer services to `appmgr` and consume services offered by `appmgr` create |
| a **dependency cycle** that you may need to resolve during the migration. |
| |
| ```none |
| Strong dependency cycles were found. Break the cycle by removing a dependency or |
| marking an offer as weak. Cycles: { { ... }, { ... } } |
| ``` |
| |
| To avoid build-time errors resulting from dependency cycles, apply the |
| `weak_for_migration` tag to one of the capability routes. For example: |
| |
| ```json5 |
| // core.cml / component.core_shard.cml |
| { |
| offer: [ |
| { |
| protocol: [ "fuchsia.pkg.FontResolver" ], |
| from: "#appmgr", |
| to: [ "#font_provider" ], |
| {{ '<strong>' }}dependency: "weak_for_migration",{{ '</strong>' }} |
| }, |
| { |
| protocol: "fuchsia.fonts.Provider", |
| from: "#font_provider", |
| to: [ "#appmgr" ], |
| }, |
| ] |
| } |
| ``` |
| |
| You can apply `weak_for_migration` to either capability in a dependency cycle. |
| Determine which side is most appropriate for your component. In most cases, the |
| convention is to apply `weak_for_migration` on the capability offered from |
| `appmgr` until everything is migrated out of Components v1. |
| |
| ### Remove sysmgr configuration entries {#remove-config-entries} |
| |
| Before you test your component, remove the service mappings in |
| [`services.config`][example-services-config] and other sysmgr configuration |
| files you identified previously. |
| |
| Without this step, sysmgr will report errors attempting to load services from |
| your v1 component instead of using the new capabilities routed to it through |
| `core.cml`. |
| |
| ```json |
| // services.config |
| { |
| "services": { |
| ... |
| // Delete these lines |
| "fuchsia.fonts.Provider": "fuchsia-pkg://fuchsia.com/fonts#meta/fonts.cmx", |
| ... |
| } |
| } |
| ``` |
| |
| ## Migrate component features {#features} |
| |
| Explore the following sections for additional migration guidance on |
| specific features your system components may support: |
| |
| - [Component sandbox features](features.md) |
| - [Diagnostics capabilities](diagnostics.md) |
| - [Other common situations](common.md) |
| |
| ## Verify the migrated component {#verify-component} |
| |
| Build the target for your package and manually verify that your component and |
| its dependencies still work as expected. |
| |
| ```posix-terminal |
| fx build |
| ``` |
| |
| Perform manual verification of capability routing using the `verify routes` |
| command built into [scrutiny][fx-scrutiny]. |
| |
| ```posix-terminal |
| ffx scrutiny verify routes |
| ``` |
| |
| This command reports routing errors in the static component topology of the |
| current build. This can help you find missing `offer` or `expose` declarations |
| before performing runtime tests. |
| |
| Note: Scrutiny can only verify routes in the v2 component topology. It cannot |
| look into `appmgr` and the `sys` environment to review usage from v1 components. |
| |
| If your component or one of the components that depends on it isn't working |
| correctly, try following the advice in |
| [Troubleshooting components][troubleshooting-components]. |
| |
| [cf-dev-list]: https://groups.google.com/a/fuchsia.dev/g/component-framework-dev |
| [cmx-services]: /docs/concepts/components/v1/component_manifests.md#sandbox |
| [code-search]: https://cs.opensource.google/fuchsia |
| [components-topology]: /docs/concepts/components/v2/topology.md |
| [core-realm-rfc]: /docs/contribute/governance/rfcs/0089_core_realm_variations.md |
| [cs-core-cml]: /src/sys/core/meta/core.cml |
| [fx-scrutiny]: https://fuchsia.dev/reference/tools/fx/cmd/scrutiny |
| [glossary.component-manifest]: /docs/glossary/README.md#component-manifest |
| [glossary.components-v1]: /docs/glossary/README.md#components-v1 |
| [glossary.environment]: /docs/glossary/README.md#environment |
| [glossary.runner]: /docs/glossary/README.md#runner |
| [example-fonts]: https://fuchsia.googlesource.com/fuchsia/+/cd29e692c5bfdb0979161e52572f847069e10e2f/src/fonts/meta/fonts.cmx |
| [example-package-rule]: https://fuchsia.googlesource.com/fuchsia/+/cd29e692c5bfdb0979161e52572f847069e10e2f/src/fonts/BUILD.gn |
| [example-services-config]: /src/sys/sysmgr/config/services.config |
| [json5-external]: https://json5.org/ |
| [manifests-capabilities]: https://fuchsia.dev/reference/cml#capabilities |
| [manifests-expose]: https://fuchsia.dev/reference/cml#expose |
| [manifests-include]: https://fuchsia.dev/reference/cml#include |
| [manifests-program]: https://fuchsia.dev/reference/cml#program |
| [manifests-shard]: /docs/development/components/build.md#component-manifest-shards |
| [manifests-use]: https://fuchsia.dev/reference/cml#use |
| [sysmgr-config]: /docs/concepts/components/v1/sysmgr.md |
| [sysmgr-config-search]: https://cs.opensource.google/search?q=fuchsia-pkg:%2F%2Ffuchsia.com%2F.*%23meta%2Fmy_component.cmx%20-f:.*.cmx$%20%5C%22services%5C%22&ss=fuchsia |
| [troubleshooting-components]: /docs/development/components/troubleshooting.md |