Issue with third party dependencies inside a XCFramework through SPM

Hi,

I'm having an issue related with the SPM with xcframework and third dependencies.

First of all I built my framework project that has a facebook dependency (through SPM).

image

Then I generated the xcframework.

 xcodebuild archive \
  -scheme TestSDKFramework \
  -sdk iphoneos \
  -archivePath "archives/ios_devices.xcarchive" \
  BUILD_LIBRARY_FOR_DISTRIBUTION=YES \
  SKIP_INSTALL=NO
  
xcodebuild archive \
  -scheme TestSDKFramework \
  -sdk iphonesimulator \
  -archivePath "archives/ios_simulators.xcarchive" \
  BUILD_LIBRARY_FOR_DISTRIBUTION=YES \
  SKIP_INSTALL=NO

xcodebuild -create-xcframework \
    -framework archives/ios_devices.xcarchive/Products/Library/Frameworks/TestSDKFramework.framework \
   -framework archives/ios_simulators.xcarchive/Products/Library/Frameworks/TestSDKFramework.framework \
  -output TestSDKFramework.xcframework

Uploaded to a private git the xcframework and the package that has the facebook dependency as well.

// swift-tools-version:5.3
import PackageDescription
let package = Package(
    name: "TestSDKFramework",
    platforms: [
        .iOS(.v13)
    ],
    products: [
        .library(
            name: "TestSDKFramework",
            targets: ["TestSDKFramework"])
    ],
    dependencies: [
        .package(
            url: "https://github.com/facebook/facebook-ios-sdk",
            from: "8.1.0"
        )
    ],
    targets: [
        .binaryTarget(
            name: "TestSDKFramework",
            path: "TestSDKFramework.xcframework")
    ])

And finally consumed by the final client project as SP.

But then, when I try to compile the project the Facebook module was not found.

We tried to add the Facebook dependency in the final client project as well but does not work either.

How we can work with third dependencies like facebook inside a xcframework project? Any clues?

3 Likes

Hi @brunobasas,
I'm running into almost same issue (just with AppAuth rather than Facebook dependency) - have you been able to find a solution?

Actually, for your case (defining dependency for the binary target and getting it auto-pulled) there is an improvement/workaround in your Manifest file by making use of a regular source target as wrapper and define the binary target as dependency on it:

// swift-tools-version:5.3
import PackageDescription
let package = Package(
    name: "TestSDKFramework",
    platforms: [
        .iOS(.v13)
    ],
    products: [
        .library(
            name: "TestSDKFramework",
            targets: ["TestSDKFrameworkWrapper"])    // <--- reference the wrapper here
    ],
    dependencies: [
        .package(
            url: "https://github.com/facebook/facebook-ios-sdk",
            from: "8.1.0"
        )
    ],
    targets: [
        .target(
            name: "TestSDKFrameworkWrapper",         // <--- new wrapper
            dependencies: [
                .product(name: "Facebook", package: "Facebook")
                .target(name: "TestSDKFramework")    // <-- reference the actual binary target here
            ],
            path: "Sources/TestWrapper",
            publicHeadersPath: ""
        ),
        .binaryTarget(
            name: "TestSDKFramework",
            path: "TestSDKFramework.xcframework")
    ])

(the Sources/TestWrapper directory only needs to contain an empty .swift file, cmp. the Firebase implementation as mentioned here)

However, this will probably only save the step

We tried to add the Facebook dependency in the final client project

Still, for me the "No such module" remains :frowning:

The only way I could somehow get my framework and the final client app to build properly was to add the 3rd party dependency's XCFramework folder manually via drag'n'drop rather than SPM, which probably works as the Framework Search Path is then finding the dependency properly.
Interestingly, if you make an "import Facebook" in the client's app itself the SPM dependency is found, just not from within the .swiftinterface files :confused:

Any feedback/help from the other experts on this topic possible? I also tried to play around with explicitly adding linkerSetting: [ .linkedFramework("AppAuth") ], without success ...

1 Like

The general issue here is that the Swift compiler needs to be able to see all the modules being used at build-time, either via .swiftmodule/.swiftinterface or module maps. A single XCFramework can only contain one such module, though.

There are three possible solutions:

  • You could use @_implementationOnly import for the modules from the third-party SDK if you aren't using anything from that SDK as part of your module's public interface.

  • You provide the third-party SDK separately, e.g. as its own XCFramework. The important part here is that you need to make sure that it doesn't get linked statically into your other XCFramework, otherwise you will run into duplicated symbols. For packages that means making any library products dynamic. This might require some significant effort since there could be more transitive dependencies.

  • You provide the additional module definitions out-of-band and make sure they get found at build-time by the compiler using search paths.

2 Likes

Hi @NeoNacho,

I am trying to use your second solution, or something similar. My 3d party dependencies are not under my control and thus I am having problems linking them dynamically. If I use the wrapper described above, the dependency gets always linked statically, and it can not be found on runtime.
Is there anyway of forcing the linking of the deps as dynamic? Is this only possible if the vendor of the library set type: .dynamic on their SP repo?

I did also try to wrap the 3d party dep in another package offering a type:.dynamic product but of course there is a name conflict if my package since it has the same name as the library it wraps...

In the example above I would just need a way to expose Facebook as a dynamic product, so that the app can embbed it.

Thanks!

Unfortunately, there is currently no way to influence this beyond the manifest declaration itself.

@NeoNacho I am having this exact issue and the @_implementationOnly option is not viable for me. I would love to attempt either of the other 2 options, however, I don't have a lot of background on either. Would you mind shedding some more light on those options or perhaps some helpful reading you could point me to? TIA

Hi @NeoNacho
at first a large (delayed) thank you for your response!

To be honest, I haven't yet fully understood why then the build of the iOS app fails in case both our own XCFramework as well as the external framework (here: AppAuth) are defined as SPM dependency - shouldn't the Swift compiler "see" the required AppAuth dependency when building from our .swiftinterface? I have the feeling that XCode is just not searching in the correct path, but have no idea what path could be added properly to FRAMEWORK_SEARCH_PATH.

Actually this approach seems to work for us. However, we are still unsure about any side-effects of doing so, and unfortunately the @_implementationOnly feature is not very well documented and is still in some alpha state. Even after researching some links and our internal tests we fear the risk that this brings any trouble in external apps that do integrate our framework.
Is there anything that is known that might give trouble when making use of @_implementationOnly for third-party dependencies? Would it be possible for other apps to pull in the same dependency for their own app without messing up with our framework's dependency?

Thank you so much for your helpful info, and I'd be glad if you are able to provide me some more info on this.

I had the same problem as @brunobasas. The solution approach with @_implementationOnly helped me. But i had an additional problem where a file was created at runtime which defined public classes and methods and didn't use @_implementationOnly. With two additional lines in the shell script, I adjusted this file and now it works fine.

Thank you very much @NeoNacho!

1 Like

Hello, please can someone share an example of Package.swift file that has .xcframework with it's dependencies?
@NeoNacho @shariebg

If it adds any context to this topic, we weren't able to successfully include an XCFramework with the @_implementationOnly imports if the client app uses the same dependency. We're getting
Class XXX is implemented in both A and B. One of the two will be used. Which one is undefined runtime errors, here is a straightforward sample that replicates this issue: https://bugs.swift.org/browse/SR-14752.

  1. My approach was to add the dependency in my Xcode project using Swift Package Manager.
  2. In the Swift files of the Xcode project, I imported the dependency using @_implementationOnly import <Framework>.
  3. After that, I generated an XCFramework from the Xcode project.
  4. So the dependency of the XCFramework doesn't need to be mentioned in the binary target (which is not possible anyway).
Terms of Service

Privacy Policy

Cookie Policy