Packages can "contain" other packages (referred to as their subpackage s), producing a hierarchy of nested packages. Components can leverage subpackaging to organize a hierarchy of nested components, where each component is encapsulated in its own package, and brings its own set of dependencies.
- Encapsulated dependencies (a packaged component declares its direct dependencies only)
/pkgdirectories (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. Child components will then be loaded from a declared subpackage of the parent component's package. This encapsulates 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
/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
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
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
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
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.
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.
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.
No ambient authority through the
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
/pkg 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,
/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
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.
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.
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
/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.
For example, if single package
SP bundles component
A and component
B also depends on
C by relative URI fragment (
B is later modified to replace
C with two
E, the definition of package
SP must change to bundle
E, and drop
C unless (for the sake of argument) either
E (or both) also depend on
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
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,
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
S will not be loaded until and unless some other component actually
S. Depending on the business logic of the program,
be called upon minutes or hours (or more) after the parent component was
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
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:
test_components = [ ":subpackage-examples-component" ]
subpackages = [
renameable_subpackages = [
name = "my-echo-server"
package = "//examples/components/routing/rust/echo_server"
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
Subpackage targets can also be declared using the
package variable in
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:
Subpackaged child components can also be referenced in runtime declarations, such as when declaring children through Realm Builder APIs. For example:
// Add the server component to the realm, fetched from a subpackage
let echo_server = builder
.add_child("echo_server", "my-echo-server#meta/default.cm", ChildOptions::new())
// Add the client component to the realm, fetched from a subpackage, using a
// name that is still scoped to the parent package, but the name matches the
// package's top-level name. In `BUILD.gn` the subpackage name defaults to
// the referenced package name, unless an explicit subpackage name is
let echo_client = builder
.add_child("echo_client", "echo_client#meta/default.cm", ChildOptions::new().eager())