Photo by Mourizal Zativa on Unsplash
Why is it so damn difficult to create a binary framework for your Swift Package
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
AWS iOS SDK (vending binaries, not code, through SPM)
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.