POSTS

# Running Swift without Xcode

On Saturday, I finally finished Advent of Code 2018 (and pushed my solutions to GitHub). I did the whole thing in Swift (except for a couple parts which I did by hand), but I tried to avoid Xcode as much as possible, knowing that it would make automating things a whole lot harder. Instead, I ran everything directly in the command line. It took a while to figure out how to do this, with little official documentation on the subject, so I thought I’d explain how I did this so that anyone searching for it would find the information in one place.

I’m going to focus on command-line applications, though I expect the tips will also be useful for those of you looking to make macOS GUI applications.

## Prerequisites

Obviously, you’ll need Swift installed. On macOS, run xcode-select --install to install the command-line tools. And I recommend installing Xcode too. Linux users can follow the instructions on the download page.

As you won’t have access to the Swift documentation embedded in Xcode from the command line, it’s useful to know where to look. The official guide is The Swift Programming Language, and the auto-generated documentation hosted at SwiftDoc.org is also very helpful. I have the latter downloaded and installed into Dash so that it’s really fast to search.

The instructions below assume you’re on macOS or Linux. If you’re on another platform, you might end up with slightly different results, but hopefully things won’t change too much.

## Running a single script file, interpreted

If you’re just running a single script file, that’s pretty easy. swift is your interpreter, and will happily run a single file.

For example, given the following file, Sum.swift, which sums the numbers from 1 to 100:

let numbers = (1 ... 100)
let result = numbers.reduce(0, +)
print(result)

You can run it directly with swift Sum.swift:

$swift Sum.swift 5050 You can even add a shebang line at the top to make it runnable. If you add the following line to the top of the file: #!/usr/bin/env swift let numbers = (1 ... 100) ... Then make it executable:$ chmod +x Sum.swift

It’s now runnable:

$./Sum.swift 5050 ## Running a script file with arguments Let’s say we adapted our program so it summed the numbers from 1 to N, where N is provided on the command line: #!/usr/bin/env swift // unsafe code; please use guards and print an error instead let numbers = (1 ... Int(CommandLine.arguments[1])!) let result = numbers.reduce(0, +) print(result) We can run this in the same way, just by providing the argument: ./Sum.swift 10 55 ## Running a single script file, compiled To compile Swift code on the command line, use the swiftc program. You can compile the program we wrote above just by running swiftc Sum.swift. This will produce an executable program with the same name as the Swift file, except it won’t have the extension. Try it:$ swiftc Sum.swift
$ls Sum Sum.swift$ ./Sum 10
55

If you’d like to change the name, you can use the -o flag. For example, let’s say we’re looking to call it “SumFromOneTo”, so you can run ./SumFromOneTo 20:

$swiftc -o SumFromOneTo Sum.swift$ ls
SumFromOneTo
Sum.swift
$./SumFromOneTo 20 210 ### Compiler optimisations One advantage of compiling a single file is that you can tell the Swift compiler to optimise it. Turning on optimisations makes the compilation process take longer, but can significantly decrease the amount of time it takes the program to run. For example, summing the numbers from 1 to 100,000,000 takes about 30 seconds on my computer:$ time ./Sum 100000000
5000000050000000
./Sum 100000000  27.83s user 0.06s system 99% cpu 28.001 total

When optimisations are on, it takes no time at all—just under 0.1 seconds on my computer:

$swiftc -O Sum.swift$ time ./Sum 100000000
5000000050000000
./Sum 100000000  0.07s user 0.01s system 95% cpu 0.077 total

## Running multiple files, compiled

Our program’s getting bigger, and it’d be nice to split it into two files. Let’s move all the I/O into a file called Program.swift:

#!/usr/bin/env swift

let upper = Int(CommandLine.arguments[1])!
print(sum(to: upper))

This means Sum.swift needs to provide a pure function named sum:

func sum(to upper: Int) -> Int {
let numbers = (1 ... upper)
let result = numbers.reduce(0, +)
return result
}

To compile multiple files, we just add them all to the command line:

$swiftc -o Sum Program.swift Sum.swift Program.swift:1:1: error: hashbang line is allowed only in the main file #!/usr/bin/env swift ^ Program.swift:5:1: error: expressions are not allowed at the top level print(sum(to: upper)) ^ Uh oh. We get errors. They have the same root cause: we can’t write expressions at the top level, so we can’t actually do anything. There’s one exception, though, and there’s a clue in the first error message: you can have a “main file” which provides the entry point to your program. Let’s rename Program.swift to main.swift, and try again:$ mv Program.swift main.swift
$swiftc -o Sum main.swift Sum.swift$ ./Sum 10
55

It works! main.swift is special—that’s where you can kick off your program. If your program gets larger than one file, I highly suggest putting all the I/O in main.swift and keeping the rest of your program as pure as possible. This way, you can just look in one place to see how it’s all wired together, and for the rest, the function and struct/class signatures will hopefully tell the story.

### Naming your entry point something other than main.swift

Sometimes you can’t name the file main.swift. For example, for Advent of Code, I had almost 50 different programs this year (typically 2 per day), and I didn’t want each to have its own directory. So I leveraged a trick.

It turns out that main.swift doesn’t need to be in the root of your repository. So I created a file, 2018/Helpers/main.swift, which contained one line:

main()

Then, in my program’s real entry point (e.g. 2018/AOC_19_2.swift), I declared a main function and did all the work in there:

func main() {
while let line = readLine() {
...
}
...
}

When running the program, I include the entry point and everything in the Helpers directory:

$swiftc -o build/2018/AOC_19_2 2018/AOC_19_2.swift 2018/Helpers/*.swift$ ./build/2018/AOC_19_2 < 2018/AOC_19.input