Using a C-library in Swift
I’m still enjoying playing with Swift and am beginning to quite like the language. At the moment, I’ve only ever written with it on Linux, so I’m sure I’m making my life harder than if I was using OS X where it’s more mature. On the flip side, if I ever use Swift professionally, it’s most likely going to be on a Linux server as a microservice or a command line app at the other end of a queue.
One thing I’ve been trying to wrap my head around is getting access to a C library from within Swift and I picked libcurl as the one to try.
Getting this going with the Swift Package Manager isn’t that hard once you understand what you’re doing and understand its current limitations!
Firstly, install the the dev package for lib curl so that curl.h and libcurl.so are available:
sudo apt-get install libcurl4-openssl-dev
Creating the Swift package
The Swift package manager requires all packages to have a Package.swift file. In this case, it can be empty as we’re not compiling any Swift code.
$ mkdir CCurl && cd CCurl $ touch Package.swift
(Note the convention for wrapping a C library is to start the name with a capital C and then using camelCase for the rest of the name.)
To bring in the C library, we need a module.modulemap file:
module CCurl [system] { header "/usr/include/curl/curl.h" link "curl" export * }
With this file, we tell Swift where the curl.h header file is and that any functions in it are to be found in the lubcurl.so library.
We can now use curl C functions in a test app by pulling in our CCurl package as a dependency to our app.
However, as the Swift package manager only (currently) works with git repositories, we first need to create a git repo for CCurl and tag it:
$ git init $ git add Package.swift module.modulemap $ git commit -m "Initial commit" $ git tag 0.0.1
Using a package within an app
We’ll put our app at the same level as our library, so we change directory up a level out of the CCurl one and then make a new directory called app:
$ cd .. $ mkdir app && cd app
As with our library, our app needs a Package.Swift, but this time we use it to list our dependency:
import PackageDescription let package = Package( dependencies: [ .Package(url: "../CCurl", versions: Version(0,0,1)..<Version(1,0,0)) ] )
The Package call takes the url to the git repository and the version criteria. Usually you’d have a git URL from GitHub or somewhere, but as we’re doing this locally, we use a relative file-based one. The versions parameter enables us to put in a range of versions. In this case, I’ve said that I’ll accept anything from 0.0.1 up to but not including 1.0.0.
For the Swift package manager to build an application, you must have a main.swift source file. You can have others, but we only need one file, so main.swift it is:
import CCurl let handle = curl_easy_init() print("handle = \(handle)")
Build the app using swift build and it will grab the CCurl dependency and place it in a Packages directory, build it and then build main.swift for you. The executable is placed in the .build/debug directory and in this case is simply called app
This is just a test that it works and the output on my system is:
$ .build/debug/app handle = 0x0000000001adcbb0
Conclusion
That’s all that’s needed to wrap a C library and use it in Swift.
However, with curl you’ll quickly notice that there’s a wrinkle! Swift doesn’t import variadic functions! It turns out that curl_easy_setopt() is a variadic function, so we need to find a solution, which I’ll cover in my next article.
Thank you for explaining this.
It's exactly what I was trying to do, by following the scant Apple/Clang documentation, which I found to be extremely unclear.
Would love to know how you managed to arrive at this!
Got any experience doing this for libraries with more than one header file, header files which include each other, and so forth? I am trying to scale up your example and getting some uninformative errors out of swift build, such as "error: unable to handle compilation, expected exactly one compiler job".
Thanks for the clear and helpful tutorial.
I made a sandbox gist for it so we can test on difference versions of Swift.
https://gist.github.com/ontouchstart/11ccb6a6df237ac94a6fc21aacd51ea0
Your example does not seem to be working anymore with the latest swift. It does not properly detect the version number and won't use the library.