SwiftPM binary target resource bundles

It would be nice if there was an SPM friendly way of including resource bundles that are meant to be distributed with pre-built binaries. For example, I have a .binaryTarget() that also requires a .bundle. Currently, the bundle has to be copied and manually dropped into the xcode project.

Ideally, we could include this in the swift package to avoid that manual process. What if we had a resources array where resources could be added to the main bundle? Something like:

let package = Package(
  name: "MyPackage",
  platforms: [.iOS(.v11)],
  products: [
    .library(name: "MyLibrary", targets: ["MyLibrary"])
  ],
  dependencies: [],
  targets: [
    .binaryTarget(name: "MyLibrary", url: "<url>/MyLibrary.xcframework.zip", checksum: "<checksum>"),
  ],
  resources: [
    .bundle(name: "MyBundle", url: "<url>/MyBundle.bundle.zip", checksum: "<checksum>"),
  ]
)
7 Likes

@akaffenberger I'm having the same issue. Did you managed to find a solution to this problem?

Hello, we recently wrote a command line process that copies the resources from the bundle that SPM builds into our platform-specific framework that Xcode builds and then those frameworks are combined into our binary framework.

Since our product is available for iOS and Mac Catalyst, that complicated things a bit more, but we were able to overcome that as well.

A few steps for getting the resources placed in the correct location for each platform specific framework:

  • We archive our swift package for each supported platform, then we grab the ".bundle" file from the archive
  • We get the resources from within that ".bundle" file and we place them into our platform specific frameworks
    • For iOS, the resources can simply be placed under the "SomeFramework.framework" directory
    • For Mac Catalyst, it is a little more complicated where to place the resources but Apple has some documentation on that here: Anatomy of Framework Bundles.

Once the resources were placed in the correct locations, we create our binary framework and those resources can be accessed using the binary framework.

My very basic workaround is to just drag and drop the bundle files from the swift package to the XCode project’s “Copy Bundle Resources” phase.

I show an example in the readme for one of my projects: GitHub - akaffenberger/firebase-ios-sdk-xcframeworks: A small mirror for https://github.com/firebase/firebase-ios-sdk, to add support for binary (xcframework) distribution with swift package manager.

1 Like

+1, this would be really useful + it's a must for distributed static frameworks. :pray:

+1
I've tried to implement approach described in Distributing binary frameworks as Swift packages | Apple Developer Documentation to create a package with binary framework containing resources but stumbled at few pitfalls:

  • first of all this guide does not mention that for resources to be available from within xcframework it needs to contain a dynamic library, not a static, and hence the resulting xcframework should be also embedded into the app target to make it available at runtime. My project setup defaults to static libraries so I had to change that
  • in the apps with extension targets like widgets if that xcframework is not embedded to the extension target it results in widget not even showing up in the widget editor or crashing when running on the simulator without debugger attached. But when copied it results in working extensions on the simualtor but this does not pass AppStore validation with error: Asset validation failed (90685) CFBundleIdentifier Collision. There is more than one bundle with the CFBundleIdentifier value '...' under the iOS application '....app'. Maybe there is a way to resolve it with tweaking @rpath for those extension targets but I haven't succeeded with that.

I ended up with an approach similar to @zkline101's where I have a dummy app target (rather than SPM package, that would work too) that I use to archive with xcodebuild, then I grab the compiled bundle and copy it as a resource to my other framework target.

It would be nice if this was automated and supported natively with SPM

I don't think it would be a good idea to unilaterally extend SwiftPM here. The entire goal of XCFramework support was that you can distribute the same framework both on its own and as a package, so any solution here should involve extending the XCFramework format itself.

With that said, I'm not 100% sure what the contents of the XCFramework are in this case. AFAIK if the contents are frameworks with bundle resources, that should already work. Are there issues with that or are we talking about supporting resources when distributing loose static/dynamic libraries?

Speaking about my case - yes, XCFramework contains a dynamic library and resources. The issue I faced was linking to those in the extension targets but I ended up resolving it by updating @rpath in those extension targets to be able to load frameworks from the app bundle (for anyone wondering by adding '@loader_path/../../Frameworks'). So I ended up sticking to this approach in favour of copying bundles separately (no SPM involved yet as the frameworks are local to my project but can be added in future in a form of remote package with binary target).
This also probably will map better to mergeable libraries.

@NeoNacho What should be the approach when supporting xcframework with resources when distributing them with loose static libraries?