Setting up Swift: Xcode, Swift Package Manager and Swift 6
Pick a toolchain, decide between an Xcode project and a Swift package, read a Package.swift manifest, and build and run from the command line.
Toolchain and project choices
# install a toolchain from swift.org, or use the one inside Xcode
swift --version
swiftly install latest # a version manager, keeps several toolchains side by side
swiftly use 6.0.3
xcrun --sdk macosx --show-sdk-version| Shape | Best for | Cost |
|---|---|---|
| Xcode project | An Apple app with resources, assets and signing | A large binary project file |
| Swift package | Libraries, CLI tools, server code | No app signing or asset catalog workflow |
| Package plus app target | Most real apps: logic in a package, thin app shell | Slightly more setup |
| Playground | Exploring an API | Poor fit for structured code |
A common and healthy layout is a thin app target that imports one or more local packages. The logic becomes testable from the command line and can be reused by a server target without duplicating files.
Reading a Package.swift
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "Reports",
platforms: [.macOS(.v14), .iOS(.v17)],
products: [
.library(name: "ReportsCore", targets: ["ReportsCore"]),
.executable(name: "reports-cli", targets: ["ReportsCLI"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.0"),
.package(path: "../SharedModels"),
],
targets: [
.target(
name: "ReportsCore",
dependencies: [.product(name: "SharedModels", package: "SharedModels")],
swiftSettings: [.enableUpcomingFeature("ExistentialAny")]
),
.executableTarget(
name: "ReportsCLI",
dependencies: ["ReportsCore", .product(name: "ArgumentParser", package: "swift-argument-parser")]
),
.testTarget(name: "ReportsCoreTests", dependencies: ["ReportsCore"]),
],
swiftLanguageModes: [.v6]
)- The tools version comment on the first line decides the manifest API you can use; it is not a comment the compiler ignores.
swiftLanguageModesselects the language mode per package, so you can adopt Swift 6 one module at a time.- A version range with
from:is a range, not a pin. CommitPackage.resolvedso everyone builds the same graph. .product(name:package:)is required for a dependency whose product name differs from the package name.
Building without Xcode
swift package init --type library
swift build # debug
swift build -c release
swift test --parallel
swift run reports-cli --help
swift package show-dependencies --format json
swift package resolve # after editing Package.swift
swift package diagnose-api-breaking-changes 1.2.0💡
The command line is the fastest feedback loop for non-UI code. Run the tests through
swift test in CI and keep the Xcode target for the app shell only — a package-based test suite runs in seconds rather than booting a simulator.FAQ
Should I put everything in one Swift package?
Split where there is a real seam: domain, networking and a UI module. One module per concept is enough; a dozen packages for a small app creates versioning work with no benefit.
How do I use Swift 6 without rewriting everything?
Keep the package in Swift 5 language mode, enable the upcoming features one at a time, and fix the warnings they produce. Move to
swiftLanguageModes: [.v6] once the warnings are gone.Related
Packaging, build configuration and distribution Swift 6 concurrency: tasks, actors and Sendable
Last refreshed 2026-09-18.