Swift Package Manager Basics
With the arrival of Swift on the server, one of the first questions that was asked in our team was: “But what about dependency package management?”
On the iOS/macOS side, there’s CocoaPods and Carthage. Node has npm. Go has godeps. Java has maven. Ruby has gems. And Swift now has Swift Package Manager.
Swift Package Manager, or SPM for short, gives us a great way to have not just dependency management, but versioned management too. That means that we can specify anything from an exact version of a dependency, a range, or simply the latest in a series.
In all of the examples I’ll be using here I’ll be specifying the most recent of a “major” version unless otherwise stated.
Versioning a Dependency Module
In order for a repository to be used as a SPM dependency it requires the following:
- It must follow standard SPM source format (have a Package.swift, and all source files are in a Sources directory)
- The repository must be tagged with a major, minor and patch (i.e. 1.0.2)
The easiest way to tag a repo is to execute “git tag x.x.x” on the repo’s master branch. Note that at this time SPM only supports using the master. Future SPM versions should allow using branches, but as of Swift 3.0.2 only master is supported.
Don’t forget to push your tag to the upstream repo after tagging it!
For more information about tagging in git see https://git-scm.com/book/en/v2/Git-Basics-Tagging
The format of the Package.swift file is quite prescriptive. Here we are just going to cover the basics, but there’s a lot more that can be done including having more than one package or target in a repository.
The basic layout consists of providing a package “name”, and specifying the array of module dependencies (if any).
Note that each dependency can have it’s own dependencies too. Therefore if you include the “Perfect-HTTPServer” library, it includes “Perfect-HTTP” which includes “PerfectLib”, so there’s no need to include PerfectLib in your own Package.swift file.
There are few rules surrounding where and how you create your module. All your source code could be in one single file, or it could be in many. However all these files must live in a Sources directory.
Note that if you wish to have subdirectories in your Sources directory, there must be a “parent” directory in that Sources directory that corresponds to the package name in Package.swift.
For example, the Perfect App Template has a directory in Sources called “Perfect-App-Template”, which corresponds to the name of the package in the Package.swift file.
SPM presumes that the dependency repository follows “Semantic Versioning” naming. That means that each tag consists of 3 components each of which is an integer: a “major” version, “minor” version, and a “patch”.
The general rule is that a major version would be incremented when breaking and incompatible API changes are made. A minor version wild be incremented when functionality is introduced that should be backwards-compatible, and a patch version is for bug fixes or improvements.
“1.2.4” signifies that this library is tagged with a major version of 1, a minor of 2, and patch level of 4.
Incorporating Semantic Versioning for SPM
In the Packages.swift file we saw before each of the packages had an associated “majorVersion” property associated with them. This means that when an initial build (or a clean then build) is executed, the process will fetch the highest tagged version of that package within that major line.
.Package(url: “https://github.com/PerfectlySoft/Perfect-HTTPServer.git", majorVersion: 2)
will fetch the highest in the major version 2, which at this time is 2.1.15
If a version 3.x.x exists, that is ignored, only a 2.x.x version will be fetched.
If instead we wanted a 2.0.x version, we would specify:
.Package(url: “https://github.com/PerfectlySoft/Perfect-HTTPServer.git", majorVersion: 2, minorVersion: 0)
This will fetch the highest patch level in the 2.0.x line.
Adding or removing dependancies
After making any changes to your Packages.swift file I highly recommend you do two things: a clean & build, and a regeneration of your Xcode project file.
To clean and rebuild:
# swift 3.0.2
swift build --clean=dist; swift build
# swift 3.1
swift package update; swift build
To regenerate your Xcode project:
swift package generate-xcodeproj
Pro tip: use Perfect Assistant
While all of this is achievable using the command line and manually editing files, it’s actually easier to use the Perfect Assistant. Even those of us that consider ourselves Server Side Swift power users default to using PA (Perfect Assistant) as it’s quicker and easier to drag, drop and push a few quick buttons.
Adding a dependency in PA is as simple as dragging and dropping the dependency into the “Selected Dependencies” pane for the project, and pressing “Save Changes”. This updates the Packages.swift file, then fetches all the required dependencies, and regenerates the Xcode project.
If there’s dependency not listed, click the “Add Dependency…” button and copy in the repo URL, and assign it a category. It’s then added to the available dependencies to be added for all future projects.
When you add a dependency, the latest major is selected as the default. Under the dependency name is a version dropdown.
For example after adding “HTTPServer” the selected version is “2.x.x” — this equates to what we saw in the Perfect App Template, “majorVersion: 2”
If we wanted to select a 2.0.x version, or a very specific version such as 2.1.14, then that can be selected. After pressing “Save Changes”, I recommend clicking “Clean” and then the Xcode Project “Regenerate” buttons. This forces the system to clear out everything and update to the specific tagged versions you have selected.
If you’re looking for a deeper dive into SPM, have a read of the “official” site info, https://swift.org/package-manager/