[Swift Package Manager] [Xcode] XCFramework has different behaviour when integrated manual vs SPM

Hey, I found a different behaviour when integrating a XCFramework manually vs with SPM.

When I add the XCFramework directly to Xcode, it recognises it as a XCFramework, and allows me to embedded or not.

When I add a XCFramework via Swift Package Manager to the Xcode project, using a binary target, the Xcode recognises it as a static library, and I can't choose if I want to embedded it or not.
Example: GitHub - twilio/twilio-video-ios: Programmable Video SDK by Twilio

Is this a bug? Is this related to Xcode or SPM?


My understanding is, the xcframework alone (like any regular framework bundle before) is built either as a static or a dynamic framework :thinking: this “possibility” to switch it in the project settings is just there as a convenience option so you can toggle it if it's initially set incorrectly when you add the framework to the project.

The proper mode to use when importing directly can be determined f.e. by using the file utility on top of a specific architecture binary taken from xcframework's hierarchy. Alternatively, you can just set it as Do Not Embed (like it's a static one) and try running the app – if the runtime fails instantly due to missing linked framework, it's almost for sure dynamic and needs to be embedded.

TL;DR: If I integrate the TwilioVideo in MyFramework via SPM on Xcode, I will end up with an umbrella framework.

Hi, let me give a bit more context.

I'm developing a XCFramework, let's call it MyFramework.
MyFramework depends on TwilioVideo (which is also a XCFramework).

To avoid the problem of nested Frameworks or umbrella Frameworks, I need to integrate TwilioVideo in MyFramework with the option "Do Not Embed".
This way, I will only ship MyFramework, and when someone consume MyFramework, will also need to integrate TwilioVideo.
This is what Apple recommends.

The problem is that when I'm compiling MyFramework integrated with TwilioVideo directly via Xcode or integrated via SPM on Xcode produces different results.

When I integrate TwilioVideo directly on Xcode, it works great, it recognises TwilioVideo as a XCFramework and gives the me option "Do Not Embed", which is the correct behaviour.

When I integrate TwilioVideo on Xcode via SPM, Xcode thinks that TwilioVideo is a static library, which is wrong, and doesn't allow me to choose the option "Do Not Embed".
This means that MyFramework end up being an umbrella framework which is discouraged by Apple.
Check the title "Don’t Create Umbrella Frameworks".

Since it produces different behaviours when integrating the same XCFramework directly via Xcode or via SPM, I think this is a bug or some kind of limitation.

Do Not Embed is the option used for static frameworks (as such framework code is “sticked” directly into the target binary so there's no need for further, redundant → thus invalid, embedding). If Xcode recognises that somehow in the SPM scenario, giving it as a static library, that seems rather correct, dunno why exactly you consider it wrong. :thinking:

It might be some other issue with linked products handling, however.

My problem is that when I integrate TwilioVideo via SPM in MyFramework, it will copy the TwilioVideo.Framework to MyFramework.framework/Frameworks/TwilioVideo.Framework, and this is not the desired behaviour.

I see, that rather sounds like Xcode treats it as a dynamic framework :thinking: and really seems like a tooling issue. Have you tried it with the latest Xcode 12.5 that has just been released?

We've been researching binary xcframeworks for some time for our workflow and only started integrating it successfully with latest 12.5 betas, now working nicely with just a single minor issue in DerivedData which isn't blocking the releases building alone. We handle both static (Firebase) and dynamic (FacebookCore) binary xcframeworks via SPM and both are linked with the application just as expected – Firebase is merged with the binary as expected whereas FacebookCore is correctly embedded in the Frameworks folder.

Give Xcode 12.5 a try, it may've been resolved for your use case, too. :+1:

This is Xcode bug. To workaround this add post action build script that deletes the framework from the unwanted location.

rm -rf ${TARGET_BUILD_DIR}/${WRAPPER_NAME}/Frameworks/FirebaseCore.framework
rm -rf ${TARGET_BUILD_DIR}/${WRAPPER_NAME}/Plugins/FirebaseCore.framework

I just tried it with Xcode 12.5 and the issue is still there.
I will try that script.

The script did the trick, thanks :slight_smile:
Is this a known bug?
Do you have an issue where I can track this issue?

For me the script is not doing the trick. That's because the framework copy phase happens after all the custom script phases.

Note: I filed a radar for Xcode (v. 12.5), and this problem has not been fixed yet in Xcode 13 beta 2 :-(

Are there any other known work-arounds?

You can move your script phase after the copy phase in the Xcode build phases configuration. Whether that's what you need I don't, I've never implemented this workaround.

Unfortunately, it was already the last phase in "Build Phases" in my case (since I am building a framework). Somehow Xcode adds an implicit copy/embed phase after all custom user phases :-(

My only resort, probably, is to delete embedded dependency frameworks just before creating an xcframework from the main framework...
(Don't have much hope for that radar ever being addressed).

Actually after some more testing, the script ended up not working for me also.
So I opened this issue.

Xcode bugs need to be reported to Apple, via Feedback. Xcode is not part of open-source Swift, and Xcode engineers don't necessarily hang out on this forum.