Skip to main content

Swift Package Manifest

The Swift Package Manifest, typically defined in a file named Package.swift, is the blueprint of your Swift package. It defines essential information about your package, including its name, dependencies, targets, and products. Understanding how to properly configure this file is critical for creating, sharing, and using Swift packages effectively.

What is a Package Manifest?

A package manifest is a Swift file that uses the Swift Package Manager's declarative API to define the structure and requirements of your package. The manifest file:

  • Describes your package's basic information
  • Declares dependencies on other packages
  • Defines the targets (modules) that make up your package
  • Specifies the products (libraries or executables) that your package produces
  • Sets build settings, platform requirements, and other configurations

Basic Manifest Structure

Every Package.swift file starts with the Swift tools version declaration and imports the PackageDescription module:

swift
// swift-tools-version: 5.7
import PackageDescription

let package = Package(
name: "MyPackage",
// Additional package configuration...
)

The swift-tools-version comment at the top is mandatory and tells Swift which version of the Package Manager tools should be used to interpret the manifest file. The version you choose determines which features of the Package Manager's API are available to you.

Essential Package Properties

Let's explore the core properties that define a Swift package:

Name

The name of your package is a required property. It should be unique and descriptive:

swift
let package = Package(
name: "NetworkUtilities"
)

Platforms

You can specify which platforms your package supports:

swift
let package = Package(
name: "MyPackage",
platforms: [
.macOS(.v12),
.iOS(.v15),
.watchOS(.v8),
.tvOS(.v15)
]
)

Products

Products are the libraries or executables that your package makes available to other packages or applications:

swift
let package = Package(
name: "MyPackage",
products: [
.library(
name: "MyLibrary",
targets: ["MyTarget"]
),
.executable(
name: "MyTool",
targets: ["MyExecutable"]
)
]
)

Dependencies

Dependencies are other Swift packages that your package relies on:

swift
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "https://github.com/apple/swift-algorithms", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-argument-parser", .upToNextMajor(from: "1.2.0")),
.package(url: "https://github.com/apple/swift-log.git", .branch("main"))
]
)

Targets

Targets define the modules of your package. Each target can have its own source files, dependencies, and resources:

swift
let package = Package(
name: "MyPackage",
targets: [
.target(
name: "MyLibrary",
dependencies: [
"ExternalDependency",
.product(name: "Algorithms", package: "swift-algorithms")
]
),
.testTarget(
name: "MyLibraryTests",
dependencies: ["MyLibrary"]
)
]
)

Complete Example

Here's a complete example of a package manifest file with commonly used properties:

swift
// swift-tools-version: 5.7
import PackageDescription

let package = Package(
name: "ImageProcessor",
platforms: [
.macOS(.v12),
.iOS(.v15)
],
products: [
.library(
name: "ImageProcessor",
targets: ["ImageProcessor"]
),
.executable(
name: "ImageProcessorCLI",
targets: ["ImageProcessorCLI"]
)
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.0"),
.package(url: "https://github.com/example/ImageUtilities", .upToNextMajor(from: "2.0.0"))
],
targets: [
.target(
name: "ImageProcessor",
dependencies: [
.product(name: "ImageUtilities", package: "ImageUtilities")
],
resources: [
.process("Resources")
]
),
.executableTarget(
name: "ImageProcessorCLI",
dependencies: [
"ImageProcessor",
.product(name: "ArgumentParser", package: "swift-argument-parser")
]
),
.testTarget(
name: "ImageProcessorTests",
dependencies: ["ImageProcessor"],
resources: [
.copy("TestResources")
]
)
]
)

Dependency Management

Swift Package Manager offers several ways to specify dependency versions:

Exact Version

swift
.package(url: "https://github.com/example/package", exact: "1.2.3")

Version Range

swift
.package(url: "https://github.com/example/package", from: "1.0.0") // 1.0.0 and above
.package(url: "https://github.com/example/package", "1.0.0"..<"2.0.0") // Between 1.0.0 and 2.0.0 (exclusive)
.package(url: "https://github.com/example/package", .upToNextMajor(from: "1.0.0")) // Up to next major
.package(url: "https://github.com/example/package", .upToNextMinor(from: "1.2.0")) // Up to next minor

Branch, Revision, or Local Path

swift
.package(url: "https://github.com/example/package", .branch("develop"))
.package(url: "https://github.com/example/package", .revision("abc123"))
.package(path: "../LocalPackage") // Local package

Advanced Target Configuration

Resources

You can include resources in your target:

swift
.target(
name: "MyTarget",
dependencies: [],
resources: [
.process("Resources"), // Processed resources
.copy("RawResources") // Copied as-is
]
)

Target-specific Build Settings

Configure compiler flags and other build settings:

swift
.target(
name: "MyTarget",
dependencies: [],
swiftSettings: [
.define("DEBUG", .when(configuration: .debug)),
.unsafeFlags(["-enable-experimental-feature"], .when(configuration: .debug))
],
linkerSettings: [
.linkedLibrary("z"),
.linkedFramework("CoreData", .when(platforms: [.iOS]))
]
)

Conditionals in Package Manifests

Swift Package Manifests support conditionals for different platforms or configurations:

swift
// swift-tools-version: 5.7
import PackageDescription

let package = Package(
name: "MyPackage",
products: [
.library(name: "MyLibrary", targets: ["MyLibrary"])
],
dependencies: [
// iOS-only dependency
.package(url: "https://github.com/example/iOSOnlyPackage", from: "1.0.0"),
// macOS-only dependency
.package(url: "https://github.com/example/macOSOnlyPackage", from: "1.0.0")
],
targets: [
.target(
name: "MyLibrary",
dependencies: [
.target(name: "SharedCode"),
// Conditionally include platform-specific dependencies
.product(name: "iOSOnlyPackage", package: "iOSOnlyPackage", condition: .when(platforms: [.iOS])),
.product(name: "macOSOnlyPackage", package: "macOSOnlyPackage", condition: .when(platforms: [.macOS]))
]
),
.target(
name: "SharedCode"
)
]
)

Real-World Example: Creating a Network Library

Let's create a complete package manifest for a hypothetical networking library:

swift
// swift-tools-version: 5.7
import PackageDescription

let package = Package(
name: "NetworkKit",
platforms: [
.macOS(.v12),
.iOS(.v14),
.tvOS(.v14)
],
products: [
.library(
name: "NetworkKit",
targets: ["NetworkKit"]
),
.library(
name: "NetworkKitDynamic",
type: .dynamic, // Dynamic library
targets: ["NetworkKit"]
)
],
dependencies: [
.package(url: "https://github.com/apple/swift-log", from: "1.4.2"),
.package(url: "https://github.com/apple/swift-crypto", from: "2.0.0")
],
targets: [
.target(
name: "NetworkKit",
dependencies: [
.product(name: "Logging", package: "swift-log"),
.product(name: "Crypto", package: "swift-crypto")
],
resources: [
.process("Resources/Certificates")
],
swiftSettings: [
.define("ENABLE_ADVANCED_FEATURES", .when(configuration: .release)),
.define("DEBUG_NETWORK_CALLS", .when(configuration: .debug))
]
),
.executableTarget(
name: "NetworkKitDemo",
dependencies: ["NetworkKit"]
),
.testTarget(
name: "NetworkKitTests",
dependencies: ["NetworkKit"],
resources: [
.copy("TestData")
],
swiftSettings: [
.define("TEST_ENVIRONMENT")
]
)
]
)

This package:

  1. Supports multiple platforms with minimum versions
  2. Offers both static and dynamic library products
  3. Depends on Apple's logging and crypto packages
  4. Includes resources for certificates
  5. Sets conditional compilation flags
  6. Provides a demo executable
  7. Configures test targets with their own resources and settings

Best Practices for Package Manifests

  1. Version your dependencies wisely: Use semantic versioning and avoid overly tight constraints.
  2. Keep dependencies lean: Only include what you really need.
  3. Structure targets logically: Split functionality into logical modules.
  4. Set appropriate platform requirements: Don't require newer OS versions than necessary.
  5. Comment your manifest: Document special configurations or requirements.
  6. Use conditional compilation: Apply platform-specific code only where needed.
  7. Test all your targets: Include test targets for each module.

Troubleshooting Common Issues

Dependency Resolution Errors

If Swift Package Manager can't resolve your dependencies, try:

  • Check for version conflicts among transitive dependencies
  • Use swift package resolve --skip-update to see the detailed resolution process
  • Try adding explicit constraints for transitive dependencies

Build Errors

If your package doesn't build:

  • Ensure all targets can access their dependencies
  • Verify platform requirements are consistent
  • Check for missing declarations in target dependencies

Summary

The Swift Package Manifest (Package.swift) is the cornerstone of Swift package development. It defines the structure, dependencies, and products of your package. With a well-crafted manifest, you can create modular, reusable code that integrates seamlessly with other Swift projects.

By mastering the package manifest format, you can:

  • Organize your code into logical modules
  • Share your code efficiently with the Swift community
  • Integrate third-party dependencies with precise versioning
  • Create platform-specific optimizations
  • Configure complex build settings consistently

Additional Resources

Practice Exercises

  1. Create a basic package manifest for a utility library with a single target
  2. Modify an existing manifest to add a new target that depends on the original one
  3. Add a third-party dependency to your package
  4. Create a package with platform-specific code using conditional dependencies
  5. Build a manifest for a command-line tool that uses ArgumentParser


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)