每个组件都有一个声明,用于描述组件的属性和功能。对于以软件包形式分发的组件,声明使用组件清单文件表示,并借助组件解析器加载。
您可以使用组件清单语言 (CML) 文件声明组件。在构建时,组件清单编译器 (cmc
) 工具会验证并将清单源代码编译为二进制格式 (.cm
),并将其存储在组件的软件包中。在运行时,组件解析器会将二进制清单加载到 ComponentDecl FIDL 结构中,以便 Component Manager 使用。
组件清单
CML 文件是扩展名为 .cml
的 JSON5 文件。以下是运行 ELF 二进制文件的简单组件的 CML 清单文件示例,该二进制文件会向系统日志输出“Hello, World”消息:
{
// 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" },
],
}
此文件声明了与组件相关的两个主要信息部分:
program
:描述可执行文件信息,例如二进制文件、程序参数和关联的运行时。在此示例中,二进制文件会编译为 ELF 可执行文件,并使用内置的 ELF 运行程序。use
:声明此组件运行所需的功能。在此示例中,fuchsia.logger.LogSink
协议使组件能够将消息写入系统日志 (syslog
)。
清单分片
某些功能集代表了系统中许多组件(例如日志记录)共有的用例要求。为了简化在组件中添加这些功能的操作,该框架会将这些功能提取为可添加到 CML 源文件中的清单分片。
以下是与上一个示例等效的 CML。在本例中,通过添加 diagnostics/syslog/client.shard.cml
(而不是显式声明 fuchsia.logger.LogSink
)来提供必要的日志记录功能:
{
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!",
],
},
}
构建组件
Fuchsia 构建系统在 //build/components.gni
中将模板作为 GN 导入项提供,以便将软件构建并打包到 Fuchsia 组件中。以下是一个简单 C++ 组件的 BUILD.gn
文件示例:
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",
]
}
此文件包含以下主要元素:
executable()
:将源代码编译为二进制文件。此目标因编程语言而异。例如,executable
目标可用于 C++,rustc_binary
可用于 Rust,go_binary
可用于 Golang。resource()
:可选的数据文件命名集合,用于作为资源复制到其他 GN 目标中。这些文件可供组件命名空间内的二进制文件访问。fuchsia_component()
:将二进制文件、组件清单和其他资源收集到一个目标中。此目标使用cmc
将清单源代码编译为组件声明。fuchsia_package()
:组件的分发单位。允许将一个或多个组件托管在软件包仓库中,并包含在目标设备的软件包集中。此目标会生成软件包元数据并构建 Fuchsia 归档文件 (.far
)。
软件包可以包含多个组件,在 fuchsia_package()
模板中列为 deps
。您可以使用 fuchsia_package_with_single_component()
模板简化仅包含一个组件的软件包的 build 文件。
以下简化的 BUILD.gn
示例与上一个示例等效:
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",
]
}
练习:创建新组件
在本练习中,您将构建并运行一个基本组件,该组件会读取程序参数并在系统日志中回显问候语。
首先,在 //vendor/fuchsia-codelab
目录中为名为 echo-args
的新组件创建项目框架:
mkdir -p vendor/fuchsia-codelab/echo-args
在新项目目录中创建以下文件和目录结构:
Rust
//vendor/fuchsia-codelab/echo-args
|- BUILD.gn
|- meta
| |- echo.cml
|
|- src
|- main.rs
BUILD.gn
:可执行二进制文件、组件和软件包的 GN 构建目标。meta/echo.cml
:声明组件的可执行文件和所需功能的清单。src/main.rs
:Rust 可执行二进制文件和单元测试的源代码。
C++
//vendor/fuchsia-codelab/echo-args
|- BUILD.gn
|- meta
| |- echo.cml
|
|- echo_component.cc
|- echo_component.h
|- main.cc
BUILD.gn
:可执行二进制文件、组件和软件包的 GN 构建目标。meta/echo.cml
:声明组件的可执行文件和所需功能的清单。echo_component.cc
:C++ 组件功能的源代码。main.cc
:C++ 可执行二进制主入口点的源代码。
添加程序参数
组件清单文件定义了组件可执行文件的属性,包括程序参数和组件的功能。将以下内容添加到 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" ],
},
}
记录参数
打开主可执行文件的源文件,然后添加以下导入语句:
Rust
echo-args/src/main.rs
:
use log::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"
添加以下代码以实现 main()
函数:
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;
}
此代码会读取程序参数,并将其传递给一个名为 greeting()
的函数,以便为 syslog 条目生成响应。
添加以下代码以实现 greeting()
函数:
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
此函数会根据所提供参数列表的长度,从该列表中创建一个简单的字符串。
添加到 build 配置
更新 BUILD.gn
文件中的程序依赖项:
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:log",
]
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" ]
}
将新组件添加到 build 配置中:
fx set workstation_eng.x64 --with //vendor/fuchsia-codelab/echo-args
运行 fx build
并验证构建是否已成功完成:
fx build
在下一部分中,您将将此组件集成到 build 中,并测试系统日志中的输出。