Combined Swift/C++ Package Cannot Resolve C++ Headers

I have a Package that combines a Swift target and several C++ binary targets. I have a project that imports the Package as a module. When I try to build my project in Xcode, it gives me errors that it cannot resolve the C++ procedure calls from within the Swift target files of the package. The header files for the C++ procedures are stored within one of binary targets.

The purpose of the package is to bundle a set of Swift bindings for a C++ API and make it as convenient as possible for other developers to use those bindings. The bindings comprise the Swift target of the package. The package supports both macOS and iOS, and is composed of the bindings along with several xcframeworks and a set of test modules. Each xcframework encapsulates a pair of precompiled C++ dylibs, one for macOS and another for iOS. The header files are built into one of the xcframeworks.

I use Xcode to add the package to my project via File->Add Packages... And then I add the package to Targets->Frameworks, Libraries and Embedded Content. The package also includes a bridging header, which I specify in Targets->Build Settings->Swift Compiler General->Objective-C Bridging Header.

Here's my package:

// swift-tools-version:5.3
import PackageDescription

let package = Package(
    name: "BrainFlow",
    platforms: [
        .macOS(.v10_15), .iOS(.v13)
    ],
    products: [
        // Products define the executables and libraries a package produces, and make them visible to other packages.
        .library(name: "BrainFlow",
                 targets: ["BrainFlow", "BoardController", "DataHandler", "MLModule", "BrainBitLib"])
    ],
    dependencies: [
        .package(name: "swift-numerics",
                 url: "https://github.com/apple/swift-numerics.git", .upToNextMajor(from: "1.0.0"))
    ],
    targets: [
        .target(
            name: "BrainFlow",
            dependencies: [.product(name: "Numerics", package: "swift-numerics"),
                           .target(name: "BoardController")]
        ),
        .binaryTarget(
            name: "BoardController",
            path: "BoardController.xcframework" // the header files are located within this target
        ),
        .binaryTarget(
            name: "DataHandler",
            path: "DataHandler.xcframework"
        ),
        .binaryTarget(
            name: "MLModule",
            path: "MLModule.xcframework"
        ),
        .binaryTarget(
            name: "BrainBitLib",
            path: "BrainBitLib.xcframework"
        ),
        .testTarget(
            name: "BrainFlowTests",
            dependencies: ["BrainFlow", .product(name: "Numerics", package: "swift-numerics")],
            sources: ["BoardShimTests.swift",
                      "BrainFlowCItests.swift",
                      "BrainFlowTests.swift",
                      "DataFilterTests.swift"]
        )
    ]
)

And here's a screenshot of the package layout and an example of the errors:

Thanks in advance,
Scott

Swift cannot call into C++ directly today: it requires C headers.

1 Like

Yes thanks. The headers are stored inside one of the xcframeworks. If I move the Swift bindings from the package into the target group, it works fine: Xcode resolves the headers via the bridging header.

I would like to avoid that step because ultimately I want the entire package to be available via remote URL on github. And it would be nice to obviate the bridging header, so that the entire package becomes completely self-contained. Is that possible?

Yes: if you add C interfaces to your headers (or they have them already) then you can add a modulemap that indicates which headers define the interface to your module. Swift can then import that directly.

Not sure if this is relevant for your package structure, but I’ve found I have to place headers (or soft link them) in Sources/<target>/include/ for them to be picked up by the parent project.

Thanks for the suggestion, but it doesn't work for my setup. I am trying to get the Package to build on its own, without integrating it into a project.

I created a Headers subfolder under the main BrainFlow folder, and copied all my headers into Headers, including bridge.h. And then I created a module.modulemap file in Sources/BrainFlow, and edited the file to contain the following:

module BrainFlow {
    header "../../Headers/bridge.h"
    export *
}

But I still get the same errors, e.g.:

swift build
Building for debugging...
/Users/scottmiller/testpkg/BrainFlow/Sources/BrainFlow/DataFilter.swift:51:25: error: cannot find 'set_log_file_data_handler' in scope
let errorCode = set_log_file_data_handler (&cLogFile)
^~~~~~~~~~~~~~~~~~~~~~~~~
/Users/scottmiller/testpkg/BrainFlow/Sources/BrainFlow/DataFilter.swift:59:25: error: cannot find 'set_log_level_data_handler' in scope

The set_log_level_data_handler function is defined in Headers/ml_module.h:

SHARED_EXPORT int CALLING_CONVENTION set_log_level_ml_module (int log_level);

Here's a screenshot of the updated Package:

--Scott

What target are these headers coming from?

BoardController. It is an xcframework built like so:

xcodebuild -create-xcframework -library ../installed/macOS/lib/libBoardController.dylib -headers ../inc -output BoardController.xcframework

Here's a screenshot of the internal structure of the xcframework, illustrating the locations of the header files:

So to be clear, I think the modulemap needs to be in that Headers directory. Wherever you tell the xcframework the headers are you should apply a modulemap.

I tried your suggestion, but same error. The thing is: it works fine when I move the bindings files into the target group and add "import BrainFlow" to each of them. Xcode finds the header files inside the xcframeworks via the bridging header. But if the bindings remain inside the Package, then I get a warning at the import statements that the binding is already part of BrainFlow and therefore cannot import BrainFlow. But when I remove the import statements, I get the "cannot find xxx in scope" errors.

I ran across a reference to an "umbrella" header. It seems to act like a bridging header. So I'm trying "umbrella" but so far no luck.

I also tried pushing the xcframeworks down into a nested sub-Package called BrainFlowLibs, and adding "import BrainFlowLibs" to all the binding source files in the parent package. The import statements compile without error, but it still cannot resolve the C headers. I am using the following module map inside BrainFlowLibs/Source/BrainFlowLibs:

module BrainFlowLibs {
    umbrella header "bridge.h"
    export *
    link "BoardController"
    link "BrainBitLib"
    link "DataHandler"
    link "MLModule"
}

Here is the updated parent Package:

// swift-tools-version:5.3
import PackageDescription

let package = Package(
    name: "BrainFlow",
    platforms: [
        .macOS(.v10_15), .iOS(.v13)
    ],
    products: [
        // Products define the executables and libraries a package produces, and make them visible to other packages.
        .library(name: "BrainFlow",
                 targets: ["BrainFlow"])
    ],
    dependencies: [
        .package(name: "swift-numerics",
                 url: "https://github.com/apple/swift-numerics.git", .upToNextMajor(from: "1.0.0")),
        .package(name: "BrainFlowLibs",
                 path: "BrainFlowLibs")
    ],
    targets: [
        .target(
            name: "BrainFlow",
            dependencies: [.product(name: "Numerics", package: "swift-numerics"),
                           .product(name: "BrainFlowLibs", package: "BrainFlowLibs")]
        ),
        .testTarget(
            name: "BrainFlowTests",
            dependencies: ["BrainFlow", .product(name: "Numerics", package: "swift-numerics")],
            sources: ["BoardShimTests.swift",
                      "BrainFlowCItests.swift",
                      "BrainFlowTests.swift",
                      "DataFilterTests.swift"]
        )
    ]
)

And here is the new sub-Package:

// swift-tools-version: 5.6
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "BrainFlowLibs",
    products: [
        // Products define the executables and libraries a package produces, and make them visible to other packages.
        .library(
            name: "BrainFlowLibs",
            targets: ["BrainFlowLibs","BoardController", "DataHandler", "MLModule", "BrainBitLib"]),
    ],
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages this package depends on.
        .target(
            name: "BrainFlowLibs",
            dependencies: []),
        .binaryTarget(
            name: "BoardController",
            path: "BoardController.xcframework"
        ),
        .binaryTarget(
            name: "DataHandler",
            path: "DataHandler.xcframework"
        ),
        .binaryTarget(
            name: "MLModule",
            path: "MLModule.xcframework"
        ),
        .binaryTarget(
            name: "BrainBitLib",
            path: "BrainBitLib.xcframework"
        ),
        .testTarget(
            name: "BrainFlowLibsTests",
            dependencies: ["BrainFlowLibs"]),
    ]
)

--Scott