Swift Package Manager: Package.swift, Dependencies & Versioning
Swift Package Manager (SPM) is Apple's built-in dependency manager, shipped with the Swift toolchain and integrated into Xcode — no separate gem/tool install, no generated workspace files to manage or commit.
The Manifest
// swift-tools-version must be the first line
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "MyLibrary",
platforms: [.iOS(.v15), .macOS(.v12)], // minimum deployment targets
products: [
// What consumers can actually import/link against
.library(name: "MyLibrary", targets: ["MyLibrary"]),
],
dependencies: [
// Version-range requirement — SemVer-based
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.8.0"),
// Exact pin
.package(url: "https://github.com/apple/swift-collections.git", exact: "1.1.0"),
],
targets: [
// Internal build unit — imports MyLibrary's dependencies
.target(
name: "MyLibrary",
dependencies: [
.product(name: "Alamofire", package: "Alamofire"),
],
resources: [.process("Resources")]
),
.testTarget(
name: "MyLibraryTests",
dependencies: ["MyLibrary"]
),
]
)Version Requirements
// from: — up-to-next-major, the common default (SemVer compatible updates)
.package(url: "...", from: "1.2.0") // >= 1.2.0, < 2.0.0
// Explicit range operators
.package(url: "...", "1.2.0"..<"1.5.0")
.package(url: "...", .upToNextMinor(from: "1.2.0")) // >= 1.2.0, < 1.3.0
.package(url: "...", exact: "1.2.0")
// Tracking a branch/commit — bypasses SemVer resolution entirely.
// Fine for temporary debugging, risky for a committed production dependency
// since Package.resolved just records whatever commit was HEAD at resolve time.
.package(url: "...", branch: "main")
.package(url: "...", revision: "a1b2c3d")
// Local package — for developing a library alongside its consumer app,
// changes reflected immediately, no need to tag/push a release first
.package(path: "../MyInternalKit")
// SPM's resolver is unified: only one version of a given package can
// be resolved across the entire dependency graph. Two dependencies
// requiring incompatible major versions of the same transitive package
// fail to resolve until reconciled.Package.resolved
Records the exact resolved version (or commit) of every dependency — direct and transitive — actually fetched.
Commit it to source control, like Podfile.lock or package-lock.json, so every teammate and CI run builds against identical versions.
`swift package resolve` fetches according to the existing Package.resolved (or resolves fresh if none exists) — safe, reproducible, what CI should run.
`swift package update` deliberately re-resolves to the newest versions the manifest's constraints allow, rewriting Package.resolved — run intentionally, then review the diff.
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free