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 中討論的初始動機用途都與測試相關,但子封裝也很可能適用於其他情境。我們會先提供子套件進行測試,因為我們可以在測試時採取一些捷徑,但這些做法在正式版中是不可接受的。除了排程考量之外,我們的目標是在測試和實際工作環境情境中,全面支援子封裝。

為何現在發行?

目前最常見的用途是在測試情境中,需要使用套件間的依附元件。測試需要在封閉式環境中執行特定元件 (通常是虛擬或模擬元件),以及受測元件。視依附元件而定,並包含依附元件的確切版本,有助於防止全域系統狀態 (即安裝的套件組合) 影響測試的正確性。我們將這項屬性稱為「密封包裝」。

我們會在單一套件中納入所有元件依附元件,以密封方式套件樹狀結構內的測試。不過,目前是依據絕對套件網址。這些測試並非密封封裝,因此平台套件網址會成為隱含 ABI。

舉例來說,Flutter 整合測試目前會依附於 Fuchsia 的 Scenic 元件,方法是透過元件網址 fuchsia-pkg://fuchsia.com/scenic#meta/scenic.cm 宣告依附元件。這項宣告僅表示預期 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_packagemeta/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:相對元件 URL 中所述):
  • 這個片段必須參照與另一個元件 (「對等」元件) 位於相同套件中的元件,且該元件先前已解析。
  • context 值必須參照與對等項相同的套件版本 (相同的套件雜湊)。

注意:使用 context 解決相對資源元件網址 (透過 URI 片段) 會改變相對解析器的目前行為。目前,RelativeResolver 會從父項元件建構絕對套件網址,並附加相對片段部分。但無法保證父項元件和子項元件是從相同套件版本擷取。使用子套件時,不一定能從父項或同層級元件建構絕對套件網址。另一方面,將套件固定至用於解析套件中所有組合元件的特定套件雜湊,可能就是您想要的行為;在這種情況下,使用內容就是一項改進。

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

應將內容值視為 Component Resolver 內部的實作詳細資料,且對用戶端不透明。Resolver「可能」會轉送 resolved_context 值 (從 PackageResolver::Resolve...() 傳回) 做為元件解析度 context 值。(API 不會阻止 Resolver 傳回不同或擴增的內容值,但這可能不是必要做法)。

以下是解決具有子封裝子項元件的流程概念範例 (假設為開機後環境):

  1. 如要從頂層套件 P 載入元件 A,請執行下列操作:

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

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

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

  2. 如要從子封裝 child-package 載入子項元件 B,請使用相對於上方元件 A 的路徑:

    a. 元件管理服務會取得用於解析 Resolvercomponent-a 能力,並呼叫 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 檔案 - 如果持續性 FIDL 套件內容的建議 RFC 獲得核准,子套件檔案應更新為符合取代 contents 的新格式,或使用擴充欄位區分內容項目和子套件項目,合併檔案。
  • 使用摘要統計資料註解 subpackages 檔案項目 -「Persistent FIDL package contents」RFC 會提供機制,在每個子套件到套件雜湊的對應中加入額外資料。建議您加入摘要統計資料,例如內容大小 (包括每個子封裝及其巢狀依附元件的匯總大小)。舉例來說,在擷取套件作業期間,如果載入的套件具有大量依附元件,您可以使用這項功能回報進度。
  • 改善執行階段依附元件解析 - 我們也考慮支援使用絕對路徑 (也就是以斜線「/」為前置字元) 的相對套件網址,以便從提供「目前」套件 (或目前元件) 的「相同套件伺服器」要求頂層套件 (適用於未來的 RFC)。(目前套件伺服器可透過與解析目前套件中的子套件網址相同的方式得知,如本 RFC 所述)。
  • 用於解析及載入資產套件內容的通用 FIDL 服務 - 目前 Fuchsia 軟體載入套件內容的唯一方法,是將元件納入該套件。自訂元件會負責轉送目錄或提供 FIDL 通訊協定,從套件佈建資產。我們正在考慮一項提案,未來將提供更易於存取的 FIDL API,用於載入子套件和/或該套件中的資產。舉例來說,這可用於以資源套件形式發布視覺素材資源或可選取的使用者介面「主題」,不必再額外實作元件中介程序。
  • 子套件延遲載入 - 這項 RFC 建議載入頂層套件時,一併載入該套件的所有子套件 (遞迴)。未來可能推出的擴充功能會允許將特定子封包宣告為「可延遲載入」。這項功能可避免從遠端伺服器將套件匯入儲存空間受限的裝置,直到特定套件有需求為止,藉此節省費用。(詳情請參閱「Eager 套件 loading」)。
  • 子封裝元件的熱重載 - 這項 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 (was pm build)
    • 這個指令必須變更,才能接受其他引數和檔案,或是輸入/輸出修訂版檔案格式,以納入其他子封裝聲明。
  • ffx package export (was pm archive)
    • 這項指令會讀取 contents 檔案,並產生 .far 檔案,其中包含所有參照的 Blob。
    • 對於含有 subpackages 檔案的套件,export 行為會擴展為將這些子套件 (以遞迴方式) 組合到產生的封存檔中 (請參閱組合套件依附元件以供發布
  • 其他 ffx package 指令可能也需要變更,以配合 subpackages (例如 downloadimport)。實作時會調查這些指令的影響。這些變更可能會影響或受到其他預計變更的影響,因為這是 RFC-0124:去中心化產品整合:構件說明和傳播的一部分。

為發布作業組合套件依附元件

子套件的主要用途之一,是支援套件發布,確保套件及其所有直接和間接依附元件可以重新定位為套件組合。相較於開發遞迴遍歷子封裝依附元件的程序,套件組合格式較不重要。

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

本 RFC 建議擴充套件工具,使其能辨識子套件,支援發布含有子套件依附元件的套件,而非讓每個樹狀結構外的使用者負責為密封式封裝實作自訂指令碼。舉例來說,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
    • 孩子 1
      • 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. 儲存僅參照套件伺服器和套件雜湊的 url (例如 fuchsia-pkg://fuchsia.com?hash=123456789),這足以重新解析子套件的內容,但不會保留父項元件或其子套件名稱的任何資訊。
  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 並未核准或禁止這類表示法,但使用斜線分隔符號可能會造成潛在的陷阱。

目前套件名稱可包含單一斜線。後方的內容會視為「變體」,例如 /0,這在 Fuchsia 中很常見。

值得注意的是,/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 方法可以修改為包含 context_resolver 要求控制代碼 (server_end) 做為新的輸入參數,而不是傳遞及傳回 resolution_context

從概念上來說,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 型別 (在網路瀏覽器用語中,可視為「Cookie 模式」),因此 FIDL 團隊建議使用位元組陣列。

最後,我們將這類限制歸納如下:

  • 這些型別應可封送處理,且不會在 fuchsia.pkg 和 fuchsia.component.resolution 之間導入 API 依附元件。
  • 內容值不必在元件管理員重新啟動後繼續存在,但 Subpackages 實作項目不應要求將現有的「非重要」(即可以重新啟動) 套件服務元件轉換為「重要」。(這項限制似乎會排除使用服務控制代碼的情況)。請注意,如果元件管理員重新啟動,所有元件 (目前) 都會重新解析並重新啟動,且使用新的內容值。Context 值不需要在元件管理員重新啟動時保留,更不用說重新開機或電源循環。
  • 從概念上來說,這個內容可以很小 (約為套件伺服器名稱和套件雜湊的大小)。這表示每個子封裝的 VMO 控制代碼可能都非常昂貴。

替代方案:元件管理服務會使用 resolution_context 取得 Resolver

在解析相對子封裝元件網址時,我們考慮將 URI 配置 (例如 fuchsia-pkg) 納入 Component::resolution_context,讓元件管理工具透過配置查閱解析器登錄檔,取得 Resolver。遭到拒絕的原因是,理論上這麼做會允許 Resolver 傳回含有不同配置的內容,以解析相對子封裝元件,這被視為架構風險。而是由元件管理工具追蹤用於解析元件的 Resolver。元件管理服務一律會使用透過相對網址要求其他元件的元件 Resolver

替代方案:使用相對元件而非相對套件

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

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

這項 RFC 建議將 URI 相對路徑 (以路徑區隔開頭,並省略配置和授權前置字元的 URI) 解讀為對子封裝中資源的參照。目前的提案也將子封裝參照限制為僅限直接「子」套件,因此路徑「不得」有斜線。(子套件參照中的斜線保留供日後使用)。

我們也考慮過另一種做法,也就是在參照子封裝時,要求使用特殊架構前置字元 (例如 fuchsia-subpkg://),確保提供的字串明確用於子封裝解析。

此外,使用 fuchsia-subpkg 前置字元似乎表示依附於處理 fuchsia-pkg 配置的相同解析器,這可能會造成混淆。

URI 標準建議只從相對路徑開始。 要求使用特定架構前置字元可能表示依附於特定架構處理常式,因此會限制一般性。無結構定義的相對路徑已廣泛實作,且容易理解 (例如在 HTML 中,<a href="sub-path/page"> 會隱含地做為相對位置參照處理,不需要特殊結構定義)。

既有技術和參考資料

標準

已接受的 Fuchsia RFC

可能相關的 Fuchsia RFC 草案