Static frameworks, resource bundles, and a lot of pain

Hi :waving_hand:

You might know that we recently stopped generating bundles for static frameworks, since they can contain resources since Xcode 15. This is something we attempted a year ago, but we ended up reverting it because it opened a can of worms we were not prepared to deal with.

But this time, we tried and pushed through the edge cases we couldn’t foresee when we rolled it out (Claude came in handy at this). Xcode graphs are tricky to work with, which is why many of you lean on SwiftPM or us to abstract away those intricacies. But that also means that if we screw things up, your products might not compile or blow up at runtime. And this is what happened: we caused a lot of pain, and we are sorry for that, but we are almost at the end of this pain.

First of all, this change required adjusting the synthesized Bundle.module to point to the correct bundle location at runtime. And as people started using this, we uncovered new scenarios, like what happens when we link it to a test bundle? Or what if we link it from an extension? This is also connected with the second piece: where and when should the bundle be copied? If we don’t copy it, then Bundle.module won’t find it. And in both cases, we need to make sure we align with SwiftPM’s behavior so external packages work.

If that wasn’t enough, we uncovered another fun challenge. Turns out there are packages that don’t use Bundle.module and instead locate the bundle by the location of its symbols, which doesn’t work if the symbols and resources aren’t contained in the same bundle.

And we were still not done… all this work also uncovered another issue we had been ignoring for some time. Turns out that when you extract the resources of a target into an associated bundle, like we used to do, Xcode doesn’t synthesize the resource accessors because it doesn’t know the bundle resources are associated with the target. The way Apple addresses this in SwiftPM is by making the resources sources of the target, and resources of the bundle:

But even with those changes, they still face inconsistencies like this one. This left us with two paths:

  1. Keep a separate bundle for external dependencies and use the same trick as Apple, adding the resources as sources of the target and resources of the bundle
  2. Keep the resources in the static framework and let Xcode generate those symbols by default since it sees the resources belong to the target.

We leaned on 2 because it’s less hacky and more future-forward. However, this means we are reliant on packages doing a good job and using Bundle.module when the SWIFT_PACKAGE compiler directive is present. For those packages that don’t, we’ll have to open PRs, and we are happy to help with that, and users can, as a workaround, override the product to .framework, such that the symbol ends up in the binary whose bundle also contains the resources (a dynamic framework bundle).

This post is both a sorry message with an explanation of why this happened, and why it was pushed through, and also a note for ourselves so that we can get back to this at some point in the future and understand the intricacies of using static frameworks.

If you were unlucky to come across one of these scenarios, I’d appreciate it if you could respond to this topic with a reproducible project.

2 Likes

We ended up reverting the static framework with resources. We started working on this driven by this documentation page by Apple that describes how to include resources in static frameworks. The theory is that, on embed, the binaries are stripped. What happens in reality is that it fakes a binary, and it’s fine; things work locally after all the fixes here and there that we had to put out there. However… when you try to upload the app to the App Store, the validation fails:

ITMS-90171: Invalid bundle structure - The “MyApp.app/Frameworks/MyModule.framework/MyModule” binary file is not permitted. Your app cannot contain standalone executables or libraries other than a valid CFBundleExecutable for a supported bundle.

In other words, simulators and (devices?) are happy with the bundle layout, but the App Store is not, and Apple never reconciled the two worlds, so no, static frameworks don’t support resources.

I’ve reverted all the changes we made over the past two weeks, and hopefully nothing has broken; if it has, please let me know. I doubt we’ll ever try this again in the near future since we are eroding your trust by doing so. I’m very sorry for this, but we naively believed the support was there.