| 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 E2E 測試。
將 Lacewing 測試直接發布給 SDK 消費者,可直接支援 Fuchsia 的 2024 年藍圖 (即驅動程式一致性測試)。此外,其他需要版本化測試封裝和發布的測試工作 (例如平台期望測試和主機工具相容性測試) 也會受益。
在擴大採用這項測試發布模式前,我們必須確保模式可持續運作,並預先解決潛在的技術債。具體來說,目前下游 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 Experiences 和 Tools 團隊的成員分享。提案也已在 FEC 會議中討論,並獲得正式 RFC 核准。
目標
- 在執行 OOT 時,消除 Lacewing 測試二進位檔的 Python 執行階段相容性問題
- 將 Lacewing Python 測試封裝為單一密封可執行檔
- 支援 C 擴充功能程式庫 (例如 Fuchsia 控制器)
- 支援資料依附元件 (例如 FIDL IR 檔案 - 目前為 Fuchsia 控制器所需)
- 支援在定義 Lacewing 測試的位置進行測試
- 支援 Linux 主機環境
非目標
- 支援 Windows 主機環境
- 支援 Mac 主機環境
提案
我們建議針對 Fuchsia 的即時 Python 應用程式套件組合需求,採用以 Rust 為基礎的自訂方法,因為這種方法可行,且符合上述所有目標。
總覽
執行 Lacewing 測試所需的所有 Python 元件,都會以資料資源的形式嵌入編譯後的 Rust 二進位檔 (透過 Rust 的 include_bytes! 巨集內嵌在二進位檔中)。執行時,Rust 二進位檔會將 Python 內容解壓縮至暫時目錄,建構指令並執行 Python 應用程式。
與主要替代方案 PyInstaller 相比,目前我們偏好採用以 Rust 為基礎的自訂方法,因為這種方法可彈性用於 Fuchsia.git (詳情請參閱下方的 PyInstaller拒絕理由部分)。下表摘要說明這兩種方式的主要優缺點:
自訂 Rust 架構方法的優點:
- 可在 Fuchsia.git 中使用
- 已實作,且在本機和基礎架構環境中都能順利執行
- 採用 Fuchsia 現有 Rust 主機工具鍊的密封性保證
- 不需要額外維護動態連結的 Python 執行階段
- 不需要更新 Fuchsia Controller,即可找到內嵌的 FIDL-IR 檔案
PyInstaller 的優點:
- 內建對所有 Python 應用程式的通用支援
- 更清楚瞭解如何支援非 Linux 主機平台 (例如 Windows)
實作
Lacewing Rust 二進位檔包含 3 個邏輯區段:Lacewing 構件嵌入、構件擷取和測試執行。
嵌入
執行 Lacewing 測試所需的所有元件都會嵌入 Rust 二進位檔,做為單一封存檔。
目錄
內嵌封存檔包含下列項目:
- 靜態連結的 Python 執行階段及其標準程式庫 (Python 模組)
- PYZ 格式的 Lacewing 測試 (與 Fuchsia.git 中使用的
zipapp組合一致) - Fuchsia 控制器共用程式庫
- FIDL IR 檔案 (目前為 Fuchsia 控制器必要檔案)
壓縮
為簡化流程,上述所有內容都會壓縮成單一封存檔。目前的實作方式使用 ZIP 格式,產生約 40 MB 的已移除 Linux-x64 主機二進位檔。後續疊代可能會選擇壓縮比更高的壓縮格式,例如 zstd (在主機驅動的系統測試中,壓縮效能並非那麼重要)。
建構
如要將任意 Lacewing 測試套裝組合,我們會新增 GN 建構自動化功能,動態提供要嵌入的 Lacewing 構件。
我們透過 rustc_embed_files() 和 rustc_binary() GN 範本的組合達成此目標,前者提供測試專用資料資源做為 Rust 程式庫,後者則包含與測試無關的main()邏輯,用於擷取和執行。
擷取和執行
執行時,Rust 二進位檔會先將內嵌資源解壓縮至暫時目錄。然後,系統會參照擷取的內容,建構執行 Lacewing 測試的指令。最後,系統會執行指令,而 Rust 二進位檔會結束。為確保 Hermitian 性質,我們也會在指令中設定 PYTHONPATH 環境變數,確保不會使用周遭的 Python 安裝項目和程式庫。
效能
建構時間 - Lacewing 測試組合只會在 Fuchsia 的一小部分 Lacewing 測試組合上執行,因此 Rust 二進位檔組合的建構負擔可忽略不計。
執行階段 - 由於 Python 應用程式的 E2E 測試性質會一併封裝,因此不會對擷取階段發生的微小啟動負擔造成影響。根據實證基礎架構資料,未經最佳化的建構作業耗時不到 6 秒,相較於這些測試平均約 1 分鐘的執行時間,可說是微不足道。
大小 - 輸出可執行檔大小約為 40 MB,與 PyInstaller 等替代方案類似。不過,嵌入期間使用的壓縮演算法仍有進步空間。
以下是最佳化上述各維度效能的商機,隨著綁定的 Lacewing 測試數量增加,這些商機也會隨之增加:
建構時間:建構「主幹」Rust ELF 二進位檔,其中包含與測試無關的構件 (例如執行階段、stdlibs、C 擴充功能),並將其與測試專用的 Zip 封存檔 (例如 test.pyz) 串連。然後,「幹」ELF 即可透過
ZipArchive自行解壓縮,存取 Zip 內容,就像目前提議的方法一樣;但 Rust 編譯程序只會執行一次,而不是針對每個綁定的 Lacewing 測試執行。執行時間 - 使用 GN 設定,最佳化 Rust 編譯速度。您可以測量擷取時間,並匯出至成效追蹤後端,防止回歸。
大小:與上述建構時間最佳化類似,將 Rust ELF 二進位檔分割為與測試無關的「主幹」和特定測試的 Zip 封存檔,即可將大型「主幹」與明顯較小的特定測試 Zip 封存檔分開發布。這樣一來,我們就不必在每個 Lacewing 測試套件中重複加入「詞幹」,進而節省儲存空間和網路頻寬。詳情請參閱「可執行檔大小」。
人體工學
這項提案改善了 OOT Lacewing 測試執行的 UX,整合人員現在只需要啟動單一測試可執行檔。
回溯相容性
新增密封模式 (Rust 組合) 時,不需要對 Lacewing 測試來源進行任何變更。換句話說,您可以在密封 (./test) 和非密封 (./python test.pyz) 模式下執行 Lacewing 測試,不必變更 Python 原始碼。
測試
Rust 組合程序和產生的密封測試都會在 Fuchsia 基礎架構中進行浸泡測試,確保穩定性。我們會以非密封對應項目為基準,評估測試穩定性;如果沒有明顯的片狀/失敗率差異,我們就會認為基於 Rust 的組合方法已準備好用於正式版 (例如 CQ 和 SDK 發布)。
風險與資源
可執行檔大小
假設每個綁定的 Lacewing 二進位檔的儲存空間約為 40 MB,當我們將分散式測試數量擴大到合理的估計值 (例如 100 項測試為 4 GB) 時,這可能會造成問題。為減輕影響,您可以同時探索下列做法,以隨著綁定的 Lacewing 測試數量,次線性地調整儲存空間和頻寬費用。
在單體式 Rust 應用程式中分配所有測試。
- Lacewing 封存檔的最大元件 (Python 執行階段、標準程式庫和 Fuchsia 控制器擴充功能) 只會發布一次。
- 我們發布的 Python 應用程式越多,這個做法帶來的節省空間效果就越顯著。
獨立發布測試。
- 與上述情況類似,Lacewing 封存檔的最大元件會以單一 Rust 應用程式的形式發布一次。
- 測試 PYZ (500 kB) 會分開發布,並以執行階段引數的形式提供給主要測試應用程式。
縮減 Fuchsia 控制器的大小。
- 由於 Fuchsia 控制器會遷移至靜態 Python 繫結 (上游執行階段邏輯至
fidlgen),因此這項指標會自然下降。- 將移除
fidl_codec.so - 靜態 Python FIDL 繫結的詳細程度較低,因此比 FIDL IR 檔案小
- 將移除
- 由於 Fuchsia 控制器會遷移至靜態 Python 繫結 (上游執行階段邏輯至
如上文所述,探索其他壓縮格式和參數。
跨平台可行性
將 Python 組合解決方案與 Fuchsia 的 Rust 主機工具鍊配對,代表我們將免費取得 Linux x64 支援,但也會受限於目前支援的平台。目前 Linux x64 足以滿足我們的即時需求。不過,如果需要額外的主機平台支援 (例如 Windows),我們就必須與 Fuchsia Rust 和 Fuchsia Build 團隊合作,評估可行性範圍。
撰寫本文時,OOT Lacewing 測試沒有已知的非 Linux-x64 主機平台需求。
有限支援
目前的提案僅支援 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 的終端使用者系統上執行的獨立可執行檔。由於使用簡單且成熟 (自 2015 年起持續發布版本),PyInstaller 是 Python 應用程式封裝領域中較受歡迎的解決方案之一。
在「單一檔案」模式下執行 PyInstaller 時,系統會將啟動時的執行階段與所有應用程式模組,一併封裝至輸出可執行檔。執行輸出可執行檔時,PyInstaller 的開機載入程式會將內嵌封存檔 (包含 Python 解譯器、內建/使用者提供的 Python 模組、內建/使用者提供的共用程式庫和資料檔案) 擷取至暫時目錄,然後在密封環境中執行解譯器。
透過 PyInstaller,我們可以將 Lacewing 測試套裝組合為單一密封的可攜式可執行檔 (Python 執行階段 + 測試模組 + 程式庫模組 + Fuchsia Controller C 擴充功能 + 資料依附元件,例如 FIDL IR),在各種 Linux 主機環境中執行。
很抱歉,靜態連結的 Python 執行階段 (例如 Fuchsia 供應商的 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 測試可執行檔。
由於原型中使用的未提交依附元件,撰寫本文時無法在 Fuchsia 基礎架構中展示 CL 運作情形。不過,一旦 Fuchsia 原始碼提供這些依附元件,我們預期基礎架構支援不會有任何問題。
未提交的依附元件
DL-CPython3:這項功能現在已內建於 Chromium 3PP,但尚未在 Fuchsia.git 中固定。
PyInstaller 程式庫:PyInstaller 及其遞移 Python 依附元件已完成 OSRB 審查,但只能在 Fuchsia.git 中做為預先建構的二進位檔使用。
Fuchsia 控制器
Fuchsia 控制器需要更新,才能在執行階段找到資料依附元件。執行 PyInstaller 可執行檔時,系統會解壓縮其內容,並從暫時目錄執行。有了這個修補程式,Fuchsia Controller 就能辨識 PyInstaller,並在以 PyInstaller 可執行檔的形式執行時,於暫時目錄中尋找 FIDL IR。
Hermeticity
經檢查 PyInstaller 的建構中繼資料 (Analysis.toc) 後,確認其輸出內容是密封的。所有隨附內容均來自 $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 設定,確保這兩個版本以程式輔助方式建構,避免出現差異。
跨平台可行性
PyInstaller 不支援交叉編譯,因此我們需要在各自的目標環境 (例如 Windows、Mac) 中執行 PyInstaller,才能產生主機平台專屬的可執行檔。
舉例來說,如要支援 Windows 終端使用者,我們需要在 Windows 電腦上使用 Windows 版本的 DL-CPython3 執行 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,Rust 語言可提供更完善的安全保障。
不過,在 E2E 測試環境中,這些優勢大多可忽略不計。
PyOxidizer 拒絕原因
最終之所以未選擇 PyOxidizer,是因為它不符合將 C 擴充功能程式庫封裝在單一可執行檔中的目標。使用 PyOxidizer 進行初步探索時,無法將玩具 C 擴充功能與輸出可執行檔捆綁在一起。雖然 PyOxidizer 的可設定程度很高,實驗可能因此出現設定錯誤,但由於 PyOxidizer 相對較新,採用率也較低,因此這類主題的資源不多,我們目前已暫緩進一步探索。