Making a C library available in Swift using the Swift Package Manager Nov 7 2019 Latest Update: Jul 25 2020

System libraries are typically defined using C, that means we need a way to make them available to Swift. In this post, we are going to explore how to use the Swift Package Manager to give us access to C libraries, be it system or user-defined.

Let's start by talking about Swift modules.

*NOTE: check the following GitHub repositories for the code:

Swift modules

Swift uses modules to organise code. They allow us to separate code by namespace and define access controls. With namespaces and access controls, we can define cleaner APIs for our modules. We can also hide all the implementation details from the user and only let them interact with the public API. In this way, we can be sure the user won't depend on implementation details, giving us the flexibility to refactor code in the future.

In Swift when we use import A, we are saying import the module A.

The system libraries usually provide a C interface. This means that we'll need to interoperate with C code. If you want to learn more about interoperability with C, you can read two of my previous posts:

That should give you a good base on using C APIs.

If we want to make the system library accessible to our Swift code, we could take different approaches. As a first approach, we could make the system library available to our code and deal with the C interoperability. A second option could be to create a wrapper and try to abstract the "complexities" of working with C code (pointers, etcetera). If we create a wrapper, we can provide a cleaner API that will feel more "Swifty".

We'll see two examples, one for each case. Let's start with just making the system library accessible to Swift.

Making a system library accessible to Swift using module maps

If you read the two posts I recommended, you now understand the interoperability between Swift and C (also with Objective-C). In the posts, you saw that Objective-C and C use header files and Swift uses modules. We needed a way to make the header files accessible to Swift, enter Module Maps.

The idea here is the same. We are going to use a module map to encapsulate the library and make it accessible to Swift.

To have a concrete example, let's create a module map for the sqlite3 library.

Create a directory to put our code. I'll create a directory with the name Csqlite3. By convention, we prepend a letter "C" to the name of the library that we want to wrap. This makes explicit that its a wrapper for a C library. For example, if we were going to use libgit the name of our wrapper would be Clibgit.

Ok, create the directory for SQLite 3:

1
$ mkdir Csqlite3

Let's initialise the directory structure with the Swift Package Manager (SPM).

1
$ swift package init --type library

We are going to use a shim header file to import the system's sqlite3.h file. To keep the code organised, let's create a directory to hold our header files:

1
$ mkdir Headers

Here we are going to add a new file, Csqlite3-Bridging-Header.h and add the following content:

1
#import <sqlite3.h>

Ok, let's add the map module. We are going to create a new file inside our Sources/Csqlite/ directory. Give the new file the name module.modulemap, and add the following content:

1
2
3
4
5
6
//Sources/Csqlite3/module.modulemap
module CSQLite {
    header "../../Headers/Csqlite3-Bridging-Header.h"
    link "sqlite3"
    export *
}

If we make this module accessible to our Swift code, we'll be able to import CSQLite and get access to the sqlite3.h library.

How can we make it accessible? Well, the Clang importer, that is built into Swift, takes care of that. When we specify the dependency to this library, it'll be subject to the Clang importer, making the CSQLite module available. Pretty cool, eh?

If you want to learn more about how modules work, check out the Clang Module's documentation.

Another thing you might see is that when people define the module, they add the [system] attribute. Like this:

1
2
3
4
5
module CSQLite [system] {
    header "../../Headers/Csqlite3-Bridging-Header.h"
    link "sqlite3"
    export *
}

Some people call it a system module. Adding [system] is the same as when you use gcc and add the macro #pragma system_header. What this does is indicate to the compiler that the header is a system header. A system header is different than a regular header because it might include code that is not entirely standard. The code in a system header could include code that generates warnings for regular headers.

Because a system header is written for the OS, the system header might take some liberties. If you want to learn more check gcc's systems header description, or this Stack overflow question. In summary, if you are using a system library and know it's ok to suppress the warnings, use the [system] attribute.

One last thing before we are done with the system library. We need to update our Package.swift to create a system library target. From the documentation, we get the following definition of a system library target:

" System library targets are used to adapt a library installed on the system to work with Swift packages. Such libraries are generally installed by system package managers (such as Homebrew and APT) and exposed to Swift packages by providing a modulemap file along with other metadata such as the library's pkg-config name. "

So our Package.swift file should look like this(I removed the testing targets and all the comments, leaving only the bare minimum):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// swift-tools-version:5.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
  name: "Csqlite3",
  products: [
  .library(name: "Csqlite3", targets: ["Csqlite3"]),
  ],
  targets: [
    .systemLibrary(name: "Csqlite3"),
  ]
)

Ok, that's our Swift library package. The minimal structure of the package is the following:

1
2
3
4
5
6
7
Csqlite3
├── Headers
│   └── Csqlite3-Bridging-Header.h
├── Package.swift
└── Sources
    └── Csqlite3
        └── module.modulemap

Alright, let's test it. To test our library, we'll create a Swift package executable that will make use of our library.

Testing our library

We are going to create a Swift executable using SPM. We are going to name it SQLiteTester (remember, I'm terrible with names).

1
2
3
$ mkdir SQLiteTester
$ cd SQLiteTester
$ swift package init --type executable

Our Package.swift will have the following content:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// swift-tools-version:5.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "SQLiteTester",
    dependencies: [
         .package(path: "../Csqlite3"),
    ],
    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 which this package depends on.
        .target(
            name: "SQLiteTester",
            dependencies: ["Csqlite3"]),
        .testTarget(
            name: "SQLiteTesterTests",
            dependencies: ["SQLiteTester"]),
    ]
)

In our main, we'll import CSQLite and get the version of SQLite. This is how our main will look:

1
2
3
4
5
6
//Sources/SQLiteTester/main.swift
import CSQLite

print("Hello, world!")
let version = String(cString: sqlite3_libversion())
print("SQLite3 Version: \(version)")

That's it, we can run our code.

1
2
3
4
$ swift run
# on my computer I got
Hello, world!
SQLite3 Version: 3.28.0

Perfect! Now you have access to sqlite3 library. If you want to see all of the functions available you can check C-language Interface Specification for SQLite.

Ok, with that we've shown how to make the library accessible to Swift. Let's see an example of creating a Wrapper to abstract the SQLite C interface complexities and just providing a simple API.

Creating a Wrapper for SQLite3

If you've never used SQLite before, it is a small "relational Data Base Management System contained in a C library". SQLite is very lightweight and perfect as a data source for our applications and data models. macOS comes with the sqlite3 command-line utility already installed. We are going to use it to create and manage our database.

We want to write a wrapper to abstract the C code interaction, that way the use of the library is easier, and the users of our library don't have to deal with C pointers and other quirks.

The example library we are going to create is limited in scope and will be simple. Our library will only serve to illustrate the basics. It will not be production-ready (until you make it so :) ).

Ok, let's start by creating a directory for our library:

1
2
3
$ mkdir SQLite3
$ cd SQLite3
$ swift package init --type library

We are going to use the sqlite3 command-line tool to create a database and prepare our data. You can put the following instructions in a file (I'll name it init.sql):

1
2
3
4
5
6
7
8
9
create table Elements(
 id   integer primary key autoincrement not null,
 name char(50)    not null
);
insert into Elements(name) values
('Pedro'),
('Miguel'),
('Marta'),
('Patricia');

Let's run the script and build the database (I'll call it database.db):

1
$ cat init.sql | sqlite3 database.db

This will create the database. Let's check if everything is Ok.

1
2
3
4
5
6
7
8
$ sqlite3 database.db
> select * from Elements;
1|Pedro
2|Miguel
3|Marta
4|Patricia
> .quit
$ 

Our test database is ready. We can continue creating the wrapper.

We are going to use our C library wrapper to make the C API accessible to us. So let's update our Package.swift. It should look like the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// swift-tools-version:5.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "SQLite3",
    products: [
        // Products define the executables and libraries produced by a package, and make them visible to other packages.
        .library(name: "SQLite3", targets: ["SQLite3"]),
    ],
    dependencies: [
        // Dependencies declare other packages that this package depends on.
         .package(path: "../Csqlite3"),
         /*.package(url: "https://github.com/rderik/Csqlite3", from: "0.1.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 which this package depends on.
        .target(
            name: "SQLite3",
            dependencies: ["Csqlite3"]),
        .testTarget(
            name: "SQLite3Tests",
            dependencies: []),
    ]
)

We are just setting the dependency on our Csqlite3 Swift package. We could also pull it from git. I left a comment with an example.

Ok, let's now work on Our wrapper. Create a new file in Sources/SQLite3/ directory, name it SQLite3.swift and add the following content:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import CSQLite

public class SQLite3 {
  var db: OpaquePointer? = nil

  public init?(path: String) {
    if (sqlite3_open(path, &db) != SQLITE_OK) {
      return nil
    }
  }

  deinit {
    sqlite3_close(db)
  }

  public func version() -> String {
    let version = String(cString: sqlite3_libversion())
    return version
  }

  public func executeQuery(_ query: String) -> [[String]] {
    var result = [[String]]()
    var selectStatement: OpaquePointer? = nil
    if(sqlite3_prepare_v2(db, query, -1, &selectStatement, nil) == SQLITE_OK) {
      let columnCount = Int(sqlite3_column_count(selectStatement))
      let columnNames: [String] = (0..<Int32(columnCount)).map {
        String(cString: sqlite3_column_name(selectStatement, $0))
      }
      result.append(columnNames)
      while(sqlite3_step(selectStatement) == SQLITE_ROW) {
        var row = [String]()
        for i in 0..<columnCount {
        let element = String(cString: sqlite3_column_text(selectStatement,Int32(i)))
        row.append(element) 
        }
        result.append(row)
      }
      sqlite3_finalize(selectStatement)
    }
    return result
  }
}

Our wrapper is simple. It allows us to extract the objects we get from a query into an array of Strings. The first array represents the names of the columns, and the rest of the arrays are the content.

Good, let's create an executable that makes use of our wrapper. Outside the SQLite3 Swift package, let's create a new directory:

1
2
$ mkdir SQLiteClient
$ swift package init --type executable

Here is the Package.swift:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// swift-tools-version:5.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "SQLiteClient",
    dependencies: [
         .package(path: "../SQLite3"),
    ],
    targets: [
        .target(
            name: "SQLiteClient",
            dependencies: ["SQLite3"]),
        .testTarget(
            name: "SQLiteClientTests",
            dependencies: ["SQLiteClient"]),
    ]
)

Our main.swift (inside Sources/SQLiteClient/ directory) will have the following content:

1
2
3
4
5
6
7
8
import SQLite3

print("This is our database content:")
if let db = SQLite3(path: "./database.db") {
  let query = "select id, name from Elements"
  let elements = db.executeQuery(query)
  print(elements)
}

Let's run it:

1
$ swift run

Oops, we got lots of errors. The linker is having trouble finding/linking sqlite3.h. I'm not sure why using relative paths on the module.modulemap causes this. So instead of using a "shim" that imports the sqlite3.h library, we'll define the absolute path of the header. We'll go back to updating the code on our Csqlite Swift package. Our module.modulemap will look like this:

1
2
3
4
5
6
module CSQLite {
    /*header "../../Headers/Csqlite3-Bridging-Header.h"*/
    header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sqlite3.h"
    link "sqlite3"
    export *
}

In the past, we were able to access the system libraries from the usual path /usr/include/, but since Xcode 10 (check release notes), the system headers are only located in the SDK. To obtain the path in your system, you can use the following command:

1
$ echo `xcrun --show-sdk-path`/usr/include

Ok, now let's try to run it again:

1
2
3
$ swift run
This is our database content:
[["id", "name"], ["1", "Pedro"], ["2", "Miguel"], ["3", "Marta"], ["4", "Patricia"]]

Prefect, it's working correctly!

Congratulations. Now you can expand that wrapper and make it more Swifty.

Final thoughts

We saw some examples of making a C library accessible to our Swift code. Also, a basic example to demonstrate the idea of generating a wrapper to abstract the complexities of a C API. I hope you find it useful.

I'm not sure why the relative path gives us problems, so if you have any ideas, let me know, and we can figure it out together.

*NOTE: check the following GitHub repositories for the code:

Related topics/notes of interest

That means we could also have used the system's package manager to make make the libraries available to swift like this:

1
2
3
4
5
6
7
let package = Package(
    name: "CSQLite",
    providers: [
        .brew("sqlite"),
        .apt("libsqlite3-dev")
    ]
)

Then the Swift package manager will make sure to search the default directories (where brew or apt install system libraries) and link them.


** If you want to check what else I'm currently doing, be sure to follow me on twitter @rderik or subscribe to the newsletter. If you want to send me a direct message, you can send it to derik@rderik.com.