How to automate Swift Package creation with Package Editor Commands

A practical way to automate Swift Package with Swift 6.0 using the command line
Andrea Scuderi - 8/19/24

Swift packages are a powerful way to modularize your code, enabling you to manage dependencies, share code across multiple projects, and organize your codebase more effectively. While tools like Xcode provide a graphical interface for handling Swift packages, you might prefer or need to manage and edit Swift packages directly from the command line. This approach is beneficial when automating tasks, working on remote servers, or integrating with CI/CD pipelines.

This article walks you through the creation of a Swift package using the command line.

Prerequisites

Before diving in, ensure that you have the following tools installed:

On MacOS

  • Swift 6.0, included in the latest version of Xcode 16 (currently in Beta version)
  • Command line tools, are downloadable at the More downloads section of the Apple developer portal.

Note: The Package Editor Commands have been introduced by SE-0301 and implemented in Swift 6.

Creating a Swift package

To start editing a Swift package, you first need a package. If you don't have one already, you can create a new Swift package using the Swift Package Manager (SPM).

  1. Open your terminal
  2. Create and navigate to the directory where you want to create the package.
mkdir EditSwiftPackage
cd EditSwiftPackage
  1. Run the following command to create a new Swift package:
swift package init --type executable

the command will output a new Package with a basic main code:

Creating executable package: EditSwiftPackage
Creating Package.swift
Creating .gitignore
Creating Sources/
Creating Sources/main.swift

Let's run it to check that it works:

swift run

After the build output, it will output:

Hello, world!

Great, it works!

Opening the package we'll have the following structure:

// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "EditSwiftPackage",
    targets: [
        // Targets are the basic building blocks of a package, defining a module or a test suite.
        // Targets can depend on other targets in this package and products from dependencies.
        .executableTarget(
            name: "EditSwiftPackage"),
    ]
)

Adding a Product to the Package.swift

A package product defines an externally visible build artifact that’s available to clients of a package. *

The add-product command help can be obtained with the following command:

swift package add-product --help

output:

OVERVIEW: Add a new product to the manifest

USAGE: swift package add-product <name> [--type <type>] [--targets <targets> ...]

ARGUMENTS:
  <name>                  The name of the new product

OPTIONS:
  --type <type>           The type of target to add, which can be one of 'executable', 'library', 'static-library', 'dynamic-library', or 'plugin' (default: library)
  --targets <targets>     A list of targets that are part of this product
  --version               Show the version.
  -h, -help, --help       Show help information.

Let's add a product with the name EditSwiftPackage:

swift package add-product EditSwiftPackage --type executable --targets EditSwiftPackage

The command will produce the following output:

Updating package manifest at Package.swift... done.

This is the new Package.swift

// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "EditSwiftPackage",
    products: [
        .executable(
            name: "EditSwiftPackage",
            targets: [ "EditSwiftPackage" ]
        ),
    ],
    targets: [
        // Targets are the basic building blocks of a package, defining a module or a test suite.
        // Targets can depend on other targets in this package and products from dependencies.
        .executableTarget(
            name: "EditSwiftPackage"),
    ]
)

we can test it by running:

swift run EditSwiftPackage

Adding a Dependency to the Swift Package

We want to build a command line tool, to achieve our goal we will add the Swift Argument Parser dependency which implements a type-safe argument parser.

The add-dependency command help can be obtained with the following command:

swift package add-dependency --help

output:

OVERVIEW: Add a package dependency to the manifest

USAGE: swift package add-dependency <dependency> [--exact <exact>] [--revision <revision>] [--branch <branch>] [--from <from>] [--up-to-next-minor-from <up-to-next-minor-from>] [--to <to>]

ARGUMENTS:
  <dependency>            The URL or directory of the package to add

OPTIONS:
  --exact <exact>         The exact package version to depend on
  --revision <revision>   The specific package revision to depend on
  --branch <branch>       The branch of the package to depend on
  --from <from>           The package version to depend on (up to the next major version)
  --up-to-next-minor-from <up-to-next-minor-from>
                          The package version to depend on (up to the next minor version)
  --to <to>               Specify upper bound on the package version range (exclusive)
  --version               Show the version.
  -h, -help, --help       Show help information.

We want to add the package from the URL https://github.com/apple/swift-argument-parser and we want to make sure that our dependency has a minimum version from 1.3.0 up to the next major version.

swift package add-dependency https://github.com/apple/swift-argument-parser --from 1.3.0

The command will produce the following output:

Updating package manifest at Package.swift... done.

This is the new Package.swift

// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "EditSwiftPackage",
    products: [
        .executable(
            name: "EditSwiftPackage",
            targets: [ "EditSwiftPackage" ]
        ),
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"),
    ],
    targets: [
        // Targets are the basic building blocks of a package, defining a module or a test suite.
        // Targets can depend on other targets in this package and products from dependencies.
        .executableTarget(
            name: "EditSwiftPackage"),
    ]
)

we can test it by running:

swift run EditSwiftPackage

Adding a target dependency

We have introduced a new dependency, but to import it from our target we need to add it to the target.

The add-target-dependency command help can be obtained with the following command:

swift package add-target-dependency --help

output:

OVERVIEW: Add a new target dependency to the manifest

USAGE: swift package add-target-dependency <dependency-name> <target-name> [--package <package>]

ARGUMENTS:
  <dependency-name>       The name of the new dependency
  <target-name>           The name of the target to update

OPTIONS:
  --package <package>     The package in which the dependency resides
  --version               Show the version.
  -h, -help, --help       Show help information.

We want to add the ArgumentParser from the package swift-argument-parser to the EditSwiftPackage.

swift package add-target-dependency ArgumentParser EditSwiftPackage --package swift-argument-parser

The command will produce the following output:

Updating package manifest at Package.swift... done.

This is the new Package.swift:

// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "EditSwiftPackage",
    products: [
        .executable(
            name: "EditSwiftPackage",
            targets: [ "EditSwiftPackage" ]
        ),
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"),
    ],
    targets: [
        // Targets are the basic building blocks of a package, defining a module or a test suite.
        // Targets can depend on other targets in this package and products from dependencies.
        .executableTarget(
            name: "EditSwiftPackage",
            dependencies: [
                .product(name: "ArgumentParser", package: "swift-argument-parser"),
            ]),
    ]
)

we can test it by running:

swift run EditSwiftPackage

Implementing the command line:

Now, We have all the requirements to start our first and basic command line example, which will prove that our target can import the ArgumentParser dependency.

Rename the file main.swift to EditSwiftPackage.swift and update the content with the following:

import ArgumentParser

@main
struct EditSwiftPackage: ParsableCommand {
    @Argument(help: "The name you want to greet")
    var name: String

    mutating func run() throws {
        print("Hello \(name)!")
    }
}

You have created a command line tool in Swift!

To test it run:

swift run EditSwiftPackage

The command will produce an error:

Error: Missing expected argument '<name>'

USAGE: edit-swift-package <name>

ARGUMENTS:
  <name>                  The name you want to greet

OPTIONS:
  -h, --help              Show help information.

Let's add the name parameter to run the command:

swift run EditSwiftPackage Andrea

This will produce the following output:

Hello Andrea!

Add a new Target

Similarly it's possible to add a target with the command add-target add-target

swift package add-target --help

output:

OVERVIEW: Add a new target to the manifest

USAGE: swift package add-target <name> [--type <type>] [--dependencies <dependencies> ...] [--url <url>] [--path <path>] [--checksum <checksum>] [--testing-library <testing-library>]

ARGUMENTS:
  <name>                  The name of the new target

OPTIONS:
  --type <type>           The type of target to add, which can be one of 'library', 'executable', 'test', or 'macro' (default: library)
  --dependencies <dependencies>
                          A list of target dependency names
  --url <url>             The URL for a remote binary target
  --path <path>           The path to a local binary target
  --checksum <checksum>   The checksum for a remote binary target
  --testing-library <testing-library>
                          The testing library to use when generating test targets, which can be one of 'xctest', 'swift-testing', or 'none' (default: xctest)
  --version               Show the version.
  -h, -help, --help       Show help information.

Let's add the tests to our package:

swift package add-target EditSwiftPackageTests --type test --dependencies EditSwiftPackage

This is the new Package.swift:

// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "EditSwiftPackage",
    products: [
        .executable(
            name: "EditSwiftPackage",
            targets: [ "EditSwiftPackage" ]
        ),
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"),
    ],
    targets: [
        // Targets are the basic building blocks of a package, defining a module or a test suite.
        // Targets can depend on other targets in this package and products from dependencies.
        .executableTarget(
            name: "EditSwiftPackage",
            dependencies: [
                .product(name: "ArgumentParser", package: "swift-argument-parser"),
            ]),
        .testTarget(
            name: "EditSwiftPackageTests",
            dependencies: [ "EditSwiftPackage" ]
        ),
    ]
)

Recap

Here is a quick recap of all the commands used to create a package from the command line:

# Create a swift package
mkdir EditSwiftPackage
cd EditSwiftPackage
swift package init --type executable
# Add a Product
swift package add-product EditSwiftPackage --type executable --targets EditSwiftPackage
# Add a Dependency
swift package add-dependency https://github.com/apple/swift-argument-parser --from 1.3.0
# Add a Target dependency
swift package add-target-dependency ArgumentParser EditSwiftPackage --package swift-argument-parser
# Add a test Target
swift package add-target EditSwiftPackage-tests --type test --dependencies EditSwiftPackage

Conclusions

Editing a Swift package from the command line is a straightforward process that provides flexibility, especially when integrating with automation tools or working in environments without a graphical interface. By understanding how to navigate and manipulate your package's files, dependencies, and settings directly from the terminal, you gain greater control over your development workflow.

The new commands are additive only, this is enough to create a package and add Product, Target, Dependencies and Target dependencies. This new way of interacting with SPM could be handy to automate a workflow or to set up a template project.

Thanks for reading!

Tagged with: Swift