Compiling Swift Without Xcode

Sometimes you want to compile code without Xcode and all it's glory. This can be advantageous in some respects, so let's dig into the different ways we can compile some Swift code and project types from the command-line using only

make and swiftc.

Project Types

Currently, as of Xcode 6.1.1, Swift is only officially supported in essentially two types of targets:

  1. An executable target.
  2. A dynamic library (Xcode only supports a framework project).

Also, if we take a look at the output of swiftc -help, we can see:

OVERVIEW: Swift compiler

USAGE: swiftc [options] <inputs>

MODES:
  -dump-ast        Parse and type-check input file(s) and dump AST(s)
  -dump-parse      Parse input file(s) and dump AST(s)
  -emit-assembly   Emit assembly file(s) (-S)
  -emit-bc         Emit LLVM BC file(s)
  -emit-executable Emit a linked executable
  -emit-ir         Emit LLVM IR file(s)
  -emit-library    Emit a linked library
  -emit-object     Emit object file(s) (-c)
  -emit-silgen     Emit raw SIL file(s)
  -emit-sil        Emit canonical SIL file(s)
  -parse           Parse input file(s)
  -print-ast       Parse and type-check input file(s) and pretty print AST(s)

The import items from above are -emit-executable and -emit-library. Those are the ones that will get us where we want to go.

Executable Target

For the executable, all you need is a main.swift file stored in a src directory.

main.swift

println("Building with make!");

And the Makefile looks like this:

# A simple build script for building projects.
#
# usage: make [CONFIG=debug|release]

MODULE_NAME = tool
SDK         = macosx
ARCH        = x86_64

CONFIG     ?= debug

ROOT_DIR    = $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
OUTPUT_DIR  = $(ROOT_DIR)/bin
TARGET_DIR  = $(OUTPUT_DIR)/$(SDK)/$(CONFIG)
SRC_DIR     = $(ROOT_DIR)/src

ifeq ($(CONFIG), debug)
    CFLAGS=-Onone -g
else
    CFLAGS=-O3
endif

SWIFTC      = $(shell xcrun -f swiftc)
CLANG       = $(shell xcrun -f clang)
SDK_PATH    = $(shell xcrun --show-sdk-path --sdk $(SDK))
SWIFT_FILES = $(wildcard $(SRC_DIR)/*.swift)

build:
    mkdir -p $(TARGET_DIR)
    $(SWIFTC) $(SWIFT_FILES) -emit-executable -sdk $(SDK_PATH) -module-name $(MODULE_NAME) -emit-module -emit-module-path $(TARGET_DIR)/$(MODULE_NAME).swiftmodule -o $(TARGET_DIR)/$(MODULE_NAME)

clean:
    rm -rf $(TARGET_DIR)

nuke:
    rm -rf $(OUTPUT_DIR)

This is a fairly simple Makefile that supports debug and release configurations, as well as cleaning or completely removing all of the built output.

This Makefile will produce three things:

  1. The executable binary for the tool.
  2. A swiftmodule file.
  3. A swiftdoc file.

All of the intermediate files are placed into temp directories. If you would like to see those, pass -v into swiftc.

Library Target

The library target is identical except for changing -emit-executable with

-emit-library.

Notice that once you make this change, main.swift will no longer compile properly. So change the file to this:

public func message() -> String {
    return "Building with make!"
}

Since the above method is public, when you consume this library, and because we are generating the swiftmodule file, the function will be available for you to call.

Summary

You'll probably not always want to use this technique and simply opt for the comfort of Xcode and its project configuration settings. However, if you need to setup a simple project and just want to use your favorite editor, copy this

Makefile, make a few adjustments, and you're good to go.

Compiling Swift Without Xcode

We Can Do Better

Abstraction.

It is the idea that we can break things down into nice, cohesive parts. Once we do that, we start to build more and more abstractions on top of what we have until we are left with what we have today: obscurity.

It saddens me that the we are equating “modern programming” as meaning we need to abstract away the fundamental parts of programming. Many languages coming out today are trying to get away from what programming fundamentally is: manipulating memory.

Now, I get it. Memory management can be a pain. However, as I reflect on where our industry has gone, it seems to be as if we’ve thrown the baby out with the bathwater. We’ve introduced highly involved and complicated systems to attempt to solve this memory management problem.

And have we succeeded?

For some domains, we have gotten “good enough”. So when I see languages coming out that are so focused on getting rid of memory management as a core principle, it just feels wrong to me. It feels like we are solving the wrong problem.

I think the problem comes down to two things:

  1. Clarity of expression – how well can we author our code, understand our code, and maintain our code?
  2. Introspective ability – how good are our tools that we use to help us diagnose our problems?

The first, I think it is a language problem. The second is a tooling problem. If we could track the lifetime of every allocation in our program, we could understand just exactly where and why memory is being leaked. We could track who is overwriting memory that they don’t own. We could know when memory is being accessed that has been reclaimed.

Yes, to some extent we’ve invested in some of these tools. But most of them suck and the language does little to help diagnose any of these problems. The language also does little to help you prevent these problems, and when they do try and help, those languages make doing many things much more difficult. High Friction.1

That’s what I want to try and do. Create a better language that allows you to be expressive, pragmatic, and get stuff done without handcuffing and treating you like a child.

Hello Proteus.

  1. The “high friction” term is something that Jonathan Blow talks about in his exploration of a new programming language for games. If you have not seen his videos, they are great; I highly recommend you check them out.
We Can Do Better