建構元件

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

概念

請先瞭解下列概念,再建構元件:

套件是指一組檔案,這些檔案具有與套件基礎相關聯的路徑。舉例來說,套件可能會在 bin/hello_world 路徑下包含 ELF 二進位檔,並在 data/config.json 路徑下包含 JSON 檔案。您必須將檔案分組成套件,才能將這些檔案推送至裝置。

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

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

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

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

元件會以幾種方式進行實例化,所有方式都會以某種方式指定其通常,您可以使用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!",
        ],
    },
}

相對路徑

包含開頭為 "//" 的路徑,相對於您正在使用的來源樹狀結構的根目錄。如果包含路徑不以 "//" 開頭,建構系統會嘗試從 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",
        }
    ],
}

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

用戶端程式庫包含

如上所示,元件資訊清單支援「include」語法,可讓您參照一或多個資訊清單區塊,做為其他資訊清單內容的來源。部分依附元件 (例如程式庫) 會假設依附元件在執行階段具有特定功能。舉例來說,C++ 系統記錄程式庫會做出這類假設。

如果您正在建構用戶端程式庫,可以在 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 會透過定義範本來擴充 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() 目標新增至 fuchsia_package() 目標的 deps

# 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 目標

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 目標

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() 提供一種方法,可將來源檔案的目錄樹狀結構對應至套件中 destation 目錄下的相同階層。以下範例會將子目錄 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 中功能的允許清單。

疑難排解

本節說明建構元件時可能會遇到的常見問題。

缺少的分片包括

如果您缺少必要的 資訊清單區塊include,建構作業就會失敗,並顯示以下錯誤:check_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 = "2021"
      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. 元件可執行目標是 deps 鏈結至 fuchsia_package() 的一部分:

    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 = "2021"
      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 要求 usefuchsia.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", 
        }, 
    ],
}

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