How to build Swift Package as XCFramework

I find all the #import statements in my umbrella header and parse out the file name, find it in the package directory tree, and then copy it to the headers dir in the framework (had to create the headers dir too).

This could probably be more succinct, but I am no bash expert.

perl -lne 'print $1 if /\<'${NAME}'\/(\S+.h)/' $NAME/$NAME.h | \
    xargs -I {} find . -name "{}" -print | \
    xargs -I {} cp {} $HEADERS_PATH/.
cp $NAME/$NAME.h $HEADERS_PATH/.

Has anyone figured out how to distribute a binary to Linux folks? An XCFramework is only good for my iOS, macOS, tvOS, etc users, how can I distribute via SPM to my linux users via a binary?

Swift on Linux is not ABI stable and thus distributing libraries as binaries isn’t going to work reliably.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

2 Likes

@NeoNacho would you have an advice on how to build all dependencies in a dependency graph into XCFrameworks?

The script that I posted above can build a single dependency, however all of its dependencies get statically linked, so building those dependencies individually makes no sense. Is there a way to disable that static linking?

1 Like

@Srdan_Rasic Thanks for creating the script that outputs a framework from a Swift Package.

When I run the script I'm getting an error during the signing step.
I attached the output below. Would you know how to fix this?

Thanks.

Signing Identity: "-"

/usr/bin/codesign --force --sign - /Users/user/Documents/etudes/MyProject/.build/Build/Intermediates.noindex/ArchiveIntermediates/MyProject/IntermediateBuildFilesPath/UninstalledProducts/iphonesimulator/MyProject_MyProject.bundle

/Users/user/Documents/etudes/MyProject/.build/Build/Intermediates.noindex/ArchiveIntermediates/MyProject/IntermediateBuildFilesPath/UninstalledProducts/iphonesimulator/MyProject_MyProject.bundle: bundle format unrecognized, invalid, or unsuitable
Command CodeSign failed with a nonzero exit code

** ARCHIVE FAILED **

The following build commands failed:
CompileSwift normal x86_64
CompileSwift normal i386
CompileSwift normal arm64
CompileSwift normal x86_64
CompileSwift normal arm64
CodeSign /Users/user/Documents/etudes/MyProject/.build/Build/Intermediates.noindex/ArchiveIntermediates/MyProject/IntermediateBuildFilesPath/UninstalledProducts/iphonesimulator/MyProject_MyProject.bundle
(6 failures)

Does the project compile in Xcode? Maybe check your code signing identities have been set in Xcode.

I managed to create the XC framework now. However when I import it into a client project, I get the following error:

Failed to build module 'MyProject' from its module interface; it may have been damaged or it may have triggered a bug in the Swift compiler when it was produced

Any advice on how to fix this issue?

Thanks.

@Alfredo that is a very broad question so it's really hard to answer! I would recommend trying to dig deeper though and post an update with what you tried. One reason could be to see whether you have old Objective-C runtime support (see ios - Admob Framework class not detected by Xcode interface builder - Stack Overflow for a similar XCFramework ObjC runtime issue) in the framework and check that your access levels are valid. Have you followed the recommended steps to create the XCFramework?

@LemonCupcake Let me clarify my scenario. I have a Swift Package project which contains 9 Swift packages. This project has only one scheme and this is the name provided to the script written by @Srdan_Rasic as input.
There is no Objective-C code in this setup at all, and no external dependencies either.
The script runs successfully and produces an XC framework.
If I create a brand new project in Xcode, and include this XC framework as a dependency, I get the error below when I add the line to import the framework.
Please let me know if additional details are required.

Thanks.

Failed to build module 'MyProject' from its module interface; it may have been damaged or it may have triggered a bug in the Swift compiler when it was produced

@Alfredo could you please check the contents of x.swiftinterface inside the frameworks x.swiftmodule folder? Do you only import Swift here?

Also, what exactly do you mean by "9 Swift packages" and "no external dependencies"? Do you mean the .dependencies modifier passes an empty array?

@Alfredo One solution seems to add @_implementationOnly import for the modules from the third-party SDKs. This should fix your XCframework error if you aren't using anything from that SDK as part of your module's public interface.
source

Hello, This solution here suggests that xcodeproj or xcodeworkspace exists. However, ideally, they don't. We have package specification in package manifest.
So when I do this:
xcodebuild archive
-scheme MyLibrary
-destination "generic/platform=iOS"
-archivePath "archives/MyScheme-iOS"
-allowProvisioningUpdates
SKIP_INSTALL=NO
BUILD_LIBRARY_FOR_DISTRIBUTION=YES

It indeed archives the project, but no resources are in framework, no swiftmodule files. How can I build a package, that is not backed up by xcodeproj or workspace into working framework?

Specify the package directory as a workspace. If building from the package directory, specify -workspace .

xcodebuild archive 
-workspace .
-scheme MyLibrary 
-destination "generic/platform=iOS" 
-archivePath "archives/MyScheme-iOS" 
-allowProvisioningUpdates 
SKIP_INSTALL=NO 
BUILD_LIBRARY_FOR_DISTRIBUTION=YES

Adding -workspace did not help. Still no resources copied to framework and no swiftmodule files. No difference with -workspace . or without it.
It seems so that I will have to create xcodeproj, put all files there as well. This is a bit unfortunate.
It seems that SPM is only supporting delivery of already compiled xcframeworks, but is not helping much to create them.

I have tried the same thing:
Package.swift:

// swift-tools-version:5.9
...
products: [
    // Products define the executables and libraries produced by a package, and make them visible to other packages.
    .library(name: "SwiftDateKit",
             type: .dynamic,
             targets: ["SwiftDateKit"])
  ],
...

whole xcodebuild command:

xcrun xcodebuild archive \
  -workspace . \
  -scheme SwiftDateKit \
  -configuration Release \
  -destination 'generic/platform=iOS' \
  -derivedDataPath '.build' \
  -archivePath './.build/Release/SwiftDateKit-iphoneos.xcarchive' \
  -allowProvisioningUpdates \
  SKIP_INSTALL=NO \
  BUILD_LIBRARY_FOR_DISTRIBUTION=YES

output:

** ARCHIVE SUCCEEDED **

but no swiftmodule files

has anyone any idea?

Hi Stephan,

I had same question like you few weeks before, and I came to realisation that this part of tools is simply not finished yet.

We are transitioning from cocoa pods to SPM. We want local source based package for local development and testing, but final product is a compiled as dynamic library in form of .xcframework.

At the moment only reliable way to create that xcframework is to make xcode project from framework template, and reference all source files there as well. Archive with the same cmd you provided just reference your worspace or project by name, and it will work correctly. You will get correct result.

This is not ideal solution, but it works. Every time you add file to package, you have to add it to the xcode project as well.

I've already put in my feedback request for this, but this seems like if Apple made a SPM plugin to do this, it would be very helpful for a lot of people.

The community could do this, but the XCFramework format isn't specified in documentation afaik and Apple can change it whenever they want to.

Probably better to use a tool like Tuist to generate such a project to build the frameworks, otherwise you will run into bugs where you forget to add a symbol to the precompiled framework and stuff starts crashing at runtime.

1 Like

The XCFramework format isn't officially specified but it is extraordinarily simple. It's just the per-platform Frameworks arranged in a sensible directory structure plus a plist listing what frameworks are present. If Apple did change it they'd still need to support the old format indefinitely since it's a binary artifact.

The complicated part is building the Frameworks which go into the XCFramework, and that is an actually-documented format.

1 Like

Is it still not possible to build a working XCFramework from a Swift Package?
I tried and could produce an XCFramework using

xcodebuild archive -scheme "TestSDK" -destination "generic/platform=macOS" -archivePath "TestSDK-macOS" DEFINES_MODULE=YES SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES OTHER_SWIFT_FLAGS="-no-verify-emitted-module-interface" INSTALL_PATH="/Library/Frameworks"
xcodebuild archive -scheme "TestSDK" -destination "generic/platform=iOS Simulator" -archivePath "TestSDK-iOS_Simulator" DEFINES_MODULE=YES SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES OTHER_SWIFT_FLAGS="-no-verify-emitted-module-interface" INSTALL_PATH="/Library/Frameworks"
xcodebuild archive -scheme "TestSDK" -destination "generic/platform=iOS" -archivePath "TestSDK-iOS" DEFINES_MODULE=YES SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES OTHER_SWIFT_FLAGS="-no-verify-emitted-module-interface" INSTALL_PATH="/Library/Frameworks"

xcodebuild -create-xcframework -archive TestSDK-iOS.xcarchive -framework TestSDK.framework -archive TestSDK-iOS_Simulator.xcarchive -framework TestSDK.framework -archive TestSDK-macOS.xcarchive -framework TestSDK.framework -output TestSDK.xcframework

but when dragging the resulting XCFramework into an Xcode project, it simply doesn't find the TestSDK module.

The TestSDK is the most minimal package that explicitly builds a dynamic library product.

2 Likes