Two dependencies need incompatible versions of a third. What does the resolver say, and how do you get out?
Predict first
What does the resolver report when A needs Lib 1.x and B needs Lib 2.x?
// === run.sh ===
export GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@t GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@t
W=$(mktemp -d); cd $W
mk() { mkdir -p $1/Sources/$1; (cd $1; git init -q -b main
cat > Package.swift <<E
// swift-tools-version: 6.0
import PackageDescription
let package = Package(name: "$1", products: [.library(name: "$1", targets: ["$1"])], dependencies: [$3],
targets: [.target(name: "$1", dependencies: [$4])])
E
echo "public let $1Version = \"$2\"" > Sources/$1/V.swift; git add -A; git commit -qm $2; git tag $2); }
mk Lib 1.0.0 "" ""
(cd Lib; echo 'public let more = 1' > Sources/Lib/M.swift; git add -A; git commit -qm 2; git tag 2.0.0)
mk A 1.0.0 ".package(url: \"file://$W/Lib\", from: \"1.0.0\")" ".product(name: \"Lib\", package: \"Lib\")"
mk B 1.0.0 ".package(url: \"file://$W/Lib\", from: \"2.0.0\")" ".product(name: \"Lib\", package: \"Lib\")"
mkdir -p App/Sources/App; cd App
cat > Package.swift <<E
// swift-tools-version: 6.0
import PackageDescription
let package = Package(name: "App",
dependencies: [.package(url: "file://$W/A", from: "1.0.0"), .package(url: "file://$W/B", from: "1.0.0")],
targets: [.executableTarget(name: "App", dependencies: ["A", "B"])])
E
echo 'import A' > Sources/App/main.swift
swift package resolve 2>&1 | grep -E "^error|incompatible|practically" | sed "s#$W#.#g"
- An error naming b as incompatible with a, with lib 1.0.0..<2.0.0 against 2.0.0..<3.0.0
- It silently picks 2.0.0 and A breaks later
- It builds both versions of Lib side by side
Mental model. Two guests each insist on a different edition of the same dictionary, and the table only has room for one. Either one guest moves to the other edition or somebody brings a second table (a fork).
SwiftPM has one copy of each package in the graph (it does not nest versions like npm), so every requirement on a package must be satisfiable by a single version. In the check, package A 1.0.0 depends on Lib from 1.0.0 (so 1.0.0 ..< 2.0.0) and B 1.0.0 on Lib from 2.0.0 (2.0.0 ..< 3.0.0); the root needs both and resolution fails with an error that reads like a proof: “b is incompatible with a because a 1.0.0 depends on lib 1.0.0..<2.0.0 … b practically depends on lib 2.0.0..<3.0.0”. The message names the shortest chain, which is the part to read: it tells you which two packages disagree and over which range.
Ways out, in order of preference: upgrade the older dependency (a newer A may allow Lib 2), downgrade the newer one if a release of B accepts Lib 1, ask the maintainer to widen the range, or fork and relax it yourself (pointing at your fork by URL or revision, with the maintenance cost that implies). A dependency should not pin tighter than it must: libraries use a wide from: range, and only apps pin (through the resolved file). If both versions must truly coexist, SwiftPM cannot do it for different versions of one package, but module aliases (SE-0339, not run here) address two packages that each contain a module with the same name, which is a different problem from a version clash. Binary frameworks add the same issue at link time, as duplicate symbols (Q367).
One version per package is the rule; the conflict is reported at resolution time.
Common traps
- Tightening a library’s range to
exact:. It turns every shared dependency into a likely conflict for your consumers. - Skimming past the error’s middle lines. They name the exact packages and ranges; the fix is usually visible there.
They might follow up with
- Why can the same two ranges work in npm? npm can install both majors side by side; SwiftPM links one module per package identity.
- Is a conflict ever caused by a platform requirement? That is a different error (Q624).
Related questions
- 16.7.1 How do from:, upToNextMinor, exact, ranges and branch requirements differ, and which version does the…
- 16.7.2 What is Package.resolved, when does it change, and how do you make CI resolve exactly what developers tested?
- 16.2.1 How do Swift Package Manager, CocoaPods, Carthage and XCFrameworks compare, and how do you choose?
- 16.3.1 How do static and dynamic linking differ for an app, what do they cost at launch and in size, and what are…
- 16.3.2 Why must the module dependency graph be acyclic, how do you layer it, and what does its shape do to build…
← What is Package.resolved, when does it change, and how do you make CI… Why does SwiftPM refuse a dependency that uses unsafeFlags, and what… →