Migrating system components

This document provides instructions for migrating a system component from Components v1 to Components v2. A system component is a component that exists to provide services to other components in the system. Typically, in Components v1 the mapping of service to component is registered in a sysmgr configuration file.

To migrate your system component from v1 to v2, do the following:

Depending on the features your component supports, you may need to explore the following sections for additional guidance:

For more details on the components migration effort, see State of the Components v2 Migration.

Prerequisites

Before you begin, ensure that your component uses the latest build templates. If your component still uses the legacy package() in your BUILD.gn, migrate your package templates before continuing.

You should also familiarize yourself with the following topics:

Migrate the component manifest

Create a minimal CML file and configure it with GN so that it gets compiled and installed in your package.

Note: Unlike CMX, CML is JSON5, 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). Create a file in the same directory that has the same filename but with a .cml extension, with the following contents:

    {
        include: [
            // Enable system logging
            "sdk/lib/diagnostics/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.

  2. Find the build rule that defines your component. Normally, this is a fuchsia_component rule. For example, see the fonts BUILD.gn.

    fuchsia_component("fonts") {
      manifest = "meta/fonts.cmx"
      deps = [ ":font_provider" ]
    }
    
  3. Update the manifest element of the associated fuchsia_component rule to point to your new .cml file instead:

    fuchsia_component("fonts") {
      manifest = "meta/fonts.cml"
      deps = [ ":font_provider" ]
    }
    
  4. Build the target for your package:

    fx build
    

You are ready to start writing your v2 component manifest.

Adding the executable

Add the program section of your CML file along with the appropriate runner declaration.

Note: The 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.

// fonts.cmx
{
    "program": {
        "binary": "bin/font_provider"
    }
    ...
}
// fonts.cml
{
    program: {
        runner: "elf",
        binary: "bin/font_provider",
    }
}

Declaring required services

Add use declarations to your CML file. These are the approximate equivalent of the services list in CMX.

// fonts.cmx
{
    "program": {
        "binary": "bin/app"
    }
    "sandbox": {
        "services": [
            "fuchsia.logger.LogSink",
            "fuchsia.pkg.FontResolver"
        ]
        ...
    }
}

Convert each element of the services list to a use declaration for the corresponding service protocol.

// fonts.cml
{
    include: [
        // Enable system logging
        "sdk/lib/diagnostics/syslog/client.shard.cml",
    ],
    program: {
      runner: "elf",
      binary: "bin/font_provider",
    },
    use: [
        {
            protocol: [
                "fuchsia.pkg.FontResolver",
            ],
        },
    ],
}

Exposing available services

In Components v1, you typically declare information about services exposed by a component in a sysmgr configuration file. These files are referenced by config_data targets in the build, and specify mappings of services to components in the sys environment.

Note: The most common location of this service mapping is 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 to find service mappings. Here is a sample search.

    // services.config
    {
        "services": {
            ...
            "fuchsia.fonts.Provider": "fuchsia-pkg://fuchsia.com/fonts#meta/fonts.cmx",
            ...
        }
    }
    
  2. For each service mapping, add an expose declaration and a corresponding capabilities entry with the service protocol.

    // fonts.cml
    {
        include: [
            // Enable system logging
            "sdk/lib/diagnostics/syslog/client.shard.cml",
        ],
        program: {
          runner: "elf",
          binary: "bin/font_provider",
        },
        capabilities: [
            {
                protocol: [ "fuchsia.fonts.Provider" ],
            },
        ],
        use: [
            {
                protocol: [
                    "fuchsia.pkg.FontResolver",
                ],
            },
        ],
        expose: [
            {
                protocol: "fuchsia.fonts.Provider",
                from: "self",
            },
        ],
    }
    
  3. Build your updated package:

    fx build
    
  4. Verify that your package includes the compiled v2 component manifest (with a .cm extension).

    fx scrutiny -c "search.components --url {{ '<var label="component">my_component.cm</var>' }}$"
    

Migrate the tests

In most cases, tests for v1 components are themselves v1 components. The first step is to identify all tests that exercise your component’s functionality. Typically this is a fuchsia_test_package or fuchsia_unittest_package rule. For example, see the fonts BUILD.gn.

A test may include or depend on components that are separate from the test driver. Here are some things to look for:

  • Is your test self-contained in one file (a unit test)? Or does it launch other components (an integration test)?
  • Does your test have a CMX with fuchsia.test facets?
  • Does your test create environments in-process? If so, does it create a separate environment for each test case?
  • Does your test have a CMX containing system-services?

Update the test configuration

The migration procedure varies depending on the testing framework features in your v1 component:

Test with no injected services

For tests that use no injected services, your test root can be the same component as the test driver. The v2 test's component manifest should be distributed in the same package that contains the test binary. Follow the same instructions from Migrate the component manifest that you used to package your component.

Consider the following example test component:

// fonts_test.cmx
{
    "program": {
        "binary": "bin/font_test"
    }
}

To migrate this test to the v2 testing framework, do the following:

  1. Create a CML file that points to the test binary that includes the appropriate test runner:

    Note: See test runners that are provided by the framework.

    // fonts_test.cml
    {
        include: [
            // Select the appropriate test runner shard here:
            // rust, gtest, go, etc.
            "src/sys/test_runners/rust/default.shard.cml",
        ],
        program: {
            binary: "bin/font_test",
        }
    }
    
  2. Update the fuchsia_component rule for your test component to reference the new CML file:

    fuchsia_component("fonts_test_driver") {
      testonly = true
      manifest = "meta/fonts_test.cml"
      deps = [ ":font_test" ]
    }
    
    fuchsia_test_package("font_provider_tests") {
      test_components = [ ":fonts_test_driver" ]
    }
    

Test with injected services

For tests that use fuchsia.test facets, such as injected-services, your test root and test driver must be split into different components to enable proper capability routing.

In this example, suppose there's a single injected service, fuchsia.pkg.FontResolver:

// font_provider_test.cmx
{
    "facets": {
        "fuchsia.test": {
            "injected-services": {
                "fuchsia.pkg.FontResolver":
                    "fuchsia-pkg://fuchsia.com/font_provider_tests#meta/mock_font_resolver.cmx"
            }
        }
    },
    "program": {
        "binary": "bin/font_provider_test"
    },
    "sandbox": {
        "services": [
            "fuchsia.pkg.FontResolver"
        ]
    }
}

To migrate this test to the v2 testing framework, do the following:

  1. Create a CML file for the test driver that points to the test binary, and includes the appropriate test runner:

    Note: See test runners that are provided by the framework.

    // test_driver.cml (test driver)
    {
        include: [
            // Select the appropriate test runner shard here:
            // rust, gtest, go, etc.
            "src/sys/test_runners/rust/default.shard.cml",
        ],
        program: {
            binary: "bin/font_provider_test",
        }
    }
    
  2. You need CML files for each component that provides a capability needed in the test. If there is an existing CML file for the component providing the injected service, you may be able to reuse it. Otherwise, create a new CML file.

    // mock_font_resolver.cml (capability provider).
    {
        program: {
          runner: "elf",
          binary: "bin/mock_font_resolver",
        },
        capabilities: [
            {
                protocol: [ "fuchsia.pkg.FontResolver" ],
            },
        ],
        expose: [
            {
                protocol: "fuchsia.pkg.FontResolver",
                from: "self",
            },
        ],
    }
    

    Note: The CML files for the capability providers can be distributed in the same package that contained the v1 test. Follow the same instructions in Migrate the component manifest that you used to package your component.

  3. Create a new CML file for the test root that includes the test driver and capability provider(s) as children and offers the capabilities from the provider(s) to the driver. This component should also expose the test suite protocol.

    // font_provider_test.cml (test root)
    {
        children: [
            {
                name: "test_driver",
                url: "fuchsia-pkg://fuchsia.com/font_integration_test#meta/test_driver.cm",
            },
            {
                name: "font_resolver",
                url: "fuchsia-pkg://fuchsia.com/font_integration_test#meta/mock_font_resolver.cm",
            },
        ],
        expose: [
            {
                protocol: "fuchsia.test.Suite",
                from: "#test_driver",
            },
        ],
        offer: [
            {
                protocol: "fuchsia.pkg.FontResolver",
                from: "#font_resolver",
                to: [ "#test_driver" ],
            },
        ],
    }
    
  4. Add fuchsia_component rules for each CML file, and update the fuchsia_package to reference the child components as dependencies:

    fuchsia_component("test_driver") {
      testonly = true
      manifest = "meta/test_driver.cml"
      deps = [ ":font_provider_test_bin" ]
    }
    
    fuchsia_component("mock_font_resolver") {
      testonly = true
      manifest = "meta/mock_font_resolver.cml"
      deps = [ ":mock_font_resolver_bin" ]
    }
    
    fuchsia_component("font_provider_test") {
      testonly = true
      manifest = "meta/font_provider_test.cml"
    }
    
    fuchsia_test_package("font_provider_tests") {
      test_components = [ ":font_provider_test" ]
      deps = [
        ":fonts_test_driver",
        ":mock_font_resolver",
      ]
    }
    

Verify the migrated tests

Build and run your test and verify that it passes. Like any other test, use fx test to invoke the test:

fx build && fx test font_provider_tests

Your component is now tested in Components v2.

If your test doesn‘t run correctly or doesn’t start at all, try following the advice in Troubleshooting components.

Add the new component

Note: This section assumes that your component is not in apps or startup_services. If it is, reach out to component-framework-dev for guidance.

Now you're ready to add your new component to the v2 component 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. The steps below refer to the collection of all these services as your component’s “exposed services”.

// services.config
{
    "services": {
        ...
        "fuchsia.fonts.Provider": "fuchsia-pkg://fuchsia.com/fonts#meta/fonts.cmx",
        ...
    }
}

Add the component to core

Add your component as a child instance of the core.cml component, and offer its exposed services to appmgr. 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).

// core.cml
{
    children: [
        ...
        {
            name: "font_provider",
            url: "fuchsia-pkg://fuchsia.com/fonts#meta/fonts.cm",
        },
    ],
    offer: [
        ...
        {
            protocol: "fuchsia.fonts.Provider",
            from: "#font_provider",
            to: [ "#appmgr" ],
        },
    ],
}

Expose services to sys environment

Declare each of these services in appmgr.cml to make them available to v1 components under the sys environment. Change appmgr.cml as follows:

// appmgr.cml
{
    use: [
        ...
        {
            protocol: "fuchsia.fonts.Provider",
            path: "/svc_for_sys/fuchsia.fonts.Provider",
        },
    ],
}

Offer services to your component

To work properly, your component must be offered all services that appear in its 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).

There are three possible cases:

Note: You must also route all services requested by any manifest shards listed in your manifest's include.

v1 component provides service

You’ll reach this case if a mapping for the service exists in a sysmgr config file. Take a look at appmgr.cml, and search for the service. If it’s already exposed, no modifications are required. If not, you’ll need to change appmgr.cml to expose the service and route it from appmgr to your component:

// appmgr.cml
{
    expose: [
        ...
        {
            protocol: [
                ... // (Any services already exposed from appmgr are here)
                "fuchsia.pkg.FontResolver",
            ],
            from: "self",
        },
        ...
    ],
}
// core.cml
{
    offer: [
        ...
        {
            protocol: "fuchsia.logger.LogSink",
            from: "parent",
            to: [ "#font_provider" ],
        },
        {
            protocol: [
                "fuchsia.pkg.FontResolver",
            ],
            from: "#appmgr",
            to: [ "#font_provider" ],
        },
        ...
    ],
}

v2 component in core.cml provides service

Route the service from the component in core that exposes it to your component in core.cml:

// core.cml
{
    offer: [
        ...
        {
            protocol: [ "fuchsia.pkg.FontResolver" ],
            from: "#font_resolver",
            to: [ "#font_provider" ],
        },
        ...
    ],
}

Remove sysmgr configuration entries

Before you test your component, remove the service mappings in 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.

// services.config
{
    "services": {
        ...
        // Delete these lines
        "fuchsia.fonts.Provider": "fuchsia-pkg://fuchsia.com/fonts#meta/fonts.cmx",
        ...
    }
}

Test your component

It is recommended that you manually verify that your component and its dependencies still work. Perform manual verification of capability routing as it is usually outside the scope of hermetic tests.

If your component manifest contains additional system features that haven't been migrated at this point, see Other common capabilities and Converting CMX features for additional guidance.

If your component or one of the components that depends on it isn't working correctly, try following the advice in Troubleshooting components.

Once your component has been registered in the v2 topology and all tests have been converted, you can delete the Components v1 definition of your component. Find and remove any CMX files for your component and its tests, including any remaining references to it from the package rule(s) you modified when you migrated the component manifest.

Other common capabilities

This section provides guidance on migrating other capabilities that are common to most components.

Inspect

If your component is using inspect, you'll need to expose Inspect information to the framework. Inspect data is accessible under the /diagnostics directory in the component outgoing directory. A v2 component has to explicitly expose this directory to the framework. This allows inspect data to be readable by the Archivist for snapshots, iquery, etc.

You can add this to your component by including the following manifest shard:

// fonts.cml
{
    // Expose the diagnostics directory capability for Inspect
    include: [ "sdk/lib/diagnostics/inspect/client.shard.cml" ],
    ...
}

Converting CMX features

This section provides guidance on migrating CMX features. If there‘s a feature in your CMX file that’s not in this list, please reach out to component-framework-dev.

Storage features

If your component uses any of the following features, follow the instructions in this section to migrate storage access:

FeatureDescriptionStorage CapabilityPath
isolated-persistent-storageIsolated persistent storage directorydata/data
isolated-cache-storageManaged persistent storage directorycache/cache
isolated-tempManaged in-memory storage directorytemp/tmp

These features are supported in v2 components using storage capabilities.

Declare the required storage capabilities

When migrating your component manifest, add the following to your CML file:

// my_component.cml
{
    use: [
        ...
        {
            storage: "{{ '<var label="storage">data</var>' }}",
            path: "{{ '<var label="storage path">/data</var>' }}",
        },
    ],
}

Inject storage capabilities into tests

When migrating tests, you will need to inject storage access into your test component if the test driver or any of the other components in the test realm access a storage path.

Following the example in Test uses injected services, add the following to route storage access to your test driver from the test root:

// test_root.cml
}
    children: [
        {
            name: "test_driver",
            url: "fuchsia-pkg://fuchsia.com/my-package#meta/my_component_test.cm",
        },
    ],
    offer: [
        ...
        {
            storage: "{{ '<var label="storage">data</var>' }}",
            from: "parent",
            to: [ "#test_driver" ],
        },
    ],
}

Route storage from the parent realm

When adding your component, you'll need to offer the appropriate storage path to your component from its parent realm.

// core.cml
{
    children: [
        ...
        {
            name: "my_component",
            url: "fuchsia-pkg://fuchsia.com/my-package#meta/my_component.cm",
        },
    ],
    offer: [
        ...
        {
            storage: "{{ '<var label="storage">data</var>' }}",
            from: "self",
            to: [ "#my_component" ],
        },
    ]
}

Note: If the appropriate storage capability is not currently provided by your component's parent realm, reach out to component-framework-dev for assistance.

Update component storage index

Components that use storage use a component ID index to preserve access to persistent storage contents across the migration, such as core_component_id_index.json. You must update the component index to map the new component moniker to the same instance within the component that provides the storage capability.

Find any instances of your current v1 component in component index files:

// core_component_id_index.json
{
    instances: [
        ...
        {
            instance_id: "...",
            appmgr_moniker: {
                url: "fuchsia-pkg://fuchsia.com/my-package#meta/my_component.cmx",
                realm_path: [ ... ]
            }
        }
    ]
}

Replace the appmgr_moniker for your component instance with the new moniker in the migrated v2 realm, keeping the same instance_id:

// core_component_id_index.json
{
    instances: [
        ...
        {
            instance_id: "...",
            moniker: "/core/my_component"
        }
    ]
}

Note: If you are migrating your component to a realm other than core, the moniker should reflect that.

Directory features

If your component uses any of the following features, follow the instructions in this section to migrate directory access:

FeatureDescriptionDirectory CapabilityPath
factory-dataRead-only factory partition datafactory/factory
durable-dataPersistent data that survives factory resetdurable/durable
shell-commandsExecutable directory of shell binariesbin/bin
root-ssl-certificatesRead-only root certificate dataconfig-ssl/config/ssl

These features are supported in v2 components using directory capabilities.

Declare the required directory capabilities

When migrating your component manifest, add the following to your CML file:

// my_component.cml
{
  use: [
      ...
      {
          directory: "{{ '<var label="directory">config-ssl</var>' }}",
          rights: [ "r*" ],
          path: "{{ '<var label="directory path">/config/ssl</var>' }}",
      },
}

Note: Unlike storage locations, which are isolated per-component, directories are a shared resource. You may need to also determine the subdirectory your component needs to access in order to complete this migration.

Inject directory path into tests

When migrating tests, you need to inject the directory capabilities in your test if the test driver or any of the other components in the test realm require directory access.

Following the example in Test uses injected services, add the following to route directory access to your test driver from the test root:

// test_root.cml
{
    children: [
        {
            name: "test_driver",
            url: "fuchsia-pkg://fuchsia.com/my-package#meta/my_component_test.cm",
        },
    ],
    offer: [
        ...
        {
            directory: "{{ '<var label="directory">config-ssl</var>' }}",
            from: "parent",
            to: [ "#test_driver" ],
        },
    ],
}

Route directory from the parent realm

When adding your component, you'll need to offer the directory capabilities to your component.

// core.cml
{
    children: [
        ...
        {
            name: "my_component",
            url: "fuchsia-pkg://fuchsia.com/my-package#meta/my_component.cm",
        },
    ],
    offer: [
        {
            directory: "{{ '<var label="directory">config-ssl</var>' }}",
            from: "parent",
            to: [ "#my_component" ],
        },
        ...
    ],
}

Note: If the appropriate directory capability is not currently provided by your component's parent realm, reach out to component-framework-dev for assistance.

Configuration data

If your component uses any of the following features, follow the instructions in this section to migrate directory access:

FeatureDescriptionDirectory CapabilityPath
config-dataRead-only configuration dataconfig-data/config/data

These features are supported in v2 components using directory capabilities.

Declare the required directory capabilities

When migrating your component manifest, add the following to your CML file:

// my_component.cml
{
  use: [
      ...
      {
          directory: "config-data",
          rights: [ "r*" ],
          path: "/config/data",
      },
}

Inject directory path into tests

When migrating tests, you need to inject the directory capability with the appropriate subdirectory in your test if the test driver or any of the other components in the test realm require directory access. The name of the subdirectory should match the name of the package that contains the component.

Following the example in Test uses injected services, add the following to route directory access to your test driver from the test root:

// test_root.cml
{
    children: [
        {
            name: "test_driver",
            url: "fuchsia-pkg://fuchsia.com/my-package#meta/my_component_test.cm",
        },
    ],
    offer: [
        ...
        {
            directory: "config-data",
            from: "parent",
            to: [ "#test_driver" ],
            subdir: "{{ '<var label="package name">my-package</var>' }}",
        },
    ],
}

Route directory from the parent realm

When adding your component, you'll need to offer the directory capability with the appropriate subdirectory to your component.

// core.cml
{
    children: [
        ...
        {
            name: "my_component",
            url: "fuchsia-pkg://fuchsia.com/my-package#meta/my_component.cm",
        },
    ],
    offer: [
        {
            directory: "config-data",
            from: "parent",
            to: [ "#my_component" ],
            subdir: "{{ '<var label="package name">my-package</var>' }}",
        },
        ...
    ],
}