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++ 建構動作。在先前導入使用 C/C++ 包含目錄的絕對路徑的變更後,Goma 的 Fuchsia 使用者曾經歷服務中斷的情況。在某些情況下,分散式版本會失敗,並強制使用本機備用機制,這會導致速度變慢。在其他情況下,分散式版本會成功,但無法命中快取,導致後端負載量增加,進而導致連鎖性故障。

在發布建構動作時,另一個類似的失敗模式是工具叫用中的絕對路徑。我們先前發現,在發布動作前檢查這類路徑很有用,而且可以透過建構動作失敗和實用錯誤訊息的形式拒絕這些路徑。

其他情況下,絕對路徑會導致建構正確性問題。

用於管道化的可攜式構件

在分發動作時,有時會需要為不同工作建立遠端伺服器管道。舉例來說,某些機器可能更適合執行建構作業,而其他機器則更適合執行已建構的測試。有時,動作會以分支和合併的圖表表示,例如建立一組測試,然後分支至多部機器,每部機器執行部分測試,然後合併結果。

在先前的案例中,路徑是在用戶端和伺服器之間交換,但在本案例中,路徑是在管道中的不同伺服器之間交換。儘管交換的性質不同,但基於相同原因,我們仍建議使用相對路徑。

絕對路徑可能會導致管道中斷。舉例來說,在過去,產生測試涵蓋率的管道會中斷,因為在較早階段產生的涵蓋率報告包含來源程式的絕對路徑,而這類路徑無法在執行涵蓋率報告產生管道後續階段的不同伺服器中解析。產生涵蓋率對應檔案的工具使用絕對路徑,我們已將其變更,以便將路徑相對化至指定的基礎。

使用其他形式的程式碼檢測功能時,也會發生類似的損壞情形,因為在偵錯資訊檔案中,絕對路徑曾用於記錄,用於將相對 PC 偏移解析為來源程式碼行。

用於快取的行動構件

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

我們知道部分建構輸出內容含有絕對成果,因此 Fuchsia 目前不會在不同機器之間重複使用建構快取。相反地,Fuchsia 開發人員和 Fuchsia 分散式建構工具最多只會使用先前執行的版本中,他們自己的本地快取。這會導致最佳化和提升工程工作效率的重大商機流失。

可複製的構件

在構件中使用絕對路徑會導致無法產生可重現的版本

目前,Fuchsia 並未明確將可重現的版本列為目標。不過,考量到可重現版本的優點,以及在構件中使用絕對路徑會導致無法達到可重現性,我們認為不妨考慮可重現版本的優點。

另請注意,還有其他產生產物不可重現的來源,最常見的是時間戳記,但這些來源不屬於本 RFC 的範圍。

最少的工作

Fuchsia 目前會產生完整的系統映像檔,並鋪設裝置,藉此在裝置上執行測試。日後我們可能會加快這項程序,例如只將自上次更新後變更的 Blob 推送至測試裝置。預期大多數要測試的變更只會影響少數 Blob,與其基礎變更無關,因此這種運作方式可大幅加快測試裝置的啟動速度。

如果絕對路徑洩漏至構件,則在測試不同變更時,可能會比絕對必要的數量多出許多 Blob。

樹狀結構外 Fuchsia 版本

上述內容說明瞭 Fuchsia 因絕對路徑而發生的問題,以及絕對路徑導致 Fuchsia 難以發展和改善的幾種方式。解決絕對路徑問題的急迫性取決於歷史背景。舉例來說,Fuchsia 過去並未利用增量建構或快取,因此專案和相關人員學會了如何容忍讓 Fuchsia 無法採用更多增量建構和快取的缺陷。

如果 Fuchsia 成功,其他專案就會使用 Fuchsia 的程式碼和構件,並為 Fuchsia 進行開發。我們可以合理推測,至少部分專案會預期建構規則和工具的系統,比起 Fuchsia 專案更能滿足不同的需求。舉例來說,部分客戶的規模可能需要漸進式建構,因此也需要快取。如果 Fuchsia 無法提供這些屬性,Fuchsia 開發人員和其他客戶就會面臨採用的障礙。

分散式信任

如果建構的所有構件都能重現,就能為建構系統開啟新的屬性。舉例來說,可重現的版本可做為分散式替代方案,用於驗證分散式二進位檔的完整性,不信任的各方只要嘗試從相同來源和建構系統重現這些二進位檔,即可稽核這些二進位檔。如果無法重現二進位檔,可能就是惡意竄改的證據。

如果在構件中使用絕對路徑,則不信任的一方將永遠無法重現相同的結果。

便利性

在疑難排解時,相對路徑會更容易使用。通常會預期一致性,因此可以比較成功和失敗的操作之間的路徑,並找出有意義的差異。

在本機上進行作業時,絕對路徑非常方便。舉例來說,您可以從工具叫用作業中複製絕對路徑,並在不同的本機 Shell 環境中使用,而且絕對路徑不會對目前的工作目錄敏感,因此保證能夠正常運作。此外,任何兩個絕對且經過正規化的路徑 (例如 ... 部分已解析、連結已追蹤等) 都可以透過字串 ID 檢查是否相等。由於任何路徑都可以設為絕對路徑並進行正規化,且這類轉換作業是冪等的,因此可在本機環境中針對路徑提供簡單的等價檢查。不過,如果使用者認為以絕對路徑執行此轉換作業更方便,則沒有任何限制。不過,由於從相對和/或標準化形式轉換為絕對形式的轉換作業具有破壞性,因此無法以相反方向執行。因此,建議您使用相對形式,以便涵蓋所有用途。

設計

政策

我們將將已記錄的 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

如果任何建構叫用作業以絕對路徑參照檢出的路徑,則建構作業會失敗。

這種做法實作起來非常簡單,可移植,且不會造成效能負擔。其中一些缺點包括:在發生錯誤時,錯誤訊息會讓初學者感到困惑,而且仍有可能在產生的構件 (例如 depfiles、srcgen、debug info) 中洩漏絕對路徑。

執行階段環境的變更

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

由於執行階段方法可提供更強的正確性保證,因此值得考慮採用。不過,您必須考量效能問題和棘手的可移植性 問題

安全性考量

路徑可能會用於解讀路徑 (解析的實際檔案) 可能會影響敏感系統行為的情況。例如,可將檔案對應至記憶體做為可執行頁面的許可清單。

在這種情況下,建議您使用與專案相關的路徑,而非與包含指定清單的資料夾相關的路徑,或是與處理這份清單的工具的 CWD 相關的路徑。這是因為專案相對路徑的解析結果在定義這些路徑的專案中是明確的,而其他形式的相對路徑可能會根據全域可變狀態 (例如 CWD) 進行不同解讀。

隱私權注意事項

絕對路徑偶爾可能會洩漏個人識別資訊。舉例來說,使用者名稱通常會出現在使用者檢出或建構輸出目錄中包含檔案的絕對路徑中。將絕對路徑替換為相對於來源的路徑,即可消除這類 PII 的出口。

測試

我們在上述內容中探討了一些實作檢查方法,以便偵測使用絕對路徑。由於這些檢查是做為建構步驟或主機測試實作,因此可在 CQ 中執行,並做為持續測試。

另一種確保不使用絕對路徑的方法,是讓工程工作流程的關鍵部分無法容許絕對路徑。舉例來說,如果絕對路徑會中斷已發布的特定動作,而該動作是 CQ 的一部分,則開發人員就無法再引入中斷情形。

說明文件

當強制要求路徑非絕對路徑的工具失敗時,應會產生連結至適當疑難排解頁面的錯誤。舉例來說,當動作追蹤器 (會強制執行建構動作) 失敗時,會產生錯誤訊息,並附上連結至密封式建構動作頁面。

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

我們無法採取任何行動,因此錯失了在分散式建構空間和可重現性空間中提供支援的機會。

我們可以制定政策,但無法強制執行。可能的後果是無法在分散式建構或可重現性方面取得實質進展。

我們可以將這個問題擱置,但代價是需要隨著時間採取額外的回歸,這也是熵衰退的特性。

既有技術與參考資料

一位偉大的絕地武士曾說過:「只有西斯才會以絕對的態度行事。」