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.
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 thefuchsia.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.
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