建構元件

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

概念

在建構元件之前,請先瞭解以下概念:

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

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

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

開發人員必須根據套件和元件定義軟體,無論是建構實際工作環境的軟體或用於編寫測試都一樣。

/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 的運作方式,請參閱「可用性」一文。

用戶端程式庫包括

如上所示,元件資訊清單支援「包含」語法,以便將一或多個資訊清單資料分割做為其他資訊清單內容的來源。部分依附元件 (例如程式庫) 會假設依附元件在執行階段中具有特定功能。舉例來說,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 透過定義範本 來擴充 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" ]
}

Rust

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

Rust

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

Rust

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

Rust

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

Rust

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() 依附元件放在測試元件中。從建構系統的角度來看,資源依附元件可能放置在測試套件中,而建構作業會產生相同的結果。不過,建議您將依附元件放在需要這些目標的目標上。這樣一來,就可以在不同的測試套件中重複使用相同的測試元件目標,例如針對不同的 Minifier 元件進行測試,而且測試元件也會正常運作。

範例:使用 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 中的功能。

疑難排解

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

缺少資料分割包括

check_includesinclude

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", 
        "//zircon/system/ulib/async-loop:async-loop-cpp",
        "//zircon/system/ulib/async-loop:async-loop-default",
      ]
    }
    
  2. //sdk/lib/inspect/component/cpp 依附於 //sdk/lib/inspect:client_includes,也就是 expect_includes() 規則。

Rust

  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

這個指令會輸出類似以下的內容,其中顯示需要 include 的程式庫路徑:

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

Rust

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

    Rust

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

靜態能力分析工具

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

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