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.

4.7/5 3 Ratings · Leave a Rating

Version

  • Swift 4.2, macOS 10.14, Xcode 10

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.

Managing Dependencies

In this project, you would be unable to run the application without switching to the super user. For development purposes, you’ll commonly use a port different than 80. You’re usually free to use any port above port 1024 that is not already in use.

Usually, you will specify a dependency using from to set a minimum version, so that SPM can download updates of newer version of the package. But in this scenario, version 1.0.1 has the port permission problem, and the last known working version is 1.0.0. Looks like you’ll need to go back to 1.0.0.

Edit Package.swift and replace the dependency with the following to pin the dependency to exactly version 1.0.0:

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

After changing the manifest, your dependencies won’t be updated automatically. To update them, you could just update your Xcode project again, but this time, run the following command in your project’s directory from Terminal:

swift package update

This command, on its own, will look for newer or different versions of your dependencies and download them as required.

You are working in Xcode to update this project, so you'll need to regenerate your Xcode project after running this command. This is necessary because dependencies can add and remove files.

Execute the following command to regenerate your project:

swift package generate-xcodeproj

Troubleshooting Dependencies

Some versions of Xcode will prompt you with the following:

Click Revert to reload the project from the disk. And if Xcode will not compile your application after this, close the window and open the project again.

This time when you build and run in Xcode, you will see Server started and listening in the console. Open a web browser, and go to localhost:8080 to check out what you made, refreshing a few times as you go.

Now you know how to run an application using Xcode. So far, so good!

Next, you’ll learn more about SPM and running swift from the terminal. Stop the application in Xcode if it is running, and switch back to Terminal.

Running From the Terminal

Now that you’ve learned how to build and run applications on macOS, you’re all set to start developing. However, server-side Swift is commonly run on Linux servers. So you’ll need to know how to compile and run applications on Linux, too.

On macOS, as long as you have Xcode 10.1 or later installed, you’re all set. If you are working in a Linux environment, you need to set up Swift as described on the Swift Download page.

There are three primary commands that you’ll work with; the most common command is swift run. This will build your application and run the resulting executable.

If there are multiple executable targets, you simply add the target name to the command:

swift run Website

The first time you run this command, SPM compiles everything first, which might take a bit of extra time, but it will compile much faster in subsequent compiles. Once you see the Server started and listening message, you’ll know it’s up and running. To stop the server, press Control + C.

Another common task is running unit tests. Don't run this for now, but the following command is the one you'll use for running the unit tests of your package:

swift test

Note: Do not run swift test as it will run the server in the background and never stop it. Testing server-side Swift projects is outside the scope of this tutorial, so while it is important to know this command, the project is not set up for testing.

Finally, use the following command to build your executable without running it:

swift build

Build Configurations

The above commands use the debug configuration by default. There are more configurations available; the most common one is release.

The debug configuration compiles much faster than release, but compiling a binary with the release configuration results in increased optimization.

Add --configuration release to your command to change the configuration to release, and execute it:

swift run --configuration release

This command starts the server. Navigate to http://localhost:8080 in your browser. If you see an idiom, your application is working!

Your server is up and running

However, don’t call it a day just yet. There’s still a problem to be solved!

Editing the Library

In many development scenarios, another application will be using port 8080. You’ll need to move one of the applications to another port by assigning another unused port. The imported library doesn’t support this, but you can implement it yourself!

The first step is to put the package in editable mode. Press Control + C to stop the server. Then, run the following command:

swift package edit WebsiteBuilder

This moves WebsiteBuilder into the Dependencies folder in your project. SPM will not update or change the dependency. Before you can edit the package, regenerate the Xcode project.

Tip: To quickly recall commands in Terminal, you can press the up-arrow key repeatedly in Terminal. Do this until you get back to the swift package generate-xcodeproj line, then press Return to run it again and regenerate the project.

Within the Dependencies group in Xcode’s Project navigator, you’ll find a list of all dependency sources. Open Website.swift in the WebsiteBuilder dependency folder.

Remove the following line from the run() method:

let port = 8080

Also change the run() method’s signature to the following:

public func run(port: Int) throws {

Now you’ll need to specify your port. Open your own application’s main.swift file. Change the last line in that file, the one that actually runs the website, and modify the port number:

try website.run(port: 8123)

To complete this tutorial, run the application using the release configuration in Terminal with your swift run --configuration release command. Once it’s started, open the website in a browser at http://localhost:8123.

Do you see an idiom? If so, you’re done! Time flies when you’re having fun, doesn’t it?

Where to Go From Here?

You can download the completed version of this project using the Download Materials button at the top or bottom of this screen.

  • One of the most important libraries in server-side Swift, SwiftNIO, is covered in the SwiftNIO tutorial, also mentioned earlier in this tutorial.
  • If you’re interested in learning about API development, look into Kitura and Vapor. Getting Started with Vapor and Getting Started with Kitura are two tutorials that will teach you the basics.
  • In Vapor vs. Kitura, the two server-side Swift frameworks go head to head. If you’re not sure which to use, read that and make your decision!

Questions or comments? Leave them in the forum section below!

Average Rating

4.7/5

Add a rating for this content

3 ratings

Contributors

Comments