Swift Digest
SE-0236 | Swift Evolution

Package Manager Platform Deployment Settings

Proposal
SE-0236
Authors
Ankit Aggarwal
Review Manager
Boris Bügling
Status
Implemented (Swift 5.0)

01 何が問題だったのか

Swift Package Manager(SwiftPM)は、パッケージが対応する最小のデプロイメントターゲット(minimum deployment target)を Package.swift マニフェストで宣言する方法を持っていませんでした。代わりに、macOS については SwiftPM 自身がハードコードしたバージョンが使われ、他のプラットフォームでは SDK が規定するデフォルトに任されるだけでした。

この制限は、ハードコードされたバージョンより後に追加された API を使いたいパッケージにとって大きな摩擦になります。回避策としては次の 2 つしかありませんでした。

  • コード中で if #available(...) によるアベイラビリティチェックを書いて、新しい API の利用箇所をその都度ガードする
  • ビルド時にコマンドラインからデプロイメントターゲットを渡す

前者は本来パッケージとして「このバージョン以上のみサポートする」と宣言したい場面でも、ファイル単位・式単位で条件分岐を書かなければならず冗長です。後者はパッケージ利用者側に正しいオプションを要求することになり、パッケージが単体で自分のサポート範囲を表現できていません。

また、依存パッケージと利用側パッケージのデプロイメントターゲットが食い違っているケース(たとえば依存側が利用側よりも新しいバージョンを要求する)に対しても、SwiftPM にはこれを検出・診断する仕組みがなく、食い違いはビルド時のエラーとして初めて表面化していました。パッケージマネージャとして、この種の互換性は本来マニフェストの情報から事前に検査できるべきものです。

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

PackageDescription に、対応プラットフォームごとの最小デプロイメントターゲットを宣言するための API を追加します。Package のイニシャライザに platforms: 引数が増え、SupportedPlatform の配列で指定します。

let package = Package(
    name: "NIO",
    platforms: [
        .macOS(.v10_13), .iOS(.v12),
    ],
    products: [
        .library(name: "NIO", targets: ["NIO"]),
    ],
    targets: [
        .target(name: "NIO"),
    ]
)

この例は、macOS では 10.13、iOS では 12.0 を最小デプロイメントターゲットとし、それ以外のプラットフォームについてはデフォルトのまま、という意味になります。

デフォルトの挙動と platforms の省略

platforms を指定しない、あるいは一部のプラットフォームのみを列挙した場合、列挙されていないプラットフォームについてはあらかじめ決められたデフォルトが使われます。デフォルト値は「インストールされている SDK がサポートする最も古いデプロイメントターゲット」です。ただし macOS については、この規則の例外として 10.10 を下限とします。

したがって、上の例のように macOS と iOS のみを明示したパッケージは、tvOS や watchOS、Linux など他のプラットフォームでは引き続きデフォルトのデプロイメントターゲットでビルドされます。

バージョンの指定方法

各プラットフォームの API には、あらかじめ用意された定数を取るオーバーロードと、文字列を取るオーバーロードの 2 種類があります。

extension SupportedPlatform.MacOSVersion {
    static let v10_10: MacOSVersion
    static let v10_11: MacOSVersion
    static let v10_12: MacOSVersion
    // ...
}

// 定数での指定
.macOS(.v10_13)

// 文字列での指定(ドット区切りの細かいバージョンや、
// PackageDescription にまだ定数が用意されていない新バージョンを指定したいとき)
.macOS("10.13.4")

文字列の書式は各プラットフォームの API ドキュメントに従います。不正な値が渡された場合はマニフェスト解析時のエラーとして診断されます。空の配列、同じプラットフォームの重複宣言など、意味的に無効な入力も同様にエラーになります。

依存パッケージとの整合性チェック

SwiftPM は、依存パッケージのデプロイメントターゲットが利用側パッケージのそれ以下であることを要求します。プラットフォームごとに、「依存側 ≤ 利用側」が成り立たない場合はエラーとして報告されます。

各パッケージはそれぞれ自身が宣言したデプロイメントターゲットでコンパイルされます。原理的には、互換性が保証されている以上、グラフ全体をトップレベルパッケージのデプロイメントターゲットでコンパイルすることもできますが、そうすると依存側のコードで「より新しい OS 向けにビルドしている」ことによる警告が多数出る可能性があるため、各パッケージ固有の値が使われます。

また、デプロイメントターゲットの変更は、利用側パッケージのビルドを壊しうる変更なので、セマンティックバージョニング上はメジャーバージョンの変更として扱うべきです。

Xcode プロジェクトとの連携

swift package generate-xcodeproj で生成される Xcode プロジェクトには、宣言したプラットフォームごとのデプロイメントターゲットがそのまま反映されます。

既存パッケージへの影響

platforms を指定しない既存パッケージの挙動は、これまでと変わりません(macOS は SwiftPM が選んだデフォルト、その他は SDK のデフォルト)。この新 API は対応する Swift tools version でガードされるため、新機能を使いたいパッケージはマニフェストの // swift-tools-version:... を該当バージョン以上に引き上げる必要があります。

Future Directions

この提案はあくまで「Apple プラットフォーム向けに最小デプロイメントターゲットのバージョンを宣言する」ところまでをスコープとしています。以下は将来的に別提案として検討される可能性がある方向性であり、現時点で実現が約束されているものではありません。

  • Linux / Windows / Android など非 Apple プラットフォームへの対応: Swift コンパイラはこれらのプラットフォームをサポートしていますが、ランタイムのアベイラビリティチェックは現状 Apple プラットフォーム中心です。コミュニティやランタイムのサポートの広がりに応じて、Windows の最小バージョンや Android の API レベルを宣言できるよう API を拡張することが考えられます。
  • サポートするプラットフォームの制限: 特定のプラットフォーム専用パッケージを表現するために、対応プラットフォームそのものを絞り込む仕組みは、この提案とは別に検討されます。
  • ターゲット/プロダクト/依存のプラットフォーム別指定: 「この target は Linux でのみビルドしたい」「この依存は特定プラットフォームでのみ使いたい」といった粒度の細かい制御も、別提案として扱われる予定です。