WWDC gave us many reasons to both migrate libraries to SwiftPM and to develop new ones to support our work. The integration between Xcode development and SwiftPM dependencies keeps growing stronger and more important.
Apple’s Editing a Package Dependency as a Local Package assumes you’ll drag in your package to an Xcode project as a local package overrides one that’s imported through a normal package dependency.
In Developing a Swift Package in Tandem with an App, Apple writes, “To develop a Swift package in tandem with an app, you can leverage the behavior whereby a local package overrides a package dependency with the same name…if you release a new version of your Swift package or want to stop using the local package, remove it from the project to use the package dependency again.”
I don’t use this approach. It’s not bad or wrong, it just doesn’t fit my style.
On the other hand, opening the Package.swift file directly to develop has drawbacks in that it doesn’t fully offer Xcode’s suite of IDE support features yet.
So I’ve been working on a personal solution that best works for me. I want my package development and its tests to live separately from any specific client app outside a testbed. I need to ensure that my code will swift build
and swift test
properly but I also want to use Xcode’s built-in compilation and unit testing with my happy green checks.
I set out to figure out how best, at least for me, to develop Swift packages under the xcodeproj
umbrella.
I first explored swift package generate-xcodeproj
. This builds an Xcode library project complete with tests and a package target. You can use the --type
flag to set the package to executable, system-module, or manifest instead of the default (library) during swift package init
:
Generate% swift package init Creating library package: Generate Creating Package.swift Creating README.md Creating .gitignore Creating Sources/ Creating Sources/Generate/Generate.swift Creating Tests/ Creating Tests/LinuxMain.swift Creating Tests/GenerateTests/ Creating Tests/GenerateTests/GenerateTests.swift Creating Tests/GenerateTests/XCTestManifests.swift Generate% swift package generate-xcodeproj generated: ./Generate.xcodeproj
Although SwiftPM creates a .gitignore
file for you as you see, it does not initialize a git repository. Also, I always end up deleting the .gitignore
as I use a customized global ignore file. This is what the resulting project looks like:
As you see, the generated Xcode project has everything but a testbed for you. I really like having an on-hand testbed, whether a simple SwiftUI app or a command line utility to play with ideas. I looked into using a playground but let’s face it: too slow, too glitchy, too unreliable.
It’s a pain to add a testbed to this set-up, so I came up with a different way to build my base package environment. It’s hacky but I much prefer the outcome. Instead of generating the project, I start with a testbed project and then create my package. This approach naturally packs a sample with the package but none of that sample leaks into the package itself:
I end up with three targets: the sample app, a library built from my Sources, and my tests. The library folder you see here contains only an Info.plist and a bridging header. It otherwise builds from whatever Sources I’ve added.
I much prefer this set-up to the generate-xcodeproj
approach, although it takes slightly longer to set-up. The reason for this is that SwiftPM and Xcode use different philosophies for how a project folder is structured. SwiftPM has its Sources and Tests. Xcode uses a source folder named after the project.
So I remove that folder, add a Sources group to the project, and ensure that my build phases sees and compiles those files. The Tests need similar tweaks, plus I have to add a symbolic link from Xcode’s tests name (e.g. “ProjectNameTests”) to my SwiftPM Tests folder at the top level of my project to get it to all hang together. Once I’ve done so my green checks are ready and waiting just as if I had opened the Package.swift file directly. But this time, I have all the right tools at hand.
Since I’m talking about set-up, let me add that my tasks also include setting up the README, adding a license and creating the initial change log. These are SwiftPM setup tasks that swift package init
doesn’t cover the way I like. I trash .gitignore
but since I have Xcode set-up to automatically initialize version control, I don’t have to git init
by hand.
I suspect this is a short-term workaround as I expect the integration of SwiftPM and Xcode to continue growing over the next couple of years. Since WWDC, I’ve been particularly excited about developing, deploying, and integrating SwiftPM packages. I thought I’d share this in case it might help others. Let me know.