RFC-0256:針對 Lacewing 測試的 Python 應用程式組合 | |
---|---|
狀態 | 已接受 |
區域 |
|
說明 | 建議使用已編譯的 Rust 主機自解壓縮二進位檔,將 Python Lacewing 測試應用程式打包。 |
Gerrit 變更 | |
作者 | |
審查人員 | |
提交日期 (年-月-日) | 2024-05-22 |
審查日期 (年-月-日) | 2024-08-08 |
摘要
建議將 Lacewing 測試做為編譯的 Rust 主機二進位檔,以滿足 Fuchsia 近期的測試特定 Python 應用程式捆綁和發布需求 (例如驅動程式相容性、CTF)。
本提案並未排除日後探索替代方案的可能性。
提振精神
由於最近在 Fuchsia IDK 中新增了 RTC 驅動程式庫相容性測試,我們現在有了可運作且可承載負載的機制,可為樹狀結構外 (OOT) 執行作業提供版本化的樹狀結構內 Python Lacewing 端對端測試。
將 Lacewing 測試發布給 SDK 使用者的能力,可直接支援 Fuchsia 2024 路線圖 (即 Driver 相容性測試)。此外,其他需要以版本包裝和發布的測試工作 (例如平台預期測試和主機工具相容性測試) 也能從中受益。
在擴大採用這項測試發布模式之前,我們必須確保這項模式可持續運作,並提前解決潛在的技術債務。具體來說,目前要求下游 Lacewing 測試使用者提供自己的 Python 執行階段來執行測試,容易發生錯誤。因此,這項提案會完全移除這項要求,以解決這個缺點。
相關人員
協助人員:
- abarth@google.com
審查者:
- abarth@google.com
- chaselatta@google.com
- hjfreyer@google.com
- keir@google.com
- tmandry@google.com
- tonymd@google.com
諮詢:
- awdavies@google.com
- crjohns@google.com
- cpu@google.com
- jamesr@google.com
社會化:
我們透過 Google 文件與 FEC、Pigweed、Toolchain、SDK 體驗和工具團隊的成員分享這項提案。我們也在 FEC 會議中討論了這項提案,並支持正式的 RFC 批准程序。
目標
- 在 Lacewing 測試二進位檔執行 OOT 時,消除 Python 執行階段相容性問題
- 將 Lacewing Python 測試封裝為單一密封可執行檔
- 支援 C 擴充功能程式庫 (例如 Fuchsia Controller)
- 支援資料依附元件 (例如 FIDL IR 檔案 - 目前由 Fuchsia 控制器所需)
- 支援在定義 Lacewing 測試時將其捆綁
- 支援 Linux 主機環境
非目標
- 支援 Windows 主機環境
- 支援 Mac 主機環境
提案
我們建議使用以 Rust 為基礎的自訂方法,滿足 Fuchsia 的即時 Python 應用程式捆綁需求,因為這種方法既可行,也符合所有目標。
總覽
執行 Lacewing 測試所需的所有 Python 元件都會以資料資源的形式嵌入已編譯的 Rust 二進位檔 (透過 Rust 的 include_bytes!
巨集在二進位檔中內嵌)。執行時,Rust 二進位檔會將 Python 內容擷取至暫時目錄,建構指令,然後執行 Python 應用程式。
與領先的替代方案 PyInstaller 相比,我們目前偏好採用自訂的 Rust 方法,因為這類方法可靈活地用於 Fuchsia.git (詳情請參閱 PyInstaller 的拒絕理由部分)。以下摘要說明這兩種方法的主要取捨:
以自訂 Rust 為基礎的方法的優點:
- 可在 Fuchsia.git 中使用
- 已實作,並在本機和基礎架構環境中成功執行
- 採用 Fuchsia 現有 Rust 主機工具鍊的密封性保證
- 不需要額外維護動態連結的 Python 執行階段
- 無須更新 Fuchsia 控制器即可定位嵌入式 FIDL-IR 檔案
PyInstaller 的優點:
- 內建所有 Python 應用程式的通用支援
- 支援非 Linux 主機平台 (例如 Windows) 的視線更佳
實作
Lacewing Rust 二進位檔包含 3 個邏輯區段:Lacewing 構件嵌入、構件擷取和測試執行。
嵌入
執行 Lacewing 測試所需的所有元件都會以單一封存檔的形式嵌入 Rust 二進位檔。
目錄
嵌入式封存檔案包含下列項目:
- 靜態連結的 Python 執行階段及其標準程式庫 (Python 模組)
- 以 PYZ 格式呈現的 Lacewing 測試 (與 Fuchsia.git 中使用的
zipapp
捆綁相符) - Fuchsia 控制器共用程式庫
- FIDL IR 檔案 (目前為 Fuchsia Controller 所需)
壓縮
為求簡單,上述所有內容都會壓縮為單一封存檔案。目前的實作方式採用 ZIP
格式,可產生大約 40 MB 的經過精簡的 linux-x64 主機二進位檔。日後的迭代作業可能會選擇壓縮率較高的壓縮格式,例如 zstd
(在主機驅動的系統測試中,壓縮效能不如重要)。
建構
為了將任意 Lacewing 測試套件打包,我們會新增 GN 建構自動化功能,以便動態提供 Lacewing 構件供嵌入。
我們是透過結合 rustc_embed_files() 和 rustc_binary() GN 範本來達成這項目標,前者會以 Rust 程式庫的形式提供測試專屬資料資源,而後者則包含與測試無關的 main()
邏輯,用於擷取和執行。
擷取及執行
執行時,Rust 二進位檔會先在暫存目錄中解開內嵌的資源。接著,系統會參照已擷取的內容,建立執行 Lacewing 測試的指令。最後,指令會執行,並且 Rust 二進位檔會結束。為了確保 hermiticity,我們也會在指令中設定 PYTHONPATH
環境變數,確保不會使用環境 Python 安裝和程式庫。
成效
建構時間:Lacewing 測試的綁定作業只會在 Fuchsia 的 Lacewing 測試組合中執行一小部分,因此 Rust 二進位檔綁定作業的建構額外負擔可忽略不計。
執行時間:由於 Python 應用程式已綁定 E2E 測試性質,因此在擷取階段發生的輕微啟動負擔,對這些應用程式而言並非敏感問題。實證基礎架構資料顯示,未經最佳化的版本為 <6 秒,但這些測試的平均執行時間約為 1 分鐘,因此這項差異可以忽略不計。
大小:輸出可執行檔大小約為 40 MB,與 PyInstaller 等替代方案相似。不過,在嵌入期間使用的壓縮演算法仍有改善空間。
以下是可改善上述各維度效能的機會,隨著綁定的 Lacewing 測試數量增加,您可以探索這些機會:
建構時間:建構「主體」Rust ELF 二進位檔,其中包含與測試無關的構件 (例如執行階段、標準庫、C 擴充功能),並將其與測試專屬的 ZIP 封存檔 (例如 test.pyz) 連結。接著,「stem」ELF 就可以透過
ZipArchive
自行解壓縮,以便存取其 Zip 內容,就像目前建議的方法一樣;只是 Rust 編譯程序只會執行一次,而非針對每個捆綁的 Lacewing 測試執行。執行時間:使用 GN 設定來改善 Rust 編譯速度。您可以評估擷取時間,並將結果匯出至效能追蹤後端,以防回歸。
大小:與上述建構時間最佳化相同,將 Rust ELF 二進位檔拆分為與測試無關的「主體」和測試專屬的 Zip 封存檔,我們就能將大型「主體」與較小的測試專屬 Zip 封存檔分開發布。這樣一來,我們就能節省儲存空間和網路頻寬,因為在每個捆綁的 Lacewing 測試中,都會重複加入「stem」詳情請參閱「可執行檔大小」。
人體工學
這項提案可改善 OOT Lacewing 測試執行作業的使用者體驗,整合者現在只需啟動單一測試可執行檔即可。
回溯相容性
新增密封模式 (Rust 捆綁) 時,無須變更 Lacewing 測試來源。換句話說,Lacewing 測試可以在密封 (./test
) 和非密封 (./python test.pyz
) 模式下執行,而無須變更 Python 原始碼。
測試
1) Rust 封裝程序和 2) 產生的密封測試,兩者的穩定性都會在 Fuchsia 基礎架構中進行浸潤測試。我們會將測試穩定性與非密封的對應項目進行基準測試;如果在異常/失敗率方面沒有明顯差異,我們會認為以 Rust 為基礎的捆綁方法已可供實際使用 (例如 CQ 和 SDK 發布)。
風險與資源
執行檔大小
假設每個 Lacewing 二進位檔的儲存空間占用量約為 40 MB,如果我們將分散測試的數量調整為合理的預估值 (例如 100 個測試需要 4 GB),這可能會造成疑慮。為降低成本,您可以同時探索下列項目,以便根據捆綁的 Lacewing 測試數量,以亞線性方式調整儲存空間和頻寬成本。
在單體 Rust 應用程式中分發所有測試。
- Lacewing 封存檔的最大元件 (Python 執行階段、標準程式庫和 Fuchsia Controller 擴充功能) 會分發一次。
- 這個做法可節省的大小會隨著我們分發的 OOT Python 應用程式數量而增加。
獨立發布測試。
- 與上述情況類似,Lacewing 封存檔中最大的元件會在單一 Rust 應用程式中分發一次。
- 測試 PYZ (500kB) 會個別分發,並以執行階段引數的形式提供給主要測試應用程式。
縮減 Fuchsia Controller 的大小。
- 由於 Fuchsia Controller 會遷移至靜態 Python 繫結 (上游執行階段邏輯至
fidlgen
),因此這項作業會自然減少。- 將移除
fidl_codec.so
- 靜態 Python FIDL 繫結會比 FIDL IR 檔案更精簡,因此檔案大小也會比較小
- 將移除
- 由於 Fuchsia Controller 會遷移至靜態 Python 繫結 (上游執行階段邏輯至
如上文所述,請試試其他壓縮格式和參數。
跨平台可行性
將 Python 套件組合解決方案與 Fuchsia 的 Rust 主機工具鍊結合,可讓我們免費取得 Linux x64 支援,但也限制我們只能使用目前支援的平台。目前,Linux x64 已足以滿足我們當前的需要。不過,如果需要支援其他主機平台 (例如 Windows),我們就需要與 Fuchsia Rust 和 Fuchsia Build 團隊合作,以便評估可行性。
在撰寫本文時,我們尚未發現任何非 Linux-x64 主機平台的 OOT Lacewing 測試需求。
有限支援
目前的提案僅限於支援 Lacewing Python 應用程式。不過,如果有需要,這種做法可以合理地擴充,以支援一般 Python 應用程式,而且比起探索替代方案 (例如 PyInstaller/PyOxidizer),這種做法所需的努力程度要小得多。
- Windows 支援
- 位元可複製性
- 不要在幕後啟動 Python
安全性考量
在 Fuchsia 的 SDK 中發布 Rust 主機二進位檔是經過完善的程序 (例如 ffx
),因此在 Lacewing 測試中採用相同做法不會帶來額外的安全性風險。
隱私權注意事項
無
說明文件
無
替代方案
PyInstaller
我們特意將這個大篇幅的 PyInstaller 相關內容納入,以供後人參考。以下發現可協助我們決定今天採用自訂的 Rust 方法,並為日後的任何努力提供背景資訊。
PyInstaller 拒絕原因
由於 PyInstaller 的授權,因此只能在 Fuchsia.git 中使用,做為 Fuchsia 開放原始碼授權政策「開發目標」部分下預先建構的構件。不過,PyInstaller 的設計是用來做為來源執行,因此不容易套件成預先建構的項目。我們目前沒有動力解決這個非簡單的問題,因為我們有一個可用的 Rust 解決方案,可樹狀結構內使用,並滿足我們當前的需要。
總覽
PyInstaller 是開放原始碼的第三方 Python 程式庫,可將 Python 應用程式及其依附元件打包成獨立可執行檔,在未安裝 Python 的最終使用者系統上執行。由於 UIt 易於使用且成熟度高 (自 2015 年以來有許多活躍版本),因此是 Python 應用程式封裝領域中較熱門的解決方案之一。
當 PyInstaller 以「one-file」模式執行時,會將啟動時使用的執行階段,連同所有應用程式模組,打包至輸出執行檔。執行輸出可執行檔時,PyInstaller 的啟動載入器會將內嵌封存檔 (包含 Python 轉譯器、內建/使用者提供的 Python 模組、內建/使用者提供的共用程式庫和資料檔案) 擷取至暫時目錄,然後在密封的環境中執行轉譯器。
有了 PyInstaller,我們就能將 Lacewing 測試整合成單一密封可移植的可執行檔 (Python 執行階段 + 測試模組 + 程式庫模組 + Fuchsia Controller C 擴充功能 + 資料依附元件,例如 FIDL IR),以便在各種 Linux 主機環境中執行。
很遺憾,Fuchsia 的供應商 Python 等靜態連結 Python 執行階段,並不相容於 PyInstaller (相關問題:1、2)。解決方案是在 Chromium 的 3PP 基礎架構 (第三方套件) 中,建構相同 CPython 執行階段的動態連結版本。建構完成後,動態連結的版本 (從此以 DL-CPython3 表示) 即可在 Fuchsia.git 中固定為預先建構的版本,以滿足 Fuchsia 的 Python 應用程式封裝需求。
由於用於建構 DL-CPython3 的原始碼與 Fuchsia 中已使用的靜態連結版本 (從此以後稱為 ST-CPython3) 相同,因此 DL-CPython3 不需要額外的授權核准。
可運作的原型
這個 Fuchsia.git CL 會透過 Fuchsia 的 GN 建構系統,透過 PyInstaller 封裝 Lacewing 測試的可行性。我們使用原型 CL 確認,在 GN 時間點在來源中執行 PyInstaller,即可產生密封的 Lacewing Python 測試可執行檔。
由於原型設計中使用了未提交的依附元件,因此在撰寫本文時,無法展示 CL 在 Fuchsia 基礎架構中運作的情形。不過,我們不認為在 Fuchsia 原始碼中提供這些依附元件後,基礎架構支援會遇到任何問題。
未提交的依附元件
DL-CPython3:此項目目前已在 Chromium 3PP 中建構,但尚未在 Fuchsia.git 中固定。
PyInstaller 程式庫:PyInstaller 及其間接的 Python 相依元件已完成 OSRB 審查,但只能在 Fuchsia.git 中以預先建構的二進位檔形式使用。
Fuchsia 控制器
您必須更新 Fuchsia Controller,才能在執行階段找出資料依附元件。執行 PyInstaller 可執行檔時,系統會解壓縮其內容,並從暫存目錄執行。有了這個修正程式,Fuchsia Controller 就會支援 PyInstaller,並在以 PyInstaller 可執行檔執行時,在暫時目錄中尋找 FIDL IR。
Hermeticity
經檢查 PyInstaller 的建構中繼資料 (Analysis.toc) 後,我們確認 PyInstaller 的輸出內容是密封的。其中所有內含的內容皆來自 $FUCHSIA_DIR
,也就是說,不會包含任何環境系統共用程式庫。
此外,在 PyInstaller 輸出內容上執行 ldd
會顯示一組共用程式庫依附元件,這些依附元件在現代 Linux 發行版本中普遍且可靠地相容。
~/fuchsia$ ldd dist/soft_reboot_test_fc
linux-vdso.so.1
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6
/lib64/ld-linux-x86-64.so.2
為避免密封性隨著時間流逝而減弱,您可以新增建構時檢查,確認 PyInstaller 可執行檔的 ldd
輸出內容是否存在於許可清單中。
大小
在原型設計中,如果不考慮大小最佳化,透過 PyInstaller 封裝的軟重開 Lacewing 測試大小約為 43 MB (供您參考,PYZ 版本約為 400 KB)。
我們發現了許多尚未探索的機會,可縮減 PyInstaller Lacewing 測試的大小:
- 執行 PyInstaller 時,請使用 UPX 壓縮。
- 排除未使用的內建擴充功能模組。
執行階段管理和維護
Fuchsia 團隊需要同時維護 DL-CPython3 (用於版本化 Lacewing 測試封裝) 和 ST-CPython3 (用於其他所有項目),並確保這兩個版本在功能上相同,且會同步更新 (例如共用相同的 CIPD 版本標記)。這需要與 Google 內部 PEEP 軟體部署團隊合作,以便簡化 3PP 設定,讓 2 個版本以程式碼防止差異。
跨平台可行性
PyInstaller 不支援跨平台編譯,因此我們需要在各自的目標環境 (例如 Windows、Mac) 中執行 PyInstaller,產生主機平台專屬的執行檔。
舉例來說,為了支援 Windows 使用者,我們需要在 Windows 機器上使用 DL-CPython3 的 Windows 版本,執行 PyInstaller。在撰寫本文時,Fuchsia 並沒有這類以 Windows 為基礎的建構環境,因此需要設定範圍,才能在建構機隊中新增支援。至於 Windows 版的 DL-CPython3,Chromium 3PP 已支援為多個平台 (包括 Windows) 建構 ST-CPython3,因此我們應該可以輕鬆擴充對 Linux 以外平台的支援。
PyOxidizer
PyOxidizer 是另一個吸引人的一般 Python 應用程式封裝選項,相較於 PyInstaller,它具有以下多項優點:
- 密封性 - PyOxidizer 的輸出內容是編譯的二進位檔,而 PyInstaller 的輸出內容是需要在執行階段解壓縮至 Python 位元 (例如內建 Python 擴充功能) 的封存檔。
- 速度:PyOxidizer 的輸出內容不需要解壓縮即可執行,因此啟動時間比 PyInstaller 的輸出內容快。
- 安全性 - PyOxidizer 是以 Rust 編寫,這項語言提供比 PyInstaller 的 Python 和 C 更完善的安全性保證。
不過,這些優點在 E2E 測試情境中幾乎可以忽略不計。
PyOxidizer 拒絕原因
最終,我們並未選擇 PyOxidizer,因為該工具無法滿足目標,無法在單一可執行檔中綁定 C 擴充功能程式庫。使用 PyOxidizer 的粗略探索無法在輸出可執行檔中內含玩具 C 擴充功能。由於 PyOxidizer 具有高度可設定性,因此實驗可能會有設定錯誤,但由於 PyOxidizer 相對較新穎且採用率較低,因此這類主題的資源較少,因此我們目前已擱置進一步探索。