跳至內容

進階 Elixir 快取設定

請先參閱 Elixir 快取設定指南。以下選項僅在專案有額外編譯輸入、相依例外或不同 checkout 路徑時才需要。

選擇相依套件#

cache: true 會自動快取支援的相依套件。關鍵字清單具有相同的自動選擇機制,並可提供選用覆寫:

elixir
cache: [exclude: [:a_dependency_with_compile_time_side_effects]]

如要改為將快取限制為特定相依套件:

elixir
cache: [dependencies: [:nimble_options, :nimble_parsec]]

dependencies: :all 明確選擇所有支援的相依套件;dependencies: [] 則不選擇任何套件。exclude: 優先於任一選擇。省略 cache,或將其設為 false 或 nil,會停用快取。

支援的相依套件會透過 Hex 或 Git 取得,並使用標準 Mix 編譯器流程搭配 Elixir/Erlang 原始碼,或使用 rebar3 的一般純 BEAM 編譯。當上游相依套件使用 rebar3 時,Mix 相依套件也可以重複使用構件。Rebar 設定、原始碼/標頭檔以及 rebar3 可執行檔指紋都會納入鍵值。封裝的 ebin/*.app 中繼資料和被動式 Makefile 也受支援。Rebar 設定腳本在查找前已由 Mix 評估;其原始碼與有效設定會進入鍵值。應用程式原始碼腳本(*.app.src.script)稍後由 rebar3 評估,並需要經審核的建構合約,建構外掛程式與編譯鉤子亦然。缺少的 rebar3 啟動器會在指紋計算前安裝,使用 Mix 的正常安裝器;冷態工作節點仍需要存取該安裝器。

自訂追蹤器、原始碼符號連結、umbrella 根目錄以及包含本機相依套件的鏈結都能正常編譯。Tuist 自身的 bootstrap 相依套件在冷態工作節點上也能正常編譯。結構上的符合資格並不證明任意編譯程式碼具備封閉性。

宣告額外輸入#

原始碼樹、鎖定的相依身分、上游來源、BEAM 與資源、相依執行階段/選用/應用程式旗標、已解析的應用程式中繼資料、明確的有效應用程式設定、編譯器選項(包括 lexer 與 parser 選項)、Mix 環境與目標,以及工具鏈身分都會自動納入。相依專屬的 env: 與 system_env: 會進入鍵值,並在該環境下評估設定。應用程式預設值透過原始碼輸入進入,而非 VM 載入狀態。

對於在編譯時期讀取原始碼樹以外檔案或額外環境變數的相依套件,請宣告這些輸入:

elixir
cache: [
inputs: ["schemas", "config/compiler-input.json"],
env: ["YOUR_COMPILER_OPTION"]
]

Elixir 巨集與模組主體可以讀取任意檔案、環境、周邊程式碼路徑、網路回應與時間。Tuist 不會自動發現這些讀取行為。變更未宣告的輸入可能產生過時的輸出。排除具有時間/網路依賴編譯或必須在每次建構執行的副作用的相依套件;在需要更嚴格控制時,使用 dependencies: 建立經審核的相依允許清單。

標準編譯器/執行階段變數(ERL_COMPILER_OPTIONS、ELIXIR_ERL_OPTIONS、ERL_AFLAGS、ERL_FLAGS、ERL_ZFLAGS、SOURCE_DATE_EPOCH、ERL_LIBS、MIX_PATH 與 MIX_ARCHIVES)會自動捕捉。工具鏈身分包括 Elixir 建構版本、可用的完整 OTP 版本、ERTS、編譯器位元組碼、作業系統/版本、架構、字組大小與位元組順序。有效上下文會在每次查找前捕捉。每個相依的輸入模型可在一次準備工作中共用以供查找規劃;編譯會清除計畫,而還原會使已安裝應用程式的記錄失效。分階段安裝與發布一律檢查全新輸入,不會重複使用查找計畫。查找未命中也會在編譯前捕捉全新鍵值。輸入變更會拒絕分階段命中,而非安裝為先前輸入建構的程式碼。

鍵值使用確定性項編碼與排序檔案系統遍歷。無序的建構宣告與已宣告輸入集會排序並去重;有序編譯器選項保留其順序。檔案內容、名稱、可執行位元與缺失路徑會影響結果,檔案系統時間戳記則不會。檔案宣告不建模擁有權、時間戳記或其他檔案系統中繼資料;請排除從這些值衍生輸出的建構器。上游編譯器清單被排除為增量記帳;刻意讀取其內容的程式碼必須將其宣告為額外輸入。Regex 設定使用其原始碼與選項,絕不使用 VM 本機編譯參考。其他不透明執行階段值會繞過快取。更新的鍵值模型會讓舊條目失效,無需遷移。

確定性鍵值編碼與確定性編譯器輸出是分開的。如果在近期 Elixir 版本上,上游 BEAM 中繼資料在其他方面相同的建構之間發生變化,請考慮在 SDK bootstrap、種子與快取消耗前設定 ERL_COMPILER_OPTIONS='[deterministic]'。將其與任何現有編譯器選項合併,並在變更設定時重建相依輸出一次。SDK 不會自動設定它;其確切值仍是鍵值的一部分。完整的上游位元組仍會被雜湊,不會標準化或剔除。這不是跨 checkout 或任意建構器的可複現性保證。該編譯器選項也會從 module_info(:compile) 省略原始碼/選項,因此在採用前請驗證涵蓋範圍、除錯與原始碼探索工具。編譯器旗標無法修復無序的編譯時期產生器。SDK 需要 Mint 1.10.2 或更新版本,包括其安全性回溯修補。此版本會對用於產生其 HTTP/2 框函式的映射進行排序;升級 SDK 時請更新較舊的 Mint 鎖定條目。即使啟用 deterministic,其他相依套件與編譯器階段仍可能產生不同的 bootstrap BEAM。

Lexer 與 parser 輸出僅在它們不存在於鎖定的 Git 樹或 Hex 套件檔案清單時,才會被視為衍生檔案。隨附的 .erl 檔案與不透明目錄/子模組內容仍為一般輸入;重新產生的隨附檔案必須通過相同的分階段後輸入調和。來自 OTP 的 parsetools 的產生器範本也會進入工具鏈身分。在併發建構之間保持相依原始碼 checkout 隔離:產生器與原生建構器可能會寫入 deps/。巢狀/自訂 rebar 產生器佈局可能仍需要經審核的 generated 宣告。

原生與自訂建構器#

對於經審核的 Mix、rebar3 或 Make 建構器,請宣告每個相依套件的合約:

elixir
cache: [
builders: [
bcrypt_elixir: [
platform: "build-image@sha256:YOUR_IMMUTABLE_IMAGE_DIGEST",
outputs: ["priv/bcrypt_nif.so"],
tools: ["cc", "make"],
env: ["YOUR_BUILDER_OPTION"]
]
]
]

platform 識別相容的原生建構環境。使用涵蓋編譯器、SDK、系統標頭檔與共享程式庫的不可變映像檔/工具鏈身分,而非如 linux 等可重複使用的標籤。Elixir/OTP 與常見的平台檢查仍然適用。這是對建構器的明確審核,而非自動探索原生輸入或必要的副作用。無效的明確建構器合約會引發可操作的設定錯誤,而不會靜默地停用快取。

outputs 列出相對於相依套件應用程式建構目錄的必要路徑。列出執行時期實際需要的程式庫/可執行檔。即使 Mix 認為 BEAM 是最新的,缺少輸出仍會觸發還原或編譯;不完整的快照會被拒絕。使用建構器的 env 宣告額外的環境變數,並使用其 inputs 宣告外部檔案/目錄,這些路徑相對於使用該相依套件的專案。原生鍵值會自動包含標準建構環境變數,例如 PATH、CC、CXX、CFLAGS、LDFLAGS、SDKROOT 與 RUSTFLAGS,並使用相依套件的有效 system_env 覆寫。已解析的 Mix 專案設定也會進入原生建構器鍵值;不透明的函式值設定無法被快取。

選用 tools 名稱會在該有效 PATH 上解析;相對工具路徑則相對於相依套件原始碼目錄。可執行位元組與解析後的路徑會進入鍵值,而無法使用的已宣告工具會繞過快取。啟動器或編譯器包裝器的指紋並不涵蓋其 SDK、子行程或系統程式庫:不可變的 platform 與額外的 inputs 必須涵蓋這些項目。MAKEFLAGS 會按原樣保留,因為自訂建構器可以讀取它。繼承的 GNU make jobserver 識別碼因此可能導致不同執行之間的安全快取未命中;請在隔離的快取工作中使用穩定的建構旗標,而不要假設這些值會從鍵值中移除。

某些建構器會在 deps/ 下產生檔案,而不僅是在 _build/。在 generated 中列出相對於該相依套件原始碼目錄的那些路徑,例如 generated: ["priv/native"]。只有那些產生的路徑才會從原始碼輸入中排除。不要排除原始資源或原始碼檔案:它們必須保持為輸入。路徑是字面意義,不含萬用字元或遍歷。合約、平台、額外輸入、環境值與任何自訂相依套件編譯命令都會參與鍵值。compile: false 仍然是選擇退出編譯的方式。

經審核的快照會將基於原始碼的 ebin、priv、include 與 src 資源實體化到還原的應用程式中。因此,原生程式庫、連接埠可執行檔、標頭檔與產生的資源能在冷 checkout 中存活,無需修改其相依套件原始碼。可執行模式與包含的相對資源符號連結會被保留;逸出、絕對與循環連結會被拒絕。所有上游應用程式資源都會進入下游鍵值,包括一般的 priv 資源與自訂輸出目錄,而不僅是原生程式庫或 BEAM。直接讀取生產者原始碼路徑的程式碼不會被重新定位;具有應用程式產物之外必要副作用的建構器仍需要這些步驟。

在審核其輸入與輸出之前,請保持自訂建構器被排除。合約不會移除冷 SDK bootstrap 編譯、64 MiB 產物限制,或對相容建構環境的需求。

跨 checkout 路徑重用#

Checkout 路徑預設會參與鍵值。如果你的相依套件不依賴生產者路徑,請選擇在不同 checkout 位置之間重用:

elixir
cache: [portable: true]

嵌入 checkout 路徑的設定值(例如在 config/config.exs 中由 __DIR__ 建構的路徑)不會改變可攜式鍵值。Elixir 工具鏈的安裝位置也不會影響:歸檔與 rebar3 按其內容計算。

快取的 BEAM 會保留生產者除錯/原始碼路徑以及編譯時期的 __DIR__、__ENV__.file 與 File.cwd!() 值。可攜式重用不會重寫它們。資源連結指向目前的原始碼 checkout。上游 BEAM 位元組也會進入鍵值並可能嵌入路徑,因此重用可能需要從同一個快取還原上游閉包,而不是獨立重新編譯它。

儲存與存取#

選項預設值用途
remotetrue在本機未命中後使用共享的遠端快取。
uploadtrue遠端發布新捆綁包。false 仍允許讀取與本機快照。
pathTuist 的快取目錄,位於 mix/dependencies/v1 之下選擇不同的本機儲存區。
endpoint自動發現覆寫帳戶的 HTTPS REAPI 端點。

upload: false 控制客戶端行為,而非授權。專案快取權限由伺服器強制執行。只能讀取專案快取的憑證會自動跳過上傳。專案 endpoint: 選項與發現的端點需要 HTTPS,但迴環(localhost、127.0.0.1、::1)開發端點除外。

非空的 TUIST_CACHE_ENDPOINT 會覆寫 endpoint: 與發現機制。託管執行器使用它來存取其私有的叢集內快取。此由營運者控制的環境覆寫也允許在非迴環主機上使用 HTTP。HTTP 會以未加密方式傳送專案範圍的快取權杖與產物,因此請仅在受信任的網路上使用。無效的覆寫會回退至本機快照,而不會嘗試發現。

本機快照與遠端捆綁包限制為 64 MiB;較大的產物會編譯但不會被快取。本機儲存區沒有自動驅逐機制;請在需要時移除其內容。遠端產物使用 Kura 現有的保留、驅逐與專案清理規則。

Important

編譯器清單與 BEAM 可以嵌入生產者路徑與編譯時期值,包括密鑰。專案快取讀者可以存取這些產物。快取寫入者是受信任的程式碼生產者:摘要檢查偵測損壞,而非惡意的可執行 BEAM。不要將快取寫入憑證暴露給不受信任的 PR 工作,或在這些憑證可用的情況下編譯不受信任的相依套件。

疑難排解:缺少命中#

  • 安裝套件後執行 mix tuist.setup 並提交產生的 Mix 設定。Setup 會保留其他別名與 Tuist 選項;它會拒絕自訂的編譯動作,而不會靜默地移除它們。
  • 保留產生的編譯別名。直接 mix tuist.compile 工作發現可能會在快取檢查之前先編譯相依套件。
  • 對於已選取且受支援的相依套件,其已經最新且有效的輸出會以原因 up_to_date 計為本機命中。它們不會事後發布,也不帶有任何產物金鑰、傳輸位元組數、還原延遲或預估節省時間。若要測試快照還原,請在相依套件填入快取後,使用 mix deps.clean NAME --build 僅清除該受支援相依套件的建置,然後再次編譯。
  • 比對 MIX_ENV、MIX_TARGET、編譯器設定與宣告的輸入。除非啟用可攜式重用,否則路徑變更也會改變金鑰。
  • 不受支援的相依套件會顯示為 未快取,而非未命中。它們不納入命中率計算。若一次建置既沒有查詢也沒有最新的本機命中,則命中率為不可用,而非 0%。
  • 缺少憑證、端點不可用或快照損壞時,會回退至編譯。編譯器失敗仍會導致建置失敗。
  • CAS 記錄可能在建置報告之後才到達。重新整理以查看新匯入的傳輸。本機命中與舊版建置沒有遠端 CAS 輸出。

伺服器需要支援編譯報告合約 0.2 才能記錄 up_to_date 本機命中。如果較舊的伺服器明確拒絕快取資料或結構描述列舉值,套件會在不包含這些新重用觀察的情況下重試報告一次。編譯詳細資訊與其他快取結果會保留;新的本機重用功能在伺服器升級前仍然不可用。先部署伺服器可避免這額外的請求。合約版本不會協商支援,且驗證失敗、傳輸失敗與其他驗證訊息不會重試。列舉錯誤在較舊的伺服器上不會指明其欄位;保留的無效欄位在單次重試中仍會失敗。

手動編譯整合#

如果 setup 無法安全地編輯你的專案設定,請手動將此別名與這些函式加入你的 Mix 專案模組,並保留任何自訂編譯動作:

elixir
# In aliases/0:
compile: &compile/1
# In the same Mix project module:
defp compile(args) do
if "--no-compile" not in args do
compile_tuist_ex()
end
Mix.Task.run("deps.loadpaths", ["--no-compile", "--no-deps-check"])
Mix.Task.run("tuist.compile", args)
end
defp compile_tuist_ex do
deps =
["tuist_ex"]
|> Mix.Dep.filter_by_name(Mix.Dep.load_and_cache(), include_children: true)
|> Enum.reject(&(Mix.Dep.ok?(&1) and &1.scm.fetchable?()))
|> Enum.map(&Atom.to_string(&1.app))
if deps != [], do: Mix.Task.run("deps.compile", deps)
end

這些函式會在其他相依套件之前編譯並載入 Tuist,包括在冷啟動平行建置時;當 Tuist 及其相依套件已編譯,或命令以 --no-compile 執行時,則會跳過該工作。相同的函式也適用於 Hex、Git 與路徑安裝;不需要腳本路徑。

如果你的別名以 deps.compile tuist_ex --include-children 開頭,每次 mix compile 都會重建 Tuist 的相依套件,並可能重新編譯你的整個專案。請再次執行 mix tuist.setup 以取代它。

運作方式#

Elixir 快取使用 Bazel 所用的共享 REAPI ActionCache/CAS 管線、驗證與 Kura 簽署的分析資料,而非獨立的 Mix 儲存服務。模型化的輸入金鑰會參照一個 ActionResult,其中包含完整編譯捆綁包的原始位元組 SHA-256 摘要。Tuist 會在分階段安裝前驗證其檔案、應用程式中繼資料、宣告的 BEAM 與 SCM 清單。損壞的項目會在成功編譯後修復。

在 OTP 28 及更高版本中,遠端工作階段會為至少 1 KiB 的 ByteStream 傳輸協商 zstd 壓縮。不可壓縮的上傳、較舊的 OTP 執行時期與不支援壓縮的伺服器會使用身份傳輸。摘要、產物限制與回報的位元組數均指未壓縮的內容。解碼後的位元組在安裝前會進行邊界檢查與驗證。每個相依套件仍保持為單一 CAS 捆綁包,避免對小型產物發出每檔案或分塊請求。

遠端憑證/連線會延遲解析;閒置建置與本機命中(包括已經最新的輸出)不會聯絡遠端端點。每次退出時都會關閉一個工作階段,包括編譯器失敗的情況。客戶端 RPC 有 30 秒的截止時間。Mix 仍負責鎖定檢查、本機相依套件編譯與不支援的工作。

儀表板的延遲/位元組數會保留本機與遠端的來源區別,並將不可用的值與測量的零區分開來。遠端計時測量客戶端 CAS 傳輸,而本機計時包括驗證與安裝;兩者都不包括編譯。CAS 輸出使用成功的 Kura 簽署傳輸,而非本機副本或動作輸入金鑰。吞吐量是位元組數除以測量時間;壓縮大小不可用。預估避免的編譯時間是生產者的原始編譯持續時間,而非測量的端到端建置節省時間。

獨立且就緒的相依套件會透過同一個 HTTP/2 連線,以最多四個並發請求的視窗取得。這將解碼後的捆綁包緩衝限制為四個產物,每個產物都受 64 MiB 限制。金鑰規劃、Mix 狀態與安裝仍保持序列化。下游金鑰會等待上游產物;預取的位元組僅在其確切輸入金鑰下消耗,並在遠端安裝前重新檢查輸入。已經最新的輸出與本機快照不會引發猜測性的遠端請求。針對選定的快取未命中,編譯與上傳仍保持串行;並行性並不保證每次建置都會更快。

每個相依套件都能跳過編譯嗎?#

相容的命中可以跳過受支援相依套件的編譯,但 cache: true 是故障開放模式,並非零編譯保證。缺少/被驅逐的項目、不支援的建置工具與冷啟動 SDK 引導仍可能觸發編譯。高命中率本身並不代表實際掛鐘時間加速。

對於「建置一次/測試多次」的 CI,請使用

Elixir test sharding

來分發精確準備好的建置,而不是期望每個工作節點從每個相依套件的快取命中重建它。重用完整的建置,尤其是包含原生程式庫的建置,仍然需要相容的平台、工具鏈與設定。任意的自訂建置若未對其外部輸入、輸出與必要的副作用進行建模,就無法安全地跳過其建置步驟。