Importing C Libraries in Swift

find the code here https://github.com/Chandram-Dutta/SwiftRaylib/

After watching Tsoding struggle with importing a C Library (raylib) in his recent stream. I decided to make the process easier and more straightforward for developers. I totally agree that the official documentation is not very clear and lacks examples. This blog aims to address that issue.

So we start by creating a swift package using

mkdir SwiftRaylib
cd SwiftRaylib
swift package init --name SwiftRaylib --type executable

This initialises our SwiftRaylib Package and the folder structure should look like this

SwiftRaylib/
├── Package.swift
└── Sources
    └── main.swift

Now let’s move the C Library in the Sources directory. We will use the raylib 5.0 release for macOS.

Note that for Swift Package Manager(SPM) to include a C Library in project, the C Library should have a include directory with the header files present in it. In our case, raylib’s file structure looks like this

raylib-5.0_macos/
├── CHANGELOG
├── LICENSE
├── README.md
├── include
│   ├── raylib.h
│   ├── raymath.h
│   └── rlgl.h
└── lib
    ├── ...

where the include directory does contains all the necessary header files.

Now another important change to make is creating a new folder in Sources with the same name as that of the Swift Project and moving the main.swift file inside it.

SwiftRaylib/
├── Package.swift
└── Sources
    ├── SwiftRaylib
    │   └── main.swift
    └── raylib-5.0_macos

And now the last step is to edit Package.swift

Presently Package.swift should look like this

// swift-tools-version: 6.0

import PackageDescription

let package = Package(
    name: "SwiftRaylib",
    targets: [
        .executableTarget(
            name: "SwiftRaylib")
    ]
)

We are going to add a products parameter that takes in an array of the C Library and the Swift Executable

// swift-tools-version: 6.0

import PackageDescription

let package = Package(
    name: "SwiftRaylib",
    products: [
        .library(name: "raylib-5.0_macos", targets: ["raylib-5.0_macos"]),
        .executable(
            name: "SwiftRaylib",
            targets: ["SwiftRaylib"]),
    ],
    targets: [
        .executableTarget(
            name: "SwiftRaylib"),
    ]
)

Secondly we will modify the targets by adding a target of our C Library and adding that as a dependency to our Swift executable target. We will also add a swiftSettings that enables the interoperability between C and Swift.

// swift-tools-version: 6.0

import PackageDescription

let package = Package(
    name: "SwiftRaylib",
    products: [
        .library(name: "raylib-5.0_macos", targets: ["raylib-5.0_macos"]),
        .executable(
            name: "SwiftRaylib",
            targets: ["SwiftRaylib"]),
    ],
    targets: [
        .target(
            name: "raylib-5.0_macos"),
        .executableTarget(
            name: "SwiftRaylib",
            dependencies: ["raylib-5.0_macos"],
            swiftSettings: [.interoperabilityMode(.Cxx)]),
    ]
)

Now running swift run should compile and run the project.

To test we will import raylib in main.swift and write a simple program using raymath.h

import raylib_5_0_macos

let v1 = Vector2(x: 3.0, y: 2.0)
let v2 = Vector2(x: 4.0, y: 7.0)

print("Distance between \(v1) and \(v2) is \(Vector2Distance(v1, v2))")

The output should be

Distance between Vector2(x: 3.0, y: 2.0) and Vector2(x: 4.0, y: 7.0) is 5.0990195

Note that Vector2 and Vector2Distance() is being imported from the raylib.

I wasn’t able to use any functions from raylib.h as it doesn’t support arm64(atleast that’s what I got from the compiler.

error: link command failed with exit code 1 (use -v to see invocation)
Undefined symbols for architecture arm64:
  "_InitWindow", referenced from:
      _SwiftRaylib_main in main.swift.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

if someone knows more about this issue, please let me know!