Swift Digest
SE-0547 | Swift Evolution↗

SwiftPM でのコンパイルキャッシュのサポート

SwiftPM Support for Compilation Caching

Proposal
SE-0547↗
Authors
Owen Voorhees
Review Manager
Mishal Shah
Status
Accepted with modifications

このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら↗。

01 何が問題だったのか

Swift や Clang のコンパイラには、コンパイル結果を CAS(Content Addressable Store、内容アドレス指定のストア)に保存し、以降のビルドで再利用する仕組みがすでに備わっています。同一の入力に対するコンパイルであれば、実際にコンパイルし直す代わりに、前回の結果をそのまま再生(replay)できます。

しかし、日常的な開発では、以前とまったく同じ入力に対するコンパイルが何度も繰り返される場面が少なくありません。

  • 2 つのブランチを行き来しながら、そのたびにパッケージの大部分をインクリメンタルに再ビルドする
  • 複数の git worktree で同じコードをビルドし、それぞれが空のビルドフォルダから始まる
  • CI 環境で、実行のたびにクリーンビルドを繰り返す

これらはいずれも、タイムスタンプによる無効化とビルドディレクトリ内の中間生成物の再利用に頼る従来のインクリメンタルビルドでは恩恵を受けにくいワークフローです。同じソースを何度もコンパイルし直すのは無駄が多く、開発のスピードを損ないます。

コンパイルキャッシュを活用すれば、こうしたワークフローも高速化できます。実際、12 コアのマシンでのキャッシュ済みのクリーンな debug ビルドは、キャッシュが空の場合と比べて SwiftPM で 68%、SwiftSyntax で 75%、Vapor で 67% 高速になったと報告されています。

一方で、コンパイルキャッシュを実際に活かすには、キャッシュの保存場所・サイズ・退避(eviction)ポリシーといったパラメータを、ローカルビルドや CI といった用途に合わせて設定する必要があります。コンパイラを駆動するビルドシステムである SwiftPM は、こうした設定を管理しユーザーに公開するのに最も適した立場にありますが、これまで SwiftPM 側にはその手段がありませんでした。

02 どのように解決されるのか

SwiftPM にコンパイルキャッシュを設定するための仕組みを追加します。設定は次の 3 つの方法で行え、それぞれ優先度が異なります。後のものほど優先度が高く、各レベルで指定した値は加算的に適用されます。たとえば、キャッシュをグローバルに有効化しておき、キャッシュのパスだけをパッケージごとに上書きする、といった使い方ができます。

  • グローバル設定: swift package build-cache configure に --global を付けて設定します。SwiftPM の共有設定ディレクトリ(macOS では ~/Library/org.swift.swiftpm/configuration/、Linux では ~/.swiftpm/configuration/ など)の build-cache-config.json に保存されます。
  • パッケージローカル設定: swift package build-cache configure で設定します。パッケージの .swiftpm/configuration/ ディレクトリの build-cache-config.json に保存されます。このファイルは、swift package init で新規作成したパッケージの .gitignore にデフォルトで含まれます。
  • ビルドごとの設定: コマンドラインオプションで、その 1 回のビルドに対してのみ設定します。

なお、キャッシュはデフォルトで無効(opt-in)です。SwiftPM のユーザー向けインターフェースでは、用語として一貫して「compilation cache」ではなく「build cache」を用います。これは将来 CAS ベースのキャッシュを他の種類のタスクにも広げられるよう、名前を先取りしておくためです。

設定できる項目

3 つの仕組みのいずれでも、次の項目を設定できます。ビルドごとの指定にはコマンドラインオプションを、永続的な設定には swift package build-cache configure のオプションを使います。

  • キャッシュの有効/無効: ビルドごとには --enable-build-caching / --disable-build-caching、永続的には --enable-caching / --disable-caching。
  • prefix mapping の有効化: コンパイラのコマンドライン上のパスを正規化し、異なるファイルパスに置かれた同じパッケージの複数インスタンスがキャッシュ結果を共有できるようにします。キャッシュを有効にするとデフォルトで有効になります。ビルドごとには --enable-build-cache-prefix-mapping / --disable-build-cache-prefix-mapping、永続的には --enable-prefix-mapping / --disable-prefix-mapping。
  • キャッシュのパス: 有効化してもパスを指定しない場合は、SwiftPM のデフォルトのグローバルキャッシュディレクトリ(macOS では ~/Library/Caches/org.swift.swiftpm/、Linux では ~/.cache/org.swift.swiftpm/ など)のサブディレクトリが使われます。ビルドごとには --build-cache-path、永続的には --path。
  • サイズ上限: 絶対値(例: 50G)または利用可能なディスク容量に対する割合(例: 10%)で指定します。ビルドの終了時に、この上限を超えていればエントリが退避されます。ビルドごとには --build-cache-size-limit、永続的には --size-limit。デフォルトの上限は実装依存です。
  • 診断リマークの有効化: キャッシュのヒット/ミスに関する情報をビルドログに出力します。デフォルトでは無効です。ビルドごとには --enable-build-cache-diagnostic-remarks / --disable-build-cache-diagnostic-remarks、永続的には --enable-diagnostic-remarks / --disable-diagnostic-remarks。

たとえば、キャッシュをグローバルに有効化してサイズ上限を設定しておき、通常どおりビルドすると次のようになります。

swift package build-cache configure --global --enable-caching --size-limit 50G
swift build

その上で、あるパッケージだけキャッシュのパスを差し替えたい場合は、パッケージのディレクトリで次のように設定します。

swift package build-cache configure --path /path/to/cache

1 回のビルドだけキャッシュを有効にしたい場合は、コマンドラインオプションを直接渡します。

swift build --enable-build-caching --enable-build-cache-diagnostic-remarks

build-cache のサブコマンド

swift package build-cache には、configure のほかに次のサブコマンドがあります。

  • get-configuration: 現在のパッケージに対して実際に適用されるキャッシュ設定を、人間が読める形式で表示します。
  • reset-configuration: 保存された設定項目をすべて削除し、デフォルトに戻します。configure と同様、デフォルトではパッケージローカルの設定を対象とし、--global を付けるとグローバル設定を対象にします。
  • info: 現在のサイズなど、キャッシュに関する情報を表示します。
  • clean: 現在のパッケージのオンディスクのビルドキャッシュを削除します。

セキュリティ上の注意

ビルドキャッシュは、コンパイラの出力をローカルのキャッシュに保存し、取り出します。そのため、キャッシュを改変できる攻撃者は、元のソースコードを改変できる攻撃者と同様に、コンパイラの出力や最終的なビルド成果物を改竄できてしまいます。CI ノードのような共有環境でビルドキャッシュを使う場合は、この点を踏まえて権限を設定し、導入のリスクを見極める必要があります。

コンパイルキャッシュはあくまでオプトインのビルドパフォーマンス最適化であり、有効にしても出力そのものは変わりません。既存のパッケージへの影響はありません。

03 今後の見通し

この提案の初期の版には、リモートのキャッシュサービスや独自の LLVM CAS プラグインライブラリと組み合わせてコンパイルキャッシュを使うためのオプションも含まれていました。具体的には、LLVM CAS Plugin API を実装したプラグインのパスや、gRPC リモートキャッシュサービスのパスを指定できるようにする、というものです。これを使えば、複数のマシンでキャッシュを共有する分散キャッシュ実装と SwiftPM のビルドキャッシュを統合できます。

この機能自体は依然として価値があるものの、Swift がサポートするすべてのホストプラットフォームで一貫して利用できるよう、ツールチェインに同梱するデフォルトのキャッシュプラグイン経由で提供するという、少し異なるアプローチを追求するため、別のProposalへと先送りされました。あくまで将来の構想であり、この提案の一部として実現が約束されているものではありません。