# 進階 Elixir 快取設定

請先參閱<.localized_link href="/guides/features/cache/elixir">Elixir 快取設定指南</.localized_link>。僅當專案需要額外的編譯輸入、相依性例外或不同的檢出路徑時，才需要使用以下選項。

## 選擇相依性

`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 位元組也會進入鍵值並可能嵌入路徑，因此重用可能需要從同一個快取還原上游閉包，而非獨立重新編譯。

## 儲存與存取

| 選項 | 預設值 | 用途 |
| --- | --- | --- |
| `remote` | `true` | 在本機未命中後使用共享的遠端快取。 |
| `upload` | `true` | 遠端發布新的套件。`false` 仍允許讀取與本機快照。 |
| `path` | Tuist 的快取目錄，位於 `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，請使用
<.localized_link href="/guides/features/test-sharding/elixir">Elixir test sharding</.localized_link>
來分派精確準備好的建置，而非預期每個工作節點從每個相依性的快取命中重建它。重用完整的建置（尤其是包含原生程式庫的建置）仍需要相容的平台、工具鏈與設定。任意的自訂建置若未對其外部輸入、輸出與必要的副作用進行建模，就無法安全地略過其建置步驟。
