How can I use the Swift Package Manager to include C code (in my case, a single .c
file and a header file) without requiring the user to install my C library into /usr/local/lib
?
I had thought to create a Package in a subdirectory of my main package containing the header + lib, and use relative paths, and finally build with swift build -Xlinker ./relative/path/to/mylib
, however I'm not having any success resolving the dependency since it's expected to be a standalone git repository. Error message is:
error: failed to clone; fatal: repository '/absolute/path/to/mylib' does not exist
Moreover it's not clear to me whether using the -Xlinker
flag is the correct approach.
I can't use a bridging header with a pure SwiftPM approach and installing my library system-wide seems overkill as well as not very portable.
Any ideas?
I have done that in this project on github. It replaces pthread_once_t
by wrapping it in C and re-exposing it to swift. It was done as a fun exercise in getting around what Swift tries to limit you into since pthread_once_t
and dispatch_once
are not available directly.
Here is a trimmed down version the Package.swift
file:
// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "Once",
products: [
.library(
name: "Once",
targets: ["OnceC", "Once"]),
],
dependencies: [
],
targets: [
.target(
name: "OnceC",
dependencies: [],
path: "Sources/OnceC"),
.target(
name: "Once",
dependencies: ["OnceC"],
path: "Sources/Swift"),
.testTarget(
name: "OnceTests",
dependencies: ["Once"]),
]
)
You can easily replace the product library with an executable. The main part is that the product's targets needs to contain both the C and Swift targets needed to build.
Then in your targets section make the swift target lists the C target as a dependency.
You can learn more about the required layout for C targets in the SwiftPM Usage.md here
The C language targets are similar to Swift targets except that the C language
libraries should contain a directory named include
to hold the public headers.
To allow a Swift target to import a C language target, add a target dependency in the manifest file. Swift Package Manager will automatically generate a modulemap for each C language library target for these 3 cases:
If include/Foo/Foo.h
exists and Foo
is the only directory under the
include directory then include/Foo/Foo.h
becomes the umbrella header.
If include/Foo.h
exists and include
contains no other subdirectory then
include/Foo.h
becomes the umbrella header.
Otherwise if the include
directory only contains header files and no other
subdirectory, it becomes the umbrella directory.
In case of complicated include
layouts, a custom module.modulemap
can be
provided inside include
. SwiftPM will error out if it can not generate
a modulemap w.r.t the above rules.
For executable targets, only one valid C language main file is allowed i.e. it
is invalid to have main.c
and main.cpp
in the same target.
The only other important thing is how you actually do your #import
in the C code once it is compiled as a compatible module. If you use the import/Foo/Foo.h
organization you need to use #include <Foo/Foo.h>
and if you do import/Foo.h
you can use #import "Foo.h"
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With