This document demonstrates how to build and test a component, highlighting best practices for defining packages, components, and their tests.
Concepts
You should understand the following concepts before building a component:
Packages are the unit of software distribution on
Fuchsia. Packages are a collection of files with associated paths that are
relative to the base of the package. For instance, a package might contain an
ELF binary under the path bin/hello_world
, and a JSON file under the path
data/config.json
. Grouping files into a package is required in order to push
these files to the device.
Components are the unit of software execution on Fuchsia. All software on Fuchsia except for the kernel image and usermode bootstrap program is defined as a component.
A component is defined by a component manifest. Components typically include additional files, such as executables and data assets that they need at runtime.
Developers must define their software in terms of packages and components, whether for building production software or for writing their tests.
Component instances see at runtime the
contents of their package as read-only files under the path /pkg
. Defining two
or more components in the same package doesn't grant each component access to
the other's capabilities. However it can guarantee to one component that the
other is available. Therefore if a component attempts to launch an instance of
another component, such as in an integration test, it can be beneficial to
package both components together.
Components are instantiated in a few ways, all of which somehow specify their
URL. Typically components are launched by specifying
their package names and path to their component manifest in the package, using
the fuchsia-pkg://
scheme.
GN templates
GN is the meta-build system used by Fuchsia. Fuchsia extends GN by defining templates. Templates provide a way to add to GN's built-in target types. Below we will review various GN templates that can be used to define packages, components, and their tests.
Defining components, packages, and tests using GN templates
Below is a hypothetical package containing one component that runs a C++ program and a data file. The example uses the following templates:
import("//src/sys/build/components.gni")
executable("my_program") {
sources = [ "my_program.cc" ]
}
fuchsia_component("my-component") {
manifest = "meta/my_program.cmx"
deps = [ ":my_program" ]
}
fuchsia_package("my-package") {
deps = [ ":my-component" ]
}
The file my_program.cmx
should include at least the following:
{
"program": {
"binary": "bin/my_program"
}
}
Note the following details:
- This example imports
"//src/sys/build/components.gni"
. This single import includes all templates related to packages, components, and testing them. - This example defines an
executable()
, which is used to build a C++ program. This is for illustrative purposes - a component can launch all sorts of programs. - This example defines a
fuchsia_component()
which depends on theexecutable()
. The component definition attaches a manifest, which references the executable to be launched under the given package pathbin/my_program
. See: finding paths for built executables. - The manifest must be either a
.cmx
file in cmx format or a.cml
file in cml format. - The destination path for the manifest is not specified, but rather
inferred from the component's name. In this example, the manifest path will
be
meta/my-component.cmx
. - Both the component and package names are derived from their target names.
They both take an optional
component_name
andpackage_name
parameter respectively as an override.
In the example above, these names come together to form the URL for launching the component:fuchsia-pkg://fuchsia.com/my-package#meta/my-component.cmx
.
Language-specific component examples
Below you'll find basic examples for defining a package with a single component that launches a program in a variety of commonly used languages. The referenced source files and component manifest are assumed to be present in the specified paths.
C++
import("//src/sys/build/components.gni")
executable("bin") {
output_name = "my_program"
sources = [ "main.cc" ]
}
fuchsia_component("my-component") {
manifest = "meta/my-component.cmx"
deps = [ ":bin" ]
}
fuchsia_package("my-package") {
deps = [ ":my-component" ]
}
It's assumed that the file meta/my-component.cmx
contains at least the following:
{
"program": {
"binary": "bin/my_program"
}
}
Rust
import("//build/rust/rustc_binary.gni")
import("//src/sys/build/components.gni")
rustc_binary("bin") {
output_name = "my_program"
}
fuchsia_component("my-component") {
manifest = "meta/my-component.cmx"
deps = [ ":bin" ]
}
fuchsia_package("my-package") {
deps = [ ":my-component" ]
}
It's assumed that the file meta/my-component.cmx
contains at least the following:
{
"program": {
"binary": "bin/my_program"
}
}
Go
import("//build/go/go_binary.gni")
import("//src/sys/build/components.gni")
go_binary("bin") {
output_name = "my_program"
}
fuchsia_component("my-component") {
manifest = "meta/my-component.cmx"
deps = [ ":bin" ]
}
fuchsia_package("my-package") {
deps = [ ":my-component" ]
}
It's assumed that the file meta/my-component.cmx
contains at least the following:
{
"program": {
"binary": "bin/my_program"
}
}
Dart
import("//build/dart/dart_component.gni")
import("//build/dart/dart_library.gni")
import("//src/sys/build/components.gni")
dart_library("lib") {
package_name = "my_lib"
sources = [ "main.dart" ]
}
dart_component("my-component") {
manifest = "meta/my-component.cmx"
deps = [ ":lib" ]
}
fuchsia_package("my-package") {
deps = [ ":my-component" ]
}
It's assumed that the file meta/my-component.cmx
contains at least the following:
{
"program": {
"data": "data/my_component"
}
}
Flutter
import("//build/dart/dart_library.gni")
import("//build/flutter/flutter_component.gni")
import("//src/sys/build/components.gni")
dart_library("lib") {
package_name = "my_lib"
sources = [ "main.dart" ]
}
flutter_component("my-component") {
manifest = "meta/my-component.cmx"
deps = [ ":lib" ]
}
fuchsia_package("my-package") {
deps = [ ":my-component" ]
}
It's assumed that the file meta/my-component.cmx
contains at least the following:
{
"program": {
"data": "data/my_component"
}
}
Test packages
Test packages are packages that contain at least one component that will be
launched as a test. Test packages are defined using
fuchsia_test_package.gni
. This
template can be used to define all sorts of tests, although it's most useful for
integration tests -- tests where other components in addition to the test itself
participate in the test. See below for templates that specialize
in unit testing.
import("//src/sys/build/components.gni")
executable("my_test") {
sources = [ "my_test.cc" ]
testonly = true
deps = [
"//src/lib/fxl/test:gtest_main",
"//third_party/googletest:gtest",
]
}
fuchsia_component("my-test-component") {
testonly = true
manifest = "meta/my_test.cmx"
deps = [ ":my_test" ]
}
executable("my_program_under_test") {
sources = [ "my_program_under_test.cc" ]
}
fuchsia_component("my-other-component-under-test") {
manifest = "meta/my_component_under_test.cmx"
deps = [ ":my_program_under_test" ]
}
fuchsia_test_package("my-integration-test") {
test_components = [ ":my-test-component" ]
deps = [ ":my-other-component-under-test" ]
test_specs = {
environments = [ vim3_env ]
}
}
group("tests") {
deps = [ ":my-integration-test" ]
testonly = true
}
Note the following details:
- This example defines
"my-test-component"
which is assumed to implement a test. Commonly this is done using some testing framework such as C++ Googletest, Rust Cargo test, etc'. - To launch this test, you can use
fx test
. - The test is packaged with another component,
"my-other-component-under-test"
. Presumably this component participates in the test. For instance, the component under test might implement a protocol, and the test launches it and connects to it as a client while asserting correct behavior from the client's perspective.
Packaging the component under test together with the test component guarantees that the component under test will be available for launch while the test is running, and will be built at the same version as the test. If this weren't the case, and instead the test assumed that the component under test was present in another package that's already installed on the target device, then the test would be exposed to side effects and version skew. Packaging the test with its dependencies makes it more hermetic. - Note the
environments
parameter.fuchsia_test_package()
can optionally taketest_spec.gni
parameters to override the default testing behavior. In this example, this test is configured to run on VIM2 devices. - Finally, this example defines a
group()
to contain all the tests (which we have exactly one of). This is a recommended practice for organizing targets across the source tree.
An important limitation of fuchsia_test_package()
is that any
test_components
targets must be defined in the same BUILD.gn
file as the
test package target. This is due to a limitation in GN.
It's possible to work around this limitation with an indirection through
fuchsia_test()
. In one BUILD.gn
file, define:
import("//src/sys/build/components.gni")
executable("my_test") {
sources = [ "my_test.cc" ]
testonly = true
deps = [
"//src/lib/fxl/test:gtest_main",
"//third_party/googletest:gtest",
]
}
fuchsia_component("my-test-component") {
testonly = true
manifest = "meta/my_test.cmx"
deps = [ ":my_test" ]
}
fuchsia_test("my-test-component-test") {
package = "//path/to:fuchsia_package"
component = ":my-test-component"
}
Then elsewhere, you can add the fuchsia_component()
target to the deps
of a
fuchsia_package()
target, and add the fuchsia_test()
target to a standard
"tests"
group.
Dart and Flutter tests differ slightly in that they need to be built with a
flutter_test_component()
which collects all of the test mains into a single
main invocation. The flutter_test_component()
can then be used by the
fuchsia_test_package()
.
import("//build/dart/dart_test_component.gni")
import("//build/flutter/flutter_test_component.gni")
import("//src/sys/build/components.gni")
flutter_test_component("my-flutter-test-component") {
testonly = true
manifest = "meta/my-flutter-test-component.cmx"
sources = [ "foo_flutter_test.dart" ]
}
dart_test_component("my-dart-test-component") {
testonly = true
manifest = "meta/my-dart-test-component.cmx"
sources = [ "foo_dart_test.dart" ]
}
fuchsia_test("my-test-component-test") {
test_components = [
":my-dart-test-component",
":my-flutter-test-component"
]
}
Unit tests
Since unit tests are very common, two simplified templates are provided:
fuchsia_unittest_component.gni
defines a component to be run as a test, with the option to automatically generate a basic component manifest, that must then be included in a package.fuchsia_unittest_package.gni
defines a package with a single component to be run as a test, shorthand for a singlefuchsia_unittest_component
target paired with afuchsia_test_package
.
Unit tests with manifests
The examples below demonstrate building a test executable and defining a package and component for the test.
C++
import("//src/sys/build/components.gni")
executable("my_test") {
sources = [ "test.cc" ]
deps = [
"//src/lib/fxl/test:gtest_main",
"//third_party/googletest:gtest",
]
testonly = true
}
fuchsia_unittest_package("my-test") {
manifest = "meta/my_test.cmx"
deps = [ ":my_test" ]
}
Rust
import("//build/rust/rustc_test.gni")
import("//src/sys/build/components.gni")
rustc_test("my_test") {}
fuchsia_unittest_package("my-test") {
manifest = "meta/my_test.cmx"
deps = [ ":my_test" ]
}
Go
import("//build/go/go_test.gni")
import("//src/sys/build/components.gni")
go_test("my_test") {}
fuchsia_unittest_package("my-test") {
manifest = "meta/my_test.cmx"
deps = [ ":my_test" ]
}
The manifest file meta/my_test.cmx
may look like this:
{
"program": {
"binary": "bin/my_test"
}
}
The above is a minimal valid manifest file for this test. In practice a test might require additional capabilities, to be specified in its manifest.
The launch URL for the test will be
fuchsia-pkg://fuchsia.com/my-test#meta/my-test.cmx
. It can be launched using
fx test
followed by the launch URL, or followed by the GN target name.
Unit tests with generated manifests
The examples above specify a manifest for the test. However, it's possible for unit tests to not require any particular capabilities.
Below is an example for a test that performs ROT13 encryption and decryption. The algorithm under test is pure logic that can be tested in complete isolation. If we were to write a manifest for these tests, it would only contain the test binary to be executed. In such cases, we can simply specify the test executable path, and the template will generate the trivial manifest for us.
C++
import("//src/sys/build/components.gni")
executable("rot13_test") {
sources = [ "rot13_test.cc" ]
deps = [
"//src/lib/fxl/test:gtest_main",
"//third_party/googletest:gtest",
]
testonly = true
}
fuchsia_unittest_package("rot13-test") {
deps = [ ":rot13_test" ]
}
Rust
import("//build/rust/rustc_test.gni")
import("//src/sys/build/components.gni")
rustc_test("rot13_test") {}
fuchsia_unittest_package("rot13-test") {
deps = [ ":rot13_test" ]
}
Go
import("//build/go/go_test.gni")
import("//src/sys/build/components.gni")
go_test("rot13_test") {}
fuchsia_unittest_package("rot13-test") {
deps = [ ":rot13_test" ]
}
It's also possible to generate multiple unit test components and include them in a single package.
import("//src/sys/build/components.gni")
fuchsia_unittest_component("rot13-encrypt-test") {
...
}
fuchsia_unittest_component("rot13-decrypt-test") {
...
}
fuchsia_test_package("rot13-tests") {
test_components = [
":rot13-encrypt-test",
":rot13-decrypt-test",
]
}
The generated component manifest file can be found as follows:
fx gn outputs out/default unittest target_generated_manifest
To print it directly:
fx build && cat out/default/$(fx gn outputs out/default unittest target_generated_manifest)
Note that fx gn outputs
prints an output path, but the file at the path
may not exist or may be stale if you haven't built.
To launch the test:
# By launch URL
fx test fuchsia-pkg://fuchsia.com/rot13-test#meta/rot13-test.cmx
# By GN target name
fx test rot13-test
See also: fx test
You can generate a CFv2 test component by specifying:
import("//src/sys/build/components.gni")
fuchsia_unittest_package("rot13-test") {
v2 = true
...
}
Or:
import("//src/sys/build/components.gni")
fuchsia_unittest_component("rot13-encrypt-test") {
v2 = true
...
}
fuchsia_unittest_component("rot13-decrypt-test") {
v2 = true
...
}
fuchsia_test_package("rot13-tests") {
test_components = [
":rot13-encrypt-test",
":rot13-decrypt-test",
]
}
Multiple unit tests in a single package
fuchsia_unittest_component
can be used instead of fuchsia_unittest_package
to include multiple
components in a single package. This can be useful to easily run all the test components an a
single package, e.g. with fx test <package_name>
, rather than needing to type many separate
package names.
The example below creates a single test package rot13-tests
that contains two separate test
components, rot13-decoder-test
and rot13-encoder-test
. Both tests can be run using fx test
rot13-tests
, or individual tests can be run using either fx test rot13-decoder-test
or the
full URL fx test fuchsia-pkg://fuchsia.com/rot13-tests#meta/rot13-decoder-test.cmx
.
C++
import("//build/rust/rustc_test.gni")
import("//src/sys/build/components.gni")
executable("rot13_decoder_test") {}
executable("rot13_encoder_test") {}
fuchsia_unittest_component("rot13-decoder-test") {
deps = [ ":rot13_decoder_test" ]
}
fuchsia_unittest_component("rot13-encoder-test") {
deps = [ ":rot13_encoder_test" ]
}
fuchsia_test_package("rot13-tests") {
test_components = [
":rot13-decoder-test",
":rot13-encoder-test",
]
}
Rust
import("//build/rust/rustc_test.gni")
import("//src/sys/build/components.gni")
rustc_test("rot13_decoder_test") {}
rustc_test("rot13_encoder_test") {}
fuchsia_unittest_component("rot13-decoder-test") {
deps = [ ":rot13_decoder_test" ]
}
fuchsia_unittest_component("rot13-encoder-test") {
deps = [ ":rot13_encoder_test" ]
}
fuchsia_test_package("rot13-tests") {
test_components = [
":rot13-decoder-test",
":rot13-encoder-test",
]
}
Go
import("//build/go/go_test.gni")
import("//src/sys/build/components.gni")
go_test("rot13_decoder_test") {}
go_test("rot13_encoder_test") {}
fuchsia_unittest_component("rot13-decoder-test") {
deps = [ ":rot13_decoder_test" ]
}
fuchsia_unittest_component("rot13-encoder-test") {
deps = [ ":rot13_encoder_test" ]
}
fuchsia_test_package("rot13-tests") {
test_components = [
":rot13-decoder-test",
":rot13-encoder-test",
]
}
Packages with a single component
Developers often define a package that contains a single component.
The template below fuses together fuchsia_package()
and fuchsia_component()
as a convenience.
C++
import("//src/sys/build/components.gni")
executable("rot13_encoder_decoder") {
sources = [ "rot13_encoder_decoder.cc" ]
}
fuchsia_package_with_single_component("rot13") {
manifest = "meta/rot13.cmx"
deps = [ ":rot13_encoder_decoder" ]
}
Rust
import("//build/rust/rustc_binary.gni")
import("//src/sys/build/components.gni")
rustc_binary("rot13_encoder_decoder") {
}
fuchsia_package_with_single_component("rot13") {
manifest = "meta/rot13.cmx"
deps = [ ":rot13_encoder_decoder" ]
}
Go
import("//build/go/go_binary.gni")
import("//src/sys/build/components.gni")
go_binary("rot13_encoder_decoder") {
}
fuchsia_component("rot13") {
manifest = "meta/rot13.cmx"
deps = [ ":rot13_encoder_decoder" ]
}
Packages are units of distribution. It is beneficial to define multiple components in the same package if you need to guarantee that several components are always co-present, or if you'd like to be able to update several components at once (by updating a single package).
This pattern is also commonly used to create hermetic integration tests. For instance an integration test between two components where one is a client of a service implemented in another component would include both the client and server components.
However for the sake of simplicity, if you're developing a package with just a single component then this template will save you some boilerplate.
Test-driven development
The fx smoke-test
command automatically detects all tests that are known to
the build system as affected by changes in your checkout. Try the following:
fx -i smoke-test --verbose
In the command above, --verbose
will print which tests fx smoke-test
thinks
are affected by your change, and -i
will automatically repeat this command
every time you save your changes. For test-driven development, try launching
this command in a separate shell and watching your code rebuild and retest as
you're working on it.
fx smoke-test
works best with hermetic test packages. A test package is
hermetic if the package contains all the dependencies of any tests in it.
That is to say, any code changes that affect the outcome of this test should
require rebuilding that test's package as well.
Additional packaged resources
In the examples above we've demonstrated that a deps
path from a package to a
target that produces an executable ensures that the executable is included in
the package.
Sometimes there is the need to include additional files. Below we demonstrate
the use of the resource.gni
template.
Example: fonts
import("//src/sys/build/components.gni")
resource("roboto_family") {
sources = [
"Roboto-Black.ttf",
"Roboto-Bold.ttf",
"Roboto-Light.ttf",
"Roboto-Medium.ttf",
"Roboto-Regular.ttf",
"Roboto-Thin.ttf",
]
outputs = [ "data/fonts/{{source_file_part}}" ]
}
fuchsia_component("text-viewer") {
...
deps = [
":roboto_family",
...
]
}
In the example above, six files are provided to be packaged under data/fonts/
,
producing the paths data/fonts/Roboto-Black.ttf
,
data/fonts/Roboto-Bold.ttf
, etc'. The format for destination
accepts GN
source expansion placeholders.
Then, a text viewer component is defined to depend on the fonts. In this
example, the text viewer implementation renders text with Roboto fonts. The
component can read the given fonts in its sandbox under the path
/pkg/data/fonts/...
.
Example: integration test with golden data
In this example we define a hypothetical service that minifies JSON files. The service is said to receive a buffer containing JSON text, and returns a buffer containing the same JSON data but with less whitespace. We present an integration test where a test component acts as the client of the minifier component, and compares the result for a given JSON file to be minified against a known good result (or a "golden file").
fuchsia_component("minifier-component") {
...
}
fuchsia_package("minifier-package") {
...
}
resource("testdata") {
sources = [
"testdata/input.json",
"testdata/input_minified.json",
]
outputs = [ "data/{{source_file_part}}" ]
}
fuchsia_component("minifier-test-client") {
testonly = true
deps = [
":testdata",
...
]
...
}
fuchsia_test_package("minifier-integration-test") {
test_components = [ ":minifier-test-client" ]
deps = [ ":minifier-component" ]
}
Note that we place the resource()
dependency on the test component. From the
build system's perspective the resource dependency could have been placed on
the test package and the same outcome would have been produced by the build.
However, it is a better practice to put dependencies on the targets that need
them. This way we could reuse the same test component target in a different
test package, for instance to test against a different minifier component, and
the test component would work the same.
Component manifest includes
As shown above, component declarations have an associated component
manifest. The component manifest supports
"include" syntax, which allows referencing one or more files where additional
contents for the component manifest may be merged from. This is conceptually
similar for instance to #include
directives in the C programming language.
These included files are also known as component manifest shards.
Some dependencies, such as libraries, assume that dependent components have certain capabilities available to them at runtime. Practically this could mean that the code in question assumes that its dependents include a certain file in their component manifests. For instance, the C++ Syslog library makes such an assumption.
Target owners can declare that dependent components must include one or more
files in their component manifest. For example we have the hypothetical file
//sdk/lib/fonts/BUILD.gn
below:
import("//tools/cmc/build/expect_includes.gni")
# Client library for components that want to use fonts
source_set("font_provider_client") {
sources = [
"font_provider_client.cc",
...
]
deps = [
":font_provider_client_includes",
...
]
}
expect_includes("font_provider_client_includes") {
includes = [
"client.shard.cmx",
"client.shard.cml",
]
}
It is possible (and recommended) to provide both .cmx
and .cml
includes.
Dependent manifests will be required to include the expected files with the
matching extension.
.cmx
{
"include": [
"sdk/lib/fonts/client.shard.cmx"
]
...
}
.cml
{
include: [
"sdk/lib/fonts/client.shard.cml",
]
...
}
Include paths are resolved relative to the source root. Transitive includes (includes of includes) are allowed. Cycles are not allowed.
By convention, component manifest shard files are named with the suffix
.shard.cmx
or .shard.cml
.
When naming your shards, don't repeat yourself in relation to the full path.
In the example above it would have been repetitive to name the shard
fonts.shard.cml
because then the full path would have been
sdk/lib/fonts/fonts.shard.cml
, which is repetitive. Instead the file is
named client.shard.cml
, to indicate that it is to be used by clients of the
SDK library for fonts.
Troubleshooting
Listing the contents of a package
Packages are described by a package manifest, which is a text file where every line follows this structure:
<packaged-path>=<source-file-path>
To find the package manifest for a fuchsia_package()
or
fuchsia_test_package()
target, use the following command:
fx gn outputs out/default package target_manifest
The package target is a fully-qualified target name, i.e. in the form
//path/to/your:target
.
Combine this with another command to print the package manifest:
cat out/default/$(fx gn outputs out/default package target_manifest)
See also:
Finding paths for built executables
Executable programs can be built with various language-specific templates such
as executable()
, rustc_binary()
, go_binary()
etc'. These templates are
responsible for specifying where in a package their output binaries should be
included. The details vary by runtime and toolchain configuration.
- Typically the path is
bin/
followed by the target's name. - Typically if an
output_name
orname
is specified, it overrides the target name.
Some rudimentary examples are given below:
C++
# This will be packaged as `bin/rot13_encode`
executable("rot13_encode") {
sources = [ "main.cc" ]
}
Rust
# This will be packaged as `bin/rot13_encode`
rustc_binary("rot13_encode") {}
Go
# This will be packaged as `bin/rot13_encode`
go_binary("rot13_encode") {}
In order to reference an executable in a component manifest, the author will need to know its packaged path.
One way to find the packaged path for an executable is to make sure that the
target that builds the executable is in a package's deps
, then follow the
above guide for listing the contents of a
package. The executable will be among the
listed contents of the package.
Finding a component's launch URL
Component URLs follow this pattern:
fuchsia-pkg://fuchsia.com/<package-name>#meta/<component-name>.<extension>
<package-name>
: specified aspackage_name
on the package target, which defaults to the target name.<component-name>
: specified ascomponent_name
on the component target, which defaults to the target name.<extension>
: based on the component manifest -cmx
for cmx files,cm
for cml files.
Migrating from legacy package()
The example below demonstrates a migration from the legacy
package()
template to the new
fuchsia_package()
& friends.
The example is adapted from
//src/sys/time/timekeeper/BUILD.gn
.
Pre-migration
import("//build/config.gni")
import("//build/package.gni")
import("//build/rust/rustc_binary.gni")
rustc_binary("bin") {
output_name = "timekeeper"
edition = "2018"
with_unit_tests = true
deps = [ ... ]
}
config_data("timekeeper_config") {
for_pkg = "sysmgr"
outputs = [ "timekeeper.config" ]
sources = [ "service.config" ]
}
package("timekeeper") {
meta = [
{
path = "meta/service.cmx"
dest = "timekeeper.cmx"
},
]
deps = [
":bin",
":timekeeper_config",
]
binaries = [
{
name = "timekeeper"
},
]
}
test_package("timekeeper_bin_test") {
deps = [ ":bin_test" ]
tests = [
{
name = "timekeeper_bin_test"
environments = basic_envs
},
]
resources = [
{
path = "test/y2k"
dest = "y2k"
},
{
path = "test/end-of-unix-time"
dest = "end-of-unix-time"
},
]
}
group("tests") {
testonly = true
deps = [ ":timekeeper_bin_test" ]
}
Post-migration
import("//build/config.gni")
import("//build/rust/rustc_binary.gni")
import("//src/sys/build/components.gni")
rustc_binary("bin") {
output_name = "timekeeper"
edition = "2018"
with_unit_tests = true
deps = [ ... ]
}
config_data("timekeeper_config") {
for_pkg = "sysmgr"
outputs = [ "timekeeper.config" ]
sources = [ "service.config" ]
}
fuchsia_component("service") {
component_name = "timekeeper"
manifest = "meta/service.cmx"
deps = [ ":bin" ]
}
fuchsia_package("timekeeper") {
deps = [ ":service" ]
}
resource("testdata") {
sources = [
"test/y2k",
"test/end-of-unix-time",
]
outputs = [ "data/{{source_file_part}}" ]
}
fuchsia_unittest_package("timekeeper-unittests") {
manifest = "meta/unittests.cmx"
deps = [
":bin_test",
":testdata",
]
}
Migration considerations
- Targets that generate executables or data files are not expected to change in a migration.
- Names for packages are expected to have dashes ("-") instead of underscores ("_"). The same is not required for components, though it's recommended for consistency.
- Previously,
meta/service.cmx
was given the destination"timekeeper.cmx"
which placed it inmeta/timekeeper.cmx
. Withfuchsia_component()
, the given manifest is automatically renamed per the component name ("timekeeper"
) andmeta/
is prepended. As a result, the launch URL for the timekeeper component remains the same:fuchsia-pkg://fuchsia.com/timekeeper#meta/timekeeper.cmx
- Additional resources (in this case, the data asset files used in the test
such as the
test/y2k
file) are included in the unit test. Their destination path is a full packaged path, whereas before it would have haddata/
automatically prepended to it. In both cases, the data file can be read by the test at runtime from the paths/pkg/data/y2k
and/pkg/data/end-of-unix-time
. - If you're required to specify a packaged path such as the path to an executable in a manifest or a test definition, and you're not sure what the path is, then try your best guess and expect a helpful error message if your guess was not correct.
Test specifications
Both template families support test specifications, such as restricting to specific test environments or restricting log severity.
The syntax is slightly different. Where before you might specify:
import("//build/package.gni")
test_package("foo-tests") {
...
tests = [
{
name = "foo_test"
log_settings = {
max_severity = "ERROR"
}
}
]
}
You would now specify:
import("//src/sys/build/components.gni")
fuchsia_test_package("foo-tests") {
...
test_specs = {
log_settings = {
max_severity = "ERROR"
}
}
}
With the new templates, the test_specs
apply to all tests in the package.
See test packages for more examples.
Legacy features
The following special attributes are supported by the legacy package()
template:
binaries
drivers
libraries
loadable_modules
These are used with special syntax, which determines how the files that certain
targets produce are packaged.
For instance the libraries
attribute installs resources in a special lib/
directory,
drivers
are installed in drivers/
, etc'.
The legacy syntax looks like this:
package("my_driver_package") {
deps = [ ":my_driver" ]
drivers = [
{
name = "my_driver.so"
},
]
}
This special treatment is not necessary with the new templates. Simply add the
necessary target to deps = [ ... ]
and the packaging is done automatically.
fuchsia_component("my_driver_component") {
deps = [ ":my_driver" ]
...
}
fuchsia_package("my_driver_package") {
deps = [ ":my_driver_component" ]
...
}
Additionally, legacy package()
supports the resources
attribute. This is
replaced by adding a dependency on a resource()
target.
See also:
Renaming files
The legacy package()
template allowed developers to rename certain files that
are included in their package. For example, below we see an executable being
built and then renamed before it's packaged so that it's packaged under the path
bin/foo-bin
.
import("//build/package.gni")
executable("bin") {
...
}
package("foo-pkg") {
deps = [ ":bin" ]
binaries = [
{
name = "bin"
dest = "foo-bin"
}
]
meta = [
{
path = "meta/foo-bin.cmx"
dest = "foo.cmx"
}
]
}
The new templates allow targets that produce files, such as executable()
above, to decide which files they produce and where they're placed. This is
important because some targets produce multiple files, or might produce
different files based on the build configuration (for instance if building
for a different target architecture). In order to control the paths of
packaged files, developers should work with the templates for the targets
that produce those files. For instance:
import("//src/sys/build/components.gni")
executable("bin") {
output_name = "foo-bin"
...
}
fuchsia_component("foo-cmp") {
deps = [ ":bin" ]
manifest = "meta/foo-bin.cmx"
}
fuchsia_package("foo-pkg") {
deps = [ ":foo-cmp" ]
}
Shell binaries
The legacy package()
template allowed developers to make a particular binary
in the package available to fx shell
.
import("//build/package.gni")
# `fx shell echo Hello World` will print "Hello World"
executable("bin") {
output_name = "echo"
...
}
package("echo") {
binaries = [
{
name = "echo"
dest = "echo"
shell = true
}
]
deps = [ ":bin" ]
}
The new templates support this feature as follows:
import("//src/sys/build/components.gni")
# `fx shell echo Hello World` will print "Hello World"
executable("bin") {
output_name = "echo"
...
}
fuchsia_shell_package("echo") {
deps = [ ":bin" ]
}
Note that in the package()
example the binary is explicitly named "echo",
which is the same name that's used for its intrinsic name
(output_name = "echo"
). The new templates don't have this renaming behavior,
and instead let the target that produces the binary (executable()
in this
case) decide the file name, as determined by the output_name
specified (or the
executable target's name if output_name
isn't specified).
This feature was left out intentionally. Moving forward the use of legacy shell tools is discouraged.
Go grand_unified_binary
"Grand unified binary" (GUB) is a single binary that merges together multiple Go
programs. The entry point to the combined program can identify which sub-program
the caller intends to run based on the filename of the invocation (argv[0]
).
Therefore in order to include GUB in your package and invoke a sub-program the
common practice is to rename the binary.
The legacy package()
template allowed developers to accomplish this as shown
below:
import("//build/go/go_library.gni")
import("//build/package.gni")
go_library("my_tool") {
...
}
package("tools") {
deps = [
"//garnet/go/src/grand_unified_binary",
]
binaries = [
{
name = "my_tool"
source = "grand_unified_binary"
}
]
}
The new templates support this feature as follows:
import("//garnet/go/src/grand_unified_binary/gub.gni")
import("//build/go/go_library.gni")
import("//src/sys/build/components.gni")
go_library("my_tool") {
...
}
grand_unified_binary("bin") {
output_name = "my_tool"
}
fuchsia_package("tools") {
deps = [ ":bin" ]
}
Unsupported features
Note that some features of package()
are unsupported moving forward. If your
package depends on them then at this time it cannot be migrated to the new
templates. These unsupported features include:
- Marking a test as disabled. Instead, change the test source code to mark it as disabled, or comment out the disabled test component from the build file.
- The Component Index. Components using the new templates
cannot be launched using
run
followed by a fuzzy match with their launch URL. Components can still be launched using their full launch URL. Tests can still be launched withfx test
followed by the short name of the test. See fxbug.dev/55739 for more details.