What does the platforms: array in Package.swift mean, and why can a dependency stop your build?

16.8.2 · L3 · Interview

Predict first

What happens when a macOS 13 root depends on a package that needs macOS 15, and after raising the root?

// === run.sh ===
W=$(mktemp -d); cd $W; mkdir -p Hi/Sources/Hi App/Sources/App
cat > Hi/Package.swift <<'E'
// swift-tools-version: 6.0
import PackageDescription
let package = Package(name: "Hi", platforms: [.macOS(.v15)],
  products: [.library(name: "Hi", targets: ["Hi"])], targets: [.target(name: "Hi")])
E
echo 'public let x = 1' > Hi/Sources/Hi/H.swift
manifest() { cat > Package.swift <<E
// swift-tools-version: 6.0
import PackageDescription
let package = Package(name: "App", platforms: [.macOS($1)], dependencies: [.package(path: "../Hi")],
  targets: [.executableTarget(name: "App", dependencies: [.product(name: "Hi", package: "Hi")])])
E
}
printf 'import Hi\nprint(x)\n' > App/Sources/App/main.swift
cd App
manifest .v13; echo "macOS 13: $(swift build 2>&1 | grep -E 'error: The package|Build complete')"
manifest .v15; echo "macOS 15: $(swift build 2>&1 | grep -E 'error: The package|Build complete')"
  • macOS 13: error naming minimum platform 15.0 vs 13.0; macOS 15: Build complete
  • both build; the floor is advisory
  • both fail until the dependency is removed

Mental model. The floor is a height limit on the bridge: a vehicle taller than the limit is turned back at the entrance, not allowed to hit the arch half-way across.

platforms: [.macOS(.v13), .iOS(.v16)] declares the minimum deployment version this package supports on each platform. A platform you do not list gets a default floor from the tools version (SwiftPM assigns old defaults, so a library that uses a newer API needs to declare a higher floor or guard with @available). Floors are checked across the graph: a package cannot depend on another that needs a newer platform than it declares. In the check, a root that supports macOS 13 depending on a package that supports macOS 15 fails with “The package product 'Hi-product' requires minimum platform version 15.0 for the macOS platform, but this target supports 13.0”, and raising the root to .v15 builds. The error is a deliberate guard against linking code that would call APIs missing on the oldest OS you claim to support.

In an Xcode app the same idea appears as the target’s deployment target (IPHONEOS_DEPLOYMENT_TARGET): adding a package that needs iOS 17 to an iOS 16 app target is rejected the same way. Practical rules. Libraries should declare the lowest version they truly support (every raise forces all consumers up) and use @available and #available for newer APIs; apps raise the deployment target deliberately, which is a product decision (it drops users) and not a way to silence the error. Check the floor before adding a dependency (its manifest, or swift package dump-package, Q622), and treat a dependency that raises its floor in a minor release as a breaking change for you. For a multi-platform package, remember that platforms: constrains only the platforms you list; Linux and Windows have no such floor, so code that must run there needs #if os() conditions.

SwiftPM compares platform floors across the package graph.

Common traps

  • Raising a library’s platform floor in a patch release. Every app on the old OS stops resolving.
  • Fixing the error by lowering the dependency’s floor in a fork. It hides real missing-API crashes.

They might follow up with

  • How does this relate to @available? The floor is the whole package; @available gates single declarations above it.
  • What if only one platform needs the newer dependency? Use a conditional dependency with .when(platforms:).

Related questions

  • 16.8.1 How do swiftSettings and their .when conditions work in a package, and which targets do they reach?
  • 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.4.1 What are build configurations and build settings, how do xcconfig files layer, and how does #if DEBUG work?

← How do swiftSettings and their .when conditions work in a package…   How do per-target Swift language modes let a package migrate to Swift… →