Introduction
Swift Package Manager's dependency resolver uses a SAT solver to find a version of every package that satisfies all constraints. When two packages require incompatible version ranges of the same dependency, resolution fails. Unlike CocoaPods or Carthage, SPM does not support version overrides easily, making these conflicts particularly frustrating.
Symptoms
Because root depends on PackageA 1.0.0..<2.0.0 which depends on Shared 2.0.0..<3.0.0, version solving failedcannot be resolved because no versions of Shared match the requirementDependencies could not be resolvedin Xcode- Works on one machine but not another due to different
Package.resolved - Adding one new package breaks existing dependencies
Example error:
``
Because every version of PackageA depends on Shared 2.0.0..<3.0.0
and PackageB depends on Shared 1.0.0..<2.0.0,
PackageA is incompatible with PackageB.
And because root depends on both, version solving failed.
Common Causes
- Two packages depend on different major versions of the same library
- A package pins a dependency to an exact version instead of a range
- Transitive dependencies create an impossible version intersection
Package.resolvedis out of sync withPackage.swift- Local package overrides conflict with remote resolution
Step-by-Step Fix
- 1.Inspect the dependency graph:
- 2.```bash
- 3.# Show the resolved versions
- 4.swift package show-dependencies
# Dump the full package graph swift package dump-package
# Check what each package requires swift package resolve ```
- 1.Update Package.resolved:
- 2.```bash
- 3.# Remove and regenerate
- 4.rm Package.resolved
- 5.swift package resolve
# Or update specific packages swift package update swift package update Shared # Update the conflicting package ```
- 1.Use .package(url: .upToNextMajor) for flexibility:
- 2.```swift
- 3.// Package.swift
- 4.let package = Package(
- 5.name: "MyApp",
- 6.dependencies: [
- 7.// Use ranges instead of exact versions
- 8..package(url: "https://github.com/org/PackageA.git", .upToNextMajor(from: "1.0.0")),
- 9..package(url: "https://github.com/org/PackageB.git", .upToNextMajor(from: "2.0.0")),
- 10.]
- 11.)
- 12.
` - 13.Override a dependency version (Swift 5.7+):
- 14.```swift
- 15.let package = Package(
- 16.name: "MyApp",
- 17.dependencies: [
- 18..package(url: "https://github.com/org/PackageA.git", from: "1.0.0"),
- 19..package(url: "https://github.com/org/PackageB.git", from: "2.0.0"),
- 20.// Override the shared dependency version
- 21..package(url: "https://github.com/org/Shared.git", from: "2.5.0"),
- 22.]
- 23.)
- 24.
` - 25.Fork and patch the conflicting package:
- 26.```swift
- 27.// If Shared 2.x is required by PackageA and you need Shared 1.x for PackageB,
- 28.// fork PackageB and update its dependency:
- 29.// In your forked PackageB's Package.swift:
- 30..package(url: "https://github.com/org/Shared.git", .upToNextMajor(from: "2.0.0"))
// Then use your fork: .package(url: "https://github.com/you/PackageB.git", branch: "main") ```
Prevention
- Regularly run
swift package updateto avoid version drift - Commit
Package.resolvedto version control for reproducibility - Prefer
.upToNextMajorover exact version pins - Audit transitive dependencies before adding new packages
- Use
swift package editfor local development of dependencies - Monitor package maintainers for version compatibility updates