Skip to Content
Course content

99: Package.swift Manifest In Depth

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Think of your Package.swift file as a detailed Bill of Materials (BOM) and an assembly manual for a custom-built PC. When you're building a high-end rig, you don't just throw parts in a box. You have a list of specific components you need to order from external vendors (your dependencies), a plan for how those parts are grouped together—like the CPU and RAM fitting into the motherboard (your targets), and finally, the finished machine itself, which might be a "Gaming Rig" or a "Workstation" depending on how you present it to the user (your products).

In Swift, the Package.swift manifest is exactly that. It tells the Swift Package Manager (SPM) how to fetch the code, how to compile it, and what the resulting output should be. I've seen a lot of developers treat this file as a "black box" that they just copy-paste from StackOverflow, but once you understand the hierarchy, you can control exactly how your code is exposed and shared.

The Blueprint of a Package

Let's look at a real-world manifest for a hypothetical package called SecureVault—a tool designed to handle encrypted local storage. I'll walk you through the code, then we'll map it back to our PC analogy.

import PackageDescription

let package = Package(
    name: "SecureVault",
    platforms: [
        .iOS(.v15), .macOS(.v12)
    ],
    products: [
        .library(name: "SecureVault", targets: ["SecureVault"]),
        .executable(name: "vault-cli", targets: ["VaultCLI"])
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-crypto.git", from: "1.0.0"),
        .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.0"),
    ],
    targets: [
        .target(
            name: "SecureVault",
            dependencies: [
                .product(name: "CryptoKit", package: "swift-crypto")
            ]
        ),
        .executableTarget(
            name: "VaultCLI",
            dependencies: [
                "SecureVault",
                .product(name: "ArgumentParser", package: "swift-argument-parser")
            ]
        ),
        .testTarget(
            name: "SecureVaultTests",
            dependencies: ["SecureVault"]
        ),
    ]
)

Defining the Final Product

In the analogy, the products section is the "finished machine." You might have the same pile of parts, but you can package them differently. In our SecureVault example, we have two products: a .library and an .executable.

The library is what other developers will import into their apps. The executable is a standalone command-line tool. Notice how the library product points to the SecureVault target, while the CLI points to the VaultCLI target. You're essentially telling SPM: "When someone asks for the library, give them this specific slice of my code."

Ordering Parts from Vendors

The dependencies array is your shopping list. This is where you tell Swift exactly which external repositories to clone and which versions are acceptable. I usually prefer using from: "1.0.0" because it follows Semantic Versioning, allowing SPM to grab the latest bug fixes (patch versions) without breaking your build. If you need a very specific commit or a branch, you can do that too, but stick to versions unless you're fixing a critical bug in a dependency that hasn't been released yet.

Assembling the Internal Org Chart

This is where most people get tripped up: the targets. If dependencies is the shopping list, targets is the assembly process. A target is a collection of source files that get compiled together.

  • The Core Target: SecureVault is our main logic. It depends on CryptoKit, which we "ordered" in the dependencies section.
  • The CLI Target: VaultCLI is a separate target. It doesn't just need the external ArgumentParser; it also depends on our own SecureVault target. This is how you build modularity within a single package.
  • The Test Target: Always keep your tests in their own target. It ensures that your testing code doesn't accidentally end up in the final production binary.

One thing I've learned the hard way: remember that the name of the target must match the folder name inside your Sources/ directory. If you name your target "SecureVault" but your folder is called "VaultCore", SPM will throw a fit and tell you it can't find the source files.




📋 Practical Task

Manifest Construction: Building the "NetWatch" Utility

You are tasked with creating the Package.swift manifest for a new project called NetWatch. This project needs to monitor network latency and report it via a CLI tool.

Your manifest must meet the following technical requirements:

  • Platforms: Support macOS v13 and later.
  • External Dependencies:
    • Add the swift-log package (url: https://github.com/apple/swift-log.git) starting from version 1.0.0.
    • Add the swift-argument-parser package (url: https://github.com/apple/swift-argument-parser.git) starting from version 1.2.0.
  • Targets:
    • A target named NetWatchCore that depends on the Logging product from the swift-log package.
    • An executable target named netwatch-cli that depends on both NetWatchCore and the ArgumentParser product from the swift-argument-parser package.
    • A test target named NetWatchTests that depends on NetWatchCore.
  • Products:
    • A library product named NetWatch that exposes the NetWatchCore target.
    • An executable product named netwatch that exposes the netwatch-cli target.

Write the full Package.swift code that implements this architecture.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.