Every component has a declaration that describes the component's attributes and capabilities. For components that are distributed in packages, the declaration is expressed using a component manifest file and loaded with the help of a component resolver.
You declare components using component manifest language (CML) files. At build
time, the Component Manifest Compiler (cmc
) tool validates and compiles the
manifest source into a binary format (.cm
) and stores it in the component's
package. At runtime, component resolvers load the binary manifest into a
ComponentDecl
FIDL structure for
Component Manager
.
Component manifests
CML files are JSON5 files that end with a
.cml
extension. Below is an example CML manifest file for a simple component
running an ELF binary that prints a "Hello, World" message to the system log:
{
// Information about the program to run.
program: {
// Use the built-in ELF runner.
runner: "elf",
// The binary to run for this component.
binary: "bin/hello",
// Program arguments
args: [
"Hello",
"World!",
],
},
// Capabilities used by this component.
use: [
{ protocol: "fuchsia.logger.LogSink" },
],
}
This file declares two main sections of information about the component:
program
: Describes the executable information such as the binary file, program arguments, and the associated runtime. In this example, a binary is compiled as an ELF executable and uses the built-in ELF runner.use
: Declares the capabilities this component requires to run. In this example, thefuchsia.logger.LogSink
protocol enables the component to write messages to the system log (syslog
).
Manifest shards
Some collections of capabilities represent use case requirements that are common to many components in the system, such as logging. To simplify including these capabilities in your components, the framework abstracts them into manifest shards that can be included in your CML source file.
Below is an equivalent CML to the previous example. In this case, the necessary
logging capabilities are provided by including
diagnostics/syslog/client.shard.cml
instead of declaring
fuchsia.logger.LogSink
explicitly:
{
include: [ "syslog/client.shard.cml" ],
// Information about the program to run.
program: {
// Use the built-in ELF runner.
runner: "elf",
// The binary to run for this component.
binary: "bin/hello-world",
// Program arguments
args: [
"Hello",
"World!",
],
},
}
Building components
The Fuchsia SDK system provides Bazel rules to build and package software into Fuchsia components. The Fuchsia SDK environment makes these rules available within a Bazel workspace directory.
Within the Bazel workspace, you declare Fuchsia packages and components as
Bazel targets within
a Bazel package,
described by a BUILD.bazel
file.
Below is an example of a BUILD.bazel
file for a simple C++ component:
# Build rules provided by the Fuchsia SDK
load(
"fuchsia_cc_binary",
"fuchsia_component",
"fuchsia_component_manifest",
"fuchsia_package",
)
fuchsia_cc_binary(
name = "hello_world",
srcs = [
"hello_world.cc",
],
)
fuchsia_component_manifest(
name = "manifest",
src = "meta/hello_world.cml",
)
fuchsia_component(
name = "component",
manifest = ":manifest",
deps = [":hello_world"],
)
fuchsia_package(
name = "pkg",
package_name = "hello_world",
visibility = ["//visibility:public"],
deps = [
":component",
],
)
This file contains the following main elements:
fuchsia_cc_binary()
: Compiles the C++ source code into a binary, including any necessary library dependencies.fuchsia_component_manifest()
: Compiles the component manifest source file (.cml
) into a binary component declaration usingcmc
.fuchsia_component()
: Collects the binary, component manifest, and additional resources together into a single target.fuchsia_package()
: Unit of distribution for components. Allows one or more components to be hosted in a package repository and included in the target device's package sets. This target generates the package metadata and builds the Fuchsia Archive (.far
) file.
Exercise: Create a new component
In this exercise, you'll build and run a basic component that reads the program arguments and echoes a greeting out the system log.
To begin, create a new project directory in your Bazel workspace for a new
component called echo
:
mkdir -p fuchsia-codelab/echo
After you complete this section, the project should have the following directory structure:
//fuchsia-codelab/echo
|- BUILD.bazel
|- meta
| |- echo.cml
|
|- echo_component.cc
|- echo_component.h
|- main.cc
BUILD.bazel
: Bazel build targets for the executable binaries, component, and package.meta/echo.cml
: Manifest declaring the component's executable and required capabilities.echo_component.cc
: Source code for the C++ component functionality.main.cc
: Source code for the C++ executable binary main entry point.
Add program arguments
The component manifest file defines the attributes of the component's executable, including program arguments, and the component's capabilities.
Create echo/meta/echo.cml
and add the following contents:
echo/meta/echo.cml
:
{
include: [
"syslog/client.shard.cml",
],
// Information about the program to run.
program: {
// Use the built-in ELF runner.
runner: "elf",
// The binary to run for this component.
binary: "bin/echo_example",
// Program arguments
args: [
"Alice",
"Bob",
],
// Program environment variables
environ: [ "FAVORITE_ANIMAL=Spot" ],
},
// ...
}
Log the arguments
Create the echo/main.cc
source file for the main executable and add the
following import statements:
echo/main.cc
:
#include <lib/syslog/global.h>
#include <cstdlib>
#include <iostream>
#include <string>
#include "echo_component.h"
Add the following code for the main()
function:
echo/main.cc
:
int main(int argc, const char *argv[], char *envp[]) {
// Read program arguments, and exclude the binary name in argv[0]
std::vector<std::string> arguments;
arguments.resize(0);
for (int i = 1; i < argc; i++) {
arguments.push_back(argv[i]);
}
// Include environment variables
const char *favorite_animal = std::getenv("FAVORITE_ANIMAL");
if (favorite_animal != NULL) {
arguments.push_back(favorite_animal);
}
// Print a greeting to syslog
FX_LOGF(INFO, "echo", "Hello, %s!", echo::greeting(arguments).c_str());
return 0;
}
This code reads the program arguments and passes them to a function called
greeting()
to generate a response for the syslog entry.
Create echo/echo_component.h
and echo/echo_component.cc
, including the
following code to implement the greeting()
function:
echo/echo_component.h
:
#include <string>
#include <vector>
namespace echo {
std::string greeting(std::vector<std::string> &names);
} // namespace echo
echo/echo_component.cc
:
#include "echo_component.h"
#include <numeric>
namespace echo {
static std::string join(std::vector<std::string> &input_list,
const std::string &separator) {
return std::accumulate(
std::begin(input_list), std::end(input_list), std::string(""),
[&separator](std::string current, std::string &next) {
return current.empty() ? next : (std::move(current) + separator + next);
});
}
// Return a proper greeting for the list
std::string greeting(std::vector<std::string> &names) {
// Join the list of names based on length
auto number_of_names = names.size();
switch (number_of_names) {
case 0:
return "Nobody!";
case 1:
return join(names, "");
case 2:
return join(names, " and ");
default:
return join(names, ", ");
}
}
} // namespace echo
This function creates a simple string from the list of provided arguments based on the length of the list.
Add to the build configuration
Create the echo/BUILD.bazel
file and add the following build rules to include
the new component in the build configuration:
echo/BUILD.bazel
:
load(
"@fuchsia_sdk//fuchsia:defs.bzl",
"fuchsia_cc_binary",
"fuchsia_component",
"fuchsia_component_manifest",
"fuchsia_package",
"fuchsia_select",
"fuchsia_unittest_package",
)
fuchsia_cc_binary(
name = "echo_example",
srcs = [
"echo_component.cc",
"echo_component.h",
"main.cc",
],
deps = [] + fuchsia_select({
"@platforms//os:fuchsia": [
"@fuchsia_sdk//pkg/fdio",
"@fuchsia_sdk//pkg/syslog",
],
}),
)
fuchsia_component_manifest(
name = "manifest",
src = "meta/echo.cml",
component_name = "echo",
includes = [
"@fuchsia_sdk//pkg/syslog:client",
],
)
fuchsia_component(
name = "component",
component_name = "echo",
manifest = ":manifest",
visibility = ["//visibility:public"],
deps = [":echo_example"],
)
fuchsia_package(
name = "pkg",
package_name = "echo-example",
visibility = ["//visibility:public"],
components = [
":component",
],
)
Run bazel build
and verify that the build completes successfully:
bazel build //fuchsia-codelab/echo:pkg
In the next section, you'll integrate this component into the build and test the output in the system log.