Hashbang Swift: Part II

Previous post Hashbang Swift: Part I covered Swift code running from the terminal as a script, using a Unix-like mechanism called hashbang (a.k.a. shebang, sha-bang or hash-exclam). One might have noticed that a Swift code run using that approach doesn’t import Frameworks, and that’s because xcrun on that post launches swift without specifying a SDK.

1
2
3
4
5
6
$ ./test.swift
./test.swift:3:8: error: no such module 'Cocoa'
import Cocoa
       ^
<unknown>:0: note: did you forget to set an SDK using -sdk or SDKROOT?
<unknown>:0: note: use "-sdk $(xcrun --show-sdk-path --sdk macosx)" to select the default OS X SDK installed with Xcode

As mentioned on that post, xcrun is also responsible for showing SDKs paths for iOS and Mac OS X development.

1
2
$ xcrun --show-sdk-path --sdk macosx
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk

But unfortunately wrapping the call which returns the SDK path does not work on the hashbang declaration, making the elegant syntax below throw an error.

1
#!/usr/bin/xcrun swift -i -sdk $(xcrun --show-sdk-path --sdk macosx)

Option 1

What is the simplest thing to do that could make it to work? (a question we ask ourselves a lot at work, when pair programming with TDD/BDD). In this case, the simplest thing to do would be to include the full SDK path in the hashbang declaration.

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/xcrun swift -i -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk

import Cocoa

extension CGRect {
    var center : CGPoint { return CGPoint(x: origin.x + 0.5 * width, y: origin.y + 0.5 * height) }
}

let frame = CGRect(x: 10, y: 10, width: 100, height: 100)

println("frame is \(frame) and center is \(frame.center)")
1
2
$ ./test.swift
frame is (10.0,10.0,100.0,100.0) and center is (60.0,60.0)

Option 2

Another way to address this problem is by setting the SDK using the environment variable SDKROOT, without setting the SDK within the script.

1
2
3
4
5
#!/usr/bin/xcrun swift -i

import Cocoa

// code...
1
2
$ SDKROOT=$(xcrun --show-sdk-path --sdk macosx) ./test.swift
frame is (10.0,10.0,100.0,100.0) and center is (60.0,60.0)

Like the first option, this one also works, but one needs to remember to set the SDKROOT before running the script1.

Option 3

A third option would be a helper file (swiftHelper) that wraps all the xcrun calls.

1
2
#!/bin/bash
xcrun swift -i -sdk $(xcrun --show-sdk-path --sdk macosx) ${@:1}

And since all the xcrun calls are now wrapped, the hashbang needs to be changed to the following:

1
2
3
4
5
#!/usr/bin/env ./swiftHelper

import Cocoa

// code...

Where ./swiftHelper is the helper file with its mode bits changed to make it executable. In this example, the helper file is located in the same directory of the Swift code (./).

To make it more generic and directory-agnostic, the helper file can now be moved to another directory which is set in the $PATH with its hashbang declaration changed to #!/usr/bin/env swiftHelper, without the ./.

1
2
$ ./test.swift
frame is (10.0,10.0,100.0,100.0) and center is (60.0,60.0)

All the options above require something: option 1 requires the developer to update the path every time a new SDK is released; option 2 requires a SDKROOT variable to be set before running the script; and option 3 requires a helper file.

Given the fact that Apple updates its Operating Systems once a year, I wouldn’t mind adopting the first alternative. It works and doesn’t require external settings and dependencies. It also gives the ability to lock that file to a particular SDK version.


  1. It can be set in .profile/.bashrc/.bash_profile… but I personaly don’t like to set global environment variables in my environments.

Copyright © 2014 - Otavio Cordeiro. Powered by Octopress