Ship C code with swift packages using swift package manager


Code written in C can be used inside swift directly by using clang’s module maps. Module map allows to define C code as a module and import it into swift code using import statements. Swift package manager has a documentation on how to use and link system libraries using SPM.

How it works?

  • C shared libraries (.so or .dylib) should be present at correct positions (for eg /usr/local/lib on OSX and /usr/lib on ubuntu)

  • A swift package should be created containing a module.modulemap file

  • module.modulemap file defines the name of the package which will be used in swift code, header file which will be exposed, which c library has to be linked, etc

  • SPM recommends prepending C in name of the library when making its swift package. for eg if a c library is called Foo, its swift name should be CFoo

A small example

In this example we’ll create a C Library to compute factorial of the number supplied and display its result using swift.

C Library

  • Create Factorial.h and Factorial.c

    $ mkdir CGetFactorial && cd CGetFactorial && mkdir CSources && touch CSources/Factorial.h && touch CSources/Factorial.c
    

Factorial.h

long factorial(long n);

Factorial.c

long factorial(long n) {
  long result = 1;
  for(int i = 1;i <= n;i++) {
    result = result * i;
  }
  return result;
}
  • Create Package.swift, module.modulemap and makefile

Package.swift This contains name and dependencies of our package. This can be left empty but the file has to be present.

import PackageDescription

let package = Package(
	name: "CGetFactorial"
)

module.modulemap This file will help clang to define the c library as module for swift.

module CGetFactorial {
  header "CSources/Factorial.h"
  link "Factorial"
  export *
}

the module name used here will be the name of the package that can be imported into swift

makefile We will use make to install our c library into the system

SRCDIR = CSources
SHAREDLIB = libFactorial.so

UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
	SHAREDLIBPATH = /usr/lib
endif
ifeq ($(UNAME_S),Darwin)
	SHAREDLIBPATH = /usr/local/lib
endif

Factorial: $(SRCDIR)/Factorial.c
	clang -shared $(SRCDIR)/Factorial.c -o $(SHAREDLIB)
	cp libFactorial.so $(SHAREDLIBPATH)

clean:
	-rm -f $(SHAREDLIB)
	-rm -f $(SHAREDLIBPATH)/$(SHAREDLIB)

This a simple makefile which will compile our Factorial.c into a shared library and copy it into /usr/lib for ubuntu and /usr/local/lib for OSX.

And we’re done with our C package ready to be used with swift.

Github : https://github.com/aciidb0mb3r/CGetFactorial

Swift Package

Lets create a swift package called SwiftyFactorial

mkdir SwiftyFactorial && cd SwiftyFactorial && touch Package.swift && touch main.swift

Package.swift define the dependency to the C Package we created above

import PackageDescription

let package = Package(
  name: "SwiftyFactorial",
	dependencies: [
		.Package(url: "https://github.com/aciidb0mb3r/CGetFactorial", majorVersion: 1)
	]
)

main.swift import the C Package and use the method we wrote.

import CGetFactorial

let result = factorial(20)
print("factorial of 20 = \(result)")

Okay! Lets try building this package.

$ swift build
Cloning Packages/CGetFactorial
Using version 1.0.0 of package CGetFactorial
Compiling Swift Module 'SwiftyFactorial' (1 sources)
Linking Executable:  .build/debug/SwiftyFactorial
ld: library not found for -lFactorial for architecture x86_64
<unknown>:0: error: link command failed with exit code 1 (use -v to see invocation)
<unknown>:0: error: build had 1 command failures

This failed because the C factorial library is not yet installed in our system. Go to the cloned package

$ cd Packages/CGetFactorial-1.0.0/

run make to install it. (You’ll need to run sudo make for ubuntu)

$ make
clang -shared CSources/Factorial.c -o libFactorial.so
cp libFactorial.so /usr/local/lib

That should do it. Go back and try to run swift build again.

$ cd ../../
$ swift build
Compiling Swift Module 'SwiftyFactorial' (1 sources)
Linking Executable:  .build/debug/SwiftyFactorial

Great, try to run the executable

$ .build/debug/SwiftyFactorial
factorial of 20 = 2432902008176640000

Done. Swift is correctly importing the CGetFactorial package and is able to call the method.

To remove the installed library from your system just run the make clean inside the CGetFactorial package dir.

$ cd Packages/CGetFactorial-1.0.0 && make clean
rm -f libFactorial.so
rm -f /usr/local/lib/libFactorial.so

It is fairly simple to import and use C inside swift code but SPM can’t yet build C code for you which will simplify this process if we need to ship C code which has to be linked to our swift packages.

The above code can be downloaded from github: https://github.com/aciidb0mb3r/SwiftyFactorial