Drawbacks/problems with C/C++ interoperability & SwiftPM

While creating the Package.swift SPM wrapper around the LevelDB database library, I faced two issues:

Issue with C interop

LevelDB is a C++ library that has an API for importing in both C and C++ languages:

  • in C, you can access through <leveldb/c.h> with a header that has C syntax;
  • in C++, through the other all headers <leveldb/*.h> in the same directory that has C++ syntax but .h extension;

By default, SPM wants to compile all the headers with C interop, but there are headers with C++ syntax: for example, #include <cstdio> and a compilation error cames out. So I came up with the decision that I can simply exclude all those useless headers with .target(exclude: [...]), but this argument works only for source code files. So I tried another decision, renaming all C++ headers to .hpp, but compiler still wants to compile them as C headers. Finally only putting #if __cplusplus ... #endif in all those headers worked out, but this was not OK.

Also I tried to compile with .interoperabilityMode(.Cxx) everything compiled successfully, but that's topic for another issue.

Issue with C++ interop

I could simply go with .interoperabilityMode(.Cxx) swift setting and forget about all problems, but compiler would remind me if I want to use my wrapper package as dependancy somewhere else by forcing me to put .interoperabilityMode(.Cxx) argument in all packages that have dependancy with .interoperabilityMode(.Cxx) up to the top one. That freaks me out and I have no will to try most exiting feature of the last year the C++ interoperability.

For example, another theoretical situation:
Someone has a popular SPM library that is a dependency in other developers various projects, and one day the author of this library wanted to use C++ code that would be the best use scenario for him, but the next day he would have a github page full of issues complaining about compilation errors because all the developers with this dependancy would be forced to use this argument in their projects.

Summing up

Why headers can't be excluded? There are already several issues around this problem.
Why there is such decision with .interoperabilityMode(.Cxx) argument? Personally this repels me from trying using C++ in my projects.

p.s. If you need code, you can find my wrapper here LevelDB/Package.swift

4 Likes

I understand Swift not compiling C++ code when C++ interop is not enabled. If I understand correctly, the issue with C++ interoperability is that enabling it in SwiftPM leaks into modules that depend on a module that uses C++ interop. Is that right?

No, Swift compiles C++ source code with .interoperabilityMode(.C), the problem is:

LevelDB is full C++ library with public headers c.h, db.h, slice.h, iterator.h and so on.
[db.h, slice.h, iterator.h, ...] - all are C++ headers.
c.h - is the only C header, made for availability to use this library in C environment.

With .interoperabilityMode(.C) (default mode) I need to modify all C++ headers (db.h, slice.h, iterator.h,...) with #if __cplusplus <contents of header> #endif. Otherwise, I would get errors like #include <cstdint> - 'cstdint' file not found in these headers because it is C++ syntax in these headers, not C. My working solution in .interoperabilityMode(.C) is just to hide all C++ API with #if __cplusplus, so I have full C++ library with only one c.h header in default mode (.interoperabilityMode(.C) or without this argument).

My working solution requires forking and modifying third-party library to work with SwiftPM. That's why I tried to hide all those C++ headers without modifying them. I've put all paths to those headers in the exclude argument of the target: .target(name: "CLevelDB", exclude: ["include/leveldb/db.h", "include/leveldb/slice.h", "include/leveldb/iterator.h", ...], ...). But I still get an error because the compiler ignores the exclude argument for headers paths and tries to compile them as C headers.

I've also tried to rename all those C++ headers from .h to .hpp, but compiler still wants to compile them as C headers.

So compiler goes crazy when faces mixed C and C++ headers in one folder and the only working solution out of the box is to put .interoperabilityMode(.Cxx) in swiftSettings, but this faces my theoretical situation from the previous message.

Similar problem with exclude argument for headers: How to exclude specific headers in SPM?

More detailed topic: Unable to exclude specific headers from a target

Here is my commit to fix compiler error in default interoperability mode: Fix for Swift Package Manager support · google/leveldb@cc85605 · GitHub

Faced this issue again while wrapping BoringSSL by myself, not using swift-nio-ssl. Contributors of swift-nio-ssl fixed compiler error by removing pki folder with C++ headers. You can't do same thing if you use git submodules, what I wanted to do with LevelDB and BoringSSL, I wanted to use them as submodules. That's why exclude working for headers (.includeSearchPath argument) is needed.