C++ interop mode not creating headers from Swift Package Manager

(My project accompanying this post is here: GitHub - schwa/earcut-swift)

I am trying to wrap mapbox's earcut.hpp project (GitHub - mapbox/earcut.hpp: Fast, header-only polygon triangulation) for Swift.

I would like to expose a single function in C++ that looks something like this to Swift:

template <typename N = uint32_t>
std::vector<N> earcut_swift_simd(const swift::array<swift::array<simd_float2>>& poly) {
...
}

The C++ targets should only need swift::array from swift and in theory need no other types or functions from my Swift. I'm purely exposing C++ to Swift.

I understand however, that I need a Swift target, compiled with swiftSettings: [.interoperabilityMode(.Cxx)] that I can use to provide Swift types to C++. So I've done that. I then need to include it from C++ (like so… #include <earcut_bridge/earcut_bridge-Swift.h>)…

However when I compile my project no earcut_bridge-Swift.h header is generated for my target. If I check the .build directory I can see a module.modulemap file is created for my target, and that module map references the header. It looks like this:

module earcut_bridge {
    header "/Users/schwa/Desktop/earcut-swift/.build/arm64-apple-macosx/debug/earcut_bridge.build/earcut_bridge-Swift.h"
    requires objc
}

However no file exists at that path. And my import from C++ fails.

1 Like

So follow up questions.

This seems like it should work. Targets I add the CXX interop flag to should export headers that I can consume from C++ target. Right?

Also, it seems weird having to create a target when I do not need to expose my swift code to C++ - seems like there are quite a few projects with a similar need to mine - that just want to expose C++ functions (with a Swiftified API) to Swift. Is this feature potentially on a roadmap?

Alternatively I realise I can just use std::vector from Swift and pass my data into the API that way - however that does involve an extra copy of the data to from swift arrays to std::vectors… But at that point I could just use (say) CFArray from C++ potentially and just expose a C API…

1 Like

It's not yet possible to pass a Swift value type across the Swift -> C++ -> Swift boundary (only across the Swift -> C++ boundary), so I would recommend using a std::vector instead as you mentioned in your second post. Right now only Swift class types can be passed across the Swift -> C++ -> Swift boundary transparently.

I understand however, that I need a Swift target, compiled with swiftSettings: [.interoperabilityMode(.Cxx)] that I can use to provide Swift types to C++. So I've done that. I then need to include it from C++ (like so… #include <earcut_bridge/earcut_bridge-Swift.h> )…

The Swift Package Manager does not yet provide support for using the generated header to use Swift APIs in C++. This is going to be addressed by SE0403: SE-0403: Package Manager Mixed Language Target Support .

3 Likes

OK thanks for the definitive answer.

Going to a std::vector based/copy approach and there are now two issues:

  1. It seems that swiftSettings: [.interoperabilityMode(.Cxx)] is viral. And every target that imports a target compiled with that flag needs to set it too. (Warning of the form: "Module 'earcut' was built with C++ interoperability enabled, but current compilation does not enable C++ interoperability"). Is this vitality intended? I tried to nip it off with @_implementationOnly on the C++ target but that didn't do the trick.

  2. If I get passed that by adding in the flag I get a 100% reproducible crash at compile time. I'll post a JIRA but for now:

Stack dump:
0.	Program arguments: /Applications/Xcode-15.1.0-Beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-frontend -frontend -c -primary-file /Users/schwa/Desktop/earcut-swift/Sources/earcut/earcut_swift.swift -emit-dependencies-path /Users/schwa/Library/Developer/Xcode/DerivedData/earcut-swift-hgzijbejqoesmrdnqopquilkstjz/Build/Intermediates.noindex/earcut-swift.build/Debug-iphoneos/earcut.build/Objects-normal/arm64/earcut_swift.d -emit-const-values-path /Users/schwa/Library/Developer/Xcode/DerivedData/earcut-swift-hgzijbejqoesmrdnqopquilkstjz/Build/Intermediates.noindex/earcut-swift.build/Debug-iphoneos/earcut.build/Objects-normal/arm64/earcut_swift.swiftconstvalues -emit-reference-dependencies-path /Users/schwa/Library/Developer/Xcode/DerivedData/earcut-swift-hgzijbejqoesmrdnqopquilkstjz/Build/Intermediates.noindex/earcut-swift.build/Debug-iphoneos/earcut.build/Objects-normal/arm64/earcut_swift.swiftdeps -serialize-diagnostics-path /Users/schwa/Library/Developer/Xcode/DerivedData/earcut-swift-hgzijbejqoesmrdnqopquilkstjz/Build/Intermediates.noindex/earcut-swift.build/Debug-iphoneos/earcut.build/Objects-normal/arm64/earcut_swift.dia -target arm64-apple-ios12.0 -Xllvm -aarch64-use-tbi -enable-objc-interop -cxx-interoperability-mode=default -sdk /Applications/Xcode-15.1.0-Beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk -I /Users/schwa/Library/Developer/Xcode/DerivedData/earcut-swift-hgzijbejqoesmrdnqopquilkstjz/Build/Products/Debug-iphoneos -I /Applications/Xcode-15.1.0-Beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/lib -F /Users/schwa/Library/Developer/Xcode/DerivedData/earcut-swift-hgzijbejqoesmrdnqopquilkstjz/Build/Products/Debug-iphoneos/PackageFrameworks -F /Users/schwa/Library/Developer/Xcode/DerivedData/earcut-swift-hgzijbejqoesmrdnqopquilkstjz/Build/Products/Debug-iphoneos -F /Applications/Xcode-15.1.0-Beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Frameworks -F /Applications/Xcode-15.1.0-Beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk/Developer/Library/Frameworks -no-color-diagnostics -enable-testing -g -module-cache-path /Users/schwa/Library/Developer/Xcode/DerivedData/ModuleCache.noindex -swift-version 5 -enforce-exclusivity=checked -Onone -D SWIFT_PACKAGE -D DEBUG -D Xcode -serialize-debugging-options -package-name earcut_swift -const-gather-protocols-file /Users/schwa/Library/Developer/Xcode/DerivedData/earcut-swift-hgzijbejqoesmrdnqopquilkstjz/Build/Intermediates.noindex/earcut-swift.build/Debug-iphoneos/earcut.build/Objects-normal/arm64/earcut_const_extract_protocols.json -empty-abi-descriptor -validate-clang-modules-once -clang-build-session-file /Users/schwa/Library/Developer/Xcode/DerivedData/ModuleCache.noindex/Session.modulevalidation -Xcc -working-directory -Xcc /Users/schwa/Desktop/earcut-swift -resource-dir /Applications/Xcode-15.1.0-Beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift -enable-anonymous-context-mangled-names -Xcc -fmodule-map-file=/Users/schwa/Library/Developer/Xcode/DerivedData/earcut-swift-hgzijbejqoesmrdnqopquilkstjz/Build/Intermediates.noindex/GeneratedModuleMaps-iphoneos/earcut_cpp.modulemap -Xcc -ivfsstatcache -Xcc /Users/schwa/Library/Developer/Xcode/DerivedData/SDKStatCaches.noindex/iphoneos17.0-21A326-a3f61a28211dc0936fbebe5cd08c1f32.sdkstatcache -Xcc -I/Users/schwa/Library/Developer/Xcode/DerivedData/earcut-swift-hgzijbejqoesmrdnqopquilkstjz/Build/Intermediates.noindex/earcut-swift.build/Debug-iphoneos/earcut.build/swift-overrides.hmap -Xcc -I/Users/schwa/Desktop/earcut-swift/Sources/earcut_cpp/include -Xcc -I/Users/schwa/Library/Developer/Xcode/DerivedData/earcut-swift-hgzijbejqoesmrdnqopquilkstjz/Build/Products/Debug-iphoneos/include -Xcc -I/Users/schwa/Library/Developer/Xcode/DerivedData/earcut-swift-hgzijbejqoesmrdnqopquilkstjz/Build/Intermediates.noindex/earcut-swift.build/Debug-iphoneos/earcut.build/DerivedSources-normal/arm64 -Xcc -I/Users/schwa/Library/Developer/Xcode/DerivedData/earcut-swift-hgzijbejqoesmrdnqopquilkstjz/Build/Intermediates.noindex/earcut-swift.build/Debug-iphoneos/earcut.build/DerivedSources/arm64 -Xcc -I/Users/schwa/Library/Developer/Xcode/DerivedData/earcut-swift-hgzijbejqoesmrdnqopquilkstjz/Build/Intermediates.noindex/earcut-swift.build/Debug-iphoneos/earcut.build/DerivedSources -Xcc -DSWIFT_PACKAGE -Xcc -DDEBUG=1 -module-name earcut -frontend-parseable-output -disable-clang-spi -target-sdk-version 17.0 -target-sdk-name iphoneos17.0 -external-plugin-path /Applications/Xcode-15.1.0-Beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk/usr/lib/swift/host/plugins#/Applications/Xcode-15.1.0-Beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk/usr/bin/swift-plugin-server -external-plugin-path /Applications/Xcode-15.1.0-Beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk/usr/local/lib/swift/host/plugins#/Applications/Xcode-15.1.0-Beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk/usr/bin/swift-plugin-server -external-plugin-path /Applications/Xcode-15.1.0-Beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/lib/swift/host/plugins#/Applications/Xcode-15.1.0-Beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/swift-plugin-server -external-plugin-path /Applications/Xcode-15.1.0-Beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/local/lib/swift/host/plugins#/Applications/Xcode-15.1.0-Beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/swift-plugin-server -plugin-path /Applications/Xcode-15.1.0-Beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/host/plugins -plugin-path /Applications/Xcode-15.1.0-Beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/local/lib/swift/host/plugins -parse-as-library -o /Users/schwa/Library/Developer/Xcode/DerivedData/earcut-swift-hgzijbejqoesmrdnqopquilkstjz/Build/Intermediates.noindex/earcut-swift.build/Debug-iphoneos/earcut.build/Objects-normal/arm64/earcut_swift.o -index-unit-output-path /earcut-swift.build/Debug-iphoneos/earcut.build/Objects-normal/arm64/earcut_swift.o -index-store-path /Users/schwa/Library/Developer/Xcode/DerivedData/earcut-swift-hgzijbejqoesmrdnqopquilkstjz/Index.noindex/DataStore -index-system-modules
1.	Apple Swift version 5.9 (swiftlang-5.9.2.1.6 clang-1500.1.0.1.1)
2.	Compiling with the current language version
3.	While evaluating request ExecuteSILPipelineRequest(Run pipelines { Non-Diagnostic Mandatory Optimizations, Serialization, Rest of Onone } on SIL for earcut)
4.	While running pass #30 SILFunctionTransform "OwnershipModelEliminator" on SILFunction "@$s6earcutAA8polygonsSays6UInt32VGSaySays5SIMD2VySfGGG_tF".
 for 'earcut(polygons:)' (at /Users/schwa/Desktop/earcut-swift/Sources/earcut/earcut_swift.swift:9:8)
Stack dump without symbol names (ensure you have llvm-symbolizer in your PATH or set the environment var `LLVM_SYMBOLIZER_PATH` to point to it):
0  swift-frontend           0x000000010572a038 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) + 56
1  swift-frontend           0x00000001099c1688 llvm::sys::RunSignalHandlers() + 112
2  swift-frontend           0x00000001099c26f4 SignalHandler(int) + 352
3  libsystem_platform.dylib 0x00000001861d1a24 _sigtramp + 56
4  swift-frontend           0x0000000106be9a98 stripOwnership(swift::SILFunction&) + 3380
5  swift-frontend           0x0000000109362a90 (anonymous namespace)::OwnershipModelEliminator::run() (.llvm.6711774290829921419) + 568
6  swift-frontend           0x0000000109392f5c swift::SILPassManager::runFunctionPasses(unsigned int, unsigned int) + 3988
7  swift-frontend           0x0000000109388178 swift::SILPassManager::executePassPipelinePlan(swift::SILPassPipelinePlan const&) + 240
8  swift-frontend           0x00000001095418f0 swift::SimpleRequest<swift::ExecuteSILPipelineRequest, std::__1::tuple<> (swift::SILPipelineExecutionDescriptor), (swift::RequestFlags)1>::evaluateRequest(swift::ExecuteSILPipelineRequest const&, swift::Evaluator&) + 56
9  swift-frontend           0x00000001093e3df0 llvm::Expected<swift::ExecuteSILPipelineRequest::OutputType> swift::Evaluator::getResultUncached<swift::ExecuteSILPipelineRequest>(swift::ExecuteSILPipelineRequest const&) + 476
10 swift-frontend           0x0000000109406210 swift::runSILPassesForOnone(swift::SILModule&) + 184
11 swift-frontend           0x0000000105bc6a70 swift::CompilerInstance::performSILProcessing(swift::SILModule*) + 560
12 swift-frontend           0x000000010997c658 performCompileStepsPostSILGen(swift::CompilerInstance&, std::__1::unique_ptr<swift::SILModule, std::__1::default_delete<swift::SILModule>>, llvm::PointerUnion<swift::ModuleDecl*, swift::SourceFile*>, swift::PrimarySpecificPaths const&, int&, swift::FrontendObserver*) + 956
13 swift-frontend           0x0000000109978520 performCompile(swift::CompilerInstance&, int&, swift::FrontendObserver*) + 1748
14 swift-frontend           0x000000010997bc2c swift::performFrontend(llvm::ArrayRef<char const*>, char const*, void*, swift::FrontendObserver*) + 4568
15 swift-frontend           0x00000001092ee058 swift::mainEntry(int, char const**) + 4116
16 dyld                     0x0000000185e29058 start + 2224
Command SwiftCompile failed with a nonzero exit code

I've updated the GitHub project.

1 Like
  1. It seems that swiftSettings: [.interoperabilityMode(.Cxx)] is viral. And every target that imports a target compiled with that flag needs to set it too. (Warning of the form: "Module 'earcut' was built with C++ interoperability enabled, but current compilation does not enable C++ interoperability"). Is this vitality intended? I tried to nip it off with @_implementationOnly on the C++ target but that didn't do the trick.

Yes, that the intentional behavior. Enabling C++ interoperability for a Swift Package Manager target will need other targets that depend on such target to enable C++ interoperability as well.

If I get passed that by adding in the flag I get a 100% reproducible crash at compile time. I'll post a JIRA but for now:

Thanks, please file an issue on GitHub - apple/swift: The Swift Programming Language so we can take a look

1 Like

Thanks for this sort of explanation.