An Introduction to Swift Package Manager

In this introduction to the Swift Package Manager, you’ll build a website to display random idioms. You’ll learn how to manage projects and dependencies and how to read and change Package.swift files. By Joannis Orlandos.

Leave a rating/review
Download materials
Save for later
Share

In server-side Swift projects, Apple’s Swift Package Manager, or SPM, lets you manage your project dependencies, allowing you to import libraries into your applications with ease.

In this tutorial, you’ll build a website to display random idioms. You’ll learn how to manage projects and dependencies, and how to read and modify Package.swift manifest files.

Ready for a dive into logistics with SPM? Break a leg!

Creating Packages

Packages are simply repositories that contain one or more libraries and executables. Your application will have a Package.swift manifest file to manage its dependencies, and every package has its own Package.swift manifest to handle any dependencies that it may have.

The manifest files are also written in Swift, as implied by the file extension!

A manifest file describes the package; it contains a list of dependencies and targets.

A target is the specification of a single module and its dependencies. You can compile each target that contains a main.swift to an executable.

You might have seen modules in Xcode before. Your app itself is an executable module, and it has dependencies that, in turn, are also modules.

With SPM, you can create packages right from Terminal. SPM uses the directory name as your project name when setting up your application.

Getting Started

First, create a directory with the desired project name and enter that directory:

mkdir Website
cd Website

To create an executable project, run the following command:

swift package init --type=executable
Note: If you wanted to create a library instead, you would omit the type parameter and simply swift package init instead.

You should see the following output as the system creates all the boilerplate for an SPM project:

swift package init

That’s it; you’ve created your own package!

Adding Dependencies

The next step in creating a project is to add its dependencies. To do that, open the Package.swift manifest file inside the directory where you created your project.

At the top of the file is the Swift tools version. You should not remove the generated tools version comment; it’s read by SPM to understand how to interpret the manifest.

Each package has a name and a list of targets. Your executable has a regular target and a test target. The regular target is your executable or library, whereas the test target is a test suite.

Your Package.swift file should look as follows:

// swift-tools-version:4.2

import PackageDescription

let package = Package(
  name: "Website",
  dependencies: [
        
  ],
  targets: [
    .target(
        name: "Website",
        dependencies: []),
    .testTarget(
        name: "WebsiteTests",
        dependencies: ["Website"]),
  ]
)

In this tutorial, you’ll add the WebsiteBuilder library as a dependency to your application. This library was created specially for this tutorial.

Finding Dependency URLs

To add a dependency, you need three pieces of information: the URL, the version number and the name.

To get the URL, open the GitHub link to the WebsiteBuilder library and look for a green button named Clone or download. Click that button and look for Clone with HTTPS. If you don’t see this, you should see the text Use HTTPS instead. Click Use HTTPS to toggle to the HTTPS link, which you can then copy using the clipboard button.

The package version can usually be found in the README of the project. But if you want an older version of the package, or need to find the version yourself, you can find it in the releases tab of the project. For this project, you are going to start with version 1.0.0.

The package name may be in the README, but you can always find it in the Package.swift file for the dependency. The name of this dependency is WebsiteBuilder.

In your app’s Package.swift file, you’ll find an array of dependencies. There is one example dependency there, but it’s commented out as it’s only for reference purposes.

Add the following line to your manifest within the dependencies array.

.package(url: "https://github.com/raywenderlich/spm-tutorial.git", from: "1.0.0")

This references the URL and the version number from the GitHub repository.

Next, add the package name – WebsiteBuilder – to the list of named dependencies in your Website target:

.target(
    name: "Website",
    dependencies: ["WebsiteBuilder"]),

Save your file and go back to Terminal. That takes care of your dependencies. But dependencies also have properties, which you’ll learn about next.

Editing Dependency Properties

You’ve just added your first SPM dependency in it’s simplest form, but what happens when you want some more specificity into the version of the dependency your app requires?

SPM lets you specify version requirements in a very granular way, providing great control over the exact version, or even branch, of the required dependency.

Note: These are examples to show how you can specify different versions. They use truncated URLs and imaginary version numbers.

The following code specifies any version of the dependency, starting at 1.1.3 and less than 2.0.0:

.package(url: "https://github.com/...git", from: "1.1.3")

If you want specify a minimum and maximum version for one dependency, you can use a range:

.package(url: "https://github.com/...git", "1.1.0"..."1.2.1")

Use the following if you’re interested in an exact version of a dependency:

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

All of these version-specific variations also support beta-versioning. For example:

.package(url: "https://github.com/...git", from: "1.1.3-beta.4")

You can also lock the dependency to a specific branch in git. This is useful if a feature or fix is not yet released:

.package(url: "https://github.com/...git", .branch("bugfix/issue-121"))

Finally, you can specify a commit by its hash:

.package(url: "https://github.com/...git",
.revision("04136e97a73b826528dd077c3ebab07d9f8f48e2"))

Integrating With Xcode

Now that your project is set up, you’re ready to generate an Xcode project.

Execute the following command in Terminal to do so:

swift package generate-xcodeproj

This will download the dependencies and create an Xcode project — Website.xcodeproj.

Note: If you have used Swift Package Manager before, you may notice that we didn't ask you to use swift build. This is because the Xcode project generation command does this for you, as well as resolve your dependency tree before building your project. Later on, you'll get a hint of how swift build and swift package update work on their own!

You should output similar to the following:

swift package generate-xcodeproj

Finally, open the project.

Protip: you can open it quickly from Terminal by executing the following command:

open ./Website.xcodeproj

Or, by using xed, which will open the project in the current folder:

xed .

Xcode may select an iOS device or simulator as the targeted device, so make sure to switch to My Mac.

Build and run the project in Xcode. This will compile the project, start the project, print a message in the console and exit.

Build and run

Open main.swift in Xcode and replace its contents with the following code:

import NIO
import WebsiteBuilder

/// All idioms the app can respond with
let idioms = [
  "A blessing in disguise",
  "Better late than never",
  "Bite the bullet",
  "Break a leg",
  "Cutting corners",
  "Get your act together",
  "That's the last straw",
  "You can say that again"
]

/// Responds to the request with a random idiom chosen from the list above
struct IdiomResponder: HTTPResponder {
  func respond(to request: HTTPRequest) -> EventLoopFuture<HTTPResponse> {
    guard let randomIdiom = idioms.randomElement() else {
      return request.eventLoop.newFailedFuture(error: Error.noIdiomsAvailable)
    }
    
    let response = HTTPResponse(status: .ok, body: HTTPBody(text: randomIdiom))
    return request.eventLoop.newSucceededFuture(result: response)
  }
  
  enum Error: Swift.Error {
    /// This error occurs if the idiom list is empty
    case noIdiomsAvailable
  }
}

/// Creates a new website responding with the IdiomResponder
let website = Website(responder: IdiomResponder())

/// Runs the website at the default port
try website.run()

In this piece of code, you respond to an incoming network with a random idiom from the provided list using SwiftNIO. While SwiftNIO itself is out-of-scope for this tutorial, you can learn more about it in SwiftNIO: A simple guide to async on the server.

Build and run the application again using the Run button in Xcode. Oh no – it doesn’t work:

Fatal error: Error raised at top level: bind(descriptor:ptr:bytes:) failed: Permission denied (errno: 13)

Uh-oh! What kind of bug is this?

Bug

This is a deliberate error in this tutorial, and may even be a blessing in disguise. This means that the application attempted to use a port that is already in use, or one that you do not have permission to use. In this case, it’s a permission problem. The library uses port 80, which is only usable by the super user.