How do from:, upToNextMinor, exact, ranges and branch requirements differ, and which version does the resolver pick?
Predict first
Which version does each requirement resolve to against tags 1.0.0, 1.1.0, 1.2.0, 2.0.0 and an untagged main?
// === 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
mkdir -p Lib/Sources/Lib && cd Lib && git init -q -b main
cat > Package.swift <<'E'
// swift-tools-version: 6.0
import PackageDescription
let package = Package(name: "Lib", products: [.library(name: "Lib", targets: ["Lib"])], targets: [.target(name: "Lib")])
E
for v in 1.0.0 1.1.0 1.2.0 2.0.0; do
echo "public let version = \"$v\"" > Sources/Lib/V.swift; git add -A; git commit -qm $v; git tag $v
done
echo 'public let version = "main-dev"' > Sources/Lib/V.swift; git commit -qam dev
cd ..
i=0
for req in 'from: "1.0.0"' '.upToNextMinor(from: "1.0.0")' 'exact: "1.1.0"' '"1.0.0"..<"2.0.0"' 'branch: "main"'; do
i=$((i+1)); mkdir -p App$i/Sources/App
cat > App$i/Package.swift <<E
// swift-tools-version: 6.0
import PackageDescription
let package = Package(name: "App", dependencies: [.package(url: "file://$W/Lib", $req)],
targets: [.executableTarget(name: "App", dependencies: [.product(name: "Lib", package: "Lib")])])
E
printf 'import Lib\nprint(version)\n' > App$i/Sources/App/main.swift
echo "$req -> $(cd App$i; swift run 2>&1 | tail -1)"
done
- from → 1.2.0, upToNextMinor → 1.0.0, exact → 1.1.0, range → 1.2.0, branch → main-dev
- from → 2.0.0, because it means "or newer"
- every rule resolves to 1.0.0 until you run update
Mental model. A requirement is a shopping list that says “any cereal from this aisle”; the resolver picks the newest box that fits, and Package.resolved is the receipt that makes next week’s shop buy the same box.
A requirement is a range over semantic versions, and the resolver picks the highest version that satisfies every requirement in the graph. In the check, a library tagged 1.0.0, 1.1.0, 1.2.0, 2.0.0 plus an untagged commit on main resolves to 1.2.0 for from: "1.0.0" (that means 1.0.0 ..< 2.0.0, never 2.0.0) and for the explicit range "1.0.0"..<"2.0.0"; to 1.0.0 for .upToNextMinor(from: "1.0.0") (1.0.0 ..< 1.1.0); to exactly 1.1.0 for exact:; and to the tip of main for branch: "main". revision: pins a commit hash. The default for a library that follows semver is from:: you get fixes and additions but are protected from the major version that is allowed to break source compatibility.
Two caveats. A branch or revision requirement is not a version: a package that depends on you cannot use you through it (SwiftPM rejects it, a rule I did not run here), so branches are for a root app while you wait for a release. And semver is a promise, not a guarantee: a minor release of a dependency can still break your build or behaviour, which is why the resolved file, not the range, is what you commit. Pre-1.0 packages are the weak spot: from: "0.4.0" allows 0.5.0, and pre-1.0 authors often break things in minors, so prefer .upToNextMinor for 0.x dependencies. The lab applies each rule to a tag list.
from: and a half-open range stop before the next major; upToNextMinor stops before the next minor; exact is one version; branch tracks a moving commit.
Common traps
- Reading
from: "1.0.0"as “1.0.0 or newer, including 2.x”. It stops before the next major. - Shipping a library that depends on a branch. Consumers cannot resolve it by version; use a tag.
They might follow up with
- How do you try an unreleased fix? Point the root app at a branch or revision (or a local
path:checkout) temporarily and switch back once it is tagged. - What does the resolver do with several requirements on one package? It intersects the ranges and picks the highest version inside the intersection, or reports a conflict (next question).
Related questions
- 16.7.2 What is Package.resolved, when does it change, and how do you make CI resolve exactly what developers tested?
- 16.7.3 Two dependencies need incompatible versions of a third. What does the resolver say, and how do you get out?
- 16.1.1 What are targets, products and modules in a Swift package, and what does a first Package.swift look like?
- 16.2.1 How do Swift Package Manager, CocoaPods, Carthage and XCFrameworks compare, and how do you choose?
What is Package.resolved, when does it change, and how do you make CI… →