Struggling to wrap C++ library in Swift package

Hello! I've tried following many of the available resources pertaining to wrapping a C++ library in a Swift package, but I have failed on getting this to work.

The structure of my Swift package, let's call it SwiftWrapper, is:

Package.swift
Sources/
├── CLibraryName/
│   ├── build/
│   │   ├── liblibraryname.a
│   │
│   ├── install/
│   │   ├── include/
│   │   │   ├── <many subdirectories and all header files>
│   │
│   ├── libraryname/
│   │       ├── <srcs, and header files for the library>
│   │
│   ├── module.modulemap
│
├── SwiftWrapper/
│   ├── SwiftWrapper.swift
Tests/

The CLibraryName/install/include directory contains all the header files for libraryname and all of its dependencies.

The CLibraryName/build and CLibraryName/install directories come from a build script in the library.

liblibraryname.a is the universal static library.

My Package.swift is as below:

let package = Package(
    name: "SwiftWrapper",
    products: [
        .library(
            name: "SwiftWrapper",
            targets: ["SwiftWrapper"]),
    ],
    targets: [
        .target(
            name: "SwiftWrapper",
            dependencies: ["CLibraryName"]
        ),
        .testTarget(
            name: "SwiftWrapperTests",
            dependencies: ["SwiftWrapper"]
        ),
        .target(
            name: "CLibraryName",
            exclude: [
                // Exclude the actual library directory because
                // this not where the headers we are using are
                // located. Causes header errors when not excluded
                "./libraryname",
                // Build errors from these directories about
                // `__cpp_*` macros not being set or being wrong,
                // so I excluded them.
                "./build/arm64",
                "./build/x86_64",
            ],
            publicHeadersPath: "./install/include",
            cxxSettings: [
                .headerSearchPath("./install/include")
            ],
            linkerSettings: [
                .linkedLibrary("libraryname"),
                .unsafeFlags(["-L./build"])
            ]
        )
    ]
)

My module.modulemap is as below:

module CLibraryName {
    umbrella "./install/include"
    export *
}

This setup does successfully build when SwiftWrapper.swift is empty, but if I add import CLibraryName, swift build hangs forever (at least what seems like forever). I'm not sure what's happening, and I've tried many other configurations, so I'm curious if anyone has any pointers on how to get this specific setup to workout or if I'm on the right track.

If you need any more information, please let me know.

Thank you!

I figured out how to do it. You can't (at least I couldn't figure out how to) link the static library and headers to the package manually, you have to create an XCFramework of your static lib and headers and use that XCFramework as a binaryTarget. However, the contents of the XCFramework have to modified.

I wrote a script that created an umbrella header that just #includes every header file I want to be in the public API and I put that in the Headers directory of the XCFramework, and then you have to add a module.modulemap to the Headers directory that looks like:

module CMyLibrary {
    header "MyLibrary.hpp" // The umbrella header created from the script
    export *
}

Then, your Package.swift should look like:

products: [
    .library(
        name: "Wrapper",
        targets: ["Wrapper"])
],
targets: [
    .binaryTarget(name: "CMyLibrary", path: "Sources/CMyLibrary/MyLibrary.xcframework"),
    .target(
        name: "Wrapper",
        dependencies: ["CMyLibrary"],
        swiftSettings: [.interoperabilityMode(.Cxx)])
],
cxxLanguageStandard: .cxx20 // Whatever C++ version here

This setup has given me full C++ and Swift interop with this library.

You do have to make sure the static libraries that are used to create the XCFramework via xcodebuild -create-xcframework are correct from whatever build system you are using.

1 Like

I think you forgot to add -l linker flag

linkerSettings: [
                .linkedLibrary("libraryname"),
                .unsafeFlags([
                    "-L./build",
                    "-llibraryname"
                 ])
            ]

You’re right I did forget that here, but with that typo fixed linking the static library in that way didn’t seem to work for me still.

Why?

It’s been a couple weeks since I’ve tried that configuration, but I’m pretty sure it’s the same result described in the body of this topic.