| RFC-0232:多個 API 級別的 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 |
✔︎ | ✔︎ | ✔︎ |
實作
在 fidlc 中實作新的
--available功能。此外,請變更 JSON IR 中的「available」屬性,改用字串陣列表示版本。將所有現有的
legacy引數與新系統對齊 (即在最低支援 API 級別之前移除時為false,在最低支援 API 級別之後移除時為true)。如果差異過大,請考慮替代方案:覆寫機制。變更樹狀結構內的平台建構作業,產生以所有支援的 API 級別、開發中的 API 級別和
HEAD為目標的繫結。移除 FIDL 檔案中的所有
legacy引數。從 fidlc 移除
LEGACY支援。
效能
這項提案不會影響成效。
人體工學
這項提案可讓您更輕鬆地正確使用 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 層級N到M移出,我們可能會出現{N+1, ..., M}的落差。目前為止,我們為平台版本控管建構的任何項目,都未假設支援的 API 級別是連續範圍。舉例來說,version_history.json 包含 API 級別清單,而非範圍。
使用範圍不會讓 fidlc 實作變得更容易。這或許能稍微提高效率,但實際上不太可能造成影響。如果 FIDLC 效能成為問題,還有許多簡單的優化方式。
替代方案:覆寫機制
這項提案的缺點之一是,捨棄 API 級別的支援時,可能難以在 fuchsia.git 中以原子方式更新所有程式碼。如要將這類變更分成多個步驟,我們可能需要更精細地控管繫結中包含的內容。你可以採取以下幾種做法:
在個別
fidlGN 目標中覆寫<target_versions>。新增
@available引數unsupported=true,即使元素通常會納入,也會排除該元素。這與legacy類似,但只會暫時使用 (理想情況)。變更
--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 可指定 compileSdkVersion 和 minSdkVersion。請參閱「Android API 級別」和 <uses-sdk> 說明文件。