RFC-0119:系統判定為有害的路徑系統

RFC-0119:系統絕對路徑有害
狀態已接受
區域
  • 建構
說明

盡可能在所有位置採用相對路徑的政策。

問題
Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2021-06-20
審查日期 (年-月-日)2021-07-28

摘要

  1. 在多個列舉的執行個體中,制定偏好相對路徑或來源絕對路徑的政策,而非系統絕對路徑,並著眼於未來的用途。
  2. 改用自動化方式強制執行這項政策,建立回歸停止機制。
  3. 清除先前使用的系統絕對路徑。

背景

如果讀者已熟悉這個主題,可以選擇略過

路徑

定義

讀者應熟悉路徑的概念。

以下定義適用於本文:

  • 系統絕對路徑:以本機檔案系統為根目錄的路徑。通常以 / 前置字串表示。
  • 來源絕對路徑或專案絕對路徑:相對於來源樹狀結構或專案結帳根目錄的路徑。通常以 // 前置字串表示。
  • CWD 相對路徑:相對於目前工作目錄的路徑。

以下我們將系統絕對路徑稱為絕對路徑,其他路徑則稱為相對路徑,因為這些路徑是以相對於其他路徑的方式表示。

Fuchsia 建構系統中的路徑

建構系統會使用路徑參照動作的輸入和輸出檔案。 Fuchsia 建構系統使用 GN 定義建構圖。已建立最佳做法,建議您在 GN 中表示路徑時,以其他根目錄 (例如根建構目錄) 或來源根目錄為相對路徑。

生成程式碼中的路徑

程式碼可能會基於各種原因參照路徑。因為絕對路徑無法移植,所以簽入的原始碼不能參照絕對路徑,否則在 Commit Queue (CQ) 電腦上會沒有意義,而且會遭到拒絕。即使沒有遭到拒絕,當其他工程師簽出相同來源時,相同的絕對路徑在他們的電腦上也會沒有意義。不過,在特定機器上產生且未簽入的程式碼可能包含絕對路徑,但仍可運作 (成功建構和/或執行)。

Fuchsia 會使用許多工具產生原始碼。舉例來說,FIDL 使用 fidlc,而 Banjo 使用類似的工具。

提振精神

在建構系統、工具叫用、產生的原始碼和其他構件中,相較於絕對路徑,偏好使用相對路徑的原因有很多。

可攜式構件

如上所述,相對路徑可攜帶,因為它們與雙方可同意的參考點相關,例如 Fuchsia 來源結帳的根目錄或建構輸出目錄的根目錄。在許多情況下,路徑不僅最好可攜式,而且是必要條件。

分散式建構的可攜式構件

分配建構動作是指將建構動作的輸入內容 (例如將 C/C++ 檔案編譯成物件檔案) 傳送至遠端伺服器執行。遠端伺服器可能會代表用戶端執行動作,並傳回結果。遠端伺服器通常會依據內容快取略過整個動作。

分配建構動作有許多好處,但不在本文的討論範圍內。

由於用戶端和伺服器可能無法就絕對路徑達成共識,因此指定要發布的呼叫詳細資料時,以及在任何上傳構件的內容中,可能都需要使用相對路徑,因為這些路徑會互相參照。對於依賴快取的分散式建構系統來說,更是如此,因為叫用詳細資料會做為快取金鑰的一部分。即使允許使用絕對路徑,也可能導致快取機制失效,因為兩個用戶端可能會簽出相同的原始碼,但傳送至伺服器的要求絕對路徑不同。

建構路徑或產生的程式碼中若有絕對路徑,先前曾導致分散式建構發生問題。舉例來說,Fuchsia 開發人員可能會使用 Goma 分散 C/C++ 建構動作。先前導入變更時,Fuchsia 的 Goma 使用者曾發生服務中斷問題,因為這些變更透過 C/C++ include 目錄使用絕對路徑。在某些情況下,發布的建構作業會失敗,並強制使用較慢的本機備援。在其他情況下,分散式建構作業會成功,但無法命中快取,導致後端負載增加一個數量級,進而引發連鎖性故障。

發布建構動作時,工具調用中的絕對路徑也會導致類似的失敗模式。我們先前發現,在發布動作前檢查這類路徑,並以建構動作失敗和實用錯誤的形式拒絕,是很有幫助的做法。

其他執行個體中,絕對路徑會導致建構正確性問題。

管道的可攜式構件

分配動作時,有時會需要為不同工作建立遠端伺服器管道。舉例來說,有些機器可能更適合執行建構作業,有些則更適合執行建構的測試。有時,動作會以分叉和合併的圖表表示,例如建構一組測試,然後分叉到多部機器,每部機器執行測試的資料分割,然後合併結果。

在先前的案例中,路徑是在用戶端和伺服器之間交換,但在這個案例中,路徑是在管道中的不同伺服器之間交換。儘管交易所性質不同,但基於相同原因,相對路徑仍是首選。

絕對路徑可能會導致管道中斷。舉例來說,在產生測試涵蓋率的管道中,如果先前階段產生的涵蓋率報告包含原始碼的絕對路徑,導致涵蓋率報告產生管道的後續階段在不同伺服器上執行時無法解析,就會發生管道中斷的情況。產生涵蓋範圍對應檔案的工具使用絕對路徑,我們變更了路徑,將路徑相對化至指定基準。

使用另一種程式碼插樁時,也發生了類似的損壞情形,因為偵錯資訊檔案中曾使用絕對路徑,在用於將相對 PC 偏移量解析為原始碼行的記錄中。

可攜式構件,用於快取

系統可能會快取建構輸出內容,以加快後續建構作業 (又稱增量建構) 的速度。這類快取通常會保留在本機,例如開發人員的工作站或建構伺服器的特定執行個體。理論上,只要網路容量足夠,且沒有安全和隱私權方面的疑慮,不同建構工作站 (工作站和伺服器) 之間也可以交換建構快取。

Fuchsia 目前不會在不同機器之間重複使用建構快取,因為已知部分建構輸出內容含有絕對構件。Fuchsia 開發人員和 Fuchsia 分散式建構工具最多只會使用先前執行的建構作業中,自己的本地化快取。這會錯失大幅提升工程效率和最佳化的機會。

可重現的構件

在構件中使用絕對路徑會導致我們無法達成可重現的建構作業

目前 Fuchsia 的目標並非可重現的建構作業。不過,瞭解可重現建構作業的優點很有意思,因為這可能是日後所需的屬性,而使用構件中的絕對路徑會導致我們無法實現可重現性。

另請注意,還有其他造成構件無法重現的來源 (最常見的是時間戳記),但這些來源不在本 RFC 的範圍內。

最少工作

Fuchsia 目前會在裝置上執行測試,方法是產生完整的系統映像檔,然後鋪平裝置。日後我們可能會想加快這個程序,例如只將上次更新後變更的 Blob 推送至測試裝置。預計要測試的大多數變更只會影響少量 Blob,相較於基本變更,這個作業方法可大幅加快測試裝置的啟動速度。

如果絕對路徑洩漏到構件中,則在要測試的不同變更之間,可能會比絕對需要更多無效的 Blob。

樹狀結構外的 Fuchsia 建構作業

上文說明瞭絕對路徑導致 Fuchsia 發生的一些問題,以及絕對路徑如何阻礙 Fuchsia 發展和改善。解決絕對路徑問題的急迫性取決於歷史脈絡。舉例來說,Fuchsia 過去並未運用遞增建構或快取,因此專案和相關人員學會容忍缺點,導致 Fuchsia 無法採用更多遞增建構和快取。

如果 Fuchsia 成功,其他專案就會使用 Fuchsia 的程式碼和構件,並為 Fuchsia 開發。可以合理推斷,至少部分專案會期望建構規則和工具系統能更貼近不同需求,而非 Fuchsia 專案的需求。舉例來說,部分客戶的作業規模可能需要漸進式建構快取也是必要功能。如果 Fuchsia 阻礙了這些屬性的實現,Fuchsia 開發人員和其他客戶就會面臨採用障礙。

分散式信任

如果建構作業的所有構件都能重現,建構系統就能擁有新的屬性。舉例來說,可重現的建構作業可做為分散式替代方案,取代加密信任鏈結,用來驗證發布的二進位檔是否完整。不信任的當事人只要嘗試從相同來源和建構系統重現這些二進位檔,即可稽核這些二進位檔。如果無法重現二進位檔,可能表示遭到惡意竄改。

如果構件中使用絕對路徑,不信任的當事人就永遠無法重現相同的結果。

便利性

使用相對路徑時,排解問題會比較容易。通常會期望保持一致性,因此可以比較成功和不成功作業之間的路徑,找出任何有意義的差異。

如果只在本機工作,絕對路徑會非常方便。舉例來說,您可以從工具呼叫複製絕對路徑,並在不同的本機殼層環境中使用,保證可正常運作,因為這類路徑不會受到目前工作目錄的影響。此外,任何兩個絕對路徑和正規化路徑 (例如 ... 部分已解析、連結已追蹤等) 都可以透過字串身分識別檢查是否等效。由於任何路徑都可以設為絕對路徑並正規化,且這類轉換是等冪的,因此這項功能可為本機環境中的路徑提供簡單的等效性檢查。不過,如果使用者覺得這樣比較方便,可以對絕對路徑執行這項轉換,但由於從相對路徑轉換為絕對路徑和/或正規化形式是破壞性作業,因此無法反向執行。因此,建議使用相對形式,以涵蓋所有用途。

設計

政策

我們會將記錄在案的 GN 最佳做法提升為一般政策,並廣泛套用,不只適用於 BUILD.gn 檔案。具體而言,我們建議採取以下做法:

  1. 建構系統以指令列引數形式傳遞給工具的路徑,應以工具叫用的目前工作目錄為基準 (在 GN/Ninja 的情況下,這會表示為 root_build_dir)。
  2. 在建構時產生的檔案路徑應與相同的根建構目錄相對。例如:產生的原始碼、套件資訊清單、depfiles
  3. 在執行階段產生的路徑應與專案來源根目錄相對。例如:當機時的檔案資訊、測試涵蓋範圍報告。

違規處置

我們將推出新工具,針對工具調用和構件中絕對路徑的存在情形,對 Fuchsia 建構作業進行清理。這些工具將在 CQ 中執行,以防止回歸。

清除

上述工具會提供允許清單,其中會列出所有現有的政策違規事項。系統會啟動清理作業,將許可清單大小縮減為零。正常情況下,迴歸測試不會加入許可清單。

實作

執行工具的運作方式實作細節,並未達到 RFC 的等級。不過,為了滿足好奇的讀者,我們在下方提供一些概念草圖。

清除 Ninja 檔案

執行 GN 會產生說明建構圖的 build.ninja 檔案。這張圖表的說明包含所有工具呼叫,包括要呼叫的工具路徑,以及以引數形式傳遞至這些工具的路徑。使用的其他檔案會參照 depfile

這些檔案可透過 strings 處理,產生可篩選的權杖,判斷是否為絕對路徑。舉例來說,這個簡單的掃描器可以實作為主機測試,並視所有建構變數而定。

清除建構作業參照的檔案

除了清除 Ninja 檔案外,我們也可以將指定為建構動作輸入或輸出的任何檔案,進行權杖化和清除作業。假設建構作業是密封的,我們就能從 Ninja 圖表或 depfile 探索所有這類檔案。

檢查是否有絕對路徑的字串

我們可以掃描建構輸出目錄 (out/) 下的所有檔案、產生字串,並檢查是否有任何絕對路徑,然後發出錯誤。這並非萬無一失的防護措施,而是一道簡單的額外防線。

拒絕動作追蹤器中的絕對路徑

我們已有一項工具可包裝 GN 動作,且已使用該工具拒絕 depfile 中的絕對路徑。我們可以進一步擴展這項機制。

請注意,我們目前只使用動作追蹤器包裝自訂動作,這些動作是所有建構動作的子集。我們這麼做是基於上述相同的效能考量。從這個角度來看,或許進一步依賴動作追蹤並非健全的全面策略。

使絕對路徑失效

如要確保結帳程序中的所有相對路徑都能正常運作,同時完全停用絕對路徑,簡單的做法是產生 Ninja,然後將結帳目錄移至其他位置 (或直接重新命名),再進行建構。

$ fx gen
$ mv $FUCHSIA_DIR ${FUCHSIA_DIR}_renamed
$ fx build

如果任何建構呼叫將結帳程序下的路徑參照為絕對路徑,建構作業就會失敗。

這種做法非常容易實作、可攜性高,且不會造成效能負擔。缺點包括:如果發生中斷,錯誤訊息會讓新手感到困惑,而且仍有可能在產生的構件中洩漏絕對路徑 (例如 depfile、srcgen、偵錯資訊)。

執行階段環境的變更

另一種做法是變更建構作業的執行階段環境,使絕對路徑無效或無害。舉例來說,某些專案會使用 chroot 等概念,在結帳根目錄中建立沙箱。其他建構系統會使用特殊檔案系統來達成沙箱化。

值得考慮使用執行階段方法,因為這類方法可提供更強大的正確性保證。不過,您需要考慮效能問題,以及可攜性 問題

安全性考量

路徑可能用於這類位置,而路徑的解讀方式 (實際解析的檔案) 可能會影響敏感的系統行為。舉例來說,允許清單中的檔案可能會對應到記憶體,成為可執行的頁面。

在這種情況下,最好使用專案相對路徑,而非相對於包含清單的目錄的路徑,或是相對於處理這份清單的工具 CWD 的路徑。這是因為專案相對路徑的解析度在定義路徑的專案中明確無誤,而其他形式的相對路徑可能會根據全域可變動狀態 (例如 CWD) 有不同的解讀方式。

隱私權注意事項

絕對路徑有時可能會洩漏個人識別資訊。舉例來說,在包含該人員簽出或建構輸出目錄中檔案的絕對路徑中,通常會找到該人員的使用者名稱。以來源相對路徑取代絕對路徑,即可消除這個 PII 輸出內容。

測試

上文探討了幾種實作檢查的方法,可偵測絕對路徑的使用情形。由於這些檢查會以建構步驟或主機測試的形式實作,因此可以在 CQ 中執行,並做為持續測試。

確保不使用絕對路徑的另一種方法,是讓工程工作流程的關鍵環節無法容忍絕對路徑。舉例來說,如果絕對路徑會中斷某個發布的動作,而該動作是 CQ 的一部分,開發人員就無法再造成中斷。

說明文件

如果強制路徑不得為絕對路徑的工具失敗,應產生錯誤,並連結至適當的疑難排解頁面。做為參考,當動作追蹤器強制執行建構動作為密封式時,如果失敗,就會產生錯誤訊息,並提供密封式建構動作頁面的連結。

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

我們無法採取任何行動,導致分散式建構空間和可重現性空間的商機流失。

我們可以制定政策,但無法強制執行。可能導致您無法在分散式建構或可重現性方面取得有意義的進展。

我們可以暫緩處理這個問題,但隨著時間推移,熵衰減的本質會導致更多迴歸問題。

既有技術和參考資料

一位偉大的絕地武士曾說:「只有西斯才會絕對。」