RFC-0232:多個 API 級別的 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 版本管理替換語法。為了判斷候選元素,
replaced
與removed
相同。
如果有元素是候選元素,就會包含在繫結中;且 (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 中的「可用」屬性,以便為版本使用字串陣列。根據新系統變更所有現有的
legacy
引數 (亦即false
是在支援的最低 API 級別之前移除,在其後或之後移除則為true
)。如果差異很大,請考慮使用替代方法:覆寫機制。變更樹狀結構內平台版本,產生指定所有支援的 API 級別、開發中 API 級別和
HEAD
的繫結。移除 FIDL 檔案中的所有
legacy
引數。從 fidlc 移除
LEGACY
支援。
效能
這項提案成效不會影響成效。
人體工學
本提案會使 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 中的所有程式碼。如要將這類變更分割成多個步驟,我們可能需要用更精細的方式控制繫結中包含的內容。您有以下幾個選項:
覆寫個別
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 級別。
優先藝術與參考資料
Android SDK 允許指定 compileSdkVersion
和 minSdkVersion
。請參閱 Android API 級別和 <uses-sdk>
說明文件。