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
ShapeBest forCost
Xcode projectAn Apple app with resources, assets and signingA large binary project file
Swift packageLibraries, CLI tools, server codeNo app signing or asset catalog workflow
Package plus app targetMost real apps: logic in a package, thin app shellSlightly more setup
PlaygroundExploring an APIPoor 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.
  • swiftLanguageModes selects 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. Commit Package.resolved so 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.

Packaging, build configuration and distribution Swift 6 concurrency: tasks, actors and Sendable

Last refreshed 2026-09-18.