My framework was built with Library Evolution enabled.
You should take time and make effort to explain better the context.
Which version of Xcode are you using ? 12.5 ?
If so, that means that the framework was built with a more recent version of XCode (and Swift compiler).
Library Evolution enabled does not play here: it is for upward compatibility (a framework compiled in a version will work with a more recent version), not downward compatibility: a framework built with a version may not work with a more ancient version.
@Claude31 has the right answer, so I'm reiterating it — binaries built with newer tools cannot be used with older tools. Binary stability only allows a binary to work with tools that didn't exist at the time the binary was complied. Either you need to use Xcode 12 to support your clients using Xcode 12+, or you need to move the requirements for your clients to use your library to Xcode 13+.
we had expected that our framework would continue to be both forwards and backwards compatible as it has been until now.
Unfortunately, that was a wrong expectation. Where did you get it ? It happened to work "by chance", because nothing was changed that could cause problem. But the last release had more changed.
@jon-iproov, I reviewed your DTS request and had your support request routed here. I'll try to provide a little more information to help fill in your questions.
This took us (and our customers) a little by surprise as we had expected that our framework would continue to be both forwards and backwards compatible as it has been until now.
I'm sorry to hear you were surprised by this. We did mention the forward-compatibility detail in a few places. First, we presented binary Swift frameworks at WWDC 2019 with this information:
And since [textual module interfaces] behave like source code, then future versions of the Swift Compiler will be able to import module interfaces created with older versions.
You also cite a blog article on Swift.org, which contains this mention:
Swift 5 provides binary compatibility for apps: a guarantee that going forward, an app built with one version of the Swift compiler will be able to talk to a library built with another version.
can you elaborate a little on the expected behaviours around module/binary stability and what's going on here under-the-hood?
When you compile a module with a specific complier version, you're implicitly choosing a set of conditions for the layout and structure of the complied binary which will always correspond to the Swift version of the complier. Note the top of your module interface file and the lines that denote the textual interface version, complier version, and specific complier flags, for an example of where this is defined in the complied output.
The concept of a stable ABI is a lower-level concept and not everyone has familiarity with it, so let's make a rough analogy with how APIs work, as more people are familiar with the API level. Pick you favorite Apple framework, and imagine this framework added a new method in the iOS 15 SDK that fundamentally alters how you use the framework, and you'd like to adopt the new approach. However, you have to use an older Xcode with the iOS 14 SDK. How do you call this new API? Since the method didn't exist at the time the older Xcode and iOS 14 SDK were produced, you can't work with this new method. An ABI is conceptually similar — the contents of the compiled binary might have fundamental structural elements in it that didn't exist in an older version of the Swift complier, so that older complier is thus fundamentally incompatible with that binary.
Part of what might have confused you is the specific language used to discuss these issues. The language is important, because the concept of backwards- and forwards-compatibility are relative terms, and the direction of that relativity between compiler and library matters. The following are equivalent statements with the directionality flipped, and accurately represent what will work:
- A new complier is backwards-compatible with the textual interface of a binary complied by an older complier.
- An older binary's textual interface is forward-compatible with newer Swift compliers.
That is not the same as these statements, both of which are incorrect and will not work:
- An older complier is forwards-compatible with the textual interface of a binary complied by an newer complier.
- The textual interface for a binary from a newer complier is backward-compatible with older Swift compliers.
Swift's module stability is not the only complier technology where the binary module is only forward-compatible with a newer complier — bitcode is another example. An older complier cannot read the bitcode created by a newer complier. As such, a library author really needs to define two sets of requirements for clients:
- What is the minimum OS version the library supports?
- What is the minimum Xcode version the library supports?
That second point is often overlooked when defining library requirements. Ideally, a library could track this in line with the required Xcode versions for app development. For example, apps submitted to the App Store were required to use Xcode 12 as of April 2021. In April 2022, the requirement moves forward to requiring Xcode 13. There's no harm in having requirements that are more stringent and require a newer Xcode version than these baseline versions if it suits the needs of your library because you need the newer language features, so long as you are intentional about making this requirement and the impact on your clients in service of making a better library. Similarly, there's no reason for the library to support less than those Xcode versions and delay adopting language features beyond that point, as clients of your library will already be updating their Xcode versions according to that schedule.