blob: f54e7ab60b2a7082e488cfd81bea3c4dd3324bb0 [file] [log] [blame] [view]
# Subpackaging components
[Packages] can "contain" other packages (referred to as their
[subpackage][glossary.subpackage]s), producing a hierarchy of nested packages.
[Components] can leverage subpackaging to organize a [hierarchy of nested
components][hierarchy-of-nested-components], where each component is
encapsulated in its own package, and brings its own set of dependencies.
_Subpackages enable:_
* Encapsulated dependencies (a packaged component declares its direct
dependencies only)
* Isolated `/pkg` directories (grouped components don't need to merge their
files, libraries, and metadata into a single shared namespace)
* Assured dependency resolution (system and build tools ensure subpackages
always "travel with" their packages)
## Relationship to Fuchsia Components
Fuchsia uses packages to "distribute" its software (for example, to load the
software onto a device). A single component with no included dependencies is
typically contained in a single package. A component that launches other
components can define a package that _includes_ specific versions (determined at
build time) of its child components using subpackaging.
When organized this way, the subpackage hierarchy mirrors the [component
parent-child relationships][component-parent-child-relationship]. Child
components will then be loaded from a declared subpackage of the parent
component's package. This encapsulates [ABI][glossary.abi] dependencies at
package boundaries.
Components can also use a subpackage to declare a dependency on a
non-executable component (one without a `program` declaration) and gain access
to the `/pkg` data therein using [directory capabilities]. By exposing package
data as standard [directory capabilities], the components use
[capability routing] to restrict access to specific package subdirectories,
thereby upholding the Principle of Least Privilege.
### Package dependencies mirror Component dependencies
A Fuchsia system is defined by a hierarchy of components. Starting with the
first component (the root of the hierarchy), components add capabilities to the
system by launching `children` (child components) that serve those capabilities.
Each component has the opportunity to launch its own subtree of components.
To instantiate a `child` component, the parent identifies the child's source
(implementation software) by its location in the package system using a
"component URL" (a package URL combined with the intra-package resource location
of the component manifest); for example
`fuchsia-pkg://fuchsia.com/package#meta/component.cm`.
Importantly, under the Component Framework, the _only_ place a component refers
to a runtime dependency by component URL is when declaring children. Component
URLs are not used to define a dependency on a peer component or any other
component outside of its local subtree.
When a child component is defined by an absolute component URL like
`fuchsia-pkg://fuchsia.com/package#meta/component.cm`, the component developer
cedes control over the implementation of that dependency, to be determined
(potentially) at product assembly time, or at runtime from an ephemeral source
(a package server).
Subpackaging allows the developer to instead declare package dependencies with
build-time resolution, "baking in" the expected component implementations,
including known ABI and behavior, without compromising the encapsulation and
isolation benefits of package boundaries. This ensures a package with
component dependencies has a hermetic implementation, and the behavior of its
child components will not change without rebuilding the parent component's
package.
Subpackaged component URLs also avoid problems inherent with absolute component
URLs: If a parent component is loaded from (for example) an alternate repository
like `fuchsia-pkg://alt-repo.com/package#meta/parent.cm`, its likely that its
children may also be in that `alt-repo`, and there is no way to statically
define an absolute component URL that can resolve from either `fuchsia.com` or
`alt-repo.com` (or another) not known until runtime.
By using relative package paths, a subpackaged child component's implementation
is identified by a [relative component URL] with subpackage name (a subpackage
URL, with a URI fragment specifying the path to the component manifest), such as
`some-child#meta/default.cm`. The mapping from subpackage name `some-child` is
declared in a build configuration, and resolved at build time, by storing the
subpackage's package hash in the parent component's package metadata, mapped to
the subpackage name.
<aside class="key-point">
This superpackage <-> subpackage relationship between packages naturally mirrors
the parent <-> child relationship between components.
</aside>
### Dependencies are transitive and encapsulated
Component software implementations _do not `use`_ other components. Components
`use` capabilities. A component's capabilities may come from its parent
(routed directly or indirectly by the parent, without the knowledge of the
component) or from a child. Importantly, a capability exposed by a child can
also be either direct or indirect. The child's implementation is encapsulated,
so a capability it exposes may be implemented by that child, or may be routed
from one of the child's children.
Subpackaging allows a component to completely encapsulate its implementation,
including any dependencies on sub-components.
When a component declares children using absolute component URLs, the specific
implementation of that child is selected at runtime. This may be desired, for
certain use cases, but the trade-off is that the parent component is not
hermetic: It can be hard to re-use the parent component in new environments.
Distributing and porting non-hermetic code requires keeping track of all of the
external dependencies as well, and then ensuring the dependencies are always
available in each new environment.
```json5 {:.devsite-disable-click-to-copy}
children: [
{
name: "intl_property_provider",
url: "fuchsia-pkg://fuchsia.com/intl_property_manager#meta/intl_property_manager.cm",
},
...
]
```
When runtime resolution is not required, the parent component can update its
children to use relative path URLs, and declare the child components' packages
as subpackage dependencies, resolved at build time. This way, when a component
subpackages a child component, the child's package brings all of its subpackaged
components inherently, without exposing those dependencies to the other
components and runtime environments that may use it.
```json5 {:.devsite-disable-click-to-copy}
children: [
{
name: "intl_property_provider",
url: "intl_property_manager#meta/intl_property_manager.cm",
},
...
]
```
<aside class="key-point">
The subpackaged component can add, remove, or replace child components without
breaking API compatibility with the top component, as long as the child
component continues to serve the same capabilities, regardless of which
components implement which capabilities. Therefore subpackages provide a way to
mirror the encapsulation model of components.
</aside>
### No ambient authority through the `/pkg` directory
In order to support the basic runtime requirements of a Fuchsia Component,
a component may access a directory containing the contents of its package, via
the [`/pkg`][ambient-pkg-directory] directory capability.
As described above, subpackaging allows packages to declare their component
dependencies as hierarchical, encapsulated packages of components. This model
does not require a separate package per component, but it does encourage it, and
the Fuchsia runtime and tools are designed to make the process of declaring,
building, and running separately-packaged components natural and performant.
Conversely, multiple components combined in a single package share a single,
merged `/pkg` directory. Bundling more than one component in a single package
allows each component to access not only the same data, but also the metadata of
the other components in that package as well, without explicit capability
routing.
In certain cases, where multiple components share access to the same data, this
may be convenient. However, in cases where components need access to different
sets of data, or one component uses data that should not be exposed to the
other, packaging components together may undermine the [principle of least
privilege], making subpackages a better fit.
<<../../../get-started/_common/components/_no_ambient_authority.md>>
The fact that a component might not take advantage of this consequential
privilege is more of a concern than a relief because this might not always be
the case, and the privilege opens up an unexpected opportunity for one component
to exploit the data of another component.
<aside class="key-point">
Subpackages ensure each component has its own isolated <code>/pkg</code>
directory while providing the same benefits of relative URL resolution, and
improvements to software hermeticity and software encapsulation benefits through
hierarchical nesting.
</aside>
## Advantages over using multiple components in a single package
Today, Fuchsia allows a single package to contain multiple components. This
feature predates the existence of subpackages, and it provides another way
to declare child components by a relative URL; that is, by a URI fragment that
identifies the component by resource path to the component manifest. A
component URL of the form `#meta/some-child.cm` informs the Fuchsia component
resolver to load the component implementation for `some-child` from the same
package that contained the parent component's manifest.
### Built-in access controls to share package resources
The component framework helps to enforce Fuchsia's capability access control
policies by requiring components to declare their capability needs explicitly,
and by making the parent component responsible for routing any external
capabilities (including resources) from known capability sources (from the
parent's parent, or from another child).
If one component needs a resource from another component's package, the
Component Framework capability routing declarations allow the source component
to expose the specific subdirectory such that the target component can access
only what is required, and explicitly offered by its parent component.
This supports any use case that might otherwise have been satisfied by relying
on access to a shared `/pkg` directory from a common package, without exposing
the entire `/pkg` directory.
Subpackage-isolated `/pkg` directories combined with Component Framework
capability routing provide Fuchsia architecture-consistent way to control access
to and share package resources.
### Changes to transitive dependencies to not break encapsulation
When combining component dependencies into a single package, all components
share a single, flat namespace, and transitive dependencies must also be
included.
<!-- TODO(https://fxbug.dev/42068204): Add a diagram to help visualize this example. -->
For example, if single package `SP` bundles component `A` and component `B`, but
`B` also depends on `C` by relative URI fragment (`#meta/C.cm`), package `SP`
must bundle `A`, `B`, and `C`. If `B` is later modified to replace `C` with two
new components `D` and `E`, the definition of package `SP` must change to bundle
`A`, `B`, `D`, and `E`, and drop `C` _unless_ (for the sake of argument) either
`D` or `E` (or both) also depend on `C`.
Although some build environments allow a component build target to declare
transitive component dependencies, this practice amplifies the risks of merging
the contents of these components into a single namespace. If a component _or any
of its dependencies_ changes, new files could overwrite files from other
components in any part of the component subtree in that package, breaking
implementations in undefined and potentially catastrophic ways.
Subpackages greatly simplify transitive dependencies by encapsulating them in
the definition of each subpackage, so package `SP` can be replaced with package
`A` (containing component `A`) having a dependency on _only_ subpackage `B`
(containing component `B`). Package `A` requires no other dependencies, and
does not change, even if `B`'s dependencies change.
### Subpackaged implementations are build-time guarantees
Using relative URI fragment component URLs (like, `#meta/some-child.cm`), does
not actually guarantee ABI or even API compatibility between parent and child
components "in the same package" because they could in fact be resolved from
different versions of that package.
If the package is resolved ephemerally (from a package server). A new version of
the same package can be re-published between the time the parent component was
resolved and a later time when the child component is required and loaded. The
child implementation might be different from the implementation included in the
original version of the package.
This is not a rare or contrived use case: In Component Framework, components are
(by default) resolved only when needed. A component that exposes a single
service `S` will not be loaded until and unless some other component actually
requires service `S`. Depending on the business logic of the program, `S` might
be called upon minutes or hours (or more) after the parent component was
launched.
## Examples
### Declaring build dependencies to subpackages
Fuchsia-enabled build frameworks should include a pattern for declaring a
Fuchsia package and its contents. If also enabled to support subpackages, a
package declaration will list the subpackages it depends on, by direct
containment.
For example, in fuchsia.git, the GN templates for declaring Fuchsia packages
support two optional lists, `subpackages` and (less commonly used)
`renameable_subpackages`. One or both can be included. The `renameable_`
version allows the package to assign a package-specific name to the subpackage,
used when referring to the subpackage by package URL or component URL:
```gn
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/subpackages/BUILD.gn" region_tag="declare_subpackages" adjust_indentation="auto" %}
```
The `subpackages` list contains a list of GN `fuchsia_package` build targets. By
default, the subpackage name (the name the containing package will use to refer
to the package) is taken from the defined `package_name` of the subpackage's
`fuchsia_package` target.
Subpackage targets can also be declared using the `package` variable in
the `renameable_subpackages` list. `renameable_targets` also include an optional
`name` variable, to override the default name for the subpackage.
### Declaring subpackaged children
A subpackage is only visible to its parent package, and the component(s) in that
package. Consequently, subpackage names only need to be unique within that
parent package. If two subpackage targets have the same name (or for any other
reason), the parent is free to assign its own subpackage names (via
`renameable_subpackages` in GN, for instance).
When declaring subpackaged child components in CML, the `url` should be the
relative subpackaged component URL, as shown in the following example:
```json5
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/subpackages/meta/echo_client_with_subpackaged_server.cml" region_tag="declare_children_statically" adjust_indentation="auto" %}
```
Subpackaged child components can also be referenced in runtime declarations,
such as when declaring children through [Realm Builder] APIs. For example:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/subpackages/src/lib.rs" region_tag="declare_children_dynamically" adjust_indentation="auto" %}
```
[Packages]: /docs/concepts/packages/package.md
[Realm Builder]: /docs/development/testing/components/realm_builder.md
[Components]: /docs/concepts/components/v2/introduction.md
[ambient-pkg-directory]: /docs/concepts/components/v2/capabilities/life_of_a_protocol_open.md#offered-vs-ambient-capabilities
[component-parent-child-relationship]: /docs/concepts/components/v2/topology.md
[capability routing]: /docs/concepts/components/v2/topology.md#capability-routing
[directory capabilities]: /docs/concepts/components/v2/capabilities/directory.md
[hierarchy-of-nested-components]: /docs/concepts/components/v2/components_as_classes.md#component-manifests-as-classes
[principle of least privilege]: /docs/get-started/learn/intro/sandboxing.md
[relative component URL]: /docs/reference/components/url.md#relative-path-urls-to-subpackaged-components
[glossary.abi]: /docs/glossary/README.md#abi
[glossary.subpackage]: /docs/glossary/README.md#subpackage