More and more Apple Platform developers are migrating away from CocoaPods in favor the Swift Package Manager, which is Apple’s first-party tool for managing and integrating dependencies. While it is still not quite a complete replacement for CocoaPods, it is getting closer. Unfortunately, SwiftPM’s integration with Xcode still has a number of shortcomings, even though it was introduced with Xcode 11 — 4 years ago. The worst bug is that Xcode frequently and randomly deletes the Package.resolved
, which in turn produces dozens or hundreds of 'missing package product'
errors. Here’s how I’ve worked around this bug on a team I work on.
The bug
Xcode seems to specifically delete Package.resolved
when changing branches in git. Then, depending on how large your project is and how many Swift packages are included, you’ll see dozens or hundreds or thousands of 'Missing package product <product_name>'
errors.
However, I have also experienced Xcode deleting the file for seemingly no reason at all. In fact, I’ve had moments where Xcode deletes the file immediately after I restore it via git restore
— and I can just do that infinitely. I put the file back, then Xcode deletes it. In that beautiful scenario, the Xcode project must be closed and re-opened.
Searching the Apple Developer Forums returns dozens of results for this bug. Here is a recent one that correctly describes the problem. Here is another post from 2021 that has new replies from 2 weeks ago. There is also this post from the Swift.org forums — from June 2020 — describing the issue. If you read through that thread, you will see the problem was briefly fixed around Xcode 12, but regressed in either the 12.5 or 13.0 release. This forum thread also has new comments from the past 2 weeks (including from yours truly). Unfortunately, the Swift forums are not the right place to report bugs in Xcode.
Most “solutions” on the forums revolve around some magical combination of cleaning your project (cmd-shift-K
), deleting Xcode’s DerivedData/
, running File > Packages > Reset Package Caches
, running File > Packages > Resolve Package Versions
, and closing and re-opening Xcode. All of this is very heavy-handed and there’s a much lighter weight approach you can take to work around it.
This bug is happening in the latest release, Xcode 15.4 (15F31d). It is a widespread problem reported across the various forums, as well as on Mastodon and other platforms. Any developer on a team that is using SwiftPM with Xcode is experiencing this bug daily. Furthermore, running xcodebuild -resolvePackageDependencies
seems to have no effect and it does not regenerate Package.resolved
.
The Package.resolved
file and git
First, what is Package.resolved
and why is it important? This file is equivalent to Podfile.lock
in CocoaPods — similar to Gemfile.lock
in Ruby/Bundler, or package-lock.json
in Node.js/npm. This file is a manifest that describes the exact versions used for every package dependency. It allows SwiftPM to always install the exact same packages on multiple machines, for example the machines of each member on a team and CI machines.
It turns out, it’s just a JSON file. Here’s an example of Package.resolved
for an app that imports one library called Foil
.
{
"pins" : [
{
"identity" : "foil",
"kind" : "remoteSourceControl",
"location" : "https://github.com/jessesquires/Foil",
"state" : {
"revision" : "bc08a46268cb3bb22fee2c8465d97e6d7bf981e1",
"version" : "5.0.1"
}
}
],
"version" : 2
}
Because Package.resolved
is necessary to resolve the exact packages at their exact specified versions, it should be checked-in to source control.
If you are working with an Xcode project, Package.resolved
is located at:
MyApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
If you are working with an Xcode workspace, the file is located at:
MyApp.xcworkspace/xcshareddata/swiftpm/Package.resolved
When working with only Swift packages, your Package.swift
file defines all of your package dependencies. This plays a similar role to the Podfile
in CocoaPods. With SwiftPM’s Xcode integration, the project file project.pbxproj
— everyone’s favorite file — maintains the list of Swift package dependencies and their pinned versions. Again, this fulfills the same role as the Podfile
.
The project file is located at MyApp.xcodeproj/project.pbxproj
. If you search the project.pbxproj
file for XCRemoteSwiftPackageReference
and XCSwiftPackageProductDependency
, you will find all the Swift package references for your project. For the example Package.resolved
file above, here is the project.pbxproj
representation:
/* Begin XCRemoteSwiftPackageReference section */
0B3E8A0A28AD7D9C006FB785 /* XCRemoteSwiftPackageReference "Foil" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/jessesquires/Foil";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 5.0.0;
};
};
/* End XCRemoteSwiftPackageReference section */
For small teams or individuals that already include their entire .xcodeproj
or .xcworkspace
bundles in git, there is nothing else you need to do. However, most teams explicitly ignore .xcodeproj
and .xcworkspace
— ironically, because of project.pbxproj
, which is a nightmare to resolve when you have conflicts in git. These teams typically use Xcodegen or a similar tool to generate their project files, thus entirely avoiding merge conflicts on project.pbxproj
. In this scenario, including the Package.resolved
file but ignoring everything else requires a bit of work. Here are the git ignore rules needed:
# .gitignore file
MyApp.xcworkspace/*
!MyApp.xcworkspace/xcshareddata/
MyApp.xcworkspace/xcshareddata/*
!MyApp.xcworkspace/xcshareddata/swiftpm/
MyApp.xcworkspace/xcshareddata/swiftpm/*
!MyApp.xcworkspace/xcshareddata/swiftpm/Package.resolved
Note that with tools like Xcodegen, you define your packages in your project.yml
file, which essentially is a replacement for project.pbxproj
.
Update 30 May 2024
Thanks to Kyle Bashour for pointing out a couple of details I got wrong about project.pbxproj
. This section has been corrected.
Handling Package.resolved
deletions
Now that the Package.resolved
file is saved in git, we can easily handle when Xcode deletes it by simply restoring it via git. For teams that ignore their project and workspace files and use Xcodegen (or similar), you also have the issue where Xcodegen deletes Package.resolved
when it regenerates your project file. If you are also still using CocoaPods in conjunction with SwiftPM, then you have the issue where CocoaPods will delete Package.resolved
when it regenerates your workspace file.
When you notice the file gets deleted, you can restore it using git restore
. And then all the 'Missing package product'
errors will go away when you build your project.
It is common for iOS and macOS projects to use a Makefile
in order to bootstrap project setup, which often includes: generating the project file via Xcodegen, installing CocoaPods via Bundler, running pod install
, etc. In addition, makefiles are also a great place to write “shortcut” commands that are relevant to your project, for example, linting files or running tests.
Here’s a target you can add to your Makefile
to make restoring a deleted Package.resolved
file easier.
PACKAGE_FILE := "MyApp.xcworkspace/xcshareddata/swiftpm/Package.resolved"
.PHONY: swiftpm
swiftpm:
@# Restore Package.resolved, which gets deleted when re-generating the project/workspace.
@# Or, it gets deleted by Xcode.
@# Only do this if the file was completely deleted.
@# Otherwise, the user could be modifying packages which updates Package.resolved, so do not git restore it.
@if [ ! -f "$(PACKAGE_FILE)" ]; then \
echo "Restoring Package.resolved..."; \
git restore "$(PACKAGE_FILE)"; \
xcodebuild -resolvePackageDependencies; \
fi
With this target in your Makefile
, you can simply run make swiftpm
as needed.
Note that this only calls git restore
if the file is deleted, in case it has been (correctly) modified after updating packages. At the end, it calls xcodebuild -resolvePackageDependencies
, which should regenerate the Package.resolved
on its own, but that’s also broken. It will, however, download packages to the package cache if they are missing, which is what Xcode does when opening a project.
To make this even better, you can include running this target as part of your bootstrapping process if you are using CocoaPods, Xcodegen, etc. Include this target part of your primary setup flow, and you should rarely have to run it manually.
If you do not use a Makefile
, then you can write a simple bash script instead with the contents above.
Danger: accidental deletions
Unfortunately (again), what we discovered on one of my teams is that the above is not foolproof. Because Xcode can (and literally does) randomly delete Package.resolved
at any time, someone on your team might not notice and commit the file deletion. Oops! If you use a tool like Danger to automate code reviews (which, you should), then you can add a rule to catch this mistake.
# Dangerfile
if git.deleted_files.include?("MyApp.xcworkspace/xcshareddata/swiftpm/Package.resolved")
fail("It looks like you deleted `Package.resolved`. Please don't do that.")
end
This will fail a pull request if Package.resolved
was deleted.
Summary
To recap: make sure Package.resolved
is saved in git, use a Makefile
to quickly and easily restore it, and add a Danger rule to catch mistakes, if needed. This should eliminate all 'Missing package product'
errors. As a last resort, if you are still having trouble, you can run File > Packages > Resolve Package Versions
. If you search for solutions on StackOverflow or elsewhere, you will find some really elaborate bash scripts that use fswatch to monitor the file system to workaround this problem. I think this is a bit overkill and prefer the simpler solutions I’ve offered here.
This is all a bit ridiculous, and I would say it’s shocking that this bug still hasn’t been fixed after 4 years, but this kind of thing is not all that surprising for Apple. It is rather routine that many bugs remain unfixed and tools remain broken for years.