建構元件

本文將示範如何建構及測試元件,並重點介紹定義套件、元件及其測試的最佳做法。

概念

建立元件前,請先瞭解下列概念:

< 套件是一組檔案,其中包含與套件基本路徑相關聯的路徑。舉例來說,套件可能包含路徑 bin/hello_world 下的 ELF 二進位檔,以及路徑 data/config.json 下的 JSON 檔案。您必須將檔案分組到套件中,才能將這些檔案推送至裝置。

< 除了核心映像檔和使用者模式啟動程式,Fuchsia 上的所有軟體都定義為元件。

組件由 元件通常包含其他檔案,例如執行檔和執行階段所需的資料資產。

無論是建構正式版軟體或編寫測試,開發人員都必須以套件和元件的形式定義軟體。

在執行階段, /pkg在同一個套件中定義兩個以上的元件,不會授予每個元件存取其他元件功能的權限。不過,它可以向一個元件保證另一個元件可用。因此,如果元件嘗試啟動另一個元件的例項 (例如在整合測試中),建議將這兩個元件一起套件。

元件的例項化方式有幾種,但都必須指定其 <0x0A通常啟動元件時,會指定元件的套件名稱和套件中元件資訊清單的路徑,方法是使用 <fuchsia-pkg://

元件資訊清單

元件資訊清單是編碼元件宣告的檔案,通常會以套件形式發布。二進位格式是持續存在的 FIDL 檔案,內含元件宣告。資訊清單會宣告元件程式二進位檔和必要功能相關資訊。

以下是簡單「Hello, World」元件的資訊清單檔案範例:

{
    // Information about the program to run.
    program: {
        // Use the built-in ELF runner to run platform-specific binaries.
        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" },
    ],
}

資訊清單分片

某些功能集合代表系統中許多元件的常見用途需求,例如記錄。為簡化在元件中加入這些功能,元件架構支援將這些功能抽象化為資訊清單分片,可納入主要資訊清單檔案。這在概念上與 C 程式設計語言中的 #include 指令類似。

以下是與上例對應的資訊清單,其中記錄能力已由資訊清單分片 include 取代:

{
    // Include capabilities for the syslog library
    include: [ "syslog/client.shard.cml" ],

    // Information about the program to run.
    program: {
        // Use the built-in ELF runner to run platform-specific binaries.
        runner: "elf",
        // The binary to run for this component.
        binary: "bin/hello-world",
        // Program arguments
        args: [
            "Hello",
            "World!",
        ],
    },
}

相對路徑

"//" 開頭的路徑是相對於您工作所在來源樹狀結構的根目錄。如果 include 路徑不是以 "//" 開頭,建構系統會嘗試從 Fuchsia SDK 解析這些路徑。

分片間依附元件

如果一個資訊清單分片將子項新增至資訊清單,而第二個資訊清單分片新增的第二個子項依附於第一個子項,則如果第二個分片包含在資訊清單中,但第一個分片未包含在內,從第一個子項到第二個子項的供應項目聲明就會導致資訊清單驗證錯誤,因為供應項目會參照不存在的子項。

// echo_server.shard.cml
{
    children: [ {
        name: "echo_server",
        url: "fuchsia-pkg://fuchsia.com/echo_server#meta/echo_server.cm",
    } ],
}
// echo_client.shard.cml
{
    children: [
        {
            name: "echo_client",
            url: "fuchsia-pkg://fuchsia.com/echo_client#meta/echo_client.cm",
        }
    ],
    offer: [ {
        // This offer will cause manifest validation to fail if
        // `echo_client.shard.cml` is included in a manifest without
        // `echo_server.shard.cml`.
        protocol: "fuchsia.examples.Echo",
        from: "echo_server",
        to: "echo_client",
    } ],
}

為解決這個問題,可以設定優惠的 source_availability 欄位,通知資訊清單編譯作業,優惠來源可以缺漏。如果設為 unknown,系統會對方案聲明執行下列操作:

  • 如果 from 來源存在,供應情形會設為 required
  • 如果 from 來源不存在:供應情形會設為 optional,且優惠來源會重新編寫為 void
// echo_client.shard.cml
{
    children: [
        {
            name: "echo_client",
            url: "fuchsia-pkg://fuchsia.com/echo_client#meta/echo_client.cm",
        }
    ],
    offer: [
        {
            // If `echo_server.shard.cml` is included in this manifest, then
            // `echo_client` can access the `fuchsia.examples.Echo` protocol from
            // it.
            //
            // If `echo_server.shard.cml` is not included in this manifest, then
            // `echo_client` will be offered the protocol with a source of
            // `void` and `availability == optional`. `echo_client` must consume
            // the capability optionally to not fail route validation.
            protocol: "fuchsia.examples.Echo",
            from: "echo_server",
            to: "echo_client",
            source_availability: "unknown",
        }
    ],
}

同樣地,可以設定方案的 target_availablility 欄位,通知資訊清單編譯器該方案的目標可以遺失。如果設為 unknown,系統會對優惠聲明執行下列操作:

  • 如果 to 目標存在:系統會在先前的可用性 (和來源可用性規則) 之後發出 offer
  • 如果 to 目標不存在:編譯後的資訊清單會完全省略 offer

如要進一步瞭解 availability 的運作方式,請參閱「適用情形」。

用戶端程式庫包含

如上所示,元件資訊清單支援「include」語法,可將一或多個資訊清單分片做為額外資訊清單內容的來源。部分依附元件 (例如程式庫) 會假設依附元件在執行階段可使用特定功能。舉例來說,C++ Syslog 程式庫就是基於這項假設。

如果您要建構用戶端程式庫,可以在 BUILD.gn 檔案中使用 expect_includes 宣告這些必要依附元件。舉例來說,請參考下列假設檔案 //sdk/lib/fonts/BUILD.gn

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.cml",
  ]
}

這會為依附資訊清單設定建構時間需求,以納入預期的資訊清單分片:

{
    include: [
        "//sdk/lib/fonts/client.shard.cml",
    ]
    ...
}

系統會相對於來源根目錄解析包含路徑。 允許遞移包含 (包含的包含)。不允許循環。

為分片命名時,請勿重複完整路徑中的資訊。 在上述範例中,如果將分片命名為 fonts.shard.cml,完整路徑就會是 sdk/lib/fonts/fonts.shard.cml,這樣會顯得重複。但檔案名稱為 client.shard.cml,表示這是要供字型 SDK 程式庫的用戶端使用。

元件套件 GN 範本

GN 是 Fuchsia 使用的中繼建構系統。Fuchsia 會定義範本,藉此擴充 GN。範本可讓您新增至 GN 的內建目標型別。

Fuchsia 定義下列 GN 範本,用於定義套件和元件:

以下是假設的套件,內含一個執行 C++ 程式的元件:

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

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

fuchsia_component("my_component") {
  manifest = "meta/my_program.cml"
  deps = [ ":my_program" ]
}

fuchsia_package("my_package") {
  deps = [ ":my_component" ]
}

請留意下列細節:

  • 匯入 "//build/components.gni",即可存取與套件、元件和測試相關的所有範本。
  • fuchsia_component() 範本會宣告元件。這取決於程式二進位檔 (在本例中為 executable()),且需要指向元件資訊清單檔案的 manifest
  • 元件和套件名稱都是從目標名稱衍生而來。 在上述範例中,這些名稱會合併形成啟動元件的網址:fuchsia-pkg://fuchsia.com/my_package#meta/my_component.cm

特定語言的元件範例

下方提供基本範例,說明如何使用各種常用語言定義套件,並透過單一元件啟動程式。系統會假設參照的來源檔案和元件資訊清單位於指定路徑。

C++

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

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

fuchsia_component("my_component") {
  manifest = "meta/my_component.cml"
  deps = [ ":bin" ]
}

fuchsia_package("my_package") {
  deps = [ ":my_component" ]
}

荒漠油廠

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

rustc_binary("bin") {
  output_name = "my_program"
  sources = [ "src/main.rs" ]
}

fuchsia_component("my_component") {
  manifest = "meta/my_component.cml"
  deps = [ ":bin" ]
}

fuchsia_package("my_package") {
  deps = [ ":my_component" ]
}

查看

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

go_binary("bin") {
  output_name = "my_program"
  sources = [ "main.go" ]
}

fuchsia_component("my_component") {
  manifest = "meta/my_component.cml"
  deps = [ ":bin" ]
}

fuchsia_package("my_package") {
  deps = [ ":my_component" ]
}

只有單一元件的套件

套裝組合是發行單位。如果您需要確保多個元件一律會同時存在,或是想一次更新多個元件 (只要更新單一套件即可),建議在同一個套件中定義多個元件。

這個模式也常用於建立密封整合測試。舉例來說,如果兩個元件之間的整合測試中,其中一個是另一個元件中實作的服務用戶端,則會同時包含用戶端和伺服器元件。

不過,您通常可以定義只需要單一元件的套件。在這種情況下,您可以使用 fuchsia_package_with_single_component() 範本。本範本結合了 fuchsia_package()fuchsia_component()

C++

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

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

fuchsia_package_with_single_component("rot13") {
  manifest = "meta/rot13.cml"
  deps = [ ":rot13_encoder_decoder" ]
}

荒漠油廠

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

rustc_binary("rot13_encoder_decoder") {
  sources = [ "src/rot13_encoder_decoder.rs" ]
}

fuchsia_package_with_single_component("rot13") {
  manifest = "meta/rot13.cml"
  deps = [ ":rot13_encoder_decoder" ]
}

查看

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

go_binary("rot13_encoder_decoder") {
  sources = [ "rot13_encoder_decoder.go" ]
}

fuchsia_package_with_single_component("rot13") {
  manifest = "meta/rot13.cml"
  deps = [ ":rot13_encoder_decoder" ]
}

測試套件 GN 範本

測試套件是指至少包含一個元件的套件,該元件會以測試形式啟動。測試套件是使用 fuchsia_test_package.gni 定義。這個範本可用於定義各種測試,但最適合用於整合測試,也就是除了測試本身之外,其他元件也會參與測試。如需專門用於單元測試的範本,請參閱單元測試

import("//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.cml"
  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.cml"
  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
}

請留意下列細節:

  • 這個範例定義了 "my_test_component",假設該項目實作了使用某些常見測試架構 (例如 C++ Googletest、Rust Cargo 測試等) 編寫的測試。
  • 測試會與依附元件 "my_other_component_under_test" 一併封裝。這可能是測試元件所需的模擬服務供應商,或是測試需要叫用的其他元件。將這些元件封裝在一起,可確保在測試執行期間,系統能啟動依附元件,並以與測試相同的版本建構元件。
  • environments 參數可讓 fuchsia_test_package() 選擇性地採用 test_spec.gni 參數,並覆寫預設測試行為。在本例中,這項測試已設定為在 VIM3 裝置上執行。
  • 最後,這個範例會定義 group(),其中包含所有測試 (我們只有一個)。這是建議做法,可整理來源樹狀結構中的目標。

由於 GN 的限制fuchsia_test_package() 中的任何 test_component 目標都必須與測試套件目標定義在同一個 BUILD.gn 檔案中。您可以透過 fuchsia_test() 間接解決這個問題。

在一個 BUILD.gn 檔案中定義:

# Let this be //foo/BUILD.gn
import("//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.cml"
  deps = [ ":my_test" ]
}

fuchsia_test("my_test_component_test") {
  package = "//bar:my_test_package"
  component = ":my_test_component"
}

group("tests") {
  testonly = true
  deps = [ ":my_test_component_test" ]
}

然後,您可以在其他位置將 fuchsia_component() 目標新增至 depsfuchsia_package() 目標。

# Let this be //bar/BUILD.gn
import("//build/components.gni")

fuchsia_package("my_test_package") {
  testonly = true
  deps = [ "//foo:my_test_component" ]
}

單元測試

由於單元測試非常常見,建構系統提供兩種簡化的 GN 範本:

  • fuchsia_unittest_component.gni 定義要以測試形式執行的元件,並提供自動產生基本元件資訊清單的選項,該資訊清單隨後必須納入套件。
  • fuchsia_unittest_package.gni 定義要以測試形式執行的單一元件套件,是與 fuchsia_test_package() 配對的單一 fuchsia_unittest_component() 目標簡寫。

使用資訊清單進行單元測試

以下範例說明如何建構測試可執行檔,以及為測試定義套件和元件。

C++

import("//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.cml"
  deps = [ ":my_test" ]
}

荒漠油廠

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

rustc_test("my_test") {
  sources = [ "test.rs" ]
  testonly = true
}

fuchsia_unittest_package("my_test") {
  manifest = "meta/my_test.cml"
  deps = [ ":my_test" ]
}

查看

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

go_test("my_test") {
  sources = [ "test.go" ]
  testonly = true
}

fuchsia_unittest_package("my_test") {
  manifest = "meta/my_test.cml"
  deps = [ ":my_test" ]
}

使用 fx test 啟動測試元件,並提供 GN 目標名稱或完整元件網址:

GN Target

fx test my_test

元件網址

fx test fuchsia-pkg://fuchsia.com/my_test#meta/my_test.cm

使用生成資訊清單的單元測試

上述範例指定了測試的資訊清單。不過,單元測試可能不需要任何特定功能。

以下範例是執行 ROT13 加密和解密的測試。 受測演算法是純粹的邏輯,可完全獨立測試。如果我們要為這些測試編寫資訊清單,其中只會包含要執行的測試二進位檔。在這種情況下,我們只要指定測試可執行檔路徑,範本就會為我們產生簡單的資訊清單。

C++

import("//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" ]
}

荒漠油廠

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

rustc_test("rot13_test") {
  sources = [ "rot13_test.rs" ]
  testonly = true
}

fuchsia_unittest_package("rot13_test") {
  deps = [ ":rot13_test" ]
}

查看

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

go_test("rot13_test") {
  sources = [ "rot13_test.go" ]
  testonly = true
}

fuchsia_unittest_package("rot13_test") {
  deps = [ ":rot13_test" ]
}

您可以使用下列指令,找出產生的元件資訊清單檔案:

fx gn outputs $(fx get-build-dir) //some/path/to/build/file:unittest target_component_generated_manifest

如要直接列印,請按照下列步驟操作:

fx build && cat $(fx get-build-dir)/$(fx gn outputs $(fx get-build-dir) //some/path/to/build/file:unittest target_component_generated_manifest)

使用 fx test 啟動測試元件,並提供 GN 目標名稱或完整元件網址:

GN Target

fx test rot13_test

元件網址

fx test fuchsia-pkg://fuchsia.com/rot13_test#meta/rot13_test.cm

單一套件中的多個單元測試

如要將多個單元測試元件套件在一起,請使用 fuchsia_unittest_component() 規則 (不要使用 fuchsia_unittest_package()),並在 fuchsia_test_package() 中一併收集這些元件。這樣一來,您就能使用 fx test <package_name> 執行單一套件中的所有測試元件,而不必個別執行。

以下範例會建立單一測試套件 rot13_tests,其中包含兩個獨立的測試元件 rot13_decoder_testrot13_encoder_test

C++

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

executable("rot13_decoder_bin_test") {}

executable("rot13_encoder_bin_test") {}

fuchsia_unittest_component("rot13_decoder_test") {
  deps = [ ":rot13_decoder_bin_test" ]
}

fuchsia_unittest_component("rot13_encoder_test") {
  deps = [ ":rot13_encoder_bin_test" ]
}

fuchsia_test_package("rot13_tests") {
  test_components = [
    ":rot13_decoder_test",
    ":rot13_encoder_test",
  ]
}

荒漠油廠

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

rustc_test("rot13_decoder_bin_test") {}

rustc_test("rot13_encoder_bin_test") {}

fuchsia_unittest_component("rot13_decoder_test") {
  deps = [ ":rot13_decoder_bin_test" ]
}

fuchsia_unittest_component("rot13_encoder_test") {
  deps = [ ":rot13_encoder_bin_test" ]
}

fuchsia_test_package("rot13_tests") {
  test_components = [
    ":rot13_decoder_test",
    ":rot13_encoder_test",
  ]
}

查看

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

go_test("rot13_decoder_test") {}

go_test("rot13_encoder_test") {}

fuchsia_unittest_component("rot13_decoder_test") {
  deps = [ ":rot13_decoder_bin_test" ]
}

fuchsia_unittest_component("rot13_encoder_test") {
  deps = [ ":rot13_encoder_bin_test" ]
}

fuchsia_test_package("rot13_tests") {
  test_components = [
    ":rot13_decoder_test",
    ":rot13_encoder_test",
  ]
}

使用 fx test 和 GN 目標名稱,啟動套件內的所有測試元件:

fx test rot13_tests

測試推動開發

fx smoke-test 指令會自動偵測結帳程序中變更影響的所有測試,在此情況下,建議您嘗試下列疑難排解做法:

fx -i smoke-test --verbose

在上述指令中,--verbose 會列印 fx smoke-test 認為受到變更影響的測試,而 -i 則會在您每次儲存變更時,自動重複執行這項指令。如要進行測試導向開發,請嘗試在獨立的殼層中啟動這項指令,並在您處理程式碼時,觀察程式碼重建和重新測試的過程。

fx smoke-test 最適合使用密封測試套件。如果測試套件包含其中任何測試的所有依附元件,該套件就是密封套件。也就是說,如果程式碼變更會影響這項測試的結果,您也應該重建該測試的套件。

其他封裝資源

在上述範例中,我們已證明從套件到產生可執行檔的目標的 deps 路徑,可確保可執行檔包含在套件中。

有時需要加入其他檔案。下方將示範如何使用兩個 resource.gni 範本、resource()resource_group()resource_tree()

例如:字型

import("//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",
    ...
  ]
}

在上述範例中,系統提供六個檔案,以便封裝在 data/fonts/ 下,產生 data/fonts/Roboto-Black.ttfdata/fonts/Roboto-Bold.ttf 等路徑。destination 格式接受 GN 來源擴充預留位置

接著,定義文字檢視器元件,使其依附於字型。在本範例中,文字檢視器實作會以 Roboto 字型算繪文字。元件可以讀取沙箱中 /pkg/data/fonts/... 路徑下的指定字型。

範例:使用黃金資料進行整合測試

在本例中,我們定義了假設的服務,可縮減 JSON 檔案。據說這項服務會收到含有 JSON 文字的緩衝區,並傳回含有相同 JSON 資料但空白較少的緩衝區。我們將展示整合測試,其中測試元件會做為縮小器元件的用戶端,並比較要縮小的指定 JSON 檔案結果與已知良好結果 (或「黃金檔案」)。

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

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" ]
}

請注意,我們將 resource() 依附元件放在測試元件上。從建構系統的角度來看,資源依附元件可能已放在測試套件中,建構作業也會產生相同結果。不過,更好的做法是將依附元件放在需要這些元件的目標上。這樣一來,我們就能在不同的測試套件中重複使用相同的測試元件目標,例如針對不同的縮小器元件進行測試,而測試元件的運作方式會相同。

例如:使用 resource_group()

在上述範例中,所有路徑都符合特定結構,因此我們可以為多個檔案指定單一輸出模式,甚至運用 GN 來源擴充預留位置。在下一個範例中,我們需要將不同檔案重新命名為不同的目的地路徑,以進行封裝。

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

resource_group("favorite_recipes") {
  files = [
    {
      source = "//recipes/spaghetti_bolognese.txt"
      dest = "data/pasta/spaghetti_bolognese.txt"
    },
    {
      source = "//recipes/creamy_carbonara.txt"
      dest = "data/pasta/carbonara.txt"
    },
    {
      source = "//recipes/creme_brulee.txt"
      dest = "data/dessert/creme_brulee.txt"
    },
    ...
  ]
}

我們的來源都位於單一目錄中,但會封裝在不同目錄中,有些甚至會使用不同名稱。如要表達相同的關係,我們可能需要與檔案數量相同的 resource() 目標。在這種情況下,請改用 resource_group(),如上所示。

例如:使用 resource_tree()

如果檔案集較大,使用 resource_group() 將每個來源檔案對應至目的地檔案路徑可能會很麻煩。resource_tree() 提供將來源檔案的目錄樹狀結構對應至套件中目的地目錄下相同階層的方式。以下範例會將子目錄 default_repo_files/ 複製到套件目錄 repo/ (使用 sources 清單,確保只包含明確列出的檔案)。

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

resource_tree("default-repo") {
  sources_root = "default_repo_files"
  sources = [
    "keys/root.json",
    "keys/snapshot.json",
    "keys/targets.json",
    "keys/timestamp.json",
    "repository/1.root.json",
    "repository/1.snapshot.json",
    "repository/1.targets.json",
    "repository/root.json",
    "repository/snapshot.json",
    "repository/targets.json",
    "repository/timestamp.json",
  ]
  dest_dir = "repo"
}

resource()resource_group()resource_tree() 的基礎行為相同。你可以自由選擇偏好的方式。

受限的功能

如果新的元件資訊清單功能正在積極開發中,或是功能僅適用於特定對象,元件架構團隊可能會限制功能的使用者。CML 編譯器 (cmc) 會透過元件建構規則中的選擇加入屬性,控管這些受限功能的存取權。

如要使用受限制的功能,請新增 restricted_features 屬性:

fuchsia_component("my-component") {
  manifest = "meta/my-component.cml"
  # This component opts-in to the restricted "allow_long_names" feature.
  restricted_features = [ "allow_long_names" ]
  deps = [ ... ]
}

受限功能只能由允許清單中的使用者存取。 您必須在 //tools/cmc/build/restricted_features/BUILD.gn 中將元件新增至功能允許清單。

疑難排解

本節列出建構元件時可能遇到的常見問題。

缺少分片包括:

如果缺少必要資訊清單分片includecheck_includes 動作就會建構失敗,並顯示下列錯誤:

Error at ../../examples/components/echo_server/meta/echo_server.cml:
"../../examples/components/echo_server/meta/echo_server.cml" must include "../../sdk/lib/inspect/client.shard.cml".

如果元件依附元件鏈中的程式庫有 expect_includes 需求,但元件資訊清單中找不到所需的 include,就會發生這種情況。請參考以下使用「檢查」的範例:

C++

  1. 您的元件取決於 //sdk/lib/inspect/component/cpp

    executable("bin") {
      output_name = "echo_server_cpp"
      sources = [ "main.cc" ]
    
      deps = [
        "//examples/components/routing/fidl:echo",
        "//sdk/lib/sys/cpp",
        # This library requires "inspect/client.shard.cml" 
        "//sdk/lib/inspect/component/cpp", 
        "//sdk/lib/async-loop:async-loop-cpp",
        "//sdk/lib/async-loop:async-loop-default",
      ]
    }
    
  2. //sdk/lib/inspect/component/cpp 依附於 //sdk/lib/inspect:client_includes,而後者是 expect_includes() 規則。

荒漠油廠

  1. 您的元件取決於 //src/lib/diagnostics/inspect/runtime/rust

    rustc_binary("echo_server") {
      edition = "2024"
      deps = [
        "//examples/components/routing/fidl:echo_rust",
        # This library requires "inspect/client.shard.cml" 
        "//src/lib/diagnostics/inspect/runtime/rust", 
        "//src/lib/diagnostics/inspect/rust",
        "//src/lib/fuchsia",
        "//src/lib/fuchsia-component",
        "//third_party/rust_crates:anyhow",
        "//third_party/rust_crates:futures",
      ]
    
      sources = [ "src/main.rs" ]
    }
    
  2. //src/lib/diagnostics/inspect/runtime/rust 依附於 //sdk/lib/inspect:client_includes,而後者是 expect_includes() 規則。

如要解決這個問題,請在元件資訊清單中新增缺少的 include。例如:

{
    include: [
        // Add this required include 
        "inspect/client.shard.cml", 

        // Enable logging
        "syslog/client.shard.cml",
    ],

    // ...
}

如要進一步瞭解必要包含項目的來源,可以使用 gn path 指令探索依附元件路徑:

fx gn path $(fx get-build-dir) my-component expect_includes target --with-data

這個指令會列印類似以下的輸出內容,顯示需要包含的程式庫路徑:

C++

$ fx gn path $(fx get-build-dir) //examples/components/routing/cpp/echo_server //sdk/lib/inspect:client_includes --with-data
//examples/components/echo_server:bin --[private]-->
//sdk/lib/inspect/component/cpp --[data]-->
//sdk/lib/inspect:client_includes

荒漠油廠

$ fx gn path $(fx get-build-dir) //examples/components/routing/rust/echo_server //sdk/lib/inspect:client_includes --with-data
//examples/components/routing/rust/echo_server:bin --[public]-->
//examples/components/routing/rust/echo_server:bin.actual --[private]-->
//src/lib/diagnostics/inspect/runtime/rust:rust --[public]-->
//src/lib/diagnostics/inspect/runtime/rust:lib --[public]-->
//src/lib/diagnostics/inspect/runtime/rust:lib.actual --[private]-->
//sdk/lib/inspect:client_includes

無法驗證資訊清單

如果元件資訊清單含有元件套件中找不到的資源參照,cmc_validate_references 動作就會建構失敗,並顯示下列錯誤:

Error found in: //examples/components/echo/rust:rust-component_cmc_validate_references(//build/toolchain/fuchsia:x64)
    Failed to validate manifest: "obj/examples/components/echo/rust/cml/rust-component_manifest_compile/echo_rust.cm"
program.binary=bin/echo_example_oops but bin/echo_example_oops is not provided by deps!

Did you mean bin/echo_example?

Try any of the following:
...

如果元件資訊清單 program 區塊中的 binary 欄位參照的檔案路徑不在 fuchsia_package() 中,就會發生這種情況。

如要解決這個問題,請確認下列事項:

  1. 元件資訊清單中的參照路徑輸入正確。

    {
        // ...
    
        // 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_oops", 
        },
    }
    
  2. 元件可執行目標是連結至您 fuchsia_package()deps 鏈結的一部分:

    C++

    executable("bin") {
      output_name = "echo_example"
      sources = [ "main.cc" ]
    
      deps = [ ... ]
    }
    
    # Neither the component or package depend on ":bin" 
    fuchsia_component("component") {
      manifest = "meta/echo_example.cml"
      deps = [] 
    }
    
    fuchsia_package("package") {
      package_name = "echo_example"
      deps = [ ":component" ] 
    }
    

    荒漠油廠

    rustc_binary("echo_example") {
      edition = "2024"
      sources = [ "src/main.rs" ]
    
      deps = [ ... ]
    }
    
    # Neither the component or package depend on ":echo_example" 
    fuchsia_component("component") {
      manifest = "meta/echo_example.cml"
      deps = [] 
    }
    
    fuchsia_package("package") {
      package_name = "echo_example"
      deps = [ ":component" ] 
    }
    

靜態能力分析器

如果無法驗證每個功能路徑,Scrutiny 靜態分析器就會建構失敗,並顯示下列錯誤:

Static Capability Flow Analysis Error:
The route verifier failed to verify all capability routes in this build.
...

Verification Errors:
[
  {
    "capability_type": "directory",
    "results": { ... }
  },
  {
    "capability_type": "protocol",
    "results": { ... }
  },
]

如果分析無法透過有效的 exposeoffer 元件資訊清單宣告鏈,從來源追蹤能力路徑到要求能力的元件,就會發生這種情況。

在下列範例中,錯誤是由於元件 /core/echo 要求 use fuchsia.logger.LogSink 通訊協定,但父項沒有該能力的對應 offer 所致:

"errors": [
  {
    "using_node": "/core/echo",
    "capability": "fuchsia.logger.LogSink",
    "error": {
      "error": {
        "analyzer_model_error": {
          "routing_error": {
            "use_from_parent_not_found": {
              "moniker": {
                "path": [
                  {
                    "name": "core",
                    "collection": null,
                    "rep": "core"
                  },
                  {
                    "name": "echo",
                    "collection": null,
                    "rep": "echo"
                  }
                ]
              },
              "capability_id": "fuchsia.logger.LogSink"
            }
          }
        }
      },
      "message": "A `use from parent` declaration was found at `/core/echo` for `fuchsia.logger.LogSink`, but no matching `offer` declaration was found in the parent"
    }
  }
]

如要解決這個問題,請查看建構失敗時提供的錯誤詳細資料,找出導致路由錯誤的原因,然後在路由鏈中新增或修正無效的宣告。在先前的範例錯誤中,應在父項元件的資訊清單中加入 offer

{
    // ...

    children: [
        // ...
        {
            name: "echo",
            url: "echo#meta/default.cm",
        },
    ],
    offer: [
        // ...
        { 
            protocol: "fuchsia.logger.LogSink", 
            from: "parent", 
            to: "#echo", 
        }, 
    ],
}

如要進一步瞭解如何建構能力路徑,請參閱「連結元件」。