Swift Package and xcframework target for C library: where to include the header?

I am refactoring a project to use SPM. Previously in the “bridging header” I had a
#include <abc/abc.h>
which exposed the C static library in abc.xcframework to swift and worked very well.
Now I have the abc.xcframework as a binaryTarget in the SPM Package.swift, but there is no bridging header in a Swift Package and so the library is not found, it’s not getting included anywhere. Neither my swift package or the tests will compile.
Does anyone know how I can solve that? I've been trying to make a module as a target dependencies but it still won't find the structs etc declared in the abc.h

Thanks

2 Likes

If I understand what you're attempting to do, you are trying to import a C Header and use it in Swift code in your Swift Package. If this is indeed the case then what you need to do is create a separate target in your Package.swift file that includes the C Header and then include that in the target that contains your Swift code.

I've done this in one of my projects which you can find here. The CHalf target is the one that imports the C Header that I created whereas Half is the target that contains all of the Swift sources.

Hope this helps,

It sounds like the OP has a binaryTarget not a C target like you do in Half.

I was able to get this working without a module map by including an intermediate "dummy" C target. My swift target depends on a C target that has bridge.c and include/bridge.h and then the dummy C target depends on the binaryTarget that represents my xcframework.

Here is a look at the directory structure:

FooSwift on main [+?] via 🐦 v5.4.2 
❯ tree
.
├── LICENSE
├── Libs
│   └── FooRust.xcframework
│       ├── Info.plist
│       ├── ios-arm64
│       │   ├── Headers
│       │   │   └── libfoo.h
│       │   └── libfoo-ios.a
│       ├── ios-arm64_x86_64-maccatalyst
│       │   ├── Headers
│       │   │   └── libfoo.h
│       │   └── libfoo-ios-macabi.a
│       ├── ios-arm64_x86_64-simulator
│       │   ├── Headers
│       │   │   └── libfoo.h
│       │   └── libfoo-ios-sim.a
│       └── macos-arm64_x86_64
│           ├── Headers
│           │   └── libfoo.h
│           └── libfoo-macos.a
├── Package.swift
├── README.md
├── Sources
│   ├── C
│   │   ├── bridge.c
│   │   └── include
│   │       └── bridge.h
│   └── FooSwift
│       └── Foo.swift
└── Tests
    ├── LinuxMain.swift
    └── Foo-Tests
        ├── Foo_Tests.swift
        └── XCTestManifests.swift

16 directories, 18 files

and here's the package manifest:

FooSwift on main [+?] via 🐦 v5.4.2 
❯ cat Package.swift 
// swift-tools-version:5.4

import PackageDescription

let package = Package(
    name: "FooSwift",
    platforms: [
        .iOS(SupportedPlatform.IOSVersion.v14),
        .macOS(SupportedPlatform.MacOSVersion.v11),
    ],
    products: [
        .library(
            name: "FooSwift",
            targets: ["FooSwift"]),
    ],
    targets: [
        .target(
            name: "FooSwift",
            dependencies: ["C"],
            path: "Sources/FooSwift",
            sources: ["Uno.swift"]),
        .target(
            name: "C",
            dependencies: ["FooStatic"],
            path: "Sources/C"),
        .binaryTarget(
            name: "FooStatic",
            path: "Libs/FooRust.xcframework"),
        .testTarget(
            name: "Foo-Tests",
            dependencies: ["FooSwift"]),
    ]
)

You treat the intermediate C target as your "bridge" and in bridge.h you simply:

#include "libfoo.h"

My bridge.c file is essentially empty:

include "bridge.h"

void __dummy() {}
2 Likes

Thanks, this worked a treat. Was stuck initially but got it working after adding

import CTargetName

statements in the Swift Target that depends on CTargetName

I know this thread is going on two years old, but I'm having an issue with almost the same situation. I'm trying to wrap the librclone library.

Here's what I've done so far:

  1. Built the library (now have .a and .h files)
  2. Created a .xcframework bundle from those files (only including arm mac binary for now)
  3. Structured the Swift package as suggested by @dcowuno above
  4. Wrote a simple Swift interface

Here's the directory structure:

tree
.
├── Libs
│   └── librclone.xcframework
│       ├── Info.plist
│       └── macos-arm64
│           ├── Headers
│           │   └── librclone.h
│           └── librclone.a
├── Package.swift
├── Sources
│   ├── C
│   │   ├── bridge.c
│   │   └── include
│   │       └── bridge.h
│   └── LibSwiftClone
│       ├── LibSwiftClone.swift
│       └── StringExtension.swift
└── Tests
    └── LibSwiftCloneTests
        └── LibSwiftCloneTests.swift

If I build the package with swift build, it builds just fine without any complaints.

However, the minute I try to use it in another project, it complains immediately that it's missing header files and it can't build:

swift build
warning: 'swiftclone': dependency 'libswiftclone' is not used by any target
Building for debugging...
/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/module.modulemap:2:12: error: header '/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/LibSwiftClone-Swift.h' not found
    header "/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/LibSwiftClone-Swift.h"
           ^
/Users/<USER>/Developer/SwiftClone/Sources/SwiftClone/SwiftClone.swift:2:8: error: could not build Objective-C module 'LibSwiftClone'
import LibSwiftClone
       ^
/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/module.modulemap:2:12: error: header '/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/LibSwiftClone-Swift.h' not found
    header "/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/LibSwiftClone-Swift.h"
           ^
/Users/<USER>/Developer/SwiftClone/Sources/SwiftClone/SwiftClone.swift:2:8: error: could not build Objective-C module 'LibSwiftClone'
import LibSwiftClone
       ^
error: emit-module command failed with exit code 1 (use -v to see invocation)
/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/module.modulemap:2:12: error: header '/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/LibSwiftClone-Swift.h' not found
    header "/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/LibSwiftClone-Swift.h"
           ^
/Users/<USER>/Developer/SwiftClone/Sources/SwiftClone/SwiftClone.swift:2:8: error: could not build Objective-C module 'LibSwiftClone'
import LibSwiftClone
       ^
/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/module.modulemap:2:12: error: header '/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/LibSwiftClone-Swift.h' not found
    header "/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/LibSwiftClone-Swift.h"
           ^
/Users/<USER>/Developer/SwiftClone/Sources/SwiftClone/SwiftClone.swift:2:8: error: could not build Objective-C module 'LibSwiftClone'
import LibSwiftClone
       ^
/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/module.modulemap:2:12: error: header '/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/LibSwiftClone-Swift.h' not found
    header "/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/LibSwiftClone-Swift.h"
           ^
/Users/<USER>/Developer/SwiftClone/Sources/SwiftClone/SwiftClone.swift:2:8: error: could not build Objective-C module 'LibSwiftClone'
import LibSwiftClone
       ^
/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/module.modulemap:2:12: error: header '/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/LibSwiftClone-Swift.h' not found
    header "/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/LibSwiftClone-Swift.h"
           ^
/Users/<USER>/Developer/SwiftClone/Sources/SwiftClone/SwiftClone.swift:2:8: error: could not build Objective-C module 'LibSwiftClone'
import LibSwiftClone
       ^
/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/module.modulemap:2:12: error: header '/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/LibSwiftClone-Swift.h' not found
    header "/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/LibSwiftClone-Swift.h"
           ^
/Users/<USER>/Developer/SwiftClone/Sources/SwiftClone/SwiftClone.swift:2:8: error: could not build Objective-C module 'LibSwiftClone'
import LibSwiftClone
       ^
/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/module.modulemap:2:12: error: header '/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/LibSwiftClone-Swift.h' not found
    header "/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/LibSwiftClone-Swift.h"
           ^
/Users/<USER>/Developer/SwiftClone/Sources/SwiftClone/SwiftClone.swift:2:8: error: could not build Objective-C module 'LibSwiftClone'
import LibSwiftClone
       ^
/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/module.modulemap:2:12: error: header '/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/LibSwiftClone-Swift.h' not found
    header "/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/LibSwiftClone-Swift.h"
           ^
/Users/<USER>/Developer/SwiftClone/Sources/SwiftClone/SwiftClone.swift:2:8: error: could not build Objective-C module 'LibSwiftClone'
import LibSwiftClone
       ^
/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/module.modulemap:2:12: error: header '/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/LibSwiftClone-Swift.h' not found
    header "/Users/<USER>/Developer/SwiftClone/.build/arm64-apple-macosx/debug/LibSwiftClone.build/LibSwiftClone-Swift.h"
           ^
/Users/<USER>/Developer/SwiftClone/Sources/SwiftClone/SwiftClone.swift:2:8: error: could not build Objective-C module 'LibSwiftClone'
import LibSwiftClone
       ^
error: fatalError

Any ideas? You can see the Package here.

Does changing the // swift-tools-version: 5.9 down to 5.4 make a difference? Perhaps there's a change in behavior or regression?

Thanks for jumping back on! Did a quick test and seems like that doesn't make a difference. Same error either way.

Not being able to find the generated header suggests to me that whatever target contains the import LibSwiftClone has an unexpressed dependency on it. The generated header will be created as part of building that module, so if the dependency is missing, it isn't guaranteed to happen before the client code is built.

Here's my repo so you can compare: GitHub - withuno/UnoSwift: Swift bindings for the libuno C FFI found in withuno/identity.

But, I don't see anything different, except I define a version constant https://github.com/withuno/UnoSwift/blob/main/Sources/C/include/bridge.h#L6, which I guess could be important for the packaging routine when it decides if it needs to generate a modulemap or not? I don't remember where I found documentation on the conditions required for swift's package tools to generate the modulemap, but I vaguely recall it only happens under certain circumstances.

Other than that, my best guess would be the issue is something in the way you are including or building the package downstream, especially if it builds with swift build/test locally. You could try including mine as a test and see if it works /shrug. And try opening your package in Xcode ($ open Package.swift) and building that way. Xcode should be able to build your swift package and run your tests which would rule out an Xcode issue. Edit: I see you're using swift downstream so ignore the Xcode bit.

Edit2: I just realized changing the swift tools min version probably didn't do anything. You'd have to find a way to build with the older version. But that's probably not the issue regardless so save that as a last resort.

Thanks, will take a look and see what I can figure out.

Thanks all for the help. As @NeoNacho suggested, it was simply an obvious omission in my Package.swift file.

Now I'm seeing a new error:

Building for debugging...
ld: warning: '/Users/<USER>/Developer/Test/.build/arm64-apple-macosx/debug/librclone.a[2](go.o)' has malformed LC_DYSYMTAB, expected 146 undefined symbols to start at index 151643, found 167 undefined symbols starting at index 110
ld: Undefined symbols:
  _res_9_nclose, referenced from:
      _runtime.text in librclone.a[2](go.o)
  _res_9_ninit, referenced from:
      _runtime.text in librclone.a[2](go.o)
  _res_9_nsearch, referenced from:
      _runtime.text in librclone.a[2](go.o)
clang: error: linker command failed with exit code 1 (use -v to see invocation)
[2/3] Linking Test

Which tells me there's maybe something going on with the library itself? I'm not really familiar with Go, but I will investigate over at the Rclone repo.

Those errors indicates that the resolv library has not been linked in.

I have the same error in the Test, Can you resolved that issue?