Swift Digest
SE-0273 | Swift Evolution

Package Manager Conditional Target Dependencies

Proposal
SE-0273
Authors
David Hart
Review Manager
Boris Buegling
Status
Partially implemented (Swift 5.3 supports platform conditionals, but not configuration conditionals)

01 何が問題だったのか

SwiftPM のターゲット依存関係は、これまでプラットフォームやビルド構成に関係なく常に有効なものとして宣言するしかありませんでした。そのため、次のようなユースケースをうまく表現できません。

  • 低レベルのプラットフォーム固有コードを含むパッケージが、macOS と Linux で別のライブラリに依存したい
  • デバッグビルドでのみデバッグ用ライブラリをリンクしたい(リリースビルドでは不要)
  • リリースビルドでのみ計測用のロジックをリンクしたい(デバッグ時は開発者自身が原因を追えるので不要)

SE-0238 ではすでにビルド設定(definelinkedLibrary など)に対して「このプラットフォーム/この構成のときだけ有効」という条件を付ける仕組み(.when(platforms:configuration:))が入っていますが、ターゲット依存関係の側には同等の仕組みがなく、複数プラットフォームやビルド構成ごとに異なる依存関係を記述できない状態でした。

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

Package.swift のターゲット依存関係に、SE-0238 のビルド設定条件と同じ形式の condition: 引数を付けられるようになります。これにより、プラットフォームやビルド構成ごとにどのターゲット/プロダクトをビルドしリンクするかを切り替えられます。

マニフェストでの書き方

.target(name:).product(name:package:).byName(name:) のいずれにも condition: を渡せるようになります。

// swift-tools-version:5.3

import PackageDescription

let package = Package(
    name: "BestPackage",
    dependencies: [
        .package(url: "https://github.com/pureswift/bluetooth", .branch("master")),
        .package(url: "https://github.com/pureswift/bluetoothlinux", .branch("master")),
    ],
    targets: [
        .target(
            name: "BestExecutable",
            dependencies: [
                .product(name: "Bluetooth", condition: .when(platforms: [.macOS])),
                .product(name: "BluetoothLinux", condition: .when(platforms: [.linux])),
                .target(name: "DebugHelpers", condition: .when(configuration: .debug)),
            ]
        ),
        .target(name: "DebugHelpers")
    ]
)

この例では、Bluetooth は macOS ビルドでのみ、BluetoothLinux は Linux ビルドでのみリンクされ、DebugHelpers はデバッグビルドでのみリンクされます。

依存解決には影響しない

重要なのは、この条件は「ビルド時に何をコンパイル・リンクするか」だけに効くという点です。依存解決(パッケージグラフの解決)自体には影響せず、上の例でも BluetoothBluetoothLinux の両パッケージは常に解決されます。プラットフォームに応じて実際にビルド・リンクされるものが絞り込まれるだけです。

新しい PackageDescription API

Target.Dependency の各ケースに、オプションの TargetDependencyCondition を受け取るファクトリが追加されます。型は BuildSettingCondition と同じ API を持ちますが、将来それぞれ独立に進化できるよう別型として切り出されています。

extension Target.Dependency {
    @available(_PackageDescription, introduced: 5.3)
    public static func target(
        name: String,
        condition: TargetDependencyCondition? = nil
    ) -> Target.Dependency

    @available(_PackageDescription, introduced: 5.3)
    public static func product(
        name: String,
        package: String? = nil,
        condition: TargetDependencyCondition? = nil
    ) -> Target.Dependency

    @available(_PackageDescription, introduced: 5.3)
    public static func byName(
        name: String,
        condition: TargetDependencyCondition? = nil
    ) -> Target.Dependency
}

条件の書き方は SE-0238 と同じで、.when(platforms:configuration:) の形でプラットフォームと構成(.debug / .release)を指定します。両方省略した nil は「常に有効」と同じ意味になります。

既存パッケージへの影響

すべての変更は新しい tools version でゲートされるため、既存パッケージに影響はありません。SwiftPM は tools version が異なるパッケージが混在するグラフも扱えるので、ライブラリ作者は利用者への影響を抑えながら段階的に新 API へ移行できます。

実装状況に関する注意

Swift 5.3 時点の実装では、このうちプラットフォーム条件(.when(platforms:))のみが有効になっており、構成条件(.when(configuration:))は未実装です。デバッグ/リリースでの依存切り替えを行いたい場合は、対応バージョンの SwiftPM が出るまで待つ必要があります。