Challenges creating an XCFramework for native libraries

Hello,

I am trying to modernize some OpenSSL/SSH bindings that exist out in the wild to be packaged as XCFrameworks as it would allow me to ship a single package for all platforms that consume it, and today I use an assorted bag of hacks to support more than one platform. So XCFrameworks look exactly like the solution that I need.

Both libssh and openssl produce native static libraries and header files that I consume as part of the SwiftSH binding project which surfaces these low-level libraries in a Swift-friendly API. I have a fork that I am maintaining here: GitHub - migueldeicaza/SwiftSH: A Swift SSH framework that wraps libssh2..

Sadly, when I create my SwiftSH.framework, the swiftmodule file contains this line:

import Libssh2

When I try to consume this as an XCFramework, it reports an error No such module: Libssh2.

The SwiftSH binary that is produced by Xcode already contains all of the symbols for Libssh2 that I need.

I would like to either find a way for my swiftmodule to not include that line import Libssh2, or for Xcode to realize that the provided XCFramework already has the Libssh2 included.

I have been able to gather that the swiftmodule is produced by the flag -emit-module-interface-path passed to the swift compiler, but have not been able to figure out what goes into this, and how I can limit this from being included.

If I manually edit the swiftmodule file with vi and removed the offending line and things work. But I am going to be laughed at if anyone finds out I am doing this, so I am trying to avoid the public ridicule.

My butchered SwiftSH is here:

https://tirania.org/tmp/SwiftSH.xcframework.tar.gz

And the command line invocation that produces this is here:

SIDEBAR: I did attempt to create xcframeworks for both libssh and openssl and referenced those from my main project, but the error did not go away, so I considered that a failed effort.

I uploaded these here in case it is useful to peruse the work:

https://tirania.org/tmp/libssh.xcframework.tar.gz
https://tirania.org/tmp/libcrypto.xcframework.tar.gz

1 Like

This is where the somewhat-unsupported-but-still-very-useful @_implementationOnly import comes in -- @_implementationOnly import X hidden the import of X from your clients (one way that it does this is by not printing the import in the .swiftinterface file)

It also requires that you do not use the types from X in any of your public API or ABI surface, since that would require your clients to be able to see into X. And without library evolution turned on, using any type from X inside any of your public types, even if they're private, means they're part of your type's ABIs. The safest thing is to combine @_implementationOnly import with library evolution for your framework. And since declarations in @_implementationOnly imported libraries are not part of your library's ABI, those libraries do not need to maintain ABI stability or be compiled with library evolution support.

1 Like

It works perfectly! Exactly what I needed!

Thank you Harlan!

I still would love to understand why LibSSH that is used to compile the SwiftSH module does not re-surface the Libssh as a module I can use - seems like something I might want to do some day.

Miguel