Swift Digest
SE-0152 | Swift Evolution

Package Manager Tools Version

Proposal
SE-0152
Authors
Rick Ballard
Review Manager
Anders Bertelrud
Status
Implemented (Swift 3.1)

01 何が問題だったのか

Swift Package Manager では、パッケージの Package.swift マニフェスト自体が Swift で書かれているため、Swift ツール(Swift コンパイラやパッケージマネージャ)のバージョンとパッケージとの間で次のような問題が発生します。

新機能を採用すると古いツールで壊れる

パッケージが Swift や Swift Package Manager の新機能を使うようになると、古い Swift ツールではそのパッケージをビルドできないばかりか、マニフェストそのものを解釈できなくなることもあります。

新機能を取り込んだ新バージョンをタグ付けしてリリースすると、古いツールを使っているクライアントのビルドがすべて壊れてしまいます。これを避けるためにメジャーバージョンを上げるのは、本来 API が変わっていないのに依存側に手動アップデートを強いることになり、望ましくありません。

PackageDescription API の刷新と互換性

Swift 4 のタイミングで、Package.swiftPackageDescription API を Swift のコーディング規約に合わせて見直す計画がありました。しかし、既存パッケージの互換性のために古い PackageDescription も残す必要があり、あるマニフェストを読み込む際にどちらのバージョンの API を使えばよいのか判断する手段がありません。

マニフェストを解釈する Swift 言語バージョンを決められない

Package.swift マニフェスト自体が Swift コードであるため、Swift 3 の文法で読むのか Swift 4 の文法で読むのかを決めないと解釈できません。しかもこの情報は「マニフェストの中のプロパティ」では表せません。マニフェストの中身を読むには、まずどの言語バージョンで解釈するかが決まっている必要があるためです。

既存の仕組みでは不十分

Swift 3 の時点でも、version-specific tag selection や version-specific manifest selection といった、バージョンごとに別のタグやマニフェストを提供する仕組みはありました。しかしいずれもパッケージ作者が明示的にオプトインする必要があり、知らない作者も多く、デフォルトで安全に動く仕組みにはなっていませんでした。また、これらは「古いツール向けに古いバージョンを提供する」ことはできても、「未更新のパッケージを新しいツールでもそのままビルドできるようにする」という方向には機能しません。

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

各パッケージに Swift tools version(Swift ツールバージョン)という値を持たせ、以下のすべてをこのひとつの値で制御します。

  • そのパッケージをビルドするのに必要な Swift ツールの最小バージョン
  • Package.swift マニフェストを解釈する際に使う PackageDescription API のバージョン
  • Package.swift マニフェストを解釈する際に使う Swift 言語の互換バージョン
  • パッケージソースのデフォルトの Swift 言語互換バージョン(明示指定が無い場合)

これにより、パッケージ作者は新機能を採用する際に Swift tools version を引き上げるだけで済み、古いツールのクライアントを壊すことなく、新しいツールのクライアントには自動的に最新版を届けられるようになります。

書き方

Swift tools version は、Package.swift先頭行のコメント として記述します。

// swift-tools-version:4.0
import PackageDescription

let package = Package(
    name: "MyPackage"
    // ...
)

バージョン番号は Semantic Versioning 2.0.0 に従いますが、パッチバージョンは省略可能で、省略時は 0 とみなされます。パッチバージョンはツールの互換性に影響しない想定のため、swift package tools-version --set-current などで自動設定される際には省略されます。

バージョン番号の後にはオプションとして ; を置けます。; 以降は現行のツールでは無視されますが、将来の拡張用に予約されています。

先頭行が // で始まり swift-tools-version(大文字小文字を問わない)を含むのに書式が不正な場合は、スペルミスとみなしてエラーになります。先頭行がそもそもこの書式でない場合、Swift tools version は 3.0.0 とみなされます。

依存関係解決での振る舞い

依存関係解決時に、通常選ばれるはずのバージョンが、現在使用中の Swift ツールより高い Swift tools version を要求している場合、そのバージョンは対象外となり、次に良い候補が評価されます。どのバージョンも条件を満たさなければ依存関係解決エラーになります。

これにより、パッケージ作者が新機能を採用して Swift tools version を引き上げたバージョンをリリースしても、古いツールを使っているクライアントは自動的にそれ以前のバージョンを選び、ビルドが壊れません。

マニフェストと新 API の検証

Package.swift で新しい PackageDescription API を使うと、パッケージマネージャはその API を理解できる Swift tools version が指定されているかを検証します。指定が不十分な場合は、Swift tools version を更新するよう促すエラーが出ます。

swift package tools-version コマンド

Swift tools version を管理するためのサブコマンドが追加されます。

  • swift package tools-version: 現在の Swift tools version を表示します。
  • swift package tools-version --set <value>: 指定した値に設定します。設定変更に伴う影響(マニフェストを別の Swift 言語バージョンで書き直す必要があるか、PackageDescription API のどのバージョンになるかなど)を案内するメッセージも出力します。
  • swift package tools-version --set-current: 現在使用中のツールのバージョンに設定します。

新規作成時も、swift package init は作成するパッケージの Swift tools version を現在のツールのバージョンに設定します。

既存パッケージへの影響

Swift tools version を指定していない既存パッケージは、3.0.0 が指定されているものとして扱われます。ソースは Swift 3 言語互換モードでビルドされ、Swift 3 でも Swift 4 でもビルドできます。したがって、今ある Swift 3 製のパッケージに手を入れなくても Swift 4 ツールでビルドできる状態が保たれます。

version-specific tag selection / version-specific manifest selection といった既存の仕組みは撤廃されるわけではなく、複数の Swift ツールバージョン向けに並行してバージョンを出したいケースなどで引き続き利用できます。ただし、Package.swift 内で条件付きコンパイルを使って新旧の PackageDescription API を同居させる運用は、新しいツールでは機能しなくなります。新しいツールは条件付きコンパイルの中身を考慮せず「新 API が使われている」と判断してしまうため、Swift tools version を引き上げるよう促すエラーになります。このようなケースでは version-specific manifest selection に切り替える必要があります。