Organizing components

All components in the system are composed into a rooted component instance tree. Parent components in the tree are responsible for declaring instances of other components as their children and providing them with capabilities. At the same time, child components can expose capabilities back to the parent. These component instance and capability relationships make up the component topology.

Any parent component and all its children form a group within the tree called a realm. Realms enable a parent to control which capabilities flow into and out of its sub-tree of components, creating a capability boundary. This encapsulation allows the realm to be reorganized internally without affecting external components dependent on its exposed capabilities.

Diagram showing how component instances are organized into a tree and parent
components determine the capabilities available to each child through
"capability routing."

In the above diagram, a protocol capability for fuchsia.example.Foo is routed through the component instance tree from the provider to the client. Components declare the capabilities they require with the use keyword:

{
    // Information about the program to run.
    program: {
        // Use the built-in ELF runner to run core binaries.
        runner: "elf",
        // The binary to run for this component.
        binary: "bin/client",
    },

    // Capabilities required by this component.
    use: [
        { protocol: "fuchsia.example.Foo" },
    ],
}

Components declare the capabilities they implement, or provide, using the capabilities section of the component manifest. This makes the capability and its provider known to the component framework. See the following provider.cml example:

{
    // Information about the program to run.
    program: {
        // Use the built-in ELF runner to run core binaries.
        runner: "elf",
        // The binary to run for this component.
        binary: "bin/provider",
    },

    // Capabilities provided by this component.
    capabilities: [
        { protocol: "fuchsia.example.Foo" },
    ],
    // Capabilities routed through this component.
    expose: [
        {
            protocol: "fuchsia.example.Foo",
            from: "self",
        },
    ],
}

The expose keyword makes the capability available from this component to other realms through its parent, which may also include capabilities provided by this component's children. In this case, the source of the capability is self because this component is the provider.

Parent components control capability routing within the realm, creating explicit pathways from the client component to a provider. See the following example parent.cml manifest:

{
    children: [
        {
            name: "provider",
            url: "fuchsia-pkg://fuchsia.com/foo-package#meta/provider.cm",
        },
        {
            name: "client",
            url: "fuchsia-pkg://fuchsia.com/foo-package#meta/client.cm",
        },
    ],
    offer: [
        {
            protocol: "fuchsia.example.Foo",
            from: "#provider",
            to: [ "#client" ],
        },
    ],
}

The parent component declares the set of child components in the realm and routes capabilities to them using the offer keyword. In this way, the parent determines both the scope and the source of each child's capabilities. This also enables multiple components in the topology to provide the same capability, as the component framework relies on explicit routes to determine how to resolve the requests from each client.

Capability types

Fuchsia components support many different types of capabilities. So far in this module, the examples have showcased two distinct capability types: runner and protocol. You may have noticed that the protocol capability requires a routing path but the runner capability does not.

Some capabilities are explicitly routed to components by their parent, while others are provided to all components within the same realm using environments. Environments enable the framework to provision capabilities that don't make sense to route explicitly on a per-component basis. By default, a component inherits the environment of its parent. Components may also declare a new environment for their children.

The following table lists the capability types available to components, and whether they must be explicitly routed from the parent component or provided by the environment:

Type Description Provided by
directory Shared filesystem directories provided by other components. Routing
event Events generated by Component Manager, such as components starting or a capability request. Routing
protocol FIDL protocols provided by other components or the framework. Routing
resolver A component capable of resolving a URL to a component manifest. Environment
runner A runtime used to execute specific components. Environment
service Named groups of related FIDL protocols performing a common task. Routing
storage Isolated filesystem directories unique to each component. Routing

Identifying components

Components are identified by a URL. The framework resolves component URLs to component declarations with the help of a component resolver. Resolvers are components themselves that are capable of handling a particular URL scheme and fetching the component manifest, program, and assets.

Most components are published inside a Fuchsia package, so the component URL is a reference to the component manifest inside that package. See the following example:

fuchsia-pkg://fuchsia.com/foo-package#meta/foo-component.cm

Component instances are identified by a topological path reference known as a moniker. A component's moniker indicates its location within the component instance tree as an absolute or relative path. For example, the moniker path /core/system-updater refers to the instance of system-updater that exists in the core realm.

Component lifecycle

Component instances are created and destroyed when they are added and removed in the component topology. This can happen in one of two ways:

  • Statically: The instance is declared in the component manifest as a child of another component in the tree. Static components are only created and destroyed when an update changes the component topology.
  • Dynamically: The instance is added or removed in a component collection at runtime using the fuchsia.component.Realm protocol. Dynamic components are destroyed on system shutdown.

Once a component is destroyed, the framework removes its persistent state (such as local storage).

The framework starts a component instance when another component attempts to open a channel to it. This happens implicitly when connecting to a capability exposed by the component. Connecting to a component that is already started reuses the running instance.

Components may stop themselves by exiting the program (as defined by the component's runner), or the framework may stop the component as part of system shutdown.

Diagram showing how components have two distinct states: instance and
execution. Together, these states describe the "component lifecycle."

Exercise: Integrate components

In order for a component to be invoked, it must be present in the active component topology. For this exercise, you will add your component to the ffx-laboratory — a restricted collection used for development inside the product's core realm. Collections enable components to be dynamically created and destroyed at runtime.

Start the emulator

If you do not already have an instance running, start FEMU with networking support:

ffx emu start workstation_eng.x64 --headless

Publish the package

Recall from Software delivery that Fuchsia devices resolve software packages on demand from a package repository.

Use the bazel run command to build and publish the echo component package:

bazel run //fuchsia-codelab/echo:pkg.publish -- \
    --repo_name fuchsiasamples.com

This command publishes the package to a repository named fuchsiasamples.com; creating the repository if it does not exist and registering it with the target.

Add to the component topology

Create a new instance of the echo component using the following command:

ffx component create /core/ffx-laboratory:echo \
    fuchsia-pkg://fuchsiasamples.com/echo-example#meta/echo.cm

This command accepts two parameters:

  • /core/ffx-laboratory:echo: This is the component moniker, representing the path inside the component topology for the component instance.
  • fuchsia-pkg://fuchsiasamples.com/echo-example#meta/echo.cm: This is the component URL, indicating how Fuchsia should resolve the component from the package server.

A new component instance named echo now exists in the topology. Show the details of the new instance using the following command:

ffx component show echo

You should see the following output:

               Moniker: /core/ffx-laboratory:echo
                   URL: fuchsia-pkg://fuchsiasamples.com/echo-example#meta/echo.cm
                  Type: CML dynamic component
       Component State: Unresolved
       Execution State: Stopped

Notice that the instance has been created, but the component URL has not been resolved. Resolution happens when the framework attempts to start the instance.

Start the component instance

Start the new echo component instance using the following command:

ffx component start /core/ffx-laboratory:echo

This command accepts one parameter:

  • /core/ffx-laboratory:echo: This is the component moniker, representing the path inside the component topology for the component instance.

This causes the component instance to start, print a greeting to the log, then exit. Open a new terminal window and filter the device logs for messages from the example:

ffx log --filter echo

You should see the following output in the device logs:

[ffx-laboratory:echo][I] Hello, Alice, Bob, Spot!

Explore the instance

Show the details of the echo instance again using the following command:

ffx component show echo

You should now see the following output:

               Moniker: /core/ffx-laboratory:echo
                   URL: fuchsia-pkg://fuchsiasamples.com/echo-example#meta/echo.cm
                  Type: CML dynamic component
       Component State: Resolved
 Incoming Capabilities: fuchsia.logger.LogSink
                        pkg
       Execution State: Stopped

The component state has changed to Resolved and you can see more details about the component's capabilities.

Components have no ambient capabilities to access other parts of the system. Every capability a component requires must be explicitly routed to it through the component topology or provided by its environment.

The echo component requires the fuchsia.logger.LogSink capability to write to the system log. You were able to successfully view the log output because this capability is offered to components in the ffx-laboratory collection from the core realm:

{
    collections: [
        {
            name: "ffx-laboratory",
        },
    ],
    offer: [
        {
            protocol: [ "fuchsia.logger.LogSink" ],
            from: "parent",
            to: "#ffx-laboratory",
        },
    ],
}

Destroy the instance

Clean up the echo instance using the following command:

ffx component destroy /core/ffx-laboratory:echo