Omitting .private.swiftinterface file when building binary framework

According to the Swift reference guide for underscored attributes on @_spi, the .private.swiftinterface file should be generated by adding the -emit-private-module-interface-path compiler option.

The issue I am facing is that starting from Xcode 14.0, the .private.swiftinterface file is always generated alongside the .swiftinterface file upon enabling evolution via BUILD_LIBRARY_FOR_DISTRIBUTION (even when the module is not exposing any SPI definitions, nor adding the compiler option). Versions prior to 14.0 are behaving as expected.

I am wondering if this is an intended change, or if the control method for generating the private file has been modified.
I understand that in nature the underscored attributes are subject to change, but I would highly welcome input from anyone who can share their knowledge or experience around this topic.

Best regards,
RM_TP

Xcode did start requesting .private.swiftinterface files by default in version 14. As you said, the .private.swiftinterface file is needed to support use of the @_spi attribute and without help from the build system to ensure it gets emitted it would be unclear how to use @_spi in xcframework. Whether or not to pass -emit-private-module-interface-path to the compiler is a decision made at the level of the build system, and as such it would be difficult to make it depend on whether there are any @_spi annotations in your source since that would require parsing the source.

Can you elaborate on why it's a problem for you that .private.swiftinterface is always emitted now? I understand that it's a bit of a waste for space for your library since it doesn't have any @_spi declarations, but is that the main concern?

Thanks for the reply Allan.

In my current use case, I am considering the usage of @_spi to access some internal classes and members from one module to another (both are xcframeworks). The architecture is as such that one module is the "core" and the other is an "extension" that imports the former as a dependency.

My issue with auto generating .private.swiftinterface is that it will enlist every type/class/member that's @_spi marked, and since the file is not a binary but a human-readable file, it could potentially lead to exploits. Of course, a solution could be to remove the private file from the .swiftmodule directory, but I'm hesitant in doing so since it could effect the module's stability.

My current solution is to build the frameworks using compilers prior to Xcode 14. When building the "core" framework I omit the -emit-private-module-interface-path flag, and I include the flag when building the "extension" framework. This grants access to the @_spi annotations from "core" to "extension" and simultaneously conceals them when building "core" for distribution.

I am also aware of the package access modifier that can be compatible to the architecture I described, but it's supported only since Swift 5.9 which makes it a non-viable option for now.

Thanks for elaborating! There probably ought to be a build setting in Xcode that can be used to prevent emission of the .private.swiftinterface for cases like this. I recommend leaving feedback about that via https://feedbackassistant.apple.com.

Distributing the .swiftinterface of course makes it trivial to discover and use the SPI, but it's important to note that recreating the .private.swiftinterface is not difficult to accomplish with other tools for someone really motivated to do so. Using nm to dump the global symbols from the executable and swift demangle to turn those symbols into Swift declarations would get you most of the way there. @_spi should not be considered a security tool; it's at best security through obscurity.

Can you elaborate on what you mean by "effect the module's stability"? Removing the .private.swiftinterface file prior to distribution is exactly the solution that I would recommend in the short term.

1 Like