RFC-0083:FIDL 版本管理

RFC-0083:FIDL 版本管理
狀態已接受
領域
  • FIDL
說明

提供對 FIDL 元素的版本,並針對特定版本產生繫結。

問題
更小鳥
作者
審查人員
提交日期 (年月分)2021-02-12
審查日期 (年-月-日)2021-04-05

摘要

本文件說明如何使用版本為 FIDL 元素加上註解,並採用一種機制,以指定版本產生繫結。此做法可將 API 演進與採用情況分離,讓程式庫作者能更輕鬆地進行變更,並為開發人員提供穩定性。這樣就能奠定了 FIDL 在 RFC-0002:平台版本管理中的基礎概念。

提振精神

雖然 FIDL 在變更期間提供了許多 ABI 相容性的功能,但在實際演變的 API 中並不容易。在 Fuchsia SDK 中,若要建立與 ABI 相容但與 API 相容的 FIDL 變更,需要經過精心協調的軟性轉換,以避免破壞下游編譯。當某些事情故障時,我們通常必須還原 Fuchsia 中的變更。隨著 SDK 程式庫的使用率增加,進行這些變更的難度也會提高。

FIDL 版本管理功能解決了這個問題,讓 FIDL 程式庫作者和取用端能以自己的步調前進。當程式庫作者新增、移除或修改 API 時,變更會以新的 API 級別發布。針對指定舊版 API 級別的應用程式,除非採用新的 API 級別,否則繫結不會有任何變更。除了提供穩定性以外,由於系統會為每個元件指定目標 API 級別,因此這可讓開發人員一次遷移至新的 API。

圖 1 說明瞭破壞性的 API 變更。如果沒有版本管理,應用程式會破壞應用程式並還原。使用版本管理功能時,應用程式只需固定使用至舊的 API 級別。當然,若嘗試將應用程式的固定 API 級別從 12 提高到 13,也會發生同樣的問題。不過,這些作業能以非同步方式固定,而不還原 Fauchsia 中的原始變更以及暫停專案。

上方有文字說明的 API 演進圖表

圖 1:在 FIDL 版本管理之前 (左側) 和之後 (右側) 的 API 演進

術語

API 級別ABI 修訂版本是在 RFC-0002:平台版本管理中定義,變更如下:

修訂 RFC-0002。Fuchsia API 級別是未簽署的 63 位元整數。換句話說,這個值是 64 位元,但較高位元必須是 0。

FIDL 元素是 FIDL 程式庫中的獨立部分,會影響系統產生的繫結。具體措施包括:

  • FIDL 程式庫本身
  • 常數、列舉、位元、結構、資料表、聯集、通訊協定和服務宣告
  • 別名和新類型 (來自 RFC-0052:類型別名和新類型)
  • 列舉、位元、結構、資料表、聯集和服務的成員 (包括資料表和聯集的 reserved 成員)
  • 通訊協定中的方法和 compose 節錄內容
  • 方法中的要求和回應參數

FIDL 屬性是 FIDL 元素的可修改方面,本身並不是專屬元素。具體措施包括:

  • 屬性
  • strictflexibleresource 修飾符
  • 常數、列舉成員和位元成員的值
  • 結構體成員的預設值
  • 類型限制 (成員、參數和類型別名)
  • 方法種類 (單向、雙向、事件)
  • 方法錯誤語法 (存在和類型)

這樣只會保留非 FIDL 元素或屬性的幾個項目:

  • 個別 .fidl 檔案
  • 匯入其他 FIDL 程式庫
  • 僅限 FIDL 的 using 別名,RFC-0052 會從語言中移除
  • 實驗性 resource_definition 宣告
  • 註解,包括說明文件註解

設計

本文件所描述的設計,是為 FIDL 元素進行版本管理的一般用途。其主要用途是依 API 級別調整 Fuchsia Platform 中的 FIDL 程式庫。

範圍

這項設計採用 FIDL 語言中的版本管理做為概念,為 FIDL 程式庫提供暫時維度。用於指定版本管理屬性的語法和語意,包括這些屬性與 FIDL 的其他部分 (例如父項/子項) 和使用定義關係的方式互動。這會在產生 FIDL 繫結時將版本做為輸入提供方式。

這項設計建議 FIDL 的套件管理員。版本解析演算法、套件發布和依附元件衝突等主題超出範圍。儘管如此,解決這些問題的系統應可重複使用此設計提供的工具。

本提案並不會處理執行階段行為,而是著重於 API,而非 ABI。因此通訊協定演變已超出範圍。這包括「FIDL 伺服器如何支援多個 ABI 修訂版本?」這類問題。未來,FIDL 和元件管理服務可能會採用多種通訊協定演變策略。本提案透過引進 FIDL 中的版本概念來說明通訊協定演變的原理,但其遠由此決定。

能夠在這個設計下呈現轉場效果,並不會影響轉換效果是否安全或相容。相反地,版本化 FIDL 程式庫可代表幾乎任何語法的有效變更順序。

正教

「平台 ID」是一種標籤,可提供版本背景資訊。平台 ID 必須是有效的 FIDL 程式庫名稱元素,即本文撰寫時與規則運算式 [a-z][a-z0-9_]* 相符。

「版本 ID」是介於 1 到 2^63-1 (含首尾) 之間的無正負號 64 位元整數,或等於 2^64-1。後者稱為 HEAD,並以特殊方式處理。

修訂條款 (2022 年 10 月)。為了支援舊版方法,我們會改用 2^64-2 來處理 HEAD,針對 LEGACY 使用 2^64-1。

修訂條款 (2024 年 4 月)。RFC-0246 中重新定義 HEADLEGACY

版本 ID 的順序是依「新舊」關係排序。當「X」 >「Y」時,X 版本比版本 Y 要更新。

平台相關 FIDL 元素的「可用性」是指元素「導入」時的版本,也可以是「淘汰」和「已移除」時的版本。淘汰和移除程序必須比簡介早。1如果同時提供兩者,移除時間就必須比淘汰值還新。

如果 FIDL 元素具有該平台的可用性,則屬於該平台的「版本之下」。在任何平台下推出版本皆可使用versioned

如果平台版本低於或等於元素的簡介,但版本在其淘汰及移除之前仍舊 (如果有的話),則 FIDL 元素「可用」。如果版本低於或等於該元素的淘汰作業,但版本早於移除項目 (如果有的話),此版本已淘汰顯示 表示可用或已淘汰。否則為「缺少」

選擇版本是將版本指派給一組平台。舉例來說,您可以先選取 red 的第 2 版,以及 blueHEAD 版。

如果 FIDL 元素適用於所有平台,則取決於所選版本。如果它適用於所有平台,就已淘汰,且在一或多個平台中已淘汰。否則為「缺少」

語法

供應情形屬性包含下列格式 2,以 Swift 的 available 屬性為靈感:

@available(added=<V>, deprecated=<V>, removed=<V>)

每個 <V> 都是版本 ID。addeddeprecatedremoved 欄位分別代表介紹、淘汰和移除元素。這些都是選用項目,但至少要提供一個。

在程式庫上,必須提供 added 欄位 (選用 deprecatedremoved)。還有一個選填欄位 platform,可指定平台 ID。程式庫中的所有版本 ID 都是指這個平台的版本。例如:

@available(platform="red", added=2)
library colors.red.auth;

省略時,系統會預設為程式庫名稱的第一個元件:

@available(added=HEAD)  // implies platform="blue"
library blue.auth;

透過 deprecated 欄位,使用者可在警告訊息中加入額外的 note 欄位。例如:

@available(added=12, deprecated=34, note="Use X instead")

可用性屬性讓 [Deprecated] 屬性從 RFC-0058:加入 [Deprecated] 屬性已過時。

版本管理元素

FIDL 元素是使用供應情形屬性進行版本管理。每個 FIDL 元素最多能包含一個可用性屬性,而且只能在版本化程式庫中完成此操作。換句話說,如果程式庫中有任何 FIDL 元素已加註,程式庫也必須為程式庫加註。

FIDL 程式庫中的每個檔案都有自己的程式庫宣告,但都代表同一個 FIDL 元素:程式庫。這與 FIDL 樣式指南一致:

將程式庫分割成檔案,並不會對程式庫使用者造成技術影響。...將程式庫分割為多個檔案,盡可能提高可讀性。

因此,程式庫中只有一個程式庫宣告可擁有供應情形屬性。文件註解的限制也相同,因此建議您選擇同一個檔案來指定程式庫的可用性及其文件註解。

版本管理屬性

FIDL 屬性無法直接版本。如要變更屬性,您必須「切換」其所屬的元素。這表示複製元素、移除舊副本,並在同一版本中加入新副本。例如,如要變更版本 12 的繫結:

@available(removed=12)
string:50 info;
@available(added=12)
string:100 info;

您也可以將列舉從 strict 變更為 flexible

@available(removed=12)
strict enum Color { ... };
@available(added=12)
flexible enum Color { ... };

除程式庫外的所有 FIDL 元素皆可切換。因為播映資訊不會重疊,因此名稱衝突不會發生。

本提案未來不會將供應情形屬性直接套用至 FIDL 屬性的語法。如果導入這類語法,它可能只支援 addedremoved,因為沒有任何 deprecated 對所有 FIDL 屬性都合理的解讀。

繼承

FIDL 元素會形成有向非循環圖,子項元素會繼承父項元素的可用性。

頂層宣告會從程式庫繼承。列舉、位元、結構、資料表、聯集和服務的成員會繼承封閉宣告。要求/回應參數會繼承其方法。方法和 compose 權杖繼承自封閉通訊協定。Compose 方法會繼承原始方法和 compose 段落。未使用通訊協定組合時,圖表則會呈現樹狀結構。

如果子元素具有供應情形屬性,則會覆寫沿用的供應情形。這樣一來,該元件不能是多餘或牴觸:簡介版本只能較新的版本,淘汰和移除版本只能回復為較早的版本。

以組合方法來說,如果兩個父項都在同一個平台下版本,則其可用性是指其父項可用性 (最新簡介、最舊的淘汰和最舊的移除) 的交集。如果每個平台是在不同平台中建立版本,則組合方法會沿用兩個不同的功能。在這種情況下,「可用選項取決於特定版本」的定義。在這兩種情況下,淘汰的 note 會從兩個父項合併。

以下是平台內組合的一般情況:

library foo;
protocol Def { @available(added=A, deprecated=B, removed=C) Go(); };
protocol Use { @available(added=D, deprecated=E, removed=F) compose Def; };

原始方法 foo/Def.Go 是在 A 導入,於 B 淘汰,並在 C 中移除。組合方法 foo/Use.Go 是在 max(A,D) 導入,並在 min(B,E) 淘汰,並在 min(C,F) 中移除。這表示所有組合方法均受到 compose 集數的可用性限制,但如果 Defcompose 推出後推出這些類別,或者在 compose 淘汰/移除前淘汰/移除這些方法,部分方法的可用性可能會更大。

使用驗證

某些 FIDL 元素在其中一個「使用」彼此相關。如果結構/資料表/聯集成員或要求/回應參數在類型中發生元素,就會使用 FIDL 元素;如果元素的值出現在其錯誤類型中,就會使用 FIDL 元素;如果元素的值出現在其值中,就會使用 struct 成員;如果元素出現在其預設值中,則會使用 struct 成員。以下列舉部分範例:

const uint32 X = 10;
const uint32 Y = X;  // Y uses X

table Entry {};
protocol Device {};
resource struct Info {
    vector<Entry>:X entries;  // entries uses Entry and X
    request<Device> req;      // req uses Device
    uint32 val = Y;           // val uses Y
    Info? next;               // next uses Info
};

在指定版本的情況下,fidlc 會在以下情況產生錯誤:

  • 當前元素使用缺少的元素;或
  • 可用的元素使用已淘汰的元素。

生命週期語意

指定版本後,如果有可用的 FIDL 元素,系統會照常發出。如果已淘汰,我們會在 JSON IR 中提及這點,繫結中的行為如 RFC-0058:加入 [Deprecated] 屬性中所述。如果沒有,則會從 JSON IR 中省略。

如果 FIDL 元素並非被其他單位使用,使用 @available(removed=<N>) 加上註解就等同於從 .fidl 檔案中刪除該元素,但使用 removed 並不會維持屬性記錄的準確性。這樣做可避免 .fidl 檔案因變更逐漸過大而無法讀取。

HEAD 的用途

HEAD 版本 ID 代表開發的出血邊緣。用戶端可以針對 HEAD 繫結編寫程式,但其應不會穩定。舉例來說,假設您下載了 red.fidl,其中註解中使用的最大版本 ID 為 12 和 HEAD。如果您下載了較新的 red.fidl 副本,可以預期版本 12 的 API 會完全相同,也就是作者並未變更記錄。但 HEAD API 可能完全不同。

這項功能可在採用 FIDL 版本管理時持續運作。依附版本化程式庫的 HEAD 繫結會因版本未版本程式庫的繫結而相同。

此外,這個 API 也能讓協作專案中的 FIDL 變更更加容易。編寫 CL 時,查詢目前版本相當繁瑣且容易競爭,特別是在程式碼審查期間變更的版本。協作者只要改用 HEAD 即可,專案擁有者則可在日後將其替換為特定版本。

舊版支援

修訂條款 (2022 年 10 月)。本節是在接受 RFC 後加入的。

使用 @available(removed=<N>) 移除 API 後,該 API 就不會再出現在為 N 以上版本產生的繫結中。因此很難建構支援多個 API 級別的 Fauchsia 系統映像檔。如果系統映像檔是根據「N-1」繫結建構,就無法針對在 N 新增的方法提供實作。如果是根據「N」繫結建構,則無法提供在「N」移除方法的實作。

為解決這個問題,我們推出名為 LEGACY 的新版本,這個版本與 HEAD 相同,但包含舊版方法。「舊版方法」是標示 @available(removed=<N>, legacy=true) 的方法。這會使用名為 legacy 的新布林引數,該引數預設為 false,且只有在 removed 存在時才允許。例如:

@available(added=1)
library example;

protocol Foo {
    @available(removed=2)  // implies legacy=false
    NotLegacy();

    @available(removed=2, legacy=true)
    Legacy();
};

指定不同版本時,該範例的繫結中包含的方法如下:

目標版本 涵蓋的方法
1 NotLegacyLegacy
2
HEAD
LEGACY Legacy

根據政策規定,移除舊版 Fuchsia 平台中的所有方法時,都必須保留舊版支援。一旦 Fuchsia 平台在方法移除前停止支援所有 API 級別,您就可以放心移除 legacy=true 和方法的實作項目。

當 Fuchsia 平台做為用戶端 (而非伺服器) 時,舊版方法可讓平台繼續針對指定舊版 API 級別的方法呼叫方法。對於未預期的新 API 級別,該方法必須標示為 flexible,以便忽略呼叫。詳情請參閱 RFC-0138:處理不明互動

legacy 引數可用於任何 FIDL 元素,而不只是方法使用。舉例來說,如果您一併移除類型及其使用該類型的方法,那麼該類型也必須標示為 legacy=true。這只是使用驗證 (而非新規則) 的後果。

再舉一個例子,假設在要求中使用的資料表。移除其中一個欄位時,建議您使用 legacy=true,讓伺服器能夠繼續支援設定該欄位的用戶端。另一方面,如果忽略欄位足以保留 ABI,則不需要舊版支援。同樣地,對於回應中使用的資料表,只有在需要設定該欄位來保留舊用戶端的 ABI 時,才需要在移除欄位時使用 legacy=true

由於可用性代表變更,而非移除,因此「替換」元素時不應使用舊版支援。否則會導致錯誤:

protocol Foo {
    @available(removed=2, legacy=true)
    Bar();

    @available(added=2)
    Bar();
}

由於第一個 Bar 會加回 LEGACY,第二個 Bar 從未移除,因此兩者都位於 LEGACY,fidlc 將引發錯誤,例如播映資訊重疊的相同名稱元素已經存在。

JSON IR

為了表示已經在 IR 中的情況,我們新增兩個欄位:

deprecated: <bool>,          // required
deprecation_note: <string>,  // optional

這些範例新增至以下 JSON IR 結構定義定義:

#/definitions/bits
#/definitions/bits-member
#/definitions/const
#/definitions/enum
#/definitions/enum-member
#/definitions/interface
#/definitions/interface-method
#/definitions/service
#/definitions/service-member
#/definitions/struct
#/definitions/struct-member
#/definitions/table
#/definitions/table-member
#/definitions/union
#/definitions/union-member
#/definitions/type_alias

請注意,IR 並不代表程式庫的淘汰。此功能仍會透過繼承以及下一節所述的警告而產生。

指令列介面

如要指定版本,fidlc 會接受 --available <P>:<V>,其中 <P> 是平台 ID,<V> 是版本 ID。您可以針對不同的平台 ID 多次提供這個標記。例如:

fidlc --json out.json --available red:2 --available blue:HEAD
      --files red.fidl --files blue.fidl

如果版本選取項目缺少平台,或有未使用的平台 (相較於採用指定程式庫版本的平台),fidlc 會產生錯誤。如果在選取版本方面有任何程式庫已淘汰/缺少,fidlc 會產生警告/錯誤。3

政策

FIDL 版本管理功能可以在不破壞應用程式的情況下更新 API,但不保證一定會發生。為此,我們針對 Fuchsia Platform 平台採用以下政策:

  • 為所有發生在 HEAD 的新變更加上註解。
  • 請勿變更 FIDL 程式庫的記錄。唯一的例外是刪除舊 FIDL 元素的程序,如下所述。
  • 在移除前淘汰 FIDL 元素,除非切換變更屬性。
  • 淘汰元素時:
    • 請使用 note 欄位向開發人員說明要使用的方式。
    • 在文件註解中編寫 # Deprecation 區段,提供更詳細的說明並告知淘汰時程。
  • 變更 FIDL 屬性時請務必謹慎。舉例來說,將類型從嚴格變更為彈性,或從值變更為資源,可能會對 API 產生重大影響。API 委員會應根據個案情況,判斷這些異動內容。

這些政策的實施方式如下:

  • SDK 中的所有 FIDL 變更仍必須通過 API 委員會核准。
  • fidl-lint 應檢查已淘汰的元素,是否設定 note 欄位,以及文件註解中的 # Deprecation 區段。
  • 日後應該會有一項 CQ 工作依據 FIDL API 摘要強制執行其他政策,包括修改記錄、在移除前淘汰記錄,以及與 API/ABI 不相容的變更。

此外,還有兩個新程序的詳細資料,我們會遵從後續的 RFC:

  • 推出新的 API 級別。這種情況可能會以固定的時間表發生,也就是自上一個 API 級別以來所做的部分或所有變更,都會在新的 API 級別中發布,方法是將 HEAD 替換成新的級別。
  • 刪除舊的 FIDL 元素。經過足夠時間後,您就可以從 .fidl 檔案中刪除標示為已移除的元素。只有在未參照於任何位置的元素才能刪除該元素,因此這項程序可能涉及在固定的排程中,刪除特定 API 級別之前的所有元素。

我們可以建立工具,讓上述兩種程序變得更加簡單,並採用與 Firedl 格式相同的樹狀目錄方法。

實作

這項設計大部分可在 fidlc 中實作。剖析 @available 語法必須在其他 RFC 上變更 FIDL 的註解語法。語意一開始可能會在實驗旗標後方實作。

當 fidlc 編譯程式庫時,即使它會在單一版本中產生 JSON IR,仍應同時驗證所有可能的版本。但不應透過依序產生及檢查每個版本來完成。而應暫時將元素分解為 (名稱、版本範圍) 元組。這項程序與將 NFA 轉換成 DFA 的程序類似。例如:

type MyTable = table {
    @available(added=2)
    1: name string;
    @available(added=HEAD)
    2: age uint32;
};

這種分解方式如下 (使用虛擬語法示範):

type «MyTable, [0,1]»    = table {};
type «MyTable, [2,HEAD)» = table { 1: name «string, [0,HEAD]»; }
type «MyTable, HEAD»     = table { 1: name «string, [0,HEAD]»; 2: age «uint32, [0,HEAD]» };

在發出 IR 之前,fidlc 會修剪宣告內容,僅納入版本選取中要求的項目。

開啟問題。當不同平台版本的 FIDL 程式庫一起編譯時,時間分解方法會很難一般化。由於我們的主要用途 (依 API 級別版本管理 Fuchsia Platform) 不需要這項作業,因此我們可以延後這個問題,並在最初將 fidlc 設為僅允許一個 --available 標記。

您可以將 HEAD 版本 ID 做為內容專屬常數實作,類似於可當做字串和向量長度限制的 MAX 常數

在 fidlc 之外也可執行部分實作作業。首先,fidldoc 必須考量版本舉例來說,如果某個元素已淘汰,說明文件應以醒目方式指出這一點。您也可以提供 API 級別下拉式選單,方便您查看歷來說明文件。其次,Fidlgen 後端需要使用 JSON IR 中的 "deprecated" 欄位。舉例來說,fidlgen_rust 可以將其轉譯為 #[deprecated] Rust 屬性。如需其他語言的範例,請參閱 RFC-0058:加入 [Deprecated] 屬性

SDK 中的程式庫開始使用註解之前,需要將 --available fuchsia:HEAD 新增至 GN 範本,用於建構 FIDL 繫結。這是假設所有樹狀結構內程式碼都會使用 HEAD 繫結。當有 C++ 的平台版本管理提案時,可能需要針對其他版本的 FIDL 繫結建構樹狀結構內程式碼進行測試。

在寵物建構系統中,我們會新增 fuchsia_api_level 宣告並將其連接至 --available 旗標。這需要在先接受及忽略 --available 旗標之前,與 fidlc CLI 變更協調。

效能

這項提案對執行階段效能沒有影響。這會影響 fidlc 執行更多工作的程度,但 FIDL 編譯在 Fuchsia 建構時間從未成為重要因素。

安全性考量

本提案應該對安全性有正面影響,因為版本管理功能可讓您輕鬆遷移至具備較安全屬性的新版 FIDL API。這應該高於支援舊版 ABI 修訂版本增加攻擊面的負面影響。

本提案並未提供根據應用程式目標 ABI 修訂版本隱藏 ABI 的機制,如 RFC-0002 的建議。雖然這可以增強安全性,但最好是將通訊協定演進為綜合 RFC 的一部分。

隱私權注意事項

本提案應該對隱私權有正面影響,因為版本管理功能可讓您輕鬆改用具備較優質隱私權屬性的新版 FIDL API。

測試

我們目前正在使用單元測試和黃金測試的組合來測試 FIDL 工具鍊。單元測試主要用於 fidlc 內部。黃金測試的運作方式是編譯一系列 .fidl 檔案,確保產生的構件 (JSON IR 和所有繫結) 與先前審查的黃金檔案相同。

FIDL 版本設定將採取類似的做法。這個函式會針對 fidlc 中的小型邏輯進行單元測試。例如,當資料表成員的註解表示是在資料表本身之前引入時,系統會執行測試,確保編譯失敗並顯示適當的錯誤訊息。也會使用黃金測試,但不會擴充現有的黃金測試架構。為每個版本產生程式庫的構件會使得黃金檔案變得龐雜,導致我們難以驗證正確性。反之,這項專案會具備專屬的 .fidl 檔案集,每個版本的 JSON IR 差異。您應能輕鬆驗證版本管理的行為是否符合預期。

這不會對平台 API 的實作者造成更難的測試:系統會針對 HEAD 編寫測試,與目前不對舊版 Git 修訂版本執行 FIDL 檔案測試的方式相同。也不會讓 SDK 使用者更難進行測試:他們使用單一版本的 SDK 進行測試,就跟目前使用單一 SDK 版本測試一樣。

說明文件

@available 語法會記錄在 FIDL 語言規格中。有程序推出新的 API 級別後,就會需要更多說明文件。舉例來說,我們需要訓練程式庫作者,以便在新增 API 元素時使用 @available(added=HEAD)。有了適當的工具,您絕不會忘記這樣做的風險。詳情請參閱政策一節

另外,由於供應情形屬性已過時,因此我們必須從「FIDL 屬性」頁面中移除 [Deprecated] 屬性。

請更新 FIDL 來源相容性說明文件,以使用可用性屬性顯示 FIDL 變更,或示範如何在使用版本管理時套用不同類型的 FIDL 差異。本文件也應該說明 FIDL 版本管理與轉換的一般互動方式。使用版本管理功能,變更 FIDL 元素就跟使用樹狀結構外時一樣簡單。這樣應可減少需要某些柔軟轉換。但不會移除所有多步驟轉場效果,只會在協調時移除單一共用時間軸的限制。

缺點、替代項目和未知

採行此提案需要多少費用?

本提案使 FIDL (語言) 和 fidlc 更加複雜。這會讓程式庫作者在進行簡單的安全變更時更加困難,但也讓程式庫作者能更安心地進行其他類型的變更 (例如將成員新增至嚴格的列舉)。

替代方法:使用舊版 SDK

FIDL 版本管理功能可讓應用程式保持固定使用舊的 API 級別,同時持續推出新的 SDK。但為什麼不直接使用舊版 SDK,不需要顯示整個提案?這有幾個原因:

  • 使用最新版的 SDK 時,使用者可以取得所有其他內容 (例如 FIDL 工具鍊) 的最新副本。
  • 目標 API 級別為每個元件指定。為每個元件使用不同的 SDK 是複雜且不實際的做法。

替代選項:變更記錄檔案

單獨的變更記錄可以記錄 FIDL 程式庫的記錄,而非可用性屬性。其中一種方法是從每個 .fidl 檔案回歸原始檔案的文字差異這樣會簡化許多事項,例如無法順利版本 FIDL 屬性。如同建議的設計,一次驗證程式庫的所有版本並不容易。不過,由於這個替代替代方案可排除意外修改記錄的問題,因此可能較不必要。但更難回答 「這個元素何時推出?」這類問題基本上,系統會複製 Git 記錄,主要差別在於,當您建立可下載的 SDK 時,系統會保留歷史記錄。

如果我們日後變更 FIDL 語法,就會很難維護文字差異。變更記錄設計的另一個變化版本,則是定義新格式來記錄 FIDL 程式庫的變更內容,以及發生時的版本。這項設計支援一次驗證所有版本,因為 fidlc 可以讀取變更記錄並產生暫時的 AST,方法與資訊來自屬性時相同。但這需要更多工具。例如,我們希望開發人員可以像現在一樣編輯 .fidl 檔案,並在修訂前執行可附加至變更記錄檔案的工具。

替代選項:個別程式庫版本

另一個替代方式是為每個 FIDL 程式庫建立個別版本。這會導致從 API 級別對應至 SDK 中每個 FIDL 程式庫的版本。舉例來說,API 級別 42 可能代表 fuchsia.auth v1.2、fuchsia.device v5.7 等。

這種做法對關注個別程式庫的人員而言有優點。 每個版本對該程式庫而言都很有意義,您可以預估程式庫從目前的版本號碼演變到多少。相反地,因平台版本而異,如果程式庫有變動,版本之間可能會存在巨大的落差。

但它觸發了許多問題。擁有個別程式庫版本是否代表 SDK 程式庫必須追蹤依附的其他 SDK 程式庫版本?SDK 消費者可以混合或比對不同版本的 SDK 程式庫嗎?無論回答哪一個問題,都會讓 FIDL 版本管理更加複雜。我們如何判斷一組特定版本是否搭配運作?如何避免將同一個程式庫繫結的多個副本同時編譯?如果兩者的答案皆為「否」,則個別程式庫版本管理看似無間接的,因為這會表明,版本管理作業並非發生在程式庫層級。

替代方法:非對稱式淘汰

RFC-0002 狀態位於其生命週期一節中:

元素可能遭到淘汰。如果元件指定舊版 ABI 修訂版本,則在較新的平台版本上執行時,仍可使用該元素。然而,指定較新 API 級別的最終開發人員將無法再使用該元素。

接著說明 FIDL 的影響

如果通訊協定元素 (例如表格中的欄位或通訊協定中的訊息) 在特定 API 級別淘汰,在理想情況下,指定該 API 級別的元件就能接收包含該通訊協定元素的訊息,但想要防止這些元件傳送包含該通訊協定元素的訊息。

FIDL 版本管理功能會造成這項行為脫離,因此納入這裡做為替代方案。防止開發人員在特定 API 級別使用 FIDL 元素,同時允許 Fuchsia 平台中的程式碼在執行階段支援 FIDL 元素並不容易。如先前所述,這種做法的假設有誤,那就是 Fuchsia Platform 一律做為伺服器,且 SDK 取用者一律做為用戶端。在某些情況下,角色會遭到逆向,或甚至模稜兩可。我們可導入 @platform_implemented@user_implemented 等屬性來區分這些內容。這有助於瞭解方法,但對於類型和類型成員 (以下稱為「類型元素」) 的非對稱行為較難解決。

為達成非對稱式淘汰類型的元素,其中一個方法是產生虛設常式,以防止它們使用。舉例來說,已淘汰的資料表欄位可能會在繫結中顯示為 FidlDeprecated 類型的值,並在使用時產生類型檢查錯誤。Fuchsia Platform 中的程式碼可透過新的 fidlgen 旗標 --allow-deprecated 繼續支援已淘汰的元素,這個旗標會在沒有遭淘汰的情況下產生程式碼。但這種方法有兩個問題。 首先,將難以移除在 Fauchsia 中使用已淘汰的元素,因為這些元素不會顯示為已淘汰元素。其次,最終開發人員也能輕鬆使用這個標記。這會解除所需獎勵

這個方法會結合存取新的 API 來執行這類遷移作業,藉此鼓勵開發人員停用已淘汰的介面。具體來說,如要取得新版 API 的存取權,開發人員必須變更目標 API 級別,要求他們遷移任何在該 API 級別中淘汰的介面。

也就是說,透過 --allow-deprecated,開發人員只需使用旗標,即可存取新引入的 API,而不用遷移已淘汰的 API。

類型元素的另一種方法是在執行階段產生錯誤。舉例來說,如果資料表欄位已淘汰,繫結期間如果有欄位存在,繫結在編碼期間可能就會產生錯誤 (但維持解碼作業維持不變)。但執行階段行為超出本提案的範圍

總結來說,不對稱淘汰項目太過複雜,無法納入這個提案。如果不對稱淘汰的好處值得複雜,這些挑戰可能會在日後的 RFC 中解決。

替代文字:完整記錄 IR

在本提案中,只有 JSON IR 之前存在版本資訊。等到 IR 產生後,我們將使用修正版本。這足以產生繫結,但對於可能會想使用版本資訊的 fidldoc 等工具而言較不實用。這些工具並不會透過這些工具剖析 .fidl 檔案,或透過比較多個版本的 JSON IR 來推論生命週期,而是導入新的 JSON IR 模式,其中包含所有記錄和可用性資訊。這與在 IR 中加入供應情形屬性不同之處,因為這意味著包含被標示為已在最新版本中標示為已移除的元素。

這個替代方案有兩個問題。首先,某些 JSON IR 檔案的結構定義和用途應略有不同。設計全新的全新格式也許是更好的做法,但也會出現缺點。 其次,如果不知道 UI Fidldoc 應顯示什麼,要決定這個完整記錄 IR 應該是什麼樣子。例如,對於已新增和移除 resource 修飾符的類型,該類型會顯示什麼資訊?這類問題以及使用的表示法,都是使用單獨的 RFC 來解決。

優先藝術與參考資料

本提案屬於 RFC-0002:平台版本管理中列出的整體計畫。瞭解 RFC 對瞭解此政策背後的背景資訊和動機至關重要。其中「主要圖片與參考資料」一節著重於其他作業系統:Android、Windows 和 macOS/iOS。本節將著重說明其他程式設計語言和 IDL,以及 API 版本管理方法

Swift、Objective-C

Swift 使用與此提案中的屬性相似的 @available 屬性,而 Objective-C 使用了類似的 API_AVAILABLE 屬性。而且僅限於 macOS 和 iOS 等 Apple 平台的硬式編碼清單。他們也可以使用 swift 平台,根據編譯期間使用的 Swift 語言版本來控管可用性。系統會依 semver 語意指定版本,指定版本為一、二或三個數字,並以半形句號分隔。在執行階段檢查平台版本時,這兩種語言都提供類似的語法。

Rust

Rust 會使用穩定性屬性 #[stable]#[unstable]#[rustc_deprecated] 為標準程式庫加上註解。每個不穩定元素都會連結到 GitHub 問題,只有選擇加入對應的 #[feature] 屬性的開發人員才能使用。穩定版屬性代表元素穩定運作的 Rust 版本。不過,這僅適用於說明文件,但無法控管瀏覽權限。

Protobuf、gRPC

通訊協定緩衝區不提供版本管理工具。相反地,與 FIDL 相比,這些元件會更加重視前向和回溯相容性。例如,沒有結構 (僅限與 FIDL 資料表類似的訊息)、沒有嚴格類型 (所有類型都有彈性行為),且列舉並未支援詳盡的比對 (截至 proto3 為止)。

Google Cloud API 會使用通訊協定緩衝區與 gRPC,並提供版本管理相容性的相關指南。版本管理策略是以慣例而非系統內建功能為基礎。API 會將主要版本號碼編碼至 protobuf 套件的結尾,並包含在 URI 路徑中。這樣一來,服務就能一次支援多個主要版本,客戶也能收到回溯相容的更新,也就是無需採取行動進行遷移。


  1. 在實作期間,此規則十分寬鬆,是為了能在適當的情況下引入及淘汰。這樣就能透過「切換」,在任何版本邊界手動分解 FIDL 宣告。

  2. 本文件使用 RFC-0086:更新 RFC-0050: FIDL 屬性語法的語法。

  3. 在實作期間,系統會省略這些規則,以簡化與建構系統的整合作業。根據預設,fidlc 在選取版本時會使用 HEAD,並忽略未使用的平台。對程式庫宣告而言,可用性與繼承無關,因此缺少的程式庫等同於空白程式庫,而且沒有關於淘汰的警告。