Download Failures with Swift Package Manager Binary Targets
Our iOS PDF SDK has supported Swift Package Manager since PSPDFKit 10 was released. This occurred alongside the introduction of support for binary (precompiled) frameworks in packages in Xcode 12.0. Swift Package Manager is a fabulously convenient way to add PSPDFKit’s advanced PDF functionality to any app. However, since around the release of Xcode 13.0 (and tested up until Xcode 14.0 beta 1 at the time of writing), we observed that when first cloning or downloading our Catalog and Minimal sample projects, the following error would show up in Xcode:
failed downloading ‘https://my.pspdfkit.com/pspdfkit/xcframework/11.3.1.zip’ which is required by binary target ‘PSPDFKit’: ~/Library/Developer/Xcode/DerivedData/Minimal-enhpcflwoobkdifztstuqbqotegx/SourcePackages/artifacts/pspdfkit-sp/11.3.1.zip already exists in file system
At first, I basically read this error message as “failed downloading PSPDFKit, some long path, blah blah”.
To fix this issue, we found you just need to select File > Packages > Reset Package Caches at any point after opening the Xcode project, and then the download would work. Adding the packages from scratch also worked reliably. Since downloading the package sometimes worked and sometimes didn’t, we were fairly certain this was a bug in Swift Package Manager, and we thought we couldn’t do anything about it.
And so this problem remained for months — and it was a really awful first experience with PSPDFKit for our customers. We documented the requirement to reset package caches, but of course, iOS developers commonly expect that you can just open and run an Xcode project without additional steps.
We considered other options, such as if our sample projects didn’t have the Swift package dependencies already configured, and we documented how to add these. This workaround would be more time-consuming for people trying out PSPDFKit, but at least the process would work reliably.
New Information
Recently, I was in the process of documenting this limitation more thoroughly when I noticed a slightly different error in Xcode:
failed downloading ‘https://my.pspdfkit.com/pspdfkitui/xcframework/11.3.1.zip’ which is required by binary target ‘PSPDFKitUI’: ~/Library/Developer/Xcode/DerivedData/Minimal-enhpcflwoobkdifztstuqbqotegx/SourcePackages/artifacts/pspdfkit-sp/11.3.1.zip already exists in file system
What caught my eye was that this said PSPDFKitUI. Note the UI part at the end.
For some background information, we separate our core PSPDFKit for iOS product into two frameworks: PSPDFKit, for model operations like document parsing and rendering; and PSPDFKitUI, which provides ready-to-use UI components for SwiftUI and UIKit. This means customers who only need model-level processing operations don’t need to add our UI framework to their app at all, and even when using both, files only working with the data don’t need to import the UI module.
Therefore, our PSPDFKit-SP package has two binary targets. This was our Package.swift
file:
let package = Package( name: "PSPDFKit", products: [ .library( name: "PSPDFKit", targets: ["PSPDFKit", "PSPDFKitUI"]), ], targets: [ .binaryTarget( name: "PSPDFKit", url: "https://my.pspdfkit.com/pspdfkit/xcframework/11.3.1.zip", checksum: "8b72c61db0497111cb5e94af2269c8e8a5a7b5935f5600d87264e1df266f0144"), .binaryTarget( name: "PSPDFKitUI", url: "https://my.pspdfkit.com/pspdfkitui/xcframework/11.3.1.zip", checksum: "d2ba1b1a01ac5a4cb36610200f8e13e671d8608608a6dcde46721e9b0ab04ab1"), ] )
Looking back at the two error messages, we can see that despite these messages being for PSPDFKit and PSPDFKitUI respectively, the file system path is the same in both cases. Inside the derived data directory, the path is:
SourcePackages/artifacts/pspdfkit-sp/11.3.1.zip
And that looks like the bug. When first opening a project with Swift package dependencies already configured, Swift Package Manager tries to download all the binary targets of each package into a path determined only by the package name and the last path component of the URL of the binary. Since both our URLs ended with 11.3.1.zip
, the second download to complete would find a file already existed at the target file system location. This issue has been reported to Apple as FB10471859.
Note that we suggest unique file names in the HTTP headers, but Swift Package Manager seems to ignore these.
A Simple Solution
With this knowledge, an obvious workaround would be to serve the binary files with distinct last path components in the URLs. So we set up an additional route on the server side, tested it, and boom: Downloading PSPDFKit after opening the Xcode project just worked. We’re back to the smooth first experience we had with Xcode 12.
We’ve now updated our PSPDFKit Swift Package repository to fix this issue when using PSPDFKit for iOS version 11.3.2 or later. (We could fix this for older versions too if we rewrote the Git history of our Swift Package repository and re-pushed all its tags, but this seems like a drastic step to take.)
Takeaways
The specific point to remember is that when setting up a Swift package with binary targets, ensure the URLs have distinct last path components. Of course, the more general lesson is to read error messages very carefully for clues! Now that our PDF SDK is working correctly with Swift Package Manager, the tight integration between Xcode and Swift Package Manager is so, so nice. You can see it working right now in our Catalog sample app.