How to build Swift Package as XCFramework

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.

3 Likes

I tried multiple solutions which I found online, but none were working. And now that swift package generate-xcodeproj has been removed I don't see any way of getting this working the plain swift package way without 3rd party software like swift-create-xcframework. Only feasible solution is to go back to standard xcode project.

If anyone is interested in what I tried:

  1. Using swift build does not support building for a specific platform. There are workarounds but they are ugly.
  2. Using xcodebuild build does not work when using release build if you have tests. It will fail because it says "... was not build for testing" and the only way to stop xcode from building the tests (which I don't want do build in the first place) is to manually edit the scheme and uncheck "run" for the test target. Because I want this process to be fully automated that it not an option.
  3. Using xcodebuild archive runs but the generated archive only contains a simple .o file and no headers, swiftmodule files or whatsoever.

Hey all, there actually is a solution, but it's a bit more complicated.

Firstly, build the framework using

xcodebuild archive -scheme {SchemeName} -destination {Destination} -archivePath Release-iphoneos.xcarchive -derivedDataPath {DerivedDataPath} SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES

Where the SchemeName is name of the scheme (package product name) that you are building, and the Destination is one of generic/platform=iOS, generic/platform=iOS Simulator or generic/platform=macOS.

Secondly, copy the generated Swift module files from derived data. BUILD_LIBRARY_FOR_DISTRIBUTION=YES in the previous command will ensue that the module gets generated, but you need to copy it manually.

cp -r {DerivedDataPath}/Build/Intermediates.noindex/ArchiveIntermediates/{SchemeName}/BuildProductsPath/Release-iphoneos/{SchemeName}.swiftmodule Release-iphoneos.xcarchive/Products/usr/local/lib/{SchemeName}.framework/Modules/{SchemeName}.swiftmodule

Thirdly, if the product has resources, you need to copy the resources bundle manually as well!

cp -r {DerivedDataPath}/Build/Intermediates.noindex/ArchiveIntermediates/{SchemeName}/IntermediateBuildFilesPath/UninstalledProducts/iphoneos/{SchemeName}_{SchemeName}.bundle Release-iphoneos.xcarchive/Products/usr/local/lib/{SchemeName}.framework/{SchemeName}_{SchemeName}.bundle

Finally, repeat this process for each {Destination} you support and then generate an xcframework. Here you can also embed dSYMs if you want to.

xcodebuild -create-xcframework 
    -framework Release-iphoneos.xcarchive/Products/usr/local/lib/{SchemeName}.framework 
    -debug-symbols Release-iphoneos.xcarchive/dSYMs/{SchemeName}.framework.dSYM 
    -framework Release-iphonesimulator.xcarchive/Products/usr/local/lib/{SchemeName}.framework 
    -debug-symbols Release-iphonesimulator.xcarchive/dSYMs/{SchemeName}.framework.dSYM -output {SchemeName}.xcframework

Note that if you are dealing with C/ObjC package product, you'll also need to copy headers manually and generate the modulemap manually.

Note as well that all this only works if you mark the package products as dynamic libraries:

.library(
    name: "Test",
    type: .dynamic,
    targets: ["Test"]
)
14 Likes

My problem is that when using swift package manager (no xcode project files in the directory) the generated xcarchive only contains a single <Scheme>.xcarchive/Products/Users/<Name>/Objects/<Scheme>.o file (Mach-O 64-bit object arm64). There is no .framework folder at all.

I also get this behaviour in a newly generated project (empty folder -> swift package init -> run xcodebuild).

Using:

xcodebuild archive -scheme "$SCHEME" -destination "generic/platform=macOS" -archivePath ".build/archives/$SCHEME-macOS" -derivedDataPath ".build/dd/" SKIP_INSTALL=NO ARCHS="arm64" BUILD_LIBRARY_FOR_DISTRIBUTION=YES

swift-driver version: 1.75.2 Apple Swift version 5.8.1 (swiftlang-5.8.0.124.5 clang-1403.0.22.11.100)
Xcode 14.3.1
Build version 14E300c

Sorry, I forgot to say that the package product needs to be a dynamic library.

.library(
    name: "Test",
    type: .dynamic,
    targets: ["Test"]
)
4 Likes

I'm getting the following error:

The following build commands failed:
      CompileSwift normal arm64 (in target '_NIODataStructures' from project 'swift-nio')

It's because SwiftNIO doesn't define stable ABI, is there some workaround for this? I tried searching for this and suggestion is to use @_implementationOnly but that's not applicable for this case.

Archive works when I set BUILD_LIBRARY_FOR_DISTRIBUTION=NO, will it still work or is BUILD_LIBRARY_FOR_DISTRIBUTION=YES strictly necessary?

Unfortunately, BUILD_LIBRARY_FOR_DISTRIBUTION=YES seems to be strictly necessary. I wasn't able to find a way to get the module interface file without this, even though

I has issues with swift-algorithms and swift-collection, but it was a different kind of error I got (something in the module interface file). My only solution was to fork the dependencies and tweak them. Which is terrible.

Unfortunately, this doesn't seem to work if you depend on binary system libraries. It fails because those binaries only have a single architecture. Is there a way to build a single-architecture XCFramework? I tried to do -destination platform=macOS,arch=arm64 but that didn't seem to fix the issue.

1 Like

I was able to make this work by adding the EXCLUDED_ARCHS=x86_64 build setting.

1 Like

For the record, I've gathered on this Apple Developer forums post all the info I gathered on this topic.

5 Likes

Given that XCFrameworks seem to be the chosen path for shipping binary targets in SwiftPM, it'd be seriously helpful to have SwiftPM be capable of building them without all of this extra effort.

4 Likes

Yes I'd really expect to have something like xcodebuild -create-xcframework -swiftpackage Foo and that would be it.

4 Likes

Or, even better swift package create-xcframework ...

It is unfortunate that we have to rely on Xcode for such a low-level capability as binary targets.

2 Likes

Then again, as the name suggests, this is an Xcode feature, and only really exists on Darwin platforms. So it doesn't really fit into the package manager. :thinking:.

I fully agree though, that it is way too hard to actually build a working XCFramework out of a Swift package that actually works. But that's an Xcode (+ tooling) problem, not a Swift problem.

2 Likes

That doesn't really make sense. Platform support doesn't fit in the package manager's platform support? It's really no different than being able to search platform-specific locations for linking or producing the correct binary type (no one else uses MachO). If nothing else it could be a build plugin Apple offers by default for SPM on their platforms. But first we need to be able to actually do everything within SPM itself.

1 Like