Prevent optimizing away public interfaces needed by an XCFramework (aka dyld: Symbol not found)

I am working on two SDKs, one which we plan to distribute as a Swift source package and another that will sit on to, to be packaged as an XCFramework. Let's call these Core and Feature packages.

  • In development, the test app consumes the Core and Feature packages as source. Feature interacts with Core and Core has a dependency, swift-protobuf.
  • In production, a real app will consume Core as a source package and Feature as an xcframework. Feature again interacts with Core and Core with swift-protobuf.

Everything worked smoothly in development. Code built, tests built, life was good. Now that I've packaged Feature as an XCFramework, I'm running into issues. Specifically, this error:

dyld[96653]: Symbol not found: _$s13HeapSwiftCore11InteractionO5touchyA2CmFWC
  Referenced from: <07A52697-7FCA-3827-B116-D396EB9020A8>
     [DERIVED DATA]/HeapIOSAutocapture.framework/HeapIOSAutocapture
  Expected in:     <7CA2918D-6BC7-3F93-93B9-AE13FE7821CA>
     [DERIVED DATA]/PackageFrameworks/HeapSwiftCore.framework/HeapSwiftCore

This translates to HeapSwiftCore.Interaction.touch. I've verified that the code exists. It's a relatively basic enum:

public enum Interaction {
    case custom(String)
    case unspecified, click, touch, change, submit
    case builtin(Int)
}

However, the Core SDK doesn't ever use it and PackageFrameworks/HeapSwiftCore.framework/HeapSwiftCore has it completely optimized away. The below screenshot shows the symbols for the enum in the version embedded in the app (top) and the version generated when building the Feature framework (bottom):

The problem goes both ways, too. The version used when building the Feature framework has had the property HeapSwiftCore.Interaction.kind erased because it's only used in methods that the Feature framework doesn't call.

I'm kind of desperate for solutions here, some questions:

  • Is it possible to annotate the package or methods in not to erase public API?
  • Is it possible to tell Swift to check for usage inside the XCFramework before erasing API?

If not, I think the only solution is to package Core, Framework and swift-protobuf as frameworks and use them that way to build their compatriots, rather than SPM. But then I'd probably run into trouble if the app also packaged swift-protobuf.

I've found a solution, based on the following lead:

While I don't know exactly what's causing the cross-module symbol culling, enabling library evolution does appear to fix the problem:

        .target(
            name: "HeapSwiftCore",
            dependencies: [
                .product(name: "SwiftProtobuf", package: "swift-protobuf"),
            ],
            swiftSettings: [
                .unsafeFlags([
                    "-enable-library-evolution",
                    "-emit-module-interface"
                ]),
            ]),

The only problem I have now is warning about swift-protobuf not being compiled with evolution support, but I believe I can fix this with @_implementationOnly import attributes, as described in below since I'm not exposing this outside of testing:

I'll probably end up with something like:

#if BUILD_FOR_DEVELOPMENT
import SwiftProtobuf
#else
@ _implementationOnly import SwiftProtobuf
#endif