-
Notifications
You must be signed in to change notification settings - Fork 0
Swift Package Manager (SPM)
SPM (git repo) is Apple's concept for a Swift-based package manager. It's been around for a few years, and (as of 2020) is finally becoming somewhat usable. The documentation is still lousy, though.
It's not quite the same purpose as Carthage. You need to explicitly write a Package.swift to make your library usable with SPM, for one. By luck, though, all of the frameworks I use today via Carthage are also available via SPM.
I think it claims to be able to package C libraries ("Down" certainly includes a C library within itself), but the mechanism for this is not clear (and the docs are lousy). I've never been able to make it work.
-
When you put a Package.swift in your project to import other packages, it will clone and build them in a ./.build/ folder. They don't say, but I assume you would generally commit Package.resolved, but not the .build/ folder.
-
Alternatively, Xcode 11 has a new tab to let you add "Swift Packages". Instead of Package.swift, it saves them in its own project file, and instead of .build/, it clones and builds them in the project's DerivedData folder.
There's a way in Package.swift to specify whether you want static/dynamic linking (and for libraries, whether they support one or both). Default is for libraries to support both, and the compiler to use static linking. Xcode doesn't even present this as a choice: you get static linking.
I suppose this opens up more possibilities for optimization, and eliminates otool
naming issues. OTOH, I added a single package, and even when I don't use it, my binary is 3.4 MB bigger (or 4.5 MB in debug mode). I can't seem to find any linker/optimizer flags to make it smarter about what it includes.
- The linker does have an option called
DEAD_CODE_STRIPPING
, but it's nearly useless here. It saves almost no space when applied to an unused SPM package.
SPM is integrated in Xcode 11/12. (I'm not sure if there's any significant difference between SPM support in these versions.) The way it works is a little different from Package.swift:
- You go to the "Swift Packages" tab of your project
- You add a SPM project, by URL and version
- You pick a target to add it to
- Instead of Package.swift, it adds this as a rule to your project.pbxproj file
- It builds in a private/hidden folder, and manages linking for you
This all sounds good (apart, perhaps, from using a proprietary mechanism for storing your SPM references). However, there's a couple major limitations of this:
- It always uses static linking. SPM allows libraries to declare if they support static/dynamic linking, and for applications to use one or the other. Xcode's implementation of SPM, though, always uses static linking.
- (What happens if two Swift packages pull in C libraries with the same symbol names? Does it detect that they're the same library? I haven't tried.)
- A given project URL can only be added to one target. This means if you have a project that builds both a Mac and iOS app, and you add an SPM to it, it can't be used by both of them. You can't even add that same URL a second time for the other target.
- I think the only reasonable way to make a Mac/iOS app from the same codebase, in 2020, is to extract all the common functionality into SPM packages (using
#if
as needed, for papering over the AppKit/UIKit differences), and then create separate Xcode projects for each one. I haven't tried this myself.
- I think the only reasonable way to make a Mac/iOS app from the same codebase, in 2020, is to extract all the common functionality into SPM packages (using
- It caches heavily, in strange ways, and there doesn't seem to be any reliable way to flush this cache. (In Xcode 12 there's a File -> Swift Packages -> Reset Package Caches, but it permanently broke the one project I tried it on, so I don't trust it.) When you include a SPM in your project, and then update that repo, and "Update to Latest Package Versions", it'll build with the latest code, but if you command-click the module name, it'll show you an old generated interface. It will eventually update, but it doesn't seem to be based on time, or updates, or cleaning, or Derived Data, or re-opening Xcode, or anything else that I can think of (FB8904343).