I have a framework project that uses third-party packages integrated via Swift Package Manager. The framework is then bundled as an XCFramework to support both simulator and device builds. However, when this XCFramework is consumed in a different version of Xcode than the one it was originally built with, it results in Swift compiler errors.
For example, compiling the framework in Xcode 15.4 and then using it in a consumer app built with Xcode 16.2 results in Swift compiler errors(and vice-versa)
Swift should work across different Xcode versions due to ABI stability.
It appears the issue is originating from the referenced library, CryptoSwift. I’ve attached two projects: the [framework] project and the consumer app. https://bit.ly/4iWgi9i
I’d really appreciate any help in identifying a solution to this problem.
ABI stability isn't the issue here — enabling module stability through the library evolution build flags is. When you build an XCFramework, you need to enable the BUILD_LIBRARY_FOR_DISTRIBUTION
build setting, which enables library evolution for the module defined by the Xcode target to which you're applying that build setting. That causes a swiftinterface
file to be output as part of the build, which is the textual representation of your module's interface that is stable for newer complier versions to consume.
When you import SomePackage
into your module, module stability is not conveyed for that imported module, it too would need to enable module stability. Further, the public interface with all of the types of that imported module becomes part of the public interface in your final XCFramework. So if I look at the arm64-apple-ios.swiftinterface
in your test project's XCFramework, I see this:
// swift-interface-format-version: 1.0 // swift-compiler-version: Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4) // swift-module-flags: -target arm64-apple-ios17.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -Onone -enable-bare-slash-regex -module-name DemoFramework import SomePackage
That import line really means public import SomePackage
, which means that your DemoFramework
clients could call any part of the public API SomePackage
, which often is not intended. Further, Swift packages cannot be built into an XCFramework with library evolution enabled — that's only available for modules built from an Xcode framework target.
Both of those circumstances mean that you need to hide the module from clients of your framework. That will remove the SomePackage
public API from the interface of DemoFramework
. With the interface to SomePackage
then hidden from public, the build of SomePackage
does not itself need to be built with library evolution enabled. Swift Evolution proposal 409: Access-level modifiers on import declarations provides you with the tools to achieve this, such as using either the internal
or private
modifier on your import declaration, like private import SomePackage
. I recommend you read the entire proposal so you understand what additional options are available to you. Note in particular how public
is the assumed default for package imports with Swift 6, for compatibility reasons, but that this will change in the future.
— Ed Ford, DTS Engineer