More Swift on Linux

Categories:

Editor’s Note: This article was written on December 6, 2015, days after Apple open-sourced Swift and made an Ubuntu distribution of the Swift compiler available. All of the techniques used below should be forward compatible, however, there may be easier ways of doing things in the future as the Foundation classes are implemented. Apple has posted a status page that outlines what works and what doesn’t.

Using Glibc Routines with Swift

As I mentioned in Swift on Linux!, the Foundation classes that Objective-C and Swift developers have come to know and love are only partially implemented. And by partially implemented I really mean hardly implemented. Okay, NSError is there and a few others, but no NSURL, NSURLSession, etc.

What is there is the wealth of routines from the GNU C Library, also known as Glibc. You know, the library of rotuines you’d look up with a man page. Functions like popen and fgets, getcwd and qsort. Swift won’t be displacing Python any time soon if this is all we’re left to work with, but you can do something useful and begin exploring the possibilities of intermixing C with Swift. In this tutorial we’ll do exactly that and write up some Swift code that uses popen to spawn wget to make up for the lack of NSURLSession.

So let’s get stuck in and write some Swift.

Swift cat

Create a file named swiftcat.swift and add the following code:

To get access to all of the Glibc routines we use import Glibc. Easy enough. Swift 2 brought us the guard construct, so we’ll use that to ensure that we have an argument to our script. Our first exposure to using a Glibc function is exit(-1). That’s right, nothing special about calling it, it is just the void exit(int status) function.

We’re going to cheat a bit and leverage the /bin/cat command to read the file and write to standard out. To call it though we’ll use popen which will pipe us a stream of bytes that we can read with fgets. There is one thing to notice here, and that is that Glibc routines which take const char* arguments can be given Swift Strings directly. Routines that take char*, as in the case of fgets require some finesse.

fgets does take a char*, so we cannot pass it a String, but rather will use a buffer allocated as a [CChar] (C char) array. The array has a fixed size of 1024 and is initialized with zeroes. Our while loop calls fgets with the stream pointer, and non-nil results contain a buffer from which we can create a Swift String.

Go ahead and save this to a file called swiftcat.swift and then run it!

# swift swiftcat.swift 
Usage:  swiftcat FILENAME

Pass it a file to get the equivalent of cat output!

Mixing in C

You aren’t limited to using Glibc routines with your Swift code. Let’s say we want to use libcurl to escape some strings and get them ready to be included in a URL. This is easy to do with libcurl.

In a file called escapetext.c put the following:

Make sure you have libcurl installed with apt-get install -y libcurl4-gnutls-dev.

Now, compile the file with:

clang -D__TEST__ -o escapetext escapetext.c -lcurl

We include the -D__TEST__ here to pick up the main function. In a minute I’ll show you how to take this routine and include it in a Swift application. Run the C application:

# ./escapetext "hey there\!"
Escaped text:  hey%20there%21

Easy enough. Now, we want to write a Swift application that uses our C routine escapeText. The first thing to do is compile an escapetext.o object file without the -D__TEST__ flag set. This will get rid of main().

clang -c escapetext.c

Now, create a file called escapetext.h and put the function prototype in it.

Write a new file called escapeswift.swift and add the following:

Compile this Swift code with:

swiftc -c escapeswift.swift -import-objc-header escapetext.h 

Notice that we included -import-objc-header escapetext.h. Without this header the Swift compiler won’t be able to find the prototype for escapeText and will subsequently fail with use of unresolved identifier.

Bringing it all together, we link our escapeswift.o and escapetext.o objects together, and pass in the Curl library.

swiftc escapeswift.o escapetext.o -o escapeswift -lcurl

And run it!

# ./escapeswift "how now brown cow"
Escaped text:  how%20now%20brown%20cow

Translator Application

This is a more complex example, but the principals are the same as those outlined above. We’re going to mix C objects and Swift modules together to write a command line application that translates strings from one language to another.

The REST API we’ll be using to do the actual translation returns results in JSON. Since NSJSONSerialization isn’t yet available in Foundation on Linux, we’ll use the libjson-c-dev library, so install it with apt-get install libjson-c-dev.

jsonparse

Two files make up our JSON-parsing routine, parsejson.c and its companion header parsejson.h.

parsejson.c:

parsejson.h

We can easily compile this file with clang -c jsonparse.c.

Translator module

The workhorse of the translator application will be a Swift module called translator. To create this module and prepare it for inclusion with the rest of our project, start with the class file translator.swift:

Take a moment to read through the code. We’re including direct calls to the Curl library here, as well as popen and fgets, and our translatedText routine that is compiled into an object file created by clang.

In addition, create a bridgingHeader.h with the contents:

There are two steps to getting this ready to use in our application:

  • Create a shared library with the translator routine
  • Create a swiftmodule that describes the interface

I will confess, I didn’t understand this until I read on Stackoverflow:

The .swiftmodule describes the Swift module’s interface but it does not contain the module’s implementation. A library or set of object files is still required to link your application against.

First, compile the code into a .o and create a shared library:

swiftc -emit-library translator.swift -module-name translator -import-objc-header bridgingHeader.h
clang -shared -o libtranslator.so translator.o

Now, create the module:

swiftc -emit-module -module-name translator translator.swift -import-objc-header bridgingHeader.h

This leaves us with three files: libtranslator.so, translator.swiftmodule, and translator.swiftdoc.

Main Routine

Our main file, main.swift looks like this:

Again, we’ve made use of Foundation and Glibc, but we’re also using import translator. You must have a translator.swiftmodule in your module search path, which we add with -I.:

swiftc -I. -c main.swift -import-objc-header bridgingHeader.h

Let’s link everything together:

swiftc -o translate.exe jsonparse.o main.o -L. -ltranslator -lcurl -ljson-c -lswiftGlibc -lFoundation

The resulting binary is translate.exe because we intend to wrap a helper script around it to set the LD_LIBRARY_PATH to find the libtranslator.so shared library. Without the helper script (or using ldconfig to update the search path), you need to invoke the excecutable like this:

LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ./translate.exe "Hello world\!" from en to es
Translation:  ¡Hola, mundo!

Let’s try Irish:

LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ./translate.exe "Hello world\!" from en to ga
Translation:  Dia duit

Makefile

It’s not clear how “interpreter” friendly Swift will become. Yes, one can create a single monolithic Swift script right now and run it with swift. In fact we did that above. Using bits of code from other Swift files though, without specifying everything on the command line, remains, well, impossible. Maybe I’m wrong and just haven’t figured out the magic incantation to have Swift greedily open up files searching for code to run.

At any rate, our translator application above needs a little help to build. There is a new Swift builder tool, but I found make could get the job done with some appropriate rules:

Getting the Code

You can get all of the code above from Github.

git clone https://github.com/iachievedit/moreswift

The swiftcat code is meant to be run with the swift command, where as escapetext has a simple build.sh script, and translate has a full-on Makefile.

If you’ve enjoyed this tutorial please follow us Twitter at @iachievedit! There will be more to come as Swift on Linux matures.

2 thoughts on “More Swift on Linux”

  1. Cool resource you have provided here!

    Found small typos related to file naming

    … in bridgingHeader.h:

    #include
    #include "jsonparse.h"

    should be

    #include "parsejson.h"

    and a related one

    Let’s link everything together:

    swiftc -o translate.exe jsonparse.o main.o -L. -ltranslator -lcurl -ljson-c -lswiftGlibc -lFoundation

    should be

    swiftc -o translate.exe parsejson.o main.o -L. -ltranslator -lcurl -ljson-c -lswiftGlibc -lFoundation

    Of course, alternatively should have it have different filenames when same. It would be helpful if they were all consistent.

    Also, I was having strange escaping the bang behavior on my Ubuntu boxes (it was appearing as a backslash + a bang), but I found this to work (note the single quotes, and no backslash):

    LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ./translate.exe 'Hello world!' from en to es

Leave a Reply

Your email address will not be published. Required fields are marked *