Introducing the Swift Package Manager

Categories:

Linux Swift 2.2

Update: If you don’t have Swift installed yet, head on over to our Ubuntu Packages page and get the latest with apt-get install.

In today’s world any serious programming language is going to come with a package manager, an application designed to aid managing the distribution and installation of software “packages”. Ruby has a de jure package management system with the rubygems application, formally added to the language in Ruby 1.9. Python has various competing package management systems with pip, easy_install, and others. NodeJS applications and libraries are delivered via npm.

As a part of the open source release of Swift, Apple has released a Swift Package Manager designed for “managing distribution of source code, aimed at making it easy to share your code and reuse others’ code.” This is a bit of understatement of what the Swift Package Manager can do in that its real power comes in managing the compilation and link steps of a Swift application.

In our previous tutorial we explored how to build up a Swift application that relied on Glibc functions, linking against libcurl routines with a bridging header, and linking against a C function we compiled into an object file. You can see from the Makefile, there are a lot of steps and commands involved!

In this post we’re going to replace all that with a succinct and clean Swift Package Manager Package.swift manifest and simplify our code while we’re at it.

Package.swift

Package.swift is the equivalent of npms package.json. It is the blueprint and set of instructions from which the Swift Package Manager will build your application. As of this writing (December 8, 2015), Package.swift contains information such as:

  • the name of your application (package)
  • dependencies that your application relies on and where to retrieve them
  • targets to build

Unlike package.json however, Package.swift is Swift code, in the same way an SConstruct file is Python. As the Swift Package Manager evolves to meet the complex use cases of building large software packages it will undoubtedly grow to contain additional metadata and instructions on how to build your package (for example, package.json for NodeJS contains instructions on how to execute your unit tests).

There’s quite a bit of documentation available for the Swift Package Manager on Github, so I won’t rehash it here, but will rather dive right in to a working example of a Package.swift for our translator application.

Each Package.swift file starts off with import PackageDescription. PackageDescription is a new Swift module that comes with the binary distribution of Swift for the Ubuntu system. The class Package is provided, which we utilize on the next line.

Don’t let the “declarative” nature fool you, this is Swift code. package gets assigned a new Package object which we’ve created with the init(name: String? = nil, targets: [Target] = [], dependencies: [Dependency] = []) initializer.

The name attribute is self-explanatory, and dependencies is an array of package dependencies. Our application will rely on two packages, CJSONC (which is a wrapper around libjson-c), and CcURL (a wrapper around, you guessed it, libcurl).

The Swift Package Manager authors have devised an interesting mechanism by which to pull in package dependencies which relies on Git and git tags. We’ll get to that in a moment.

Directory Structure

The Swift Package Manager relies on the convention over configuration paradigm for how to organize your Swift source files. By this we simply mean, if you follow the convention the package manager expects, then you have to do very little with your Package.swift. Notice that we didn’t specify the name of our source files in it. We don’t have to because the package manager will figure it out by looking in expected locations.

In short, the Swift Package Manager is happiest when you organize things like this:

project/Package.swift
       /Sources/sourceFile.swift
       /Sources/...
       /Sources/main.swift

In our Sources directory we will place two files: Translator.swift and main.swift. Note: Our previous tutorial used lowercase filenames, such as translator.swift. This convention is used by NodeJS developers. It appears that the Swift community is going with capitalized filenames.

Translator.swift has changed a bit from our previous version. Here is the new version which leverages system modules rather than trying to link against C object files we created by hand.

Two new import statements have been added for CJSONC and CcURL, and a routine we did have in C is now in pure Swift. To be sure under the hood the compile and link system is relying on libraries that were compiled from C source code, but at the binary level, its all the same.

Now, here is where it gets really simple to build! Type swift build and watch magic happen:

# swift build
Cloning Packages/CJSONC
Cloning Packages/CcURL
Compiling Swift Module 'translator' (2 sources)
Linking Executable:  .build/debug/translator

That’s it! Our binary is placed in .build/debug and takes its name from our Package.swift file. By default a debug build is created; if we want a release build, just add -c release to the command:

# swift build -c release
Compiling Swift Module 'translator' (2 sources)
Linking Executable:  .build/release/translator

Running our application:

# .build/debug/translator "Hello world\!" from en to es
Translation:  ¡Hola, mundo!

System Modules

Let’s talk about the two dependencies listed in our Package.swift manifest. If you go to the Github repository of either “packages” you will find very little. Two files, in fact:

  • module.modulemap
  • Package.swift

and the Package.swift file is actually empty!

The format of the module.modulemap file and its purpose is described in the System Modules section of the Swift Package Manager documentation. Let’s take a look at the CJSON one:

module CJSONC [system] {
  header "/usr/include/json-c/json.h"
  link "json-c"
  export *
}

All this file does is map a native C library and headers to a Swift module. In short, if you create a modulemap file you can begin importing functions from all manner of libraries on your Linux system. We’ve created a modulemap for json-c which is installed via apt-get on an Ubuntu system.

The authors of the Swift Package Manager, in the System Modules documentation state:

The convention we hope the community will adopt is to prefix such modules with C and to camelcase the modules as per Swift module name conventions. Then the community is free to name another module simply JPEG which contains more “Swifty” function wrappers around the raw C interface.

Interpretation: if you’re providing a straight-up modulemap file and exposing C functions, name the module CPACKAGE. If at a later date you write a Swift API that uses CPACKAGE underneath, you can call that module PACKAGE. Thus when you see CJSONC and CcURL above you know that you’re dealing with direct C routines.

Creating a System Module

There are several examples of creating system modules in the documentation, but we’ll add one more. Creating a system module is broken down into 3 steps:

  • Naming the module
  • Creating the module.modulemap file
  • Versioning the module

In this directory (CDB) add module.modulemap with the following contents:

Package dependencies in Package.swift are specified with URLs and version numbers. Version.swift lays out the current versioning scheme of major.minor.patch. We need a mechanism by which to version our system module, and the Swift Package Managers have developed a scheme by which you can use git tags.

Now, I’m not sure if git tags will be the only way to specify the version of your package; it does have the downside of tying one to using git for source control of your Swift code.

In our CDB directory.

git init # Initialize a git repository
git add . # Add all of the files in our directory
git commit -m"Initial Version" # Commit
[master (root-commit) d756512] Initial Version
 2 files changed, 5 insertions(+)
 create mode 100644 Package.swift
 create mode 100644 module.modulemap

And the crucial step:

git tag 1.0.0 # Tag our first version

Now we want to use our new module. In a separate directory named use-CDB, adjacent to our CDB directory, create a Package.swift file:

It’s important to note here your directory structure should look like this:

    CDB/module.modulemap
       /Package.swift
use-CDB/Package.swift

In use-CDB run swift build:

# swift build
Cloning Packages/CDB

What swift build has done here is read the package descriptor and “pulled in” the dependency on the CDB package. It so happens that this package is in your local filesystem vs. on a hosted Git repository like Github or BitBucket. The majorVersion is the first tuple of your git tag.

Now let’s say you made an error and needed to change up module.modulemap. You edit the file, commit it, and then run swift build again. Unless you retag you will not pick up these changes! Versioning in action. Either retag 1.0.0 with git tag -f 1.0.0 (-f is for force), or bump your version number with a patch level, like git tag 1.0.1.

To use our new system module we write a quick main.swift in use-CDB:

Use swift build and it will pull in our CDB module for us to use. The next step is to figure out how to use the myDatabase pointer to open the database!

Closing Remarks

It has been less than a week since Apple put Swift and the new package manager out on Github. It’s under heavy churn right now and will undoubtedly rapidly gain new features as time goes on, but it is a great start to being able to quickly build Swift applications on a Linux system!

Getting the Code

You can get the new version of our translator application which uses the Swift Package Manager on Github.

# git clone https://github.com/iachievedit/moreswift
# cd translator_swiftpm
# swift build
Cloning Packages/CJSONC
Cloning Packages/CcURL
Compiling Swift Module 'translator' (2 sources)
Linking Executable:  .build/debug/translator

2 thoughts on “Introducing the Swift Package Manager”

  1. Hi Thank you very much for the tutorial.

    I am having a simple doubt for you but its big unto me.We are specifying all the needed dependencies in the Package.swift and where there are located when they cloned from git?.

    Also how to use the PackageManger with Xcode? is Package Manager only for non Xcode and macOS environments (say making swift app on Linux), is it so?

    Also list final thing, when we integrate libraries in Xcode either manually or Cocoa pods we are having access to it and linking against, so we are accessing the their API in our code. How we can do all this in SPM. at the tim elf writing my code can i able to see the autocompletions of the dependencies members in my code?

    Please help me on this.But your tutorial is useful.Continue doing this.May LORD God Almighty bless you.

Leave a Reply

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