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.
1 2 3 4 5 6 7 8 9 |
import PackageDescription let package = Package( name: "translator", dependencies: [ .Package(url: "https://github.com/iachievedit/CJSONC", majorVersion: 1), .Package(url: "https://github.com/PureSwift/CcURL", majorVersion: 1) ] ) |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
import Glibc import Foundation import CcURL import CJSONC public class Translator { let BUFSIZE = 1024 public init() { } public func translate(text:String, from:String, to:String, completion:(translation:String?, error:NSError?) -> Void) { let curl = curl_easy_init() guard curl != nil else { completion(translation:nil, error:NSError(domain:"translator", code:1, userInfo:nil)) return } let escapedText = curl_easy_escape(curl, text, Int32(strlen(text))) guard escapedText != nil else { completion(translation:nil, error:NSError(domain:"translator", code:2, userInfo:nil)) return } let langPair = from + "%7c" + to let wgetCommand = "wget -qO- http://api.mymemory.translated.net/get\\?q\\=" + String.fromCString(escapedText)! + "\\&langpair\\=" + langPair let pp = popen(wgetCommand, "r") var buf = [CChar](count:BUFSIZE, repeatedValue:CChar(0)) var response:String = "" while fgets(&buf, Int32(BUFSIZE), pp) != nil { response = response + String.fromCString(buf)! } let translation = getTranslatedText(response) guard translation.error == nil else { completion(translation:nil, error:translation.error) return } completion(translation:translation.translation, error:nil) } private func getTranslatedText(jsonString:String) -> (error:NSError?, translation:String?) { let obj = json_tokener_parse(jsonString) guard obj != nil else { return (NSError(domain:"translator", code:3, userInfo:nil), nil) } let responseData = json_object_object_get(obj, "responseData") guard responseData != nil else { return (NSError(domain:"translator", code:3, userInfo:nil), nil) } let translatedTextObj = json_object_object_get(responseData, "translatedText") guard translatedTextObj != nil else { return (NSError(domain:"translator", code:3, userInfo:nil), nil) } let translatedTextStr = json_object_get_string(translatedTextObj) return (nil, String.fromCString(translatedTextStr)!) } } |
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
1 2 3 4 |
sudo apt-get install -y libdb5.3-dev # Since we're going to be using BerkeleyDB mkdir CDB cd CDB touch Package.swift |
In this directory (CDB
) add module.modulemap
with the following contents:
1 2 3 4 5 |
module CDB [system] { header "/usr/include/db.h" link "db" export * } |
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:
1 2 3 4 5 6 7 8 |
import PackageDescription let package = Package( name:"use-CDB", dependencies:[ .Package(url:"../CDB", majorVersion:1) ] ) |
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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import CDB import Glibc var myDatabase:UnsafeMutablePointer<DB> = nil let rc = db_create(&myDatabase, nil, 0) guard rc == 0 else { print("Unable to create database") exit(-1) } print("Database created") |
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
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.
Good! So i can download modules from github manualy and use them in project? There is some project in github https://github.com/TomasLinhart/SwiftGtk dependencies have empty manifest files both MacOS and Linux. So i search info about how include them manualy. I think you answer this question. thanx