Developing and Debugging Swift Packages using Swift Package Manager

Apple opensourced the swift language as promised and with it also comes a new shiny tool - Swift Package Manager (SPM)

SPM is kind of similar to CocoaPods and Carthage. A rough comparison would look like this :

SPM — decentralised packages, resolves dependencies, configures project

CocoaPods — centralised packages, resolves dependencies, configures project

Carthage — decentralised packages, resolves dependencies, developer has to configure project

SPM is very young compared to other two but it already has an advantage of running on linux from day one.


Environment Setup

The easiest way to get running is to download the snapshots current development version of swift toolchain as opposed to building from source. Head over to https://swift.org/download/#latest-development-snapshots and install the lastest snapshot of Mac or Linux.

Confirm that swift and SPM is installed:

$ swift --version
Swift version 2.2-dev (LLVM 46be9ff861, Clang 4deb154edc, Swift 778f82939c)
$ swift build --version
Apple Swift Package Manager 0.1

You should be seeing “-dev” in swift’s version. swift build command will not work with swift 2.1

Development

SPM requires a manifest file called Package.swift same as cocoapods Podfile and Carthage’s Cartfile containing information about the package and its dependencies. This has to be in the root directory where as all the Source files have to be inside Sources folder. Complete layout information is here.

All the source files will be compiled as a module which can be used in other modules. If a package is a executable then it should contain a main.swift which will act as entry point for execution.

Create a Package

SimpleGetClient :

is a simple package I wrote which can be used to perform basic HTTP GET. It uses OS’s socket APIs which are provided by Modules: Darwin on OSX and Glibc on Linux. It contains two files Package.swift and Sources/GetClient.swift

Package.swift : Defines the name of the package as SimpleGetClient

import PackageDescription 
let package = Package( 
name: "SimpleGetClient"
)

Sources/GetClient.swift : Contains Implementation of the Get client which will make the socket connection and fetch the data.

now running swift build will build and create the package

$ swift build
Compiling Swift Module 'SimpleGetClient' (1 sources)
Linking Library: .build/debug/SimpleGetClient.a

SPM currently only works on tags so after commiting working code, create a tag and push it

$ git tag 1.0.0
$ git push origin --tags

Github link: https://github.com/aciidb0mb3r/SimpleGetClient

Create a executable package

SwiftGetClientDemo :

is the package which will use SimpleGetClient. This too contains two files Package.swift and Sources/main.swift

Package.swift : Defines name of this package and its dependency ie SimpleGetClient

import PackageDescription
let package = Package(
name: "GetClientUser",
dependencies: [ .Package(url:"https://github.com/aciidb0mb3r/SimpleGetClient.git", majorVersion: 1)
]
)

URL to local git repo can also be used for e.g. :

import PackageDescription
let package = Package(
name: "GetClientUser",
dependencies: [ .Package(url:"../SimpleGetClient", majorVersion: 1)
]
)

Sources/main.swift : has code which will be using the SimpleGetClient to make a GET Request and print the response.

import SimpleGetClient
import Foundation
#if os(Linux)
import Glibc
#else
import Darwin
#endif
let client = GetClient()
let response = client.fetch("httpbin.org/get?foo=bar&baz=bang")
print(response)

now, run swift build for magic

$ swift build
Cloning Packages/SimpleGetClient
Compiling Swift Module 'SimpleGetClient' (1 sources)
Linking Library: .build/debug/SimpleGetClient.a
Compiling Swift Module 'SwiftGetClientDemo' (1 sources)
Linking Executable: .build/debug/SwiftGetClientDemo

run the executable

$ .build/debug/SwiftGetClientDemo
HTTP/1.1 200 OK
Server: nginx
Date: Sun, 06 Dec 2015 15:46:07 GMT
Content-Type: application/json
Content-Length: 189
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Connection: keep-alive
{
"args": {
"baz": "bang",
"foo": "bar"
},
"headers": {
"Host": "httpbin.org"
},
"origin": "124.155.254.144",
"url": "http://httpbin.org/get?foo=bar&baz=bang"
}

Works! Its pretty simple to get packages up and running.

Github link: https://github.com/aciidb0mb3r/SwiftGetClientDemo

You can try out the demo with these commands :

$ git clone https://github.com/aciidb0mb3r/SwiftGetClientDemo
$ cd SwiftGetClientDemo
$ swift build
$ .build/debug/SwiftGetClientDemo

Debugging

LLDB debugger is available for debugging. Lets try setting a breakpoint to the above demo at line number 48 of GetClient.swift

First pass the executable as an argument to lldb

$ lldb .build/debug/SwiftGetClientDemo
(lldb) target create ".build/debug/SwiftGetClientDemo"
Current executable set to '.build/debug/SwiftGetClientDemo' (x86_64).
(lldb)

This will open the (lldb) prompt. Enter this command to set a breakpoint

(lldb) breakpoint set -f GetClient.swift -l 48

enter r to run the program

(lldb) r

It will break at that file

Process 6994 launched: ‘/home/aciid/swift/GetClientUser/.build/debug/SwiftGetClientDemo’ (x86_64)
Process 6994 stopped
* thread #1: tid = 6994, 0x00000000004045d8 SwiftGetClientDemo`GetClient.fetch(urlString=”httpbin.org/get?foo=bar&baz=bang”, self=(bufferSize = 10240, sock = 3)) -> String + 136 at GetClient.swift:48, name = ‘SwiftGetClientD’, stop reason = breakpoint 1.1
frame #0: 0x00000000004045d8 SwiftGetClientDemo`GetClient.fetch(urlString=”httpbin.org/get?foo=bar&baz=bang”, self=(bufferSize = 10240, sock = 3)) -> String + 136 at GetClient.swift:48
45
46 let url = URL(string: urlString)
47
-> 48 let ipAddress = self.getIPFromHost(url.host)
49
50 //Connect
51 var remote = sockaddr_in()
(lldb)

print an object using p or po

(lldb) p url
(SimpleGetClient.URL) $R0 = 0x000000000061f4e0 (host = “httpbin.org”, port = 80, query = “/get?foo=bar&baz=bang”)
(lldb) po url
host: httpbin.org Query: /get?foo=bar&baz=bang Port: 80

n to go to next line

(lldb) n
Process 6994 stopped
* thread #1: tid = 6994, 0x0000000000404635 SwiftGetClientDemo`GetClient.fetch(urlString=”httpbin.org/get?foo=bar&baz=bang”, self=(bufferSize = 10240, sock = 3)) -> String + 229 at GetClient.swift:51, name = ‘SwiftGetClientD’, stop reason = step over
frame #0: 0x0000000000404635 SwiftGetClientDemo`GetClient.fetch(urlString=”httpbin.org/get?foo=bar&baz=bang”, self=(bufferSize = 10240, sock = 3)) -> String + 229 at GetClient.swift:51
48 let ipAddress = self.getIPFromHost(url.host)
49
50 //Connect
-> 51 var remote = sockaddr_in()
52 remote.sin_family = sa_family_t(AF_INET);
53 inet_pton(AF_INET, ipAddress, &remote.sin_addr.s_addr)
54 remote.sin_port = htons(url.port)
(lldb) po ipAddress
"54.175.222.246"

c to resume

(lldb) c
Process 7007 resuming
HTTP/1.1 200 OK
Server: nginx
Date: Sun, 06 Dec 2015 16:07:03 GMT
Content-Type: application/json
Content-Length: 189
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Connection: keep-alive
{
"args": {
"baz": “bang”,
"foo": "bar"
},
"headers": {
"Host": "httpbin.org"
},
"origin": "124.155.254.144",
"url": "http://httpbin.org/get?foo=bar&baz=bang"
}
Process 7007 exited with status = 0 (0x00000000)

more lldb’s commands here.

lldb is crashing on OSX 10.11 while using p or po in lldb

error: Error in auto-import:
failed to get module ‘SimpleGetClient’ from AST context

This might be because while creating executable SPM doesn’t append -g in arguments which generates dSYM for debugging. I have filed a ticket.

You can get around it by running the last command SPM performs manually with -g appended. Turn on verbose mode to see all the commands

$ swift build --clean #to remove build data if exists
$ swift build -v

This will output something like :

now, just re-run the last command (4th in this case) with -g appended at the end.

Now restart lldb and everything should work as expected.

More Links:

SPM Community Proposal

SPM’s Developing Packages Documentation

Swift.org Getting Started