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