Why is it so damn difficult to create a binary framework for your Swift Package

ยท

3 min read

Swift Package Manager (SPM) allows you to

  • Share source code (open-source)

  • Distribute binaries to protect your intellectual property (closed source)

Unfortunately, SPM does not help you to create a precompiled binary version of your package products. Although it is a common practice for SDKs to publish binary framework(s) in addition to source code.

Prominent examples are

App developers often expect a pre-compiled library version to speed up their build time. Otherwise, the build process for an app has to compile framework code.

Why is it so difficult creating binaries?

Challenges

You have to remember that

A frameworks can only contain a single module, so if you are trying to ship a package product that contains multiple targets, this approach won't work.

as noted in the Swift Forum by Boris Bรผgling.

Let me explain this statement with an example. A package structure like this

// swift-tools-version: 5.6
import PackageDescription

let package = Package(
    name: "MyPackage",
    products: [
        .library(name: "Lib1", targets: ["LibATarget"])
    ],
    dependencies: [
        .package(path: "../OtherPackage")
    ],
    targets: [
        .target(
            name: "LibATarget",
            dependencies: ["TargetB"],
        .target(
            name: "TargetB",
            dependencies: [.product(name: "ExternalLib", package: "OtherPackage")])

would require compiling three binary frameworks.

  • LibATarget.xcframework

    • TargetB.xcframework

      • ExernalLib.xcframework (hoping that ExternalLib consists of a single module)

If you rely on another package, you might run into situations in which it is impossible to get/create a binary framework for that package dependency. Here is an example concerning a popular Swift package (2k+ stars).

A package structure like the example above is not uncommon. Especially considering the following recommendation given in the Apple Swift Packager Manager documentation:

As a rule of thumb: more modules is probably better than fewer modules. The package manager is designed to make creating both packages and apps with multiple modules as easy as possible.

How to generate binary frameworks

You need an Xcode project (.xcodeproj) to compile a binary framework (.xcframework) but the SPM command swift package generate-xcodeproj is deprecated and does not work correctly if your Swift Package has resources :(

You can

  • manually create an Xcode project and keep it in sync with your folder structure

  • auto-create an Xcode project via tooling (e.g. Xcodegen or Tuist)

  • develop your custom tooling (e.g. by leveraging CocoaPods)

The AWS SDK for iOS is using Xcodegen and then using a python scrip on CircleCI to create their SDK binaries.

We will now use Xcodegen to generate the project files that are used to build the SDK releases.

The Firebase iOS SDK team wrote their own release tooling to generate binary frameworks from CocoaPod specs and zip them. Very impressive!

Conclusion

If your Swift package is small and simple, try community tools like swift-create-xcframework or spm-to-xcframework.

Please note that it is necessary to abstract the bundle access as Xcode won't create the internal static extension Bundle.module for XCFrameworks.

If those tools don't work for your Swift Package, you might need to invest in custom tooling. You can get inspired by the examples above.

I certainly hope Apple's Swift Package Manager provides tooling to support SDK maintainers in the future.

Did you find this article valuable?

Support Marco Eidinger by becoming a sponsor. Any amount is appreciated!