This Comes From Personal Experience, Working On Ship Code
The genesis for this series came about, as I decided to convert the majority of my repos to use SPM. I had a number of issues and stumbling blocks, and decided that documenting the process might be helpful to others.
This work will cover the newer type of SPM integration, that is embedded into Xcode, and applies to all Apple operating systems. SPM has been around for a few years, but previously, tended to be a bit “niche,” applying mostly to server-side Swift.
It should be noted that the screengrabs and walkthroughs will be using Xcode version 11.5, so the exact workflows are likely to change, over time.
I Will Not Explain the Basics of SPM
That’s because I feel that others (many others) have done a far better job on this, than I can do. Here’s an example.
Instead, I will cover some of the specific steps that I have taken in implementing SPM in my own work.
That said, I will be covering some of the basics; just not all of them.
Why Do I Want To Use A Package Manager?
I like to work in a modular fashion. I prefer developing integrated and aggregate systems, as opposed to monolithic ones.
Additionally, I like each module to be a standalone project, with its own configuration management, testing, and release lifecycle. I believe that this can have a tremendously positive effect on the overall quality of my products, and also helps me to implement new aggregate projects.
For this model to work, I need some kind of dependency (or package) manager (I will use the term “Package Manager,” from here on out).
The package manager makes sure that the included dependencies are provided in a useful form, are of the correct release version, and also include any “sub-dependencies.”
Git Submodules, however, can use all kinds of specifiers, such as commit hashes. SPM can use branch HEAD revisions (but that is considered a development/debug tool, and is not supported for deployment).
What Are My Choices?
There are a number of choices:
- Git Submodules
Git submodules are completely “baked into” Git, and will work with any git-based repository management system.
Git submodules are “bare metal,” and you can’t get a tighter integration between the code and version control. Submodules allow exact control of versions and releases.
However, they are fairly difficult to work with, easy to screw up, require a fair amount of manual or scripted intervention, and it is up to the consumer to manage every layer of the dependency tree.
Git submodules also have no provision for “version ranges.” Versions are exact.
It is possible to access any type of file in a git submodule, as it is really just a local clone of a separate repo.
Git submodules are completely “platform-agnostic,” and are built into the Git version control system, which comes installed with Xcode.
Git Submodules put all of the onus on the module consumer.
CocoaPods is an enormously popular package manager for Apple systems. It is tremendously easy to use, and tightly integrates with Xcode projects, which is a big reason that CocoaPods is so popular.
CocoaPods requires either a private CocoaPods spec repo, or access to the main CocoaPods spec repository. It is extremely well-documented, and is well-supported by a large, talented, and diverse team. It’s a fairly massive ecosystem; not just a utility.
CocoaPods have a fairly basic (but effective) way to control version ranges.
It is possible to access non-code resources via CocoaPods, but the system isn’t really designed for that.
CocoaPods requires installation of a Ruby gem. Apple is planning on removing distributed support for Ruby in the future, so it may require installing Ruby, as well as the gem, in the future.
CocoaPods puts most of the onus on the module provider, but the consumer has to specify its dependencies.
- GitHub Carthage
This is the system that I have been using, prior to converting to SPM. It is a very simple (but not as “easy” as CocoaPods) package manager for Apple systems. GitHub is a major-league repository management provider, so Carthage is well-supported by a talented, well-funded organization. I like Carthage because it has such a “light touch.” It doesn’t integrate with Xcode, and I like that.
Carthage will integrate directly with GitHub, or any standard Git repo. It will also allow direct import of binary (like “
.framework“) files over HTTPS.
Because of the loose coupling between Carthage modules and the Xcode project, Carthage can be used to create “lite” integrations, like included source files (as opposed to compiled and linked modules), or even non-code resources (like localizations or images). If you want to access non-code resources, you simply establish a cloned git repo, and reference the files, within that repo.
Another advantage of Carthage, is that there is no need for the package creator to do anything. It is possible to install any git repo.
Carthage requires installation of a utility via Homebrew (a system-level package manager), or MacPorts (another system-level package manager), or it can be built on the system.
Carthage has a fairly basic version range specification, but also has the ability to sync to git commit hashes (like git submodules).
Carthage puts all of the onus for integration on the module consumer.
- Swift Package Manager (SPM)
SPM is the “native” system for Swift. Until fairly recently, it was restricted to server-side and Mac code, but now applies to all Apple systems. It has also been integrated into Xcode, which gives it many of the advantages that CocoaPods had.
It only applies to Swift (with some interaction with Objective-C).
SPM package version ranges are declared via the integrated SPM client in Xcode. It is similar to the scheme used by Carthage.
SPM does not require installation. It is built into the Swift build system, and Xcode 11.5 or above.
SPM puts most of the onus on the module provider, but does require the module consumer to use the Apple-provided integration.
All package managers feature some form of Package Manifest. Most are declarative formats, but SPM is actually executable (more on that, in a bit).
- Git Submodules
Git Submodules use the
.gitmodulesfile. This file is in a proprietary declarative format, and describes requirements for particular Git repositories. The repository references can be local or remote. The actual versions are handled in the
.gitdirectory. This file simply defines which repositories are available as modules.
Most Git implementations will take care of the Git manifest editing automatically.
CocoaPods use two manifest files (also in a proprietary format).
One file is implemented by the module provider, and is called the
podspec. This proprietary-format file defines the pod structure, published resources, author information, and version of the pod.
The other file is a proprietary-format file, provided by the module consumer, and is called the
podfile. This defines the pod, its source, and a required version range.
Carthage uses what is called the
Cartfile. This is a fairly simple semi-proprietary-format file, provided by the module consumer, that defines the module source, and version range.
- Swift Package Manager
SPM uses the
Package.swiftfile. This implements an instance of the class
Package, on the module provider side (which requires import of the
PackageDescriptionAPI). Since this is an actual Swift source file, it can be executable, and it is possible to make it a “smart manifest.”
Why Did I Choose SPM?
This was because I wanted the tight integration that CocoaPods provides, with the simplicity of Carthage, and the control of Git Submodules.
I feel that SPM provides all these. Additionally, it is the “official” package manager for Swift, so its support is about as solid as you can get.
And Now I’m Done Talking About Other Package Managers
This series will be about the Swift Package Manager. If we have decided to use the SPM, then it is my hope that this will be a useful set of posts. If we want to find out about the other managers mentioned here, then there are probably many articles on the topic, written by folks that understand them better than I.