HACK - Swift Module Development

Let’s say you want to develop a Swift thing on Linux. Instead of throwing everything in one big package, you would like to organize things in neat little separate packages.

Or maybe even more interesting, one of the modules is actually a system library package (module map), one is a wrapper for that and yet another one is the package consuming everything.

So how do you do this? You are supposed to use the Swift Package Manager.

Swift Package Manager Packages

Now the interesting part is that SPM packages are actually GIT repositories. The thing is that SPM is not just the ‘build tool’, but also the package management system. It does dependency management, versions, etc. Which has some interesting implications during the ‘regular’ development of the packages, you’ll see.

We are building a wrapper for GCD aka libdispatch. It’ll consist of three packages:

  • CDispatch - the system module importing libdispatch
  • Dispatch - a wrapper for CDispatch providing more stuff
  • TestDispatch - test tool which uses Dispatch

Setup Package Structure

mkdir crazy; cd crazy
mkdir CDispatch Dispatch TestDispatch \
      Dispatch/Sources TestDispatch/Sources
touch CDispatch/Package.swift Dispatch/Package.swift \
      TestDispatch/Package.swift

The CDispatch directory is just a wrapper for the libdispatch library. To import it into Swift, create a file called module.modulemap within and add this content:

module CDispatch [system] {
  header "/usr/local/include/dispatch/dispatch.h"
  export *
  link "dispatch"
}

Next we create our wrapper module, Dispatch. This is going to import our CDispatch module and add some stuff on top. Add this to the Dispatch/Package.swift:

import PackageDescription

let package = Package(
  dependencies: [
    .Package(url: "../CDispatch", majorVersion:1, minor: 0)
  ]
)

And in Sources, add the actual source file, e.g. Dispatch/Dispatch.swift:

import Foundation
import CDispatch    

Go into the Dispatch package directory and do a swift build. What follows may surprise a little:

helge@SwiftyUbuntu:~/crazy/Dispatch$ swift build
Cloning /home/helge/crazy/CDispatch
/usr/bin/git clone --recursive --depth 10 /home/helge/crazy/CDispatch /home/helge/crazy/Dispatch/Packages/CDispatch
fatal: repository '/home/helge/crazy/CDispatch' does not exist

error: Failed to clone /home/helge/crazy/CDispatch to /home/helge/crazy/Dispatch/Packages/CDispatch

As mentioned above a Swift SPM module has to be a GIT repository! No matter what.

pushd ~/crazy/CDispatch
git init; git add .; git commit -m "auto"

Back to the Dispatch directory and do a swift build. What follows may surprise a little:

helge@SwiftyUbuntu:~/crazy/Dispatch$ swift build
Cloning /home/helge/crazy/CDispatch
error: The dependency graph could not be satisfied (/home/helge/crazy/CDispatch)

Hm, what is wrong now. This is a little less obvious. The Swift Package Manager uses GIT tags to implement versions, and we said we want CDispatch v1.0.* in the Package.swift.

git tag 1.0.0

Back to the Dispatch directory and do a swift build. What follows may surprise a little:

helge@SwiftyUbuntu:~/crazy/Dispatch$ swift build
error: The dependency graph could not be satisfied (/home/helge/crazy/CDispatch)

Hm, well, still doesn’t work. Turns out swift build caches the package in the Packages subdirectory. Drop that, and it’ll work:

helge@SwiftyUbuntu:~/crazy/Dispatch$ rm -rf Packages
helge@SwiftyUbuntu:~/crazy/Dispatch$ swift build
Cloning /home/helge/crazy/CDispatch
Using version 1.0.0 of package CDispatch
Compiling Swift Module 'Dispatch' (1 sources)
Linking Library:  .build/debug/Dispatch.a

Now we are good. Let’s do the same for Dispatch, as this is also going to be used as a module by TestDispatch:

pushd ~/crazy/Dispatch
git init; git add .; git commit -m "auto"
git tag 1.0.0

Go on to TestDispatch. Create a main.swift in Sources like that:

import Dispatch

print("Dizpatch")

And import our Dispatch module into that:

import PackageDescription

let package = Package(
  dependencies: [
    .Package(url: "../Dispatch", majorVersion:1, minor: 0)
  ]
)

Compile and run it:

helge@SwiftyUbuntu:~/crazy/TestDispatch$ swift build
Compiling Swift Module 'Dispatch' (1 sources)
Linking Library:  .build/debug/Dispatch.a
Compiling Swift Module 'TestDispatch' (1 sources)
Linking Executable:  .build/debug/TestDispatch

helge@SwiftyUbuntu:~/crazy/TestDispatch$ .build/debug/TestDispatch
Dizpatch

Nice! All this works. We have setup our project structure using submodules.

Working with the Package Structure

Now let’s work with our packages. We’ll add a simple function to Dispatch/Dispatch.swift:

public func printIt() {
  print("Swifter is fazter!!")
}

And we call it from our TestDispatch/main.swift:

import Dispatch

print("Dizpatch")
printIt();

Call swift build. What follows may surprise a little:

helge@SwiftyUbuntu:~/crazy/TestDispatch$ swift build
Compiling Swift Module 'TestDispatch' (1 sources)
/home/helge/crazy/TestDispatch/Sources/main.swift:4:1: error: use of unresolved identifier\
 'printIt'
printIt();
^~~~~~~
<unknown>:0: error: build had 1 command failures
error: exit(1): ["/home/helge/swift-not-so-much/swift-2.2-SNAPSHOT-2016-01-11-a-ubuntu15.1\
0/usr/bin/swift-build-tool", "-f", "/home/helge/crazy/TestDispatch/.build/debug/TestDispat\
ch.o/llbuild.yaml"]

So we though we can just change sources and recompile. Stupid us. What is happening is obvious of course: Dispatch is a module and a module is a GIT repository and a module version a GIT tag. To get TestDispatch to pick up our change, we have to commit our changes to GIT and re-tag it.

pushd ../Dispatch; git commit Sources -m "auto"; git tag -f 1.0.1; popd
swift build

Same error, still doesn’t work. Turns out that 2.2 snaphost 2016-10-11 doesn’t update the packages:

helge@SwiftyUbuntu:~/crazy/TestDispatch$ rm -rf Packages
helge@SwiftyUbuntu:~/crazy/TestDispatch$ swift build
helge@SwiftyUbuntu:~/crazy/TestDispatch$ .build/debug/TestDispatch
Dizpatch
Swifter is fazter!!    

Done. Nice.

If you want to avoid adding a new tag for each change: git tag -f 1.0.0. Be careful with that, you know: man git-tag.

Sample makefile

rules.make:

UNAME_S := $(shell uname -s)

ifeq ($(UNAME_S),Darwin)
  SWIFT_SNAPSHOT=swift-2.2-SNAPSHOT-2016-01-11-a
  SWIFT_TOOLCHAIN_BASEDIR=/Library/Developer/Toolchains
  SWIFT_TOOLCHAIN=$(SWIFT_TOOLCHAIN_BASEDIR)/$(SWIFT_SNAPSHOT).xctoolchain/usr/bin
else
  OS=$(shell lsb_release -si | tr A-Z a-z)
  VER=$(shell lsb_release -sr)
  SWIFT_SNAPSHOT=swift-2.2-SNAPSHOT-2016-01-11-a-$(OS)$(VER)
  SWIFT_TOOLCHAIN_BASEDIR=~/swift-not-so-much
  SWIFT_TOOLCHAIN=$(SWIFT_TOOLCHAIN_BASEDIR)/$(SWIFT_SNAPSHOT)/usr/bin
endif

SWIFT_BUILD_TOOL=$(SWIFT_TOOLCHAIN)/swift build
SWIFT_CLEAN_TOOL=$(SWIFT_TOOLCHAIN)/swift clean

GNUmakefile:

include config.make

PACKAGE_VERSION = 0.0.1

all : clean commit
  (cd Dispatch;     $(SWIFT_BUILD_TOOL))
  (cd TestDispatch; $(SWIFT_BUILD_TOOL))

clean :
  rm -rf Dispatch/.build Dispatch/Packages
  rm -rf TestDispatch/.build TestDispatch/Packages

commit :
  (cd Dispatch; git commit -m "Auto Commit" .; git tag -f $(PACKAGE_VERSION))

run :
  TestDispatch/.build/debug/TestDispatch

Summary

If you are looking for a libdispatch wrapper for Swift: PDispatch. Doesn’t work though! :-)

Did we get anything wrong? Let us know: wrong@alwaysrightinstitute.

It is a little weird to work with and somewhat annoying during development … But then they are honest this time and mark SPM as “Work In Progress”.

And of course the ARI was just kidding. The next step is to throw away everything you just created because you don’t need it anymore. Instead just use Swifter!

Swifter is a programming language in active development (not), which is wicked fast. It compiles swiftly and executes even swifter. Swifter promises to be the Objective-Z without the Z, but with a C.

No one wants a C++ in disguise.

Written on January 25, 2016