Swift Package Manager
01 / 02

Package.swift, Dependencies & Versioning

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