Declaring components

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.

Diagram showing how components are declared using a "component manifest." The
manifest is compiled by the developer tools and resolved onto the device at
runtime.

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, the fuchsia.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 build system provides templates as GN imports in //build/components.gni to build and package software into Fuchsia components. Below is an example of a BUILD.gn file for a simple C++ component:


import("//build/components.gni")

executable("bin") {
  sources = [ "main.cc" ]
}

resource("my_file") {
  sources = [ "my_file.txt" ]
  outputs = [ "data/{{source_file_part}}" ]
}

fuchsia_component("hello-world-component") {
  component_name = "hello-world"
  deps = [
    ":bin",
    ":my_file",
  ]
  manifest = "meta/hello-world.cml"
}

fuchsia_package("hello-world") {
  package-name = "hello-world"
  deps = [
    ":hello-world-component",
  ]
}

This file contains the following main elements:

  • executable(): Compiles the source code into a binary. This target varies depending on the programming language. For example, an executable target can be used for C++, rustc_binary for Rust, go_binary for Golang.
  • resource(): Optional named collection of data files to copy as resources into another GN target. These files are accessible to the binary inside the component's namespace.
  • fuchsia_component(): Collects the binary, component manifest, and additional resources together into a single target. This target compiles the manifest source into a component declaration using cmc.
  • 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.

Packages can contain multiple components, listed as deps in the fuchsia_package() template. You can simplify the build file for packages containing only one component using the fuchsia_package_with_single_component() template.

The following simplified BUILD.gn example is equivalent to to the previous example:


import("//build/components.gni")

executable("bin") {
  sources = [ "main.cc" ]
}

resource("my_file") {
  sources = [ "my_file.txt" ]
  outputs = [ "data/{{source_file_part}}" ]
}

fuchsia_package_with_single_component("hello-world") {
  manifest = "meta/hello-world.cml"
  deps = [
    ":bin",
    ":my_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 project scaffold for a new component called echo-args in the //vendor/fuchsia-codelab directory:

mkdir -p vendor/fuchsia-codelab/echo-args

Create the following file and directory structure in the new project directory:

Rust

//vendor/fuchsia-codelab/echo-args
                        |- BUILD.gn
                        |- meta
                        |   |- echo.cml
                        |
                        |- src
                            |- main.rs
  • BUILD.gn: GN build targets for the executable binaries, component, and package.
  • meta/echo.cml: Manifest declaring the component's executable and required capabilities.
  • src/main.rs: Source code for the Rust executable binary and unit tests.

C++

//vendor/fuchsia-codelab/echo-args
                        |- BUILD.gn
                        |- meta
                        |   |- echo.cml
                        |
                        |- echo_component.cc
                        |- echo_component.h
                        |- main.cc
  • BUILD.gn: GN 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. Add the following contents to meta/echo.cml:

Rust

echo-args/meta/echo.cml:

{
    include: [
        // Enable logging on stdout
        "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-args",

        // Program arguments
        args: [
            "Alice",
            "Bob",
        ],

        // Program environment variables
        environ: [ "FAVORITE_ANIMAL=Spot" ],
    },
}

C++

echo-args/meta/echo.cml:

{
    include: [
        // Enable logging.
        "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-args",

        // Program arguments
        args: [
            "Alice",
            "Bob",
        ],

        // Program environment variables
        environ: [ "FAVORITE_ANIMAL=Spot" ],
    },
}

Log the arguments

Open the source file for the main executable and add the following import statements:

Rust

echo-args/src/main.rs:

use tracing::info;

C++

echo-args/main.cc:

#include <lib/syslog/cpp/log_settings.h>
#include <lib/syslog/cpp/macros.h>

#include <cstdlib>
#include <iostream>

#include "vendor/fuchsia-codelab/echo-args/echo_component.h"

Add the following code to implement the main() function:

Rust

echo-args/src/main.rs:

#[fuchsia::main(logging = true)]
async fn main() -> Result<(), anyhow::Error> {
    // Read program arguments, and strip off binary name
    let mut args: Vec<String> = std::env::args().collect();
    args.remove(0);

    // Include environment variables
    let animal = std::env::var("FAVORITE_ANIMAL").unwrap();
    args.push(animal);

    // Print a greeting to syslog
    info!("Hello, {}!", greeting(&args));

    Ok(())
}

C++

echo-args/main.cc:

int main(int argc, const char* argv[], char* envp[]) {
  fuchsia_logging::LogSettingsBuilder builder;
  builder.WithTags({"echo"}).BuildAndInitialize();
  // Read program arguments, and exclude the binary name in argv[0]
  std::vector<std::string> arguments;
  for (int i = 1; i < argc; i++) {
    arguments.push_back(argv[i]);
  }

  // Include environment variables
  const char* favorite_animal = std::getenv("FAVORITE_ANIMAL");
  arguments.push_back(favorite_animal);

  // Print a greeting to syslog
  FX_LOG_KV(INFO, "Hello", FX_KV("greeting", 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.

Add the following code to implement the greeting() function:

Rust

echo-args/src/main.rs:

// Return a proper greeting for the list
fn greeting(names: &Vec<String>) -> String {
    // Join the list of names based on length
    match names.len() {
        0 => String::from("Nobody"),
        1 => names.join(""),
        2 => names.join(" and "),
        _ => names.join(", "),
    }
}

C++

echo-args/echo_component.h:

#include <string>
#include <vector>

namespace echo {

std::string greeting(std::vector<std::string>& names);

}  // namespace echo

echo-args/echo_component.cc:

#include "vendor/fuchsia-codelab/echo-args/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

Update the program's dependencies in your BUILD.gn file:

Rust

echo-args/BUILD.gn:

import("//build/components.gni")
import("//build/rust/rustc_binary.gni")


group("echo-args") {
  testonly = true
  deps = [
    ":package",
  ]
}

rustc_binary("bin") {
  output_name = "echo-args"
  edition = "2021"

  # Generates a GN target for unit-tests with the label `bin_test`,
  # and a binary named `echo_bin_test`.
  with_unit_tests = true

  deps = [
    "//src/lib/fuchsia",
    "//third_party/rust_crates:anyhow",
    "//third_party/rust_crates:tracing",
  ]

  sources = [ "src/main.rs" ]
}

fuchsia_component("component") {
  component_name = "echo-args"
  manifest = "meta/echo.cml"
  deps = [ ":bin" ]
}

fuchsia_package("package") {
  package_name = "echo-args"
  deps = [ ":component" ]
}

C++

echo-args/BUILD.gn:

import("//build/components.gni")


group("echo-args") {
  testonly = true
  deps = [
    ":package",
  ]
}

executable("bin") {
  output_name = "echo-args"

  sources = [ "main.cc" ]

  deps = [
    ":cpp-lib",
    "//sdk/lib/async-default",
    "//sdk/lib/async-loop:async-loop-cpp",
    "//sdk/lib/async-loop:async-loop-default",
    "//sdk/lib/syslog/cpp",
  ]
}

source_set("cpp-lib") {
  sources = [
    "echo_component.cc",
    "echo_component.h",
  ]
}

fuchsia_component("component") {
  component_name = "echo-args"
  manifest = "meta/echo.cml"
  deps = [ ":bin" ]
}

fuchsia_package("package") {
  package_name = "echo-args"
  deps = [ ":component" ]
}

Add your new component to the build configuration:

fx set workstation_eng.x64 --with //vendor/fuchsia-codelab/echo-args

Run fx build and verify that the build completes successfully:

fx build

In the next section, you'll integrate this component into the build and test the output in the system log.