RFC-0232:適用於多個 API 級別的 FIDL 繫結 | |
---|---|
狀態 | 已接受 |
區域 |
|
說明 | 啟用 FIDL 繫結功能,以便與多個 API 級別相容 |
問題 | |
Gerrit 變更 | |
作者 | |
審查人員 | |
提交日期 (年-月-日) | 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 monorepo 權限,因此更難將元件從單一資料庫和發布程序中分割。
利害關係人
協助人員: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
;如果是在該級別或之後移除,請使用true
)。如果差異很大,請考慮使用替代方案:覆寫機制。變更樹狀結構內平台版本,產生針對所有支援的 API 級別、開發中的 API 級別和
HEAD
產生繫結。移除 FIDL 檔案中的所有
legacy
引數。從 fidlc 中移除
LEGACY
支援。
成效
這項提案不會影響成效。
人體工學
這項提案可讓 FIDL 版本管理更容易正確使用,因為您不必再擔心 legacy
引數。
回溯相容性
這項提案可移除個別 FIDL 程式庫作者選擇 legacy=true
的負擔,有助於實現 ABI 回溯相容性。這也讓我們聲明的「支援的 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 級別提供長期支援,可能會比較容易。這會導致版本集出現空隙,而非範圍。
我們可能會想支援以 API 級別
N
為目標的個別舊元件,而無須重新編譯。如果其他所有內容都已移出 API 等級N
至M
,我們可能會出現{N+1, ..., M}
的空白。我們目前為平台版本建立的內容,都沒有假設支援的 API 級別是連續範圍。舉例來說,version_history.json 包含 API 級別清單,而非範圍。
使用範圍而非集合,不會讓 fidlc 實作更容易。這可能會讓效能稍微提升,但在實際操作中不太可能有差異。如果 fidlc 效能出現問題,可以優化許多容易取得的效益。
替代做法:覆寫機制
這項提案的一個缺點是,當您停止支援某個 API 級別時,可能很難同時更新 fuchsia.git 中的所有程式碼。為了將這類變更分成多個步驟,我們可能需要更精細的方式來控管綁定項目中包含的內容。您可以採取以下幾種做法:
在個別
fidl
GN 目標中覆寫<target_versions>
。新增
@available
引數unsupported=true
,即使該元素通常會納入,也要排除該元素。這與legacy
類似,但只會暫時使用 (理想情況)。變更
--available
引數,以便接受 JSON 檔案,除了<target_versions>
之外,還可提供要納入或排除的完整元素名稱清單。
我拒絕這個替代方案,因為我們不確定是否需要這項機制。因此,我們應該先嘗試在單一 CL 中進行變更。如果這麼做無法順利進行,我們應該嘗試使用條件式編譯來分階段進行變更,這樣一來,只有在停止支援 API 級別之前,才會納入實作項目。如果這麼做無法解決問題,我們可以重新考慮上述的覆寫機制。
我們也可以透過增加 API 級別的週期來減緩這種情況,這樣一來,每個 API 級別的移除作業就會減少。不過,這會對平台版本控制產生許多其他影響,因此不在本提案的範圍內。
替代做法:讓 legacy
預設為 true
這個替代方案可改善現況。以 false
做為預設值時,如果忘記新增 legacy=true
,可能會導致 ABI 中斷。以 true
為預設值時,如果忘記新增 legacy=false
,只會導致 fidlc 編譯錯誤或繫結中未使用的 API,這並非嚴重問題。
不過,這只是次要變更,無法解決本 RFC 中提出的所有問題。legacy
狀態仍會依 API 控制,導致特定 API 級別的執行階段支援不一致,進而難以判斷特定版本是否完全支援 API 級別。
既有技術與參考資料
Android SDK 可讓您指定 compileSdkVersion
和 minSdkVersion
。請參閱「Android API 級別」和 <uses-sdk>
說明文件。