Publishing binaries with SPM

Helder Pinhal
Helder Pinhal
Dec 11 2020
Posted in Engineering & Technology

Building and distributing XCFrameworks with SPM

Publishing binaries with SPM

With the release of Xcode 12 Apple added a new way of distributing compiled iOS frameworks — that is, Swift Package Manager. In practice, SPM can distribute packaged frameworks, namely XCFrameworks.

For the scope of this article, we will build and distribute a fictitious framework named StarWarsKit.

1. Building the XCFramework

Before we can create the actual XCFramework we need to compile our source code for each platform / architecture variant we want to support. In this case, we're adding support for iOS and the simulator.

# iOS devices
xcodebuild archive \
    -scheme StarWarsKit \
    -archivePath "archives/StarWarsKit-iOS.xcarchive" \
    -destination "generic/platform=iOS" \
    -sdk iphoneos \
    SKIP_INSTALL=NO \
    BUILD_LIBRARY_FOR_DISTRIBUTION=YES

# iOS simulators
xcodebuild archive \
    -scheme StarWarsKit \
    -archivePath "archives/StarWarsKit-iOS-simulator.xcarchive" \
    -destination "generic/platform=iOS Simulator" \
    -sdk iphonesimulator \
    SKIP_INSTALL=NO \
    BUILD_LIBRARY_FOR_DISTRIBUTION=YES

It's important to note the BUILD_LIBRARY_FOR_DISTRIBUTION setting is enabled. This will instruct the compiler to generate the .swiftmodule interface that will allow us to import the framework into our application.

Once the individual archives have been compiled, we can create the XCFramework itself.

xcodebuild -create-xcframework \
    -framework "archives/StarWarsKit-iOS.xcarchive/Products/Library/Frameworks/StarWarsKit.framework" \
    -framework "archives/StarWarsKit-iOS-simulator.xcarchive/Products/Library/Frameworks/StarWarsKit.framework" \
    -output "StarWarsKit.xcframework"

At this point you should be able to see the StarWarsKit.xcframework at the root of your project. For testing purposes you can already include it in an application as it's fully ready to be consumed.

2. Publicly host the XCFramework

In order to distribute the compiled package, we need to host it somewhere with public accessibility. One way to do this is to push the XCFramework to a git repository and use that as the binary target URL.

Alternatively, we can create a GitHub Release with our artifacts. The advantage of this approach is the fact we don't need to keep a large history of artifacts alongside our source code, therefore, not hindering the checkout process.

Going with this route, we need to create a ZIP file with the XCFramework at the root and calculate the checksum of the resulting file, which will be needed for the Package manifest.

# Create the ZIP file
zip -r -X StarWarsKit.xcframework.zip StarWarsKit.xcframework

# Calculate the checksum
swift package compute-checksum StarWarsKit.xcframework.zip

After publishing a GitHub release with the changelog and the zip file artifact we can proceed with the creation of the Package manifest.

3. Creating the Package manifest

SPM allows us to publish source code in various formats - actual source code, locally stored binaries and URL-based binaries. In our approach, we've decided to go with the URL-based approach (a.k.a. GitHub Release), hence the need for the checksum.

At the root of the project, create the following Package.swift. Be aware that you need to use the correct URL to your GitHub repo as well as the checksum calculated in the previous step.

// swift-tools-version:5.3
import PackageDescription

let package = Package(
    name: "StarWarsKit",
    platforms: [
        .iOS(.v10),
    ],
    products: [
        .library(
            name: "StarWarsKit",
            targets: ["StarWarsKit"]
        ),
    ],
    dependencies: [],
    targets: [
        .binaryTarget(
            name: "StarWarsKit",
            url: "https://github.com/YOUR_ORG/star-wars-kit/releases/download/1.0.0-alpha.1/StarWarsKit.xcframework.zip",
            checksum: "b24b18fb3c11ca154242742ad9a18c3c67d4a75cb75b3e05d7dbb82cbd367227"
        ),
    ]
)

4. Consuming the framework

In your application project, by going to File > Swift Packages > Add Package Dependency you can type the URL of your GitHub repository and select the appropriate version if you want to consume.

All there is left to do is build the project and use the framework. 🚀

Final thoughts

All this manual and tedious process could be automated by leveraging GitHub Actions which provides developers with a generous amount of build minutes. Taking away the manual process of the equation, aside of saving precious time, we also improve the reliability of our workflow.

If you liked this article or have something to add, we are available, as always, via our Support Channel.

Keep up-to-date with the latest news