Swift Digest
SE-0534 | Swift Evolution

build metadataを含むバージョン識別子に対するオプトインの厳密一致

Opt-in exact matching for version identifiers with build metadata

Proposal
SE-0534
Authors
Byoungchan Lee
Review Manager
Franz Busch
Status
Rejected

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

01 何が問題だったのか

Swift Package Manager(SwiftPM)はバージョンの優先順位付けに Semantic Versioning 2.0.0 を採用しています。SemVer では、バージョン番号のうち + から始まる build metadata(例: 1.0.0+debug+debug 部分)は、バージョンの優先順位を比較するときには無視するという決まりになっています。SwiftPM もこのルールに従っており、範囲(range)指定でバージョンを比較する場面ではこの挙動が正しく機能します。

しかし、build metadata は単なる順序付けの対象外というだけでなく、公開された成果物の「バリエーション」を区別するために使われることがあります。

  • 1.0.0+debug1.0.0+release
  • 1.0.0+vendor.1
  • 1.0.0+corp.20250324

これらは同じ 1.0.0 として同じ優先順位を共有しつつ、build metadata で別物であることを表しています。ところが SwiftPM は exact 指定でも build metadata を無視して照合するため、.exact("1.0.0+debug") と書いても、その build metadata まで含めて 1.0.0+debug が確実に選ばれる保証がありません。つまり、「特定の公開バリエーションを名指しで選ぶ」という用途には対応できていませんでした。

代わりの手段はいずれも筋が良くありません。

  • revision: はバージョンレベルのセレクタではなく、レジストリ依存(registry dependency)では使えません。
  • 1.0.0-debug のような pre-release バージョンは、build metadata とは意味が異なるうえ、バージョンの順序まで変えてしまいます。
  • バリエーションごとにパッケージ名を分けると、依存グラフが断片化してしまいます。

既存の依存解決のセマンティクスを変えずに、特定の公開バリエーションを明示的に選べる、小さくはっきりした仕組みが求められていました。

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

マニフェストで build metadata まで含めて厳密に一致させるための、オプトインの新しい要件コンストラクタ .exactLiteral(...) を追加します。

.exactLiteral("1.0.0+debug")

セマンティクスは次のように整理されます。

  • .exact(...): 従来どおりの挙動で、照合時に build metadata を無視します。
  • .exactLiteral(...): build metadata まで含めて識別子全体を一致させます。
  • 範囲(range)指定: 変更なし。

これにより、既存のマニフェストはソース互換・挙動互換のまま保たれ、必要な場合だけ build metadata を考慮した選択にオプトインできます。

新しい要件ケースとオーバーロード

SwiftPM は、source-control 依存とレジストリ依存の双方に exactLiteral ケースを追加します。

extension Package.Dependency {
    public enum SourceControlRequirement {
        case exact(Version)
        case exactLiteral(Version)
        case range(Range<Version>)
        case revision(String)
        case branch(String)
    }

    public enum RegistryRequirement {
        case exact(Version)
        case exactLiteral(Version)
        case range(Range<Version>)
    }
}

あわせて、これらの要件型を直接受け取る package(...) のオーバーロードも追加されます。

dependencies: [
    .package(url: "https://example.com/Foo.git", .exactLiteral("1.0.0+debug")),
    .package(id: "mona.Bar", .exactLiteral("2.1.3+vendor.1")),
]

依存解決での扱い

  • .exact(R) は従来どおりの semantic-exact 一致(build metadata を無視した一致)を維持します。
  • .exactLiteral(R) は、候補バージョンが build metadata まで含めて R と完全に同一の場合にのみ一致します。
  • バージョンの順序付けや範囲への包含判定は、引き続き build metadata を無視します。

この結果、要件同士の整合は次のようになります。

  • .exact("1.0.0").exactLiteral("1.0.0+debug") は両立し、リテラル側の要件によって選択が 1.0.0+debug に絞り込まれます。
  • .exactLiteral("1.0.0+debug").exactLiteral("1.0.0+release") は両立せず、競合になります。

解決に失敗した場合、診断には要求された識別子の全体(build metadata を含む)が表示され、どの metadata バリエーションが競合しているかが分かるようになります。Package.resolved はすでにバージョン識別子を完全な形で記録しているため、この提案によるスキーマ変更は不要です。

既存パッケージへの影響

既存のマニフェストはそのまま変わりません。.exact(...) と範囲指定は従来の挙動を保ち、新しい挙動を観測するのは .exactLiteral(...) にオプトインしたパッケージだけです。他のマニフェスト API と同様に、.exactLiteral(...) の利用は tools version で gating できます。