RFC-0154:子套件

RFC-0154:子檔案包
狀態已接受
區域
  • 軟體推送
說明

允許套件宣告特定版本子套件的依附元件,以改善密封性和可重新定位性。

問題
Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2022-01-25
審查日期 (年-月-日)2022-03-23

摘要

這項提案旨在為 Fuchsia 新增支援功能,以便定義及解析子套件。子套件是從一個套件 (「A」) 到另一個套件 (「B」) 的命名參照,用於指定單向依附元件 (從「A」到「B」)。子套件也可以包含子套件,從頂層套件建立相關的版本固定套件的有向非循環圖 (DAG)。使用子套件時,套件「A」中的資源可以透過子套件名稱參照套件「B」,而子套件名稱是由包含套件定義。如果「B」是「A」的子包,則提供「A」的任何系統 (例如 Fuchsia 封存庫、blobstore) 也必須提供「B」。因此,子套件可啟用兩個重要的系統屬性:

  • 密封性:使用特定套件依附元件來封裝程式 (或測試)。
  • 原子性 - 系統應保證,如果套件含有子套件且已順利解析,則所有子套件也能順利解析。(本 RFC 中提出的方法會在從套件解析要求傳回成功結果之前,先急切解析整個子套件階層,以確保這一點)。
  • 可重新定位性:可將套件和所有必要的資源分發至單一封存檔 (例如下載) 或其他 Fuchsia 套件伺服器。

提振精神

這份 RFC 提出了一種方法,可表達並支援巢狀的跨套件依附元件 (子套件) 概念。雖然目前不允許套件依附其他套件,但這項 RFC 修訂了限制條件,指出套件不得依附其他套件,除非是透過包含。

請注意,雖然本 RFC 討論的最初動機用途都與測試相關,但子包可能也會在其他情況下派上用場。我們會先測試子套件,因為我們可以以正式版無法接受的方式進行測試。除了排程考量因素外,我們的目標是在測試和實際工作環境情境中,全面支援子套件。

為何現在發行?

目前最常見的用途是需要套件間依附元件的測試情境。測試需要在封閉環境中執行特定元件 (通常是假造或模擬),以及要測試的元件。請務必依據並納入確切的依附元件版本,以免全域系統狀態 (即安裝的套件組合) 影響測試的正確性。我們稱這項屬性為「密封包裝」。

我們會在單一套件中納入所有元件依附元件,以便樹狀結構內密封測試套件。不過,樹外 (OOT) 的現況是依賴絕對套件網址。這些測試並未密封包裝,因此會將平台套件網址設為隱含的 ABI。

舉例來說,今天的 Flutter 整合測試會透過元件網址 fuchsia-pkg://fuchsia.com/scenic#meta/scenic.cm,宣告對 Fuchsia 的 Scenic 元件的依附元件。這只是宣告 Fuchsia 平台會透過該網址參照提供某些 Scenic 例項。如果 Scenic 或其中一個依附元件 (測試開發人員也必須發現及宣告) 變更其介面或行為,或遭到移除,測試可能會在執行階段失敗。這已是導致測試中斷的常見原因,而且隨著規模擴大,情況只會更糟。

相較之下,如果使用子套件,Flutter 整合測試作者可以:

  1. 在建構期間取得包含特定 Scenic 版本的套件。(這項機制不在本文討論範圍內)。
  2. 將該套件納入測試套件的子套件。
  3. 在測試的元件資訊清單中,使用網址 scenic#meta/scenic.cm 宣告子項元件,而非 fuchsia-pkg://fuchsia.com/scenic#meta/scenic.cm

這個版本的測試更為完整,無論執行測試的裝置上安裝哪個版本的 Scenic,都會一律以相同方式運作。

注意:子套件可用於這類 OOT 測試情境,但 Fuchsia 平台套件在測試中重複使用時的發布程序不在 RFC 的範圍內。不過,一旦 RFC 獲得核准,我們很可能會接著定義這項程序,做為 Fuchsia SDK 的擴充功能。

我們希望在樹狀結構內和 OOT 元件之間提供一致的體驗:單一測試套件會明確指出所有密封式依附元件,並以原子群組的形式提供給測試。

我們建議以子包巢狀結構的形式支援套件間的依附元件,而非重新套用樹狀結構內中的現有策略 (這需要建構包含多個元件的全新套件)。這樣一來,我們就能獨立套件及依附元件,而無須考量複雜的命名空間。

相關人員

導師:hjfreyer@google.com

審查者:

名稱 重點領域
wittrock@google.com SWD
jsankey@google.com SWD
geb@google.com 元件架構
shayba@google.com 建構
etryzelaar@google.com 工具
aaronwood@google.com 組裝/包裝運送
cgonyeo@google.com RealmBuilder
ampearce@google.com 安全性

諮詢對象:computerdruid@google.com

社會化:

在發布前,SWD 團隊已參與本 RFC 的初步草案。元件架構團隊會持續掌握這項工作的進度,並在每週會議中提供有關範圍和功能的意見回饋,而 RFC 作者中也有 CF 團隊成員。

設計

受子套件影響的用途

  • 巢狀套件依附元件的套件內宣告
    • 套件可能會包含列出其子套件的中繼資料檔案,而建構系統必須能夠根據套件目標的依附元件填入這個中繼資料檔案。
  • 相對元件解析度
    • 舉例來說,開發人員可以將對元件 fuchsia-pkg://servername/some-package#meta/child_component.cm 的參照替換為相對參照 child_package#meta/child_component.cm;其中 child_package 是參照來源 (或「父項套件」) 對 some-package 的命名參照,在建構期間會鎖定版本 (依套件雜湊)。
  • 套件依附元件樹狀結構的檢索作業
    • 舉例來說,只要發布單一頂層套件,套件伺服器就能使用嵌入的套件參照,自動尋找及載入套件依附元件。
  • 封存套件樹狀結構
    • 從特定頂層套件建構單一檔案,其中包含套件和所有依附元件。

子檔案包表示法

套件會透過子套件的套件層級名稱宣告對子套件的參照,並對應至在相同套件商店和套件集中定義的套件套件雜湊值。(例如,如果父項是從「universe」套件組解決,則其子項也必須從「universe」套件組解決)。其餘的 RFC 會說明這些宣告的概念性表示法,以 meta/subpackages 檔案的形式呈現,並與 meta/contents 目前使用的格式相符 (=)。

如果有此檔案,則至少必須包含子套件的名稱和套件雜湊 (參照子套件的 meta.far 的「內容位址」)。

在單一 meta/subpackages 檔案中,子套件名稱不得重複,但在不同套件中則不在此限。因此,套件可完全控制其子套件的命名方式,且多個套件可能會在不同的本機名稱下包含相同的子套件。

子套件參照會視為參照套件的「私人資料」。舉例來說,父項套件 A 可能包含兩個子項套件:套件 BC (由各自的套件雜湊參照)。套件 B 也可能包含參照相同套件雜湊 C 的子套件。頂層父項 A 和其子包 B 都不會知道常見的依附元件。換句話說,父項套件只能解析其子項套件,一個層級往下。父項無法直接將套件網址解析為子套件的子套件。事實上,本 RFC 並未定義表示子套件的子套件的語法)。

注意:個別子包也可以獨立發布,並做為頂層套件提供。(例如,您可以將正式版元件發布為由 fuchsia-pkg 網址識別的頂層元件,但該元件的套件也可以納入一或多個密封測試的子套件)。使用 Fuchsia 套件 URI 解析頂層套件時,預設行為是擷取該套件最近發布的版本。另一方面,子套件一律會透過「套件雜湊」參照特定版本。

建構含有子套件的套件

將子套件新增至套件時,可遵循類似於新增一般檔案的模式,但需考量一些特殊事項。

各種 Fuchsia SDK 建構系統 (目前為 GN 和 Bazel) 通常支援建立「套件」目標,可取用其他目標的相依項目,以便納入套件。在這些建構系統中,新增套件依附元件會通知建構系統也產生依附元件套件,但目前不會在產生的套件中編碼該依附元件。舉例來說,下列 GN 程式碼只會在產生套件「A」時產生套件「B」。

# GN format
fuchsia_package("A") {
  deps = [
    ":B",
    other_deps,
    ...
  ]
}

fuchsia_package("B") {
  ...
}

我們曾快速調查 fuchsia.git 中現有的套件間依附元件,發現在大多數目標套件依附於其他套件的情況下,目標套件中的元件會預期載入依附元件。在這些情況下,依附元件套件可與目標一起捆綁,做為子套件。不過,由於現有的 GN 規則不會強制執行這項解讀,因此我們無法推斷。

因此,本 RFC 建議使用明確的變數 subpackages 來宣告子套件目標。subpackages 清單中的每個目標都必須在產生的 fuchsia_package meta/subpackages 檔案中產生對應的項目。

清單中的目標也會推斷建構依附元件,因此 subpackages 中的目標不必同時顯示在 deps 中。如要將含有已宣告套件依附元件的套件轉換為含有內含子套件的套件,請將所有 (或所選) 套件目標從 deps 移至 subpackages。理論上,fuchsia_package 的變更可能會顯示為:

# GN format
fuchsia_package("A") {
  subpackages = [ ":B" ]

  deps = [
    other_deps,
    ...
  ]
}

fuchsia_package("B") {
  ...
}

建構系統應將子套件名稱預設為所包含目標的名稱,但可能會透過常見慣用語支援覆寫此名稱。

為了方便使用現有工具建構含有子套件的套件,建構規則和指令碼務必在 Fuchsia 樹狀結構內建構系統中更新,並應在 Fuchsia GN SDK 和後續建構環境中更新。(受影響的建構環境和必要變更 (在已知範圍內) 請參閱「實作」一節)。

解析子檔案包

子套件解析會將相對參照解析為已知套件的命名子套件。

fuchsia.pkg.PackageResolver 通訊協定的 Resolve 方法會修改為傳回 ResolutionContextResolve 只會解析絕對套件網址。系統會新增額外的方法 ResolveWithContext,以便接收額外的 context 引數 (ResolutionContext),並為解析的套件傳回新的 ResolutionContextResolveWithContext 會解析絕對套件網址 (忽略 context) 或相對套件網址。這些變更由下列概念性 FIDL 程式碼片段表示:

   library fuchsia.pkg;

   ...

   const MAX_RESOLUTION_CONTEXT_LENGTH = <TBD>;
   type ResolutionContext = bytes:MAX_RESOLUTION_CONTEXT_LENGTH;

   protocol PackageResolver {

     /// Resolves an absolute component URL.
     /// ...
     Resolve(resource struct {
         package_url string;
         dir server_end:fuchsia.io.Directory;
     }) -> (struct {
         resolved_context ResolutionContext;
     }) error ResolveError;

     /// Resolves a component URL, which may be absolute or relative. If
     /// relative, the component will be resolved relative to the supplied
     /// `context`.
     ResolveWithContext(resource struct {
         package_url string;
         context ResolutionContext;
         dir server_end:fuchsia.io.Directory;
     }) -> (struct {
         resolved_context ResolutionContext;
     }) error ResolveError;

     ...
   }

注意:我們之所以選擇以位元組陣列做為內容,是因為考量到多項限制、部分 FIDL 限制,以及經過仔細考量的意見。如需更多背景資訊,請參閱「替代方案」部分:使用特定情境的 Resolver替代方案:解析情境值的 FIDL 類型表示法

對於位於 package_url 且宣告子套件 (「父項」套件) 的套件,在解析子套件時,傳回的 resolved_context 必須以 context 輸入參數的形式傳遞至 ResolveWithContext 的後續呼叫。(如果呼叫端未解析已解析套件的子套件,則可能會忽略傳回的 resolved_context)。

子套件的實作方式絕對不得降低現有服務和元件的健全性和可用性。換句話說,子套件不得導致非關鍵 (可重新啟動的) 元件需要標示為關鍵,且無狀態通訊協定 (例如 fuchsia.pkg.Resolve(),但也包括建議的 ResolveWithContext,可取代對 Resolve 的部分呼叫) 必須保持無狀態。

這項限制可能會影響 context 值的實作方式。只要父項套件在系統中處於有效使用狀態,ResolveWithContext 就必須接受 ResolveResolveWithContext 傳回的任何 context。(概念上,父項套件的檔案包雜湊值就足以在相同的 Merkle 雜湊索引套件儲存庫中,以無狀態方式解析子檔案包)。

注意:決定父項是否仍處於「使用中」狀態,是套件垃圾收集的關切項目,需要在套件解析器中實作子套件時解決。

對於絕對 package_url,系統會忽略 ResolveWithContextcontext 引數。

注意:由於這個 RFC 會將子包解決程序限制為父項宣告的子包 (向下一個層級),因此相對路徑不得包含斜線 (/)。

從子套件載入元件

元件解析器負責剖析元件網址,將其轉換為可解析的套件網址,然後從解析的套件載入元件資訊清單。系統會使用已載入的資訊清單和套件內容建立新元件,其中包括用來依據相對路徑解析子套件的內容。

fuchsia.component.resolution.Resolver 通訊協定的 Resolve 方法只會解析絕對元件網址。系統會新增額外的方法 ResolveWithContext,並採用額外的 context 引數 (fuchsia.component.resolution.Context)。ResolveWithContext 會解析絕對元件網址 (忽略 context) 或相對元件網址。這些變更由下列概念性 FIDL 程式碼片段表示:

   library fuchsia.component.resolution;

   ...

   // Note the context length, in bytes, must be at least the size of
   // fuchsia.pkg.MAX_RESOLUTION_CONTEXT_LENGTH, plus the size required to
   // accommodate additional component context information, if any.

   /// The maximum number of bytes for a `Context`.
   const MAX_RESOLUTION_CONTEXT_LENGTH uint32 = 8192;

   /// A byte array that persists a value used by the target `Resolver` to locate
   /// and resolve a component by relative URL (for example, by a subpackage
   /// name).
   alias Context = bytes:MAX_RESOLUTION_CONTEXT_LENGTH;

   protocol Resolver {

     /// Resolves a component with the given absolute URL.
     /// ...
     Resolve(struct {
         component_url string;
     }) -> (resource struct {
         component Component;
     }) error ResolverError;

     /// Resolves a component with the absolute or relative URL. If relative, the
     /// component will be resolved relative to the supplied `context`.
     ///
     /// `component_url` is the unescaped URL of the component to resolve, the
     /// format of which can be either:
     ///
     ///   * a fully-qualified absolute component URL
     ///   * a subpackaged-component reference, prefixed by a URI relative
     ///     path to its containing subpackage (for example,
     ///     `child_package#meta/some_component.cm`); or
     ///   * a URI fragment to a component in the current package (for
     ///     example,`#meta/other_component.cm`)
     ///
     /// `context` is the `resolution_context` of a previously-resolved
     /// `Component`, providing the context for resoving a relative URL.
     ResolveWithContext(struct {
         component_url string;
         context Context;
     }) -> (resource struct {
         component Component;
     }) error ResolverError;
   }

傳回的 Component 類型會修改為包含 resolution_context,以便在解析與此 Component 相關的其他元件時使用。

   type Component = resource table {

       ...

       /// The context used to resolve `component_url`s relative to this
       /// component.
       5: resolution_context Context;
   };

舉例來說,在解析名為 parent 的元件後,後續對 ResolveWithContext(subpackaged_url, parent.resolution_context) 的呼叫可解析子包元件。

對於絕對 component_url,系統會忽略 ResolveWithContextcontext 引數。

對於相對 component_url

  • 用戶端必須使用 ResolveWithContext。使用相對元件網址呼叫 Resolve 會傳回錯誤代碼 ResolveError::INVALID_ARGS

對於相對的子包元件網址:

  • component_url 開頭為相對路徑 (套件子集名稱,後面接特定元件 (例如 child_package#meta/some_component.cm) 的 # 片段)。
  • 如果套件解析器傳回 PACKAGE_NOT_FOUND (或某些等同的套件商店相關錯誤),元件解析器就必須傳回 ResolveError:PACKAGE_NOT_FOUND

相對資源元件網址 (來自與其他元件相同的套件):

  • component_url 是 URI 片段 (例如 #meta/other_component.cm,如 RFC-0104:相對元件網址 所述):
  • 片段必須參照先前已解析的另一個元件 (「同儕」元件) 所屬的套件中的元件。
  • context 值必須參照對等點的相同套件版本 (相同的套件雜湊)。

注意:使用 context 解析相對資源元件網址 (依 URI 片段) 會變更相對解析器的目前行為。目前,相對解析器會從父項元件建構絕對套件網址,並附加相對片段部分。這項行為無法保證父項元件和子項元件會從相同的套件版本擷取。使用子包時,您不一定能從父項或同級元件建構絕對套件網址。另一方面,將套件綁定至用於解析套件中所有元件的特定套件雜湊,可能是預期的行為;在這種情況下,使用情境是一種改善方式。

當元件管理員代表現有元件 (「父項元件」) 使用相對 URI (路徑或片段) 解析元件網址時,必須將解析作業委派給用於解析父項元件的相同 Resolver

您應將內容值視為元件解析器內部的實作細節,對用戶端而言是不可見的。Resolver 可將 resolved_context 值 (從 PackageResolver::Resolve...() 傳回) 轉寄為元件解析度 context 值。(API 不會阻止 Resolver 傳回不同的或增強的背景資料值,但這可能不是必要的)。

以下是使用子專案的子項解析元件的程序概念示例 (假設為啟動後環境):

  1. 如要從頂層套件 P 載入元件 A,請按照下列步驟操作:

    a. 元件管理服務會取得 fuchsia-pkg 配置方案的已註冊 Resolver 能力,並呼叫 Resolver::Resolve("fuchsia-pkg://fuchsia.com/package-p#meta/component-a.cm")

    b. Resolver 會擷取套件網址並呼叫 PackageResolver::Resolve("fuchsia-pkg://fuchsia.com/package-p", ...),傳回 fuchsia.io.Directory 以便載入元件,以及 resolved_context 值。

    c. Resolver::Resolve() 會使用 resolution_context 建構並傳回已解析的 Component,元件管理服務會將其快取在該元件的狀態中。

  2. 如要從子包 child-package 載入子項元件 B,請參考上述元件 A

    a. 元件管理服務會取得用於解析 component-aResolver 能力,並呼叫 Resolver::ResolveWithContext("child-package#meta/component-b.cm", component_a.resolution_context)

    b. Resolver 會從元件網址擷取相對套件路徑 (子套件名稱),並從元件 context 輸入參數擷取 package_context,然後呼叫 PackageResolver::ResolveWithContext("child-package", package_context),傳回用於載入元件的 fuchsia.io.Directory,以及子套件本身的 resolved_context 值。

    c. Resolver::ResolveWithContext() 會建構並傳回已解析的 Component,並使用新元件的 resolution_context,該元件管理服務會在元件狀態中快取。

日後作業

  • 整合 subpackagescontents 檔案 - 如果 Persistent FIDL 套件內容 的 RFC 提案獲得核准,則應更新子套件檔案,以符合取代 contents 的新格式;或者,您可以使用擴充欄位來區分內容項目和子套件項目,藉此合併檔案。
  • 使用摘要統計資料註解 subpackages 檔案項目 - 「Persistent FIDL package contents」RFC 會提供一種機制,可在每個子套件與套件雜湊對應項目中加入其他資料。建議您加入摘要統計資料,例如內容大小 (包括每個子套件及其巢狀依附元件的總和大小)。例如,當您載入含有大量依附元件的套件時,可在套件擷取作業期間使用此方法回報進度。
  • 改善執行階段依附元件解析功能:我們也正在考慮 (未來的 RFC) 概念,即支援含有絕對路徑 (前面加上斜線「/」) 的相對套件網址,以便從提供「目前」套件 (或目前元件) 的「相同套件伺服器」要求頂層套件。(如同本 RFC 所述,從目前的套件解析子套件網址時,目前的套件伺服器是可知的)。
  • 用於解析及載入資產套件內容的泛用 FIDL 服務:目前,Fuchsia 軟體載入套件內容的唯一方法,就是在該套件中加入元件。自訂元件負責將目錄轉送至 FIDL 通訊協定,以便從套件中佈建資產。我們正在考慮在未來提案中納入更容易存取的 FIDL API,用於載入該套件中的子套件和/或資產。例如,您可以使用此方法將視覺素材資源或可選取的使用者介面「主題」以資源套件形式分發,而不需要實作元件中介的額外步驟。
  • Lazy Loading of Subpackages - 這份 RFC 建議在載入頂層套件時,將會遞迴地載入套件和所有子套件 (也就是提前載入)。日後推出的擴充功能可讓您宣告特定子套件為「可延遲載入」的項目。這可用於避免從遠端伺服器將套件匯入儲存空間受限的裝置,除非有特定套件要求。(詳情請參閱「積極套件載入」)。
  • 子套件元件熱重載 - 這份 RFC 建議嚴格實作套件間版本管理:套件雜湊會根據其內容的雜湊計算,包括子套件的雜湊。因此,如果子套件發生變更 (可透過子套件的套件雜湊值得知),父項套件也會隨之變更。建議您在重新載入父項時,不要重新載入子項 (例如在開發期間「熱載入」元件),反之亦然。允許這類行為可能會降低子套件的部分預期優點,包括可靠性和安全性。此外,元件管理服務在解析及重新載入新版元件時,會出現已知的差距 (請參閱 https://fxbug.dev/42145182)。如果這些已知問題已解決,則可在日後的提案中修訂子包依附元件限制,讓父項套件可根據較鬆散的合約 (例如行為、API 和/或 ABI 保證) 宣告特定依附元件。

實作

Fuchsia 樹狀結構內建構系統 (GN 規則和指令碼)

建構規則、指令碼和檔案格式 (包括 Fuchsia 套件產生的中繼檔案格式) 都必須更新,才能在套件建構和/或封存的每個階段轉送 subpackages 清單,並最終在套件 contents 檔案旁邊產生新的 meta/subpackages 檔案。

針對 subpackages 中的每個套件目標,meta/subpackages 檔案必須將宣告的子套件名稱 (預設為子套件目標名稱) 對應至子套件目標的套件雜湊值 (即子套件的 meta.far 的 blob 雜湊值)。

部分檔案格式可能需要變更格式 (以便新增子包參照),或是為相同階段提供輔助檔案 (例如 contents 如何與 subpackages 配對),包括:

  • 套件「建構資訊清單」(或 archive_manifest),通常會以 .manifest 副檔名結尾
  • package_manifest.json

Fuchsia 樹狀結構外建構環境 (GN、Bazel 和支援指令碼)

建構規則和指令碼應以類似於 Fuchsia 樹狀結構內建構規則和指令碼的方式更新,以便在樹狀結構外存放區中啟用子包。受影響的存放區包括 chromium 和 flutter/engine。(請注意,flutter/engine 目前實作的是經過修改的 GN SDK 建構規則和指令碼副本,因此需要在 GN SDK 存放區和 flutter/engine 中進行類似 (如果不是完全相同) 的變更)。

  • package.gni 會使用 --manifest-path 叫用 prepare_package_inputs.py,以產生 ${package_name}.manifest (在指令碼中稱為 archive_manifest,或在 pm CLI 說明文件中稱為「建構資訊清單」)
  • pm_tool.gni 會使用 -m ${archive_manifest}-output-package-manifest 叫用 pm build,以產生 package_manifest.json

套件管理員套件指令列介面 (CLI)

套件管理員指令會透過變更 ffx package 而有所調整。

注意:pm 指令已淘汰,請改用 ffx package。只有在工作流程需要時,才會變更 pm,且無法更新為使用 ffx package 取代指令。

  • ffx package build (原本為 pm build)
    • 這個指令必須變更,才能接受額外的引數和檔案,或是輸入和輸出的修訂檔案格式,以便納入額外的子包宣告。
  • ffx package export (原本為 pm archive)
    • 這個指令會讀取 contents 檔案,並產生包含所有參照 Blob 的 .far 檔案。
    • 對於含有 subpackages 檔案的套件,export 行為將會延伸,將這些子套件 (遞迴) 合併至產生的封存檔 (請參閱「將套件依附元件合併以供發布」)
  • 其他 ffx package 指令也可能需要變更,以便支援 subpackages (例如 downloadimport)。我們會在實作時調查這些指令的影響。這些變更可能會影響或受到其他已規劃變更的影響,這也是 RFC-0124:去中心化產品整合:構件說明和傳播 的一部分。

將套件依附元件打包以供發布

子套件的其中一個主要用途,是透過確保套件及其所有直接和間接依附元件可重新定位為套件組合,來支援套件分發作業。比起開發用於遞迴方式檢查子包依附元件的程序,套件格式就顯得沒那麼重要。

ffx package 工具是實作識別及定位各個子套件的內容 (展開的套件目錄或 .far 封存檔) 的邏輯工具。

與其將為密封封裝實作自訂指令碼的負擔,轉嫁給每位樹狀結構外使用者,不如將套件工具擴充為可辨識子套件的工具,以便支援套件及其子套件依附元件的發布作業。舉例來說,ffx package 會擴充至將套件與其依附元件封裝至單一檔案封存檔。

建議的密封套件封存格式 (做為單一檔案) 將是所有貢獻套件的展開內容,產生所有套件中所有 Blob 的索引式平面集合。

套件解析器

fuchsia.pkg.PackageResolver 通訊協定的實作項目必須更新,才能實作上述設計部分所述的行為。

急切套件載入

套件解析器必須在內部遞迴解析所有子套件,直到擷取所有子套件為止,然後才會傳回根套件的解析結果。我們選擇採用這種做法,是為了簡化原子性質屬性的實作方式,確保在元件啟動前,所有必要的子套件都已解析。

強制執行急切套件解析作業,也許也能支援已核准的 RFC-0145:急切套件更新中的一些需求。值得注意的是,套件及其子套件樹狀結構可用於實作急切套件更新 RFC 中「套件群組」的先決條件。

元件解析器

fuchsia.component.resolution.Resolver 通訊協定的實作項目必須更新,才能實作上述設計部分所述的行為。

RealmBuilder

RealmBuilder 必須更新,才能宣告子套件,並回應在元件網址中使用相對路徑的元件解析要求。使用 RealmBuilder 的密封測試目前無法存取套件解析器。必須透過子套件宣告,支援透過子套件宣告載入密封捆綁套件,並提供給密封和系統測試。

成效

遍歷子套件可能會增加識別所有要下載的 Blob 的延遲時間,因為它會遵循多個間接層級,但這在實際上不太可能造成影響。舉例來說,請比較載入含有 N 個 Blob 的單一套件,以及載入含有子套件的套件,其中子套件總共含有 N 個 Blob:

    • 上層
    • N 個 Blob

VS

    • 上層
    • N_0 blob
    • Child1
      • N_1 blob
      • Child2
      • N_2 blob
      • ...

在第一種情況下,所有 N 個 Blob 都會事先知曉,並可並行載入。在第二種情況下,套件解析器必須依序追蹤父項和子項之間的連結,累積要載入的完整 N 個 Blob 集合。這項檢索會產生額外的延遲時間,且受樹狀結構深度的限制。如果遞迴 Blob 載入作業的延遲時間變成問題,您可以使用多種實作選項來縮短延遲時間。舉例來說,您可以並行處理子套件連結和載入 Blob,或是在建構套件時納入完整的子套件內容位址集。

回溯相容性

相對資源元件網址的解析行為

解析現有套件網址和元件網址的行為並未改變,但有一個例外:相對資源元件網址 (以 URI 片段表示,例如 #meta/child.cm) 需要元件 context (建議實作方式會保證這一點),但行為會稍微改變。目前的相對解析器會將片段連結至父項元件的套件網址,然後重新解析套件和元件。但解析器可能會載入新版套件。這被認為是原始實作項目的潛在風險區域。context 可確保從「父項元件」解析的相同套件中,解析相對元件。

解讀已解析元件的 fuchsia.component.resolution.Package

解析元件後,傳回的 fuchsia.component.resolution.Component 類型會包含 fuchsia.component.resolution.Package 類型的 package 欄位,其中包含包含傳回 Component 的套件 package_url 參照。

   type Package = resource table {
       /// The URL of the package itself.
       1: url string;

       /// The package's content directory.
       2: directory client_end:fuchsia.io.Directory;
   };

由於子套件一律是相對的,因此 package_url 的任何現有用途都可能受到影響。本 RFC 所述的子包解析程序不會使用 Package 類型中儲存的任何資訊,因此對 Package 或該類型的解讀方式所做的任何變更,都不會對子包 RFC 設計造成實質影響。

在導入期間可考慮的替代方案包括:

  1. 儲存只參照套件伺服器和套件雜湊值 (例如 fuchsia-pkg://fuchsia.com?hash=123456789) 的 url,這類資料足以重新解析子套件的內容,但不會保留父項元件或其子套件名稱的任何資訊。
  2. 將子套件路徑儲存在 url 中,並在 Package 中新增選用的解析內容欄位 (元件套件解析的內容)。

FIDL 方法變更

fuchsia.pkg.PackageResolverfuchsia.component.resolution.Resolver 中,Resolve() 方法都會變更為傳回新的解析內容,並新增名為 ResolveWithContext() 的其他方法,以支援 context 輸入參數。這可能不需要軟性轉換。

套件表示法異動

在 Fuchsia 中新增子包不會變更沒有子包的套件呈現方式。現有的套件網址和元件網址不會受到影響。開發人員可以選擇將完整元件網址 (例如使用 fuchsia-pkg://fuchsia.com/top-level-package#...) 替換為其中一個子套件的參照 (使用 child-package#...,將 child-package 對應至 top-level-package 的套件雜湊)。

工具異動

舊版 Fuchsia、套件伺服器和部分主機端工具 (例如 pmffx package) 不支援使用子套件的套件,或使用套件相對路徑的軟體。您必須更新並重新編譯 Fuchsia 系統和主機端工具。

套件名稱語法

子套件名稱的範圍為父項套件,且實際上是任何具有相同套件雜湊值的頂層套件名稱的別名。也就是說,子套件名稱的語法不必與套件名稱的語法相同。

不過,通常會使用相同名稱來獨立參照套件,並做為子套件。

由於子套件是階層式結構,因此自然會將巢狀子套件視為可命名的項目,或許可使用斜線做為分隔符。例如 fuchsia-pkg://fuchsia.com/parent/child/grandchild#gc-component.cmchild/grandchild#gc-component.cm。請注意,這份 RFC 既不允許也不禁止這類表示法,但使用斜線分隔符會導致潛在的陷阱。

套件名稱目前可包含單斜線。後續的內容會視為「變化版本」,例如 Fuchsia 目前常用的 /0

值得注意的是,/0 已淘汰,如果從常用用途中移除,可能會釋出 / 的意思,用於子套件 (不過,這項做法目前尚未提出,且必須等到日後的 RFC 才會實施)。

如果套件命名階層有競爭用途,則可使用其他分隔符 (例如 :) 來巢狀排列子套件,或是讓子套件使用 /,並將其他分隔符用於其他用途。

本 RFC 建議至少保留可用的子包名稱語法中的 /:

安全性考量

可稽核性和可執行性

所提設計方案已考量到 Fuchsia 安全性負責人至今提出的意見,包括盡可能減少對系統安全性狀態的變更,以及簡化可能影響可稽核性和可執行性的變更審查作業。

舉例來說,父項及其所有子項 (遞迴) 都必須來自相同的套件商店,並視為同一套件集的一部分 (例如,全部來自「base」或全部來自「universe」)。使用相同的套件集合,表示子套件階層中的所有套件都受相同的可執行性政策規範。

根據套件或元件網址建立的系統許可清單

套件或元件目前因其敏感功能而列入特權許可清單,可納入為子套件,但需要一些額外限制才能保留所需特權。除了測試外,父項套件不得為需要比父項更高權限的子項元件,加入子項。在密封測試中執行的元件可能需要例外狀況,但如果在密封測試中使用允許清單中的套件或元件做為子套件,則應盡可能以模擬或等效替代方案取代特權功能。

如果這項限制導致其他系統改善作業受阻,請洽詢安全團隊,瞭解是否有其他替代方案。

控管特權套件解析作業的存取權

目前,在平面套件命名空間中,PackageResolver 用戶端可讀取 blobfs 中的所有內容;因此,從套件伺服器要求套件並讀取其 BLOB 的權限操作是需要特權的。

子套件只會提供指定的 blobfs 套件子集 (目前為一層深),但宣告對其他套件依附元件的套件,將無法再讀取該套件的內容。這份 RFC 建議,在非特權用戶端要求子套件功能或資產時,維持相同的限制。

從特定套件執行的元件可能無法查看其子套件的內容,但如果具有 PackageResolver 能力,則可以查看 meta/subpackages 檔案並手動解析其內容。

相較於在單一套件中並排加入依附元件的現況,這項設計可提升安全性。在該解決方案中,每個元件都可以任意查看其他元件的內容。由於元件可能只會看到執行來源的套件,而不會看到子套件的內容,因此這項設計會隱藏這些實作詳細資料。

實作風險:隊頭阻塞

如果我們實作深度優先的套件解析,巢狀的子套件 DAG 可能會阻斷其他套件的解析。這可能會導致解析器發生死結,因為在遞迴方式解析子套件時,它會持續保留父項的封鎖資源。

由於 RFC 指出,這項規範只會實作急迫解析,因此實作者可以透過確保在釋出解析器資源來解析子包之前,父項不需要完全解析,來降低這項風險。

隱私權注意事項

預期不會影響隱私權立場。

測試

我們會新增單元測試,驗證其他功能。現有的測試可協助找出非預期的回歸現象。

主機端封裝測試會在處理子套件時,驗證 ffx package (和/或 pm,視需要) 和相關工具的行為。

系統會編寫密封整合測試,驗證子套件的元件解析。

說明文件

需要更新下列已知文件:

  • 現有的說明文件,說明 Fuchsia 套件網址和元件網址 (用於說明如何參照子套件)、軟體提交說明文件,以及元件架構概念文件。目前顯示 CML 範例的範例 (含 fuchsia-pkg:// 網址參照) 可使用子套件範例加以擴充。
  • 應更新相對元件參照項目的說明文件,說明與子包元件相比的相似之處和差異。

缺點、替代方案和未知事項

請參閱「未來工作」子區段 (位於「設計」一節),其中說明瞭一些未來的增強功能,以及在縮小範圍至本 RFC 目前版本前,考慮採用的其他方法和額外功能。

此外,下列各節將說明建議計畫的替代方案。

替代做法:在子元件網址中插入版本雜湊

我們不打算在軟體提交堆疊中導入子套件,而是可以新增工具,讓元件透過在子元件網址中新增套件雜湊限定符,宣告版本化的依附元件。舉例來說,parent.cmchildren: 清單中,密封包裝的依附元件會顯示為 url: "fuchsia-pkg://fuchsia.com/some_package?hash=1234#meta/a_component.cm"

只要工具和基礎架構可保證 some_package 的版本位於套件商店中,這個參照就能與現有的解析器搭配運作。不需要新的檔案 (例如本 RFC 中所述的概念性 subpackages 檔案),也不需要 SWD 變更來解讀該檔案。

這個替代方案遭到拒絕,因為與建議的解決方案相比,這項工作所需的工具和建構基礎架構會增加更多複雜性,特別是工具和建構工作流程。例如:

  • 這個替代做法假設套件雜湊並未嵌入開發人員 CML,或是在元件網址在執行階段解析的程式碼中,例如將這些雜湊新增至 collection。當開發人員 (以某種方式) 表示希望在建構期間將元件網址繫結至特定雜湊時,就需要新增某些機制來變更元件資訊清單和來源中的靜態參照所組成的編譯表示法,以便在網址中插入 ?hash=<blobid> 限定詞。
  • 元件資訊清單中的密封依附元件可啟用執行階段解析,但需要使用獨立機制,才能支援含有依附元件的可重新定位封裝。剖析已修改的資訊清單和程式碼可能不切實際,因此用來判斷修改位置的任何機制,也需要產生一些描述依附元件圖表的額外中繼資料。接著,您可以變更工具,讓其讀取中繼資料,例如封存套件及其所有依附元件,或將套件及其所有雜湊固定的依附元件發布至套件商店。

替代方案:使用特定情境的解析工具

在解析元件或套件時,您可以修改 Resolve 方法,以便在傳遞和傳回 resolution_context 時,加入 context_resolver 要求句柄 (server_end) 做為新的輸入參數。

從概念上來說,PackageResolver 和 Resolver 都可以模擬以下 FIDL 通訊協定模式:

protocol Resolver {
  Resolve(struct {
      component_url string;
      context_resolver server_end:Resolver;
  }) -> (resource struct {
      component Component;
  }) error ResolverError;

  ...
}

在這個替代方案中,後續呼叫必須使用 context_resolver 的用戶端端點,以便代表傳回的 PackageComponent 解析元件參照 (例如在解析 CML 宣告的 children 的元件網址時)。

這種做法遭到拒絕的主要原因,是因為它會增加維護解析器即時連線的複雜度。如果發生某種情況,導致解析器伺服器重新啟動,則可能需要重新建立任何或所有這些管道,這會增加不必要的複雜度。

這個參數應編碼所需資訊,將指定的子套件名稱對應至父項套件的 meta/subpackages 檔案中已固定的套件雜湊,讓 PackageResolverResolver 實作在必要時可在解析器重新啟動後繼續運作。

替代方案:解析內容參照值的 FIDL 類型表示法

我們考慮了幾種呈現解析度情境的方式,最終決定為 PackageResolverContextResolver 都使用一般位元組陣列。這種方法的其中一個優點是,位元組陣列是編碼值的常見類型,且不會排除編碼任意內容 (包括空位元組)。

FIDL 團隊建議使用位元組陣列,因為這個模式沒有其他明確的 FIDL 類型 (以網路瀏覽器的說法來說,這可以視為「Cookie 模式」)。

最終,我們將此類型的限制摘要如下:

  • 這些類型應可進行對應,且不會在 fuchsia.pkg 和 fuchsia.component.resolution 之間引入 API 依附元件。
  • 即使元件管理員重新啟動,也不需要保留內容值,但子包實作不應要求將現有的「非必要」(即可重新啟動) 元件服務套件轉換為「必要」包。(這項限制似乎會禁止使用服務的句柄)。請注意,如果元件管理員重新啟動,所有元件都會 (目前) 重新解析並重新啟動,並使用新的內容值。您不需要在元件管理員重新啟動時保留內容值,更不用說重新啟動或電源週期。
  • 這個內容在概念上可以很小 (大約是套件伺服器名稱和套件雜湊的大小)。也就是說,每個子套件的 VMO 句柄可能會非常昂貴。

替代做法:元件管理服務使用 resolution_context 取得解析器

在解析相對子套件元件網址時,我們考慮將 URI 配置 (例如 fuchsia-pkg) 納入 Component::resolution_context 的一部分,讓元件管理員透過配置查詢,透過解析器註冊表取得 Resolver。這項做法遭到拒絕,因為從理論上來說,這會讓 Resolver 以不同的架構返回內容,用於解析相對的子包元件,這會被視為架構風險。元件管理員會追蹤用來解析元件的 Resolver。元件管理服務一律會使用透過相對網址要求其他元件的元件 Resolver

替代做法:使用相對元件而非相對套件

在 Fuchsia 中,套件是軟體發行單位,也是軟體安裝單位 (可執行程式碼和其他檔案)。雖然 Fuchsia 元件可提供一些較熟悉的用途,但在可能不涉及元件或元件對元件依賴項的套件之間,可能會存在跨套件依賴項 (例如,Fuchsia 殼層套件可能會依賴另一個資產套件,而該套件沒有自己的元件)。此外,在 SDK 中獨立發布預先建構的元件並非方便的方式。

替代方案:使用特殊 fuchsia-subpkg:// 配置參照子包

這份 RFC 建議將 URI 相對路徑 (開頭為路徑區段的 URI,並省略配置和授權前置字元) 解讀為子套件中的資源參照。目前的提案也將子包參照限制為僅限於直接的「子」套件,因此路徑中不得有斜線。(在子包參照中使用斜線是為了日後可能的使用需求而保留)。

我們也考慮了另一個替代方案,也就是在參照子包時要求特殊的配置文件前置字串 (例如 fuchsia-subpkg://),以確保指定字串明確用於子包解析。

此外,使用結構定義前置字 fuchsia-subpkg 似乎會暗示依賴處理 fuchsia-pkg 結構定義的相同解析器,這可能會造成混淆。

URI 標準建議您只使用相對路徑開頭。要求特殊的架構前置字元可能會暗示對特定架構處理常式的依賴性,進而限制通用性。無架構相對路徑已廣泛實作且廣為人知 (例如在 HTML 中,<a href="sub-path/page"> 會隱含地處理為相對位置參照,而不需要特殊架構)。

既有技術與參考資料

標準

已接受的 Fuchsia RFC

可能相關的 Fuchsia RFC 草稿