Swift Digest
SE-0135 | Swift Evolution

Package Manager Support for Differentiating Packages by Swift version

Proposal
SE-0135
Authors
Anders Bertelrud
Review Manager
Daniel Dunbar
Status
Implemented (Swift 3.0)

01 何が問題だったのか

Swift は言語・標準ライブラリ・Swift Package Manager(SwiftPM)のマニフェスト API が進化する過程で、バージョン間にソース非互換な変更が入ることがあります。パッケージ作者は新しい Swift にいち早く対応したい一方で、まだ古い Swift を使っている既存のクライアントも引き続きサポートする必要があります。

小さな違いであれば #if swift(...) による条件付きコンパイルで吸収できますが、差分が大きくなると 1 本のソースに両対応のコードを詰め込むのは現実的ではありません。特に Package.swift 自体は Swift のソースファイルであり、言語構文やパッケージ記述 API の変更の影響を真正面から受けるため、#if で差分を表現するのが難しくなります。

この状況で考えられる回避策はどれも筋が悪いものでした。

  • パッケージのセマンティックバージョンを Swift のバージョンに結び付ける(例: パッケージの v1 は Swift 2.3 専用、v2 は Swift 3 専用)。これではパッケージのリリースサイクルが Swift 本体に縛られ、クライアントが Swift を更新しないとパッケージの新版に移れなくなる revlock が発生します。マニフェストだけを変えたい場合でも、本来 1 本で済むはずのパッケージを分岐させる羽目になります。
  • パッケージのバージョン番号に Swift バージョンを埋め込む(例: 偶数はリリース版 Swift 用、奇数はプレリリース版 Swift 用、といった運用)。これはセマンティックバージョニングの意味を歪めてしまい、エコシステム全体の保守性を損ないます。

つまり、パッケージ自身のセマンティックバージョンとは独立に「どの Swift バージョン向けか」で候補を絞り込む仕組みが SwiftPM 側に必要でした。

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

SwiftPM に、Swift バージョンで候補を絞り込むための 2 種類の仕組みを追加します。どちらも命名規則に @swift-<version> というサフィックスを使い、通常のバージョン解決ロジックに Swift バージョンによるフィルタを前段として挟む形で動作します。

バージョン別のリポジトリタグ

同じセマンティックバージョンに対して、Swift バージョン違いの複数のタグを付けられるようにします。タグ名は <semver>@swift-<swift-version> の形式です。

たとえばバージョン 1.0 のパッケージについて、Swift 2.3 用と Swift 3 用の 2 系統を別チェックアウトで保守したい場合は、次のようにタグを打ちます。

1.0@swift-2.3
1.0@swift-3

SwiftPM は依存解決時にまず、クライアントが使用している Swift バージョンに一致する @swift-... サフィックス付きタグだけを候補に残し、そこに従来のセマンティックバージョン制約を適用します。該当するサフィックス付きタグが 1 つも無ければ、従来どおりサフィックスの無いタグ群から解決します。

Swift バージョンの指定は桁数によってマッチの精度が変わります。@swift-3 は Swift 3.x.x 全体に一致し、@swift-3.0 は Swift 3.0.x にのみ一致します。複数のサフィックスにマッチする場合は、より具体的なもの(桁数の多いもの)が優先されます。たとえば @swift-3@swift-3.1 が両方存在するとき、Swift 3.1 は後者を、Swift 3.2 は前者を選びます。

この仕組みは、Swift バージョン間の差分が大きく、同一チェックアウトで両対応するのが非現実的な場合に向いています。

バージョン別のパッケージマニフェスト

差分が小さく 1 本のタグで済ませたい場合でも、Package.swift だけは #if で書き分けにくいことがあります。そこで、マニフェストファイル名にも同じ @swift-<version> サフィックスを使えるようにします。

たとえば同じリポジトリに次の 3 つのマニフェストを同居させられます。

Package.swift
Package@swift-3.swift
Package@swift-2.3.swift

SwiftPM は、クライアントの Swift バージョンに最もよく一致する Package@swift-...swift を優先して読み込み、該当するものが無ければ通常の Package.swift にフォールバックします。マッチ規則はタグの場合と同じで、より具体的な指定が優先されます。

バージョン別のタグとバージョン別のマニフェストは排他ではなく、必要に応じて組み合わせて使えます。一般的には、マニフェストの書き分けで済むならそちらを使い、どうしても同一チェックアウトで両立できないほど差分が大きい場合にだけバージョン別タグを使う、という運用が想定されています。

使い分けと今後の見通し

Swift 言語・標準ライブラリ・パッケージ記述 API が安定してくれば、多くのパッケージはバージョン別のバリアントを捨てて Package.swift 一本に戻せるはずで、この仕組みが必要な場面は徐々に減っていくと見込まれています(この見通しは提案時点のもので、実現を約束するものではありません)。