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

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

啟用 FIDL 繫結建構功能,確保與多個 API 級別相容

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

摘要

目前產生 FIDL 繫結時,我們會以樹狀結構中的 LEGACY 和樹狀結構外的編號 API 級別為目標。本文建議將 LEGACY 一般化並淘汰,同時提供一次指定多個 API 級別的方法。這項功能最初只打算在樹狀結構中使用,但我們可能會發現,在樹狀結構外使用這項功能也很有幫助。

背景

根據 FIDL 版本管理的原始設計,您無法移除元素,同時保留該元素的 ABI 支援。舉例來說,如果 CL 將方法標示為 removed=5,也必須刪除該方法的實作項目。這是因為我們在 HEAD 建構 Fuchsia 平台,而方法伺服器繫結在 HEAD 之後就不會存在,因為該值大於 5。

為解決這個問題,我們修訂了 RFC-0083,導入 LEGACY 版本和 legacy 引數。LEGACY 版本與 HEAD 類似,但如果移除的元素標示為 legacy=true,則會重新加入。

提振精神

LEGACY」有幾個問題:

  • 這是一個虛擬版本,本身不含任何資訊。凍結 API 級別 N 的繫結包含 N 的所有 API,但 LEGACY 的繫結包含建構時標示為 legacy=true 的所有項目 (以及 HEAD 中的所有項目)。removed

  • 舊版支援服務會依據各個 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 繫結。這樣就不必使用 legacy=true 標記個別 API。

詳細資料

  • 從 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 版本管理替代語法。為判斷候選元素,replaced 的處理方式與 removed 相同。

  • 如果元素符合下列條件,就會納入繫結:(1) 該元素是候選元素,且 (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 中的「available」屬性,改用字串陣列表示版本。

  3. 將所有現有的 legacy 引數與新系統對齊 (即在最低支援 API 級別之前移除時為 false,在最低支援 API 級別之後移除時為 true)。如果差異過大,請考慮替代方案:覆寫機制

  4. 變更樹狀結構內的平台建構作業,產生以所有支援的 API 級別、開發中的 API 級別和 HEAD 為目標的繫結。

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

  6. 從 fidlc 移除 LEGACY 支援。

  7. 停用 fidlc 錯誤代碼 fi-0182fi-0183

效能

這項提案不會影響成效。

人體工學

這項提案可讓您更輕鬆地正確使用 FIDL 版本管理,因為您不必再擔心 legacy 引數。

回溯相容性

這項提案可協助實現 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,因此不必遷移已淘汰的 API。不過,現在已可透過 LEGACY 達成這個目標。如同花瓣不應在今天指定目標,也不應濫用這項新功能。LEGACY

此外,由於花瓣是透過 SDK 使用 fidlc,而非直接叫用,因此我們可以在 SDK 建構規則中加入限制,藉此減輕這個問題。舉例來說,他們可以斷言目標版本字串不含半形逗號。

替代方案:版本範圍

我們可要求以兩個端點指定範圍,而非允許任意版本的集合。我拒絕這項替代方案的原因如下:

  • 如果我們決定提高 API 級別的發布頻率,可能只會為其中一小部分提供長期支援。這會導致版本集出現間隙,而非範圍。

  • 我們可能想支援以 API 級別 N 為目標的個別舊元件,但不想重新編譯。如果其他所有項目都已從 API 層級 NM 移出,我們可能會出現 {N+1, ..., M} 的落差。

  • 目前為止,我們為平台版本控管建構的任何項目,都未假設支援的 API 級別是連續範圍。舉例來說,version_history.json 包含 API 級別清單,而非範圍。

  • 使用範圍不會讓 fidlc 實作變得更容易。這或許能稍微提高效率,但實際上不太可能造成影響。如果 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 級別的執行階段支援不一致,難以判斷特定建構版本是否完全支援 API 級別。

既有技術和參考資料

Android SDK 可指定 compileSdkVersionminSdkVersion。請參閱「Android API 級別」和 <uses-sdk> 說明文件