RFC-0153:Fuchsia 自訂 Ninja

RFC-0153:Fuchsia 的 Ninja 自訂設定
狀態已接受
區域
  • 建構
說明

建議使用 Fuchsia 專用的 Ninja 建構工具自訂版本,加快建構速度、改善狀態回報和可用性。

問題
Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2022-01-22
審查日期 (年-月-日)2022-03-17

摘要

本 RFC 建議使用 Fuchsia 平台建構作業採用的暫時自訂版開放原始碼 Ninja 工具,整合多個現有的第三方修補程式,大幅提升效能和可用性。由於動機一節中說明的各種原因,上游維護人員目前還無法接受這些修補程式。

具體來說,這麼做可讓您:

  • 最佳化 Ninja 使用的排程演算法,顯著縮短總建構時間。

  • 改善狀態報告和記錄,並解決經常在 CI 建構中觸發的嚴重可用性問題

  • 提升 Ninja 的回應速度和一般效能,但不改變其行為。例如,將特定 Ninja 作業的速度提升 22 倍。

  • 如上所述,由於 IDE 繫結可在短時間內重新產生,因此可大幅加快建構系統與 IDE (例如 Visual Studio Code 和 Vim) 之間的整合速度,並提供優質的互動式程式碼編輯體驗。

如要自訂,請在 https://fuchsia.googlesource.com/third_party/ninja 上維護自訂的 Git 分支,並使用嚴謹的分支策略管理,密切追蹤上游,並將其表示為近期上游記錄頂端的一系列小修補程式。

由於我們計畫協助上游維護人員接受感興趣的提取要求,這個實驗分支的狀況將在 2022 年第 3 季修訂,但這些要求取決於許多外部因素,超出本 RFC 的範圍。不過,預計屆時就不再需要自訂分支版本。

提振精神

Ninja 建構工具是 Google 的 Evan Martin 在 Chrome 1 團隊工作時,以實驗性質開發的工具。這項實驗獲得成功,成為非常實用的工具,並納入官方 Chrome 建構版本,且開放原始碼。隨後,許多廣泛部署的其他建構設定工具或系統也迅速採用,包括:

  • GN 建構工具,目前用於 Chrome、Fuchsia 和 Pigweed 專案。
  • Android 專案使用的 Blueprint 和 Kati 工具。
  • CMake 建構產生器,支援 Ninja 做為後端。
  • 許多 Linux 開放原始碼專案使用的 Meson 建構系統。
  • 開發人員可使用 CMake 或 GN 建構 LLVM,包括為 LLVM 貢獻心力的 Fuchsia 團隊成員。

Ninja 目前已在全球各地有數千名開發人員使用,並以 GitHub 上的開放原始碼專案形式維護。Ninja 的維護人員會盡力確保專案維持原先目標,也就是小巧、簡單、可靠且極具可攜性。

由於多種原因,上游 Ninja 專案的進度仍緩慢,有大量有趣的提取要求已等待審查數月 (甚至數年)。我們已私下聯絡目前的維護人員,對方也承認很遺憾,這主要是因為沒有時間適當審查及測試非微不足道的變更,而這也是迴歸測試套件狀態的後果,目前迴歸測試套件非常基本,在許多情況下需要手動測試提取要求。此外,該維護人員也會感謝您協助在 GitHub CI 中加速測試套件的運作,以利並加快 Ninja 的維護和演進。舉例來說,只要能確保程式碼在舊版發行套件 (即 Centos 7、Debian 8、Ubuntu 18.04 和 OSX 10.12) 上使用預設工具鍊編譯及正常運作,並透過適當的持續整合驗證強制執行,他就不反對將程式碼集從 C++03 切換至 C++11。

很遺憾,目前有大量類似先前所述的阻礙問題,解決所有問題需要大量工作,Fuchsia 建構團隊目前沒有能力完成。

這項 RFC 提供暫時解決這項限制的技術提案,但我們強烈希望很快就能投入適當的人力,與上游維護人員合作解決這個問題。這類人數可能來自 Fuchsia 專案,或是與 Google 其他團隊共同努力的成果,這些團隊對 Ninja 未來的發展和改良表示感興趣。不過,這類工作的資金不在本 RFC 的範圍內。

在此期間,Ninja 的自訂分支版本可讓您挑選影響最大的提取要求,為 Fuchsia 開發人員帶來最大效益。為確保這個分支盡可能接近上游,系統會強制執行嚴格的分支策略,確保 Fuchsia 分支一律能以一系列修補程式的形式,在近期上游版本上表示。

請注意,這種情況與自訂 Android Ninja 分支截然不同,因為自訂分支建立後,已大幅偏離上游,且缺少 Fuchsia 建構作業所需的功能。

最有趣的提取要求如下,這些要求促成了這項 RFC:

使用更完善的排程演算法加快建構速度

有三項待處理的提取要求可望大幅縮短建構時間。這兩者都會根據不同條件,修改 Ninja 擷取要啟動的新指令的方式:

  • PR 2019:使用建構記錄估算指令的持續時間,然後為要啟動的指令指派優先順序。作者表示,這樣一來,大型建構專案的建構時間可從 20 分鐘縮短至 15 分鐘!

  • PR 1949:限制產生新指令,以免達到負載限制。這樣可避免產生過多爭奪相同 CPU 資源的程序,導致建構機器過載。作者宣稱在特定條件下,測試建構時間可從 22 分鐘縮短至 15 分鐘。

  • PR 1140:為 Ninja 新增 GNU Make jobserver 支援,讓 Ninja 和執行的指令協調 CPU 處理程序的分配情形。這項功能來自 Ninja 的 Kitware 分支。請注意,這與先前的 PR 相似。作者未發布任何時間資訊,且由於需要叫用的程式明確支援這項功能,因此實用性較低。

修正 Ninja 的建構輸出內容,以處理長指令。

在建構期間,Ninja 只會列印已完成指令的狀態行。實際上,如果執行時間較長的指令導致建構作業停滯,則:

  • 如果指令逾時 (如 CI 機器人在執行時間很長的 Rust 連結指令時發生),輸出內容或 Ninja 記錄檔中完全不會顯示實際過期的目標/指令!

  • 在終端機上,單行狀態似乎凍結,且仍會顯示最後完成的目標名稱,這會造成混淆 (許多開發人員會認為這是造成延遲的根本原因)。如要查看實際情況,唯一的方法是在另一個終端機中使用「ps」,但很少人知道這一點。

第一點是 Fuchsia 建構版本的問題,必須修改 Ninja 才能解決。第二個是困擾使用者多年的可用性問題,以致於有數個以不同方式修正此問題的提取要求:

這是因為 Ninja 的輸出內容是以文字為基礎,格式相當有限,而且如果發生錯誤,還會包含任意指令輸出內容。這會導致各種細微問題,並讓輸出內容難以可靠地剖析

事實上,目前的行為是在 2015 年刻意導入,用來解決 Ninja 的建構輸出內容無法由機器剖析的問題。因此,上游認為輸出格式的任何變更都非常危險。

不過,在 Fuchsia 的環境中,由於剖析 Ninja 輸出的指令碼是由 Fuchsia 基礎架構團隊控管,因此如果能徹底解決問題,對輸出格式進行小幅變更也是合理的做法。

序列化狀態更新,方便篩選記錄

Android 自訂功能還有一個有趣之處,就是可解決輸出限制問題,將狀態更新以結構化二進位資料串流的形式,傳送至外部「前端」程式。

因此 Android 團隊可以儲存多個輸出串流,並使用不同層級的記錄,將錯誤訊息收集到個別檔案中,以及產生可透過 chrome://tracing 載入的準確建構追蹤記錄。

對於 Fuchsia 建構作業,應該可以從 Android 分支版本擷取該功能,以獲得相同優勢。

請注意,這項提案是以提取要求形式提出,但經過一些長時間討論後遭到拒絕,因為上游認為解決輸出剖析問題的更好方法是將 Ninja 變成程式庫,但考量到程式碼基底的狀態,這項工作相當龐大 (有人嘗試過,但實驗性 PR 需要 120 次提交)。

改善開發人員體驗

Android 分支版本已大幅加快 Ninja 輸入檔案的剖析速度 (最多可快 22 倍),方法是使用執行緒剖析輸入內容的分割區、在適當的權杖界線判斷,並在最後一趟合併。

這是因為 Android 建構工具產生了約 1.2 GiB 的 Ninja 建構計畫,在執行任何作業前,需要 12 秒才能剖析 (在具有熱檔案系統快取的強大工作站上)。

很遺憾,Fuchsia 建構也面臨類似情況,目前產生約 800 MiB 的 Ninja 建構計畫,剖析時間最長可達 10 秒,導致增量建構體驗令人困擾。Fuchsia 建構團隊認為這些改善項目相當重要,應整合至建構作業使用的 Ninja 版本。

編寫程式碼時,編譯器錯誤訊息和警告是開發人員的第一道回饋。對於靜態語言而言更是如此,而對於 Rust 等新語言來說,這種情況也越來越常見,因為這些語言的設計目的,就是在編譯時檢查各種條件。因此,取得意見回饋的時間對開發人員的工作效率至關重要。開發人員每等待編譯器輸出結果一秒,就多一秒不確定程式碼是否正確,或可能因其他事物而分心。對於仍在學習語言的程式設計人員來說,快速回饋尤其重要。

最有效率的環境會在檔案儲存後,立即在開發環境中提供意見回饋。這種「流程」的感覺非常重要,因此 Fuchsia 開發人員已建立工具,讓他們可以使用完全不同的建構系統 (Cargo),快速取得意見回饋並進行測試週期。儘管這項工具不受支援,且經常發生問題,部分開發人員仍在使用。

在 Fuchsia 中,Ninja 會在每次叫用時,花費最多 10 秒的時間剖析自己的建構檔案 (build.ninja 檔案和包含的其他檔案)。這代表我們提供意見回饋的速度下限非常高,至少比我們目前透過貨運提供的速度慢 25 倍。將剖析時間縮短 25 到 100 倍,再加上本季推出的其他工具工作流程,我們就能將 Fuchsia 的程式碼編寫體驗從「慢如蝸牛」提升至「快速且反應靈敏」。結果是開發週期加快、工程師更開心,以及軟體品質提升。

下方螢幕截圖顯示目前在 IDE 中變更程式碼及查看編譯錯誤的畫面。

螢幕截圖:IDE 回饋速度緩慢

開發人員意見回饋延遲,幾乎完全是因為 Ninja 剖析檔案所花費的時間。請參閱下方的 IDE 體驗示範,瞭解修補 Ninja 後可實現的功能。

螢幕截圖:IDE 快速提供意見回饋

同樣地,fx setup-go 的建立目的,是為了在本地開發期間專門使用 Go 建構系統,因為使用 Ninja 進行累加建構作業太慢了。

請注意,Android 團隊曾在 2019 年提議採用這項功能,但當時上游維護人員認為 C++11 尚未達到可接受的程度,因此拒絕了這項提議。自此之後,時間已過,如果能強制執行動機部分所述的保證,或許可以考慮進行這類切換。

改善使用者介面

以下 PR 受到 Buck 建構系統的啟發,用於在互動式終端機中實作類似表格的狀態輸出 (請參閱動畫範例)。

PR 已由作者捨棄,但可以重新設定基準,在本地建構 Fuchsia 時提供非常優良的 UI 改進項目。

利害關係人

協助人員:

FEC 已指派 pascallouis@google.com,負責引導這項 RFC 完成 RFC 程序。

審查者:

shayba@google.com、Build maruel@google.com、Fuchsia Platform EngProd abarth@google.com、Platform Lead

積極參與先前有關這個主題對話的其他團隊成員:

brettw@google.com (Fuchsia Tools 貢獻者)。 fangism@google.com,建構 haowei@google.com,Fuchsia 適用的 LLVM jayzhuang@google.com,建構 olivernewman@google.com,Fuchsia CI phosek@google.com,Fuchsia Toolchain

已諮詢:

fangism@google.com、Build jayzhuang@google.com、Build tmandry@google.com、Rust on Fuchsia rudymathu@google.com、Fuchsia EngProd

社交:

這個想法最初是由 Fuchsia 團隊的 tmandry 提出,然後在 Google 內部電子郵件討論串中進行討論,現在則是在公開論壇中提出,以供進一步討論和核准。

設計

Fuchsia 控制的 Git 存放區會用於分叉上游 Ninja master 分支,以便管理包含 Fuchsia 專屬自訂項目的分支,同時追蹤上游 origin/master。分支的 OWNERS 會審查修補程式,並根據下列情況逐一考量:

  • 提供的福利
  • 修補程式的可維護性
  • 與上游的差異,會影響可維護性。

以及擁有者認為合適的其他條件。

按照標準做法,本機修改清單會記錄在 FUCHSIA.readme 檔案中。

該分支機構的維護者是 Fuchsia 建構團隊,負責 Fuchsia 建構系統及其正確性、可維護性和效能,並對此負責。

本 RFC 的目的是確保這個分支不會分歧,以致難以整合上游變更。為達成此目標,請套用下列策略:

讓 Fuchsia 分支版本與上游版本保持一致的策略

Fuchsia Ninja 分支應受到規範,也就是說,為了盡可能貼近上游,我們會定期建立「usptream-sync」分支,將變更內容表示為一系列乾淨的修補程式,並置於最近的上游版本之上。以下幾張圖片有助於瞭解此處的含義。

建立自訂 Git 分支時,通常會在現有的上游版本上建立新分支,並新增修訂版本。下圖說明「上游」記錄分支為「紫紅色」分支的情況,並在這些記錄上新增 3 次提交:

upstream  ___U1__U2___
                      \
fuchsia                \__F1__F2__F3

提交內容會由上游專案的維護人員新增至該專案。上游和紫紅色歷史記錄現在已分歧,如下所示:

upstream  ___U1__U2______U3__U4__U5
                      \
fuchsia                \__F1__F2__F3

如要將上游變更併入 Fuchsia 分支,最簡單的方法是執行合併作業,這可能會顯示需要手動解決的提交項目衝突,如下所示:

upstream  ___U1__U2______U3__U4___U5___
                      \                \
fuchsia                \__F1__F2__F3____\F4__

其中 F4 表示合併提交。現在「fuchsia」分支已包含上游的所有改良項目,但無法再以一系列修補程式的形式,在上游的最新版本 (即 U5) 上表示。

為達成這個目標,您可以避免將上游直接合併至 Fuchsia 分支。請改為建立紫紅色分支的副本,並以 U5 為基礎重新設定,解決過程中的任何衝突 (這可以先在本地完成 git rebase 作業)。我們將這個分支稱為 upstream-sync-U5,如下所示:

upstream  ___U1__U2______U3__U4__U5
                      \            \
upstream-sync-U5       \            \__F1'__F2'__F3'
                        \
fuchsia                  \__F1__F2__F3

此時 fuchsiaupstream-sync-U5 在功能上應等同 (測試會強制執行此操作),除非必須解決某些衝突,否則新提交的 F1' 至 F3' 內容甚至可能與 F1 至 F3 相同。

現在可以將後者合併至前者 (只要從 upstream-sync-U5 取得變更,即可解決任何衝突),如下所示:

upstream  ___U1__U2______U3__U4__U5
                      \            \
upstream-sync-U5       \            \__F1'__F2'__F3'
                        \                           \
fuchsia                  \__F1__F2__F3_______________\F4

之後與其他上游變更同步時,可以重複此步驟,如下所示:

upstream  ___U1__U2______U3__U4__U5__________________U6__U7
                      \            \                       \
upstream-sync-U5       \            \__F1'__F2'__F3'        \
                        \                           \        \
upstream-sync-U7         \                           \        \__F1"__F2"__F3"__F5"
                          \                           \                            \
fuchsia                    \__F1__F2__F3_______________\F4__F5______________________\F6

每個新的 upstream-sync-XXX 分支版本都會以一系列新的修補程式表示自訂狀態,並以近期上游版本為基礎。這樣一來,就能更輕鬆瞭解 Fuchsia 的變更,並視需要將變更傳回上游。

Fuchsia 分支的維護人員應盡可能建立上游同步分支。

實作

這個分支機構會維護在 Git-on-Borg 執行個體 (https://fuchsia.googlesource.com/third_party/ninja,已存在) 上,並使用公開的 Gerrit 執行個體審查及接受修補程式。

用於建構 Fuchsia 平台建構作業的 Ninja 預先建構二進位檔的 LUCI 食譜,會相應切換來源 GIT 網址,因為目前指向上游 GitHub 存放區的鏡像。

效能

這項 RFC 的主要動機是縮短 Fuchsia 平台建構時間,並大幅縮短工具回應時間,進而提升開發人員體驗。

人體工學

縮短開發人員收到意見回饋的時間,有助於提升開發人員的生產力,並減少環境切換。

延遲時間縮短後,Fuchsia 建構系統也能支援新的用途,例如在 IDE 中回報依附於產生檔案的原始碼編譯時期錯誤。包括使用 FIDL 的程式碼,以及所有 Rust 和 Go 原始碼。

回溯相容性

樹內工具可依賴 Fuchsia 分支 Ninja 的 CLI 公開的新功能。任何要在 Fuchsia 樹狀結構外使用的工具,都不得假設存在 Fuchsia 的 Ninja 分支。

不過,這些 Fuchsia 專屬功能應僅限於嚴格的最低限度,因為能夠回溯至上游是本 RFC 的重要目標。

舉例來說,在某些用途中,可行的替代方案是編寫離線工具,直接剖析 Ninja 建構計畫,並對其執行運算 (如 https://fuchsia-review.googlesource.com/c/fuchsia/+/644561)。

安全性考量

這項變更對安全性不會造成重大影響。Ninja 已經能夠在開發人員的系統上執行任意指令。Ninja 的任何安全漏洞都可能來自上游,而我們的分叉會讓 Fuchsia 工程師審查每項變更,這比現狀有所改善。

我們使用 Google 的標準原始碼工具套件,可避免發生任何原始碼供應鏈問題。

隱私權注意事項

這項提案不應以任何方式影響隱私權。

測試

與目前情況相同,提交至 Fuchsia 的 Ninja 分支版本 (包括上游同步合併) 的任何變更,都會執行 Ninja 測試套件。變更內容必須符合 Ninja 的標準測試做法,包括單元測試。

說明文件

Fuchsia git 分支的頂端會新增標準 README.fuchsia 檔案,說明分支目前狀態與上游的差異。

其中會包含這份 RFC 的連結,說明本文所述的分支管理策略。

缺點、替代方案和未知事項

這項提案的主要缺點是,您必須盡可能頻繁地執行上游同步分支,才能讓分支盡量接近上游。這類作業可能會觸發一或多個「重設衝突」,需要由 Fuchsia 分支的維護人員手動解決。

請注意,如要大幅簡化這些修正作業,最好的方法是盡可能將 Fuchsia 分支中的變更分解成多個小修補程式 (每個修補程式都會產生可完整測試的來源樹狀結構)。

我們曾考慮使用 Android Ninja 分支做為起點,但兩者差異極大。如果只是將最近的上游變更合併到其中,就必須解決大量衝突 (例如,他們將所有內容切換至 C++17,並完全移除 C++03 支援程式碼),而且很難重新設定上游頂端的乾淨修補程式集。

從上游開始,並在頂端重新設定幾個 Android 專屬功能,似乎是更好的解決方案,可在幾週內提供結果。

此外,我們也曾多次嘗試取得與上游修補程式相同的優點,但未變更上游 Ninja,結果都遠遠不及。最值得一提的是,經過多年的調查對 GN 的一系列變更,我們得以縮短 Ninja 無作業建構時間。但我們很失望地發現,成效只略有提升

請注意,Google 的多個不同團隊都在使用 Ninja,而且有些人表示有興趣以協調一致的方式管理公司整體的 Ninja 演進,或是協助上游維護人員。這項 RFC 不會阻止上述任何事項,但其優先事項是先快速為 Fuchsia 平台帶來好處。

最後,Fuchsia 現在有 Bazel SDK,並已展示 Fuchsia 元件的 Bazel 建構作業 (請參閱 RFC-0139)。從長遠來看,Fuchsia 應考慮以 Ninja 以外的工具做為平台建構後端,並探索可能需要多年才能完成的遷移作業。為符合本 RFC 的目的,我們認為這類遷移作業不在範圍內。