RFC-0232:多個 API 級別的 FIDL 繫結

RFC-0232:多個 API 級別的 FIDL 繫結
狀態已接受
領域
  • FIDL
說明

啟用建構 FIDL 繫結,以與多個 API 級別相容

問題
更小鳥
作者
審查人員
提交日期 (年月分)2023-09-27
審查日期 (年-月-日)2023 年 10 月 24 日

摘要

今天產生 FIDL 繫結時,我們是在樹狀結構中指定 LEGACY,並在樹狀結構中為編號 API 級別。本文件建議一次指定多個 API 級別,藉此將 LEGACY 一般化並過時。這個做法初期僅適用於樹狀結構,但我們可能會尋找適合在樹狀結構中使用的情況。

背景

在 FIDL 版本管理的原始設計中,無法移除元素,同時仍能保留對 ABI 的支援。舉例來說,如果 CL 將方法標示為 removed=5,則也必須刪除該方法的實作。這是因為我們在 HEAD 中建立了 Fuchsia 平台,而該方法的伺服器繫結大於 5,因此該方法的伺服器繫結不會再存在 HEAD 中。

為解決此問題,我們修訂 RFC-0083 來導入 LEGACY 版本和 legacy 引數。LEGACY 版本與 HEAD 類似,差別在於前者會重新加入標示為 legacy=true 的元素。

提振精神

LEGACY 發生問題:

  • 這是一個虛擬版本,本身沒有任何資訊。凍結 API 級別 N 的繫結包含所有屬於 N 的 API,但 LEGACY 的繫結會包含在建構期間以 legacy=true 標示為 removed 的任何情況 (以及 HEAD 中的所有內容)。

  • 舊版支援視個別 API 而定,會隨著時間變更。因此很難保證平台版本確實支援特定的 API 級別。

  • 加入舊版支援時,無法指定特定 API 級別 (即 HEAD 以外的)。

  • 並僅解決部分的相容性難題,而 Fuchsia 平台是通訊的一端。產品元件之間會說出通訊協定,但實際上並非如此。

  • 它會授予 Fuchsia 單體存放區的權限,使您難以將元件從單體式存放區和發布流程中分割。

相關人員

講師:abarth@google.com

審查人員:hjfreyer@google.com、wilkinsonclay@google.com、ddorwin@google.com

諮詢對象:wez@google.com、sethladd@google.com

社會化:我在撰寫 RFC 之前,與 FIDL 團隊和平台版本管理工作小組討論了這個概念。

設計

我們建議在產生 FIDL 繫結時一次指定多個 API 級別。舉例來說,透過 --available fuchsia:10,15 叫用 fidlc 會指定 API 級別 10 和 15,進而產生合併這兩個層級的元素。如果指定名稱的元素在 10 和 15 底下有不同的定義,我們會使用第 15 級的定義,因為該定義較新。

這會成為 LEGACY 版本。建構 Fuchsia 平台時,我們會指定支援的 API 級別組合,而不是指定 LEGACY 繫結。這樣也不必為個別 API 加上 legacy=true 標示。

詳細說明

  • 從 FIDL 語言中移除 LEGACY 版本以及 @available 屬性的 legacy 引數。

  • 將 fidlc 的 --available 指令列引數語法從 <platform>:<target_version> 變更為 <platform>:<target_versions>,其中 <target_versions> 是以半形逗號分隔的版本清單。例如:

    • --available fuchsia:10
    • --available fuchsia:10,11
    • --available fuchsia:10,20,HEAD
  • <target_versions> 清單必須排序,且不得包含重複。也就是要強調版本會建立線性記錄,之後版本則優先處理。

  • <target_version> 清單決定一組候選元素

    • 如果 <target_versions>{v | v >= A} 相交,標示為 @available(added=A) 的元素會是候選元素。

    • 如果 <target_versions>{v | A <= v < R} 相交,標示為 @available(added=A, removed=R) 的元素即為候選元素。

    • 請注意,這個 RFC 依附於 RFC-0231:FIDL 版本管理替換語法。為了判斷候選元素,replacedremoved 相同。

  • 如果有元素是候選元素,就會包含在繫結中;且 (2) 在所有名稱相同的候選項目中,其 added 版本最大。

  • 如果上述規則在繫結中包含標示為 @available(..., deprecated=D, ...) 的元素,如果 <target_versions>{v | v >= D} 交集,繫結中就會將其視為已淘汰。這不會影響目前產生的程式碼,但日後可能會受到影響 (https://fxbug.dev/42156877)。

  • 和先前一樣,--available 旗標可以用於多個平台一次。這兩項功能 (多個平台和多個目標版本) 之間沒有重要的互動。

  • 和先前一樣,編譯成功或失敗時必須獨立於主要程式庫平台的 --available 旗標之外。(可能依附於其他平台的依附元件,並依附於 --available 旗標)。例如,如果透過 --available fuchsia:15,16 成功編譯,則 --available fuchsia:10,100,HEAD 保證也能成功執行。同樣地,如果前者失敗,後者的錯誤組合一定會失敗。

  • 建構 Fuchsia 平台時,請將 --available fuchsia:LEGACY 替換為 --available fuchsia:<target_versions>,其中 <target_versions> 包含所有執行階段支援的 API 級別、開發中的 API 級別,以及 HEAD

影響

這項設計允許為可指定任何版本組合的有效 FIDL 程式庫產生繫結,無論程式庫如何演變為何。這是一項重要的限制,因為 FIDL 版本設定可以代表任何語法的有效變更。具體來說,Fidlc 允許並存的多個名稱相同的元素,前提是其版本範圍不會重疊。當 <target_versions> 包含多個這類元素時,我們只會加入最新的元素。這支援三種一般演進模式:

  • 生命週期。元素為 added,可能為 removed。指定生命週期中的任何版本時,我們會將其納入繫結。例子:

    @available(added=1, removed=5)
    flexible Method() -> ();
    
  • 替換品。元素為 added,而後續有不同定義的 replaced。從概念上說,這是一個隨時間變化的單一元素,而不是兩個不同的元素。我們假設替換品的設計能夠與原始元素相容,而且只在繫結中加入替換元素。例子:

    @available(added=1, replaced=5)
    flexible Method() -> ();
    @available(added=5)
    flexible Method() -> () error uint32;
    
  • 重複使用名稱:元素成為 removed 後,您就可以在新元素 added 中重複使用元素的名稱。這就像是「取代」,但這兩個元素的概念在概念上不同,且生命週期之間存在落差。我們會假設建議使用較新的元素,而且只將其納入繫結中。例子:

    @available(added=1, removed=5)
    flexible Method();
    @available(added=10)
    flexible Method() -> ();
    

    請注意,以這種方式重複使用元素名稱時,該元素的參照不能涵蓋兩個定義之間的間距。例如,以下內容不會編譯:

    @available(added=1, removed=5)
    type Args = struct {};
    @available(added=10)
    type Args = table {};
    
    @available(added=2)
    protocol Foo {
        Method(Args); // ERROR: 'Method' exists at versions 5 to 10, but 'Args' does not
    };
    

範例

請參考以下 FIDL 程式庫:

@available(added=1)
library foo;

@available(replaced=2)
type E = strict enum { V = 1; }; // E1
@available(added=2)
type E = flexible enum { V = 1; }; // E2

@available(added=3, removed=6)
open protocol P {
    @available(removed=4)
    flexible M() -> (); // M1

    @available(added=5)
    flexible M(table {}) -> (); // M2
};

選取單一版本時,繫結中包含的內容如下:

--available E1 E2 P M1 M2
foo:1 ✔✔
foo:2 ✔✔
foo:3 ✔✔ ✔✔ ✔✔
foo:4 ✔✔ ✔✔
foo:5 ✔✔ ✔✔ ✔✔
foo:6 ✔✔
foo:HEAD ✔✔

以下是選取多個版本時會納入的資料:

--available E1 E2 P M1 M2
foo:1,2 ✔✔
foo:1,HEAD ✔✔
foo:1,3 ✔✔ ✔✔ ✔✔
foo:1,2,3 ✔✔ ✔✔ ✔✔
foo:3,6 ✔✔ ✔✔ ✔✔
foo:3,HEAD ✔✔ ✔✔ ✔✔
foo:2,4,6 ✔✔ ✔✔ ✔✔
foo:1,3,5 ✔✔ ✔✔ ✔✔
foo:1,2,3,4,5,6,HEAD ✔✔ ✔✔ ✔✔

實作

  1. 實作 RFC-0231:FIDL 版本管理取代語法

  2. 在 fidlc 中實作新的 --available 功能。同時變更 JSON IR 中的「可用」屬性,以便為版本使用字串陣列。

  3. 根據新系統變更所有現有的 legacy 引數 (亦即 false 是在支援的最低 API 級別之前移除,在其後或之後移除則為 true)。如果差異很大,請考慮使用替代方法:覆寫機制

  4. 變更樹狀結構內平台版本,產生指定所有支援的 API 級別、開發中 API 級別和 HEAD 的繫結。

  5. 移除 FIDL 檔案中的所有 legacy 引數。

  6. 從 fidlc 移除 LEGACY 支援。

  7. 淘汰 fidlc 錯誤代碼 fi-0182fi-0183

效能

這項提案成效不會影響成效。

人體工學

本提案會使 FIDL 版本管理功能更容易使用,因為不再需要考慮 legacy 引數。

回溯相容性

本提案有助於達到 ABI 回溯相容性,因為 ABI 可以消除從個別 FIDL 程式庫作者選擇 legacy=true 的負擔。另外,這麼做也能更加嚴格遵守我們所述的「支援的 API 級別」,因為這些 API 級別會直接用於產生平台的繫結。(當然,為了確信這些格式受到支援,我們也需要測試)。

安全性考量

本提案對安全性沒有任何影響。

隱私權注意事項

這項提案對隱私權沒有任何影響。

測試

您必須更新以下檔案,才能測試新行為:

  • tools/fidl/fidlc/tests/availability_interleaving_tests.cc
  • tools/fidl/fidlc/tests/decomposition_tests.cc
  • tools/fidl/fidlc/tests/versioning_tests.cc
  • tools/fidl/fidlc/tests/versioning_types_tests.cc

說明文件

您必須更新下列說明文件頁面:

缺點、替代項目和未知

非問題:降低遷移意願

這個提案可視為減少 RFC-0002 中所述平台版本管理的獎勵,因為您可以指定多個層級來存取已淘汰的 API。不過,現在 LEGACY 已實現這一點。就像寵物今天不應鎖定 LEGACY,也不應濫用這項新功能。

此外,由於寵物會透過 SDK 使用 fidlc,而不是直接叫用,因此我們可以透過 SDK 建構規則中的限制來減輕這個情況。例如,他們可以斷言目標版本字串不含半形逗號。

替代版本:版本範圍

我們不必允許使用任意版本的組合,反而需要兩個端點指定的範圍。我基於以下幾個原因拒絕這個替代方案:

  • 如果我們決定增加 API 級別的發布頻率,可能比較簡單的做法,只維持其中一部分的長期支援。這會產生一組有缺口的版本,而非範圍版本。

  • 我們可能想要支援以 API 級別 N 為目標的個別舊元件,而不重新設定關聯。如果所有其他項目皆已從 API 級別 N 移至 M,就可能會發生 {N+1, ..., M} 的差距。

  • 目前為止,我們未針對平台版本管理建構任何項目,並未假設支援多個 API 級別的連續範圍。例如,version_history.json 包含 API 級別清單,而非範圍。

  • 使用範圍而非組合並無法簡化 fidlc 實作。它可能會稍微提高效率,但在實務上不太可能會如此。在執行最佳化作業時,有許多可持續改進效能的理想工具,應該會導致效能出現問題。

替代方法:覆寫機制

本提案的缺點之一,就是在停止支援 API 級別時,很難全面更新 fuchsia.git 中的所有程式碼。如要將這類變更分割成多個步驟,我們可能需要用更精細的方式控制繫結中包含的內容。您有以下幾個選項:

  1. 覆寫個別 fidl GN 目標中的 <target_versions>

  2. 新增 @available 引數 unsupported=true,這會排除該元素 (即使通常會納入該元素)。這與 legacy 類似,但只會暫時使用 (最好)。

  3. 變更 --available 引數以接受 JSON 檔案,除了 <target_versions> 以外,也可提供要包含或排除的完整元素名稱清單。

我拒絕這個替代做法,因為原因不明。我們需要這個機制。 我們應該先嘗試在單一 CL 中進行變更。如果這個方法無效,我們應嘗試使用條件式編譯來暫存變更,這樣只有在停止支援 API 級別之前,才會納入實作項目。如果問題仍無法解決,我們可以重新審視上述覆寫機制。

此外,藉由提高 API 級別的執行頻率,我們也可以降低發生這種現象,進而減少各 API 級別的移除次數。不過,這可能會對平台版本管理造成許多其他影響,但不在本提案的涵蓋範圍內。

替代方法:將 legacy 預設為 true

請參閱 RFC-0233:預設 FIDL 舊版金鑰

這個替代方法可以改善現況。如果將 false 設為預設選項,忘記新增 legacy=true 可能會導致 ABI 中斷。將 true 設為預設選項時,忘記新增 legacy=false 只會導致 fidlc 編譯錯誤或繫結中未使用的 API,因此較不嚴重的問題所在。

但這只是小幅變更,並無法解決此 RFC 中產生的所有問題。legacy 狀態仍會受到控制,導致特定 API 級別的執行階段支援不一致,因而難以判斷特定版本是否完整支援 API 級別。

優先藝術與參考資料

Android SDK 允許指定 compileSdkVersionminSdkVersion。請參閱 Android API 級別<uses-sdk> 說明文件