跳至內容

進階 Elixir 快取設定

請先參閱Elixir 快取設定指南。僅當專案需要額外的編譯輸入、相依性例外或不同的檢出路徑時,才需要使用以下選項。

選擇相依性#

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 的標準安裝程式進行安裝;冷態工作節點仍需存取該安裝程式。

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

宣告額外輸入#

原始碼樹、鎖定的相依性身分、上游原始碼、BEAM 與資源、相依性執行階段/選用/應用程式旗標、已解析的應用程式中繼資料、明確的有效應用程式設定、編譯器選項(包括詞法分析器與剖析器選項)、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、編譯器位元組碼、作業系統/版本、架構、字組大小與位元組順序。有效內容會在每次查詢前擷取。每個相依性的輸入模型可在一次準備過程中共享以規劃查詢;編譯會清除該計畫,而還原會使已安裝應用程式的記錄失效。分階段安裝與發布始終檢查全新輸入,而不重複使用查詢計畫。查詢未命中也會在編譯前擷取全新鍵值。輸入變更會拒絕分階段命中,而非安裝為先前輸入建置的程式碼。

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

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

僅當詞法分析器與剖析器輸出不存在於鎖定的 Git 樹或 Hex 套件檔案清單中時,才會將其視為衍生檔案。隨附的.erl檔案與不透明的目錄/子模組內容仍為一般輸入;重新產生的隨附檔案必須通過相同的後階段輸入協調。來自 OTP parsetools的產生器範本也會進入工具鏈身分。請在並行建置之間隔離相依性原始碼檢出:產生器與原生建構器可能會寫入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 資源具體化到還原的應用程式中。因此,原生程式庫、連接埠可執行檔、標頭檔與產生的資源能在冷檢出中存活,無需修改其相依性原始碼。可執行模式與包含的相對資源符號連結會被保留;逸出、絕對與循環連結則會被拒絕。所有上游應用程式資源都會進入下游鍵值,包括一般的 priv 資源與自訂輸出目錄,而不僅限於原生程式庫或 BEAM。直接讀取生產者原始碼路徑的程式碼不會被重新安置;具有應用程式產物外必要副作用的建構器仍需要這些步驟。

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

跨檢出路徑的重用#

檢出路徑預設會參與鍵值。若您的相依性不依賴生產者路徑,請選擇在不同檢出位置之間重用:

elixir
cache: [portable: true]

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

快取的 BEAM 會保留生產者的除錯/原始碼路徑以及編譯期的 __DIR__、__ENV__.file 和 File.cwd!() 值。可攜式重用不會重寫它們。資源連結指向目前的原始碼檢出。上游 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 設定。設定會保留其他別名與 Tuist 選項;它會拒絕自訂的編譯動作,而不會靜默地移除它們。
  • 保留產生的編譯別名。直接 mix tuist.compile 工作探索可能會在快取檢查之前就先編譯相依性。
  • 針對已選取且受支援的相依性,其現行有效的輸出會計為原因為 up_to_date 的本機命中。它們不會事後發布,也不包含產物金鑰、傳輸位元組數、還原延遲或預估節省的時間。若要測試快照還原,請在相依性填入快取後,使用 mix deps.clean NAME --build 僅清理該受支援相依性的建置,然後再次編譯。
  • 比對 MIX_ENV、MIX_TARGET、編譯器設定與宣告的輸入。除非啟用可攜式重用,否則路徑變更也會改變金鑰。
  • 不受支援的相依性會顯示為 未快取,而非未命中。它們不包含在命中率中。既無查詢也無最新本機命中的建置,其命中率為無法提供,而非 0%。
  • 缺少憑證、端點無法使用或快照損毀時,會回退至編譯。編譯器失敗仍會導致建置失敗。
  • CAS 記錄可能在建置報告之後才到達。重新整理以查看新匯入的傳輸。本機命中與舊版建置沒有遠端 CAS 輸出。

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

手動編譯整合#

如果設定無法安全地編輯專案的設定,請手動將此別名與這些函式加入 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

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