This guide is designed to help teams and individuals running Swift Server applications on Linux. It focuses on how to compile, test, deploy and debug such application and provides tips in those areas.
The guide is a community effort, and all are invited to share their tips and know-how.
Triggering the build action in Xcode or running swift build
from the terminal will trigger the build.
Swift is architecture specific, so running the build command on macOS will create a macOS binary. Building on macOS is useful for development and for taking advantage of the great tooling that comes with Xcode. However, most server applications are designed to run on Linux.
To build on Linux and create a Linux binary, use Docker. For example:
$ docker run -v $PWD:/code -w /code swift:5.1 swift build
The above command will run the build using the latest Swift 5.1 Docker image, utilizing bind mounts to the sources on your Mac. Apple publishes Docker images to Docker Hub.
By default, SwiftPM will build a debug version of the application. Note that debug versions are not suitable for running in production as they are significantly slower. To build a release version of your app, run swift build -c release
.
Binary artifacts that could be deployed are be found under .build/x86_64-unknown-linux, or .build/x86_64-apple-macosx for macOS binaries. SwiftPM can show you the full binary path using swift build --show-bin-path -c release
.
-
Build production code in release mode by compiling with
swift build -c release
. Running code compiled in debug mode will hurt performance signficantly. -
For best performance in Swift 5.2 or later, pass
-Xswiftc -cross-module-optimization
(this won't work in Swift versions before 5.2) -
Integrate
swift-backtrace
into your application to make sure backtraces are printed on crash. Backtraces do not work out-of-the-box on Linux, and this library helps to fill the gap. Eventually this will become a language feature and not require a discrete library.
Triggering the test action in Xcode or running swift test from the terminal will trigger the unit tests.
SwiftPM is integrated with XCTest, Apple’s unit test framework. Test results will be displayed in Xcode or printed out to the terminal.
Like building on Linux, testing on Linux requires the use of Docker. For example:
$ docker run -v $PWD:/code -w /code swift:5.1 swift test
The above command will run the tests using the latest Swift 5.1 Docker image, utilizing bind mounts to the sources on your file system.
Swift supports architecture-specific code. By default, Foundation imports architecture-specific libraries like Darwin or Glibc. While developing on macOS, you may end up using APIs that are not available on Linux. Since you are most likely to deploy a cloud service on Linux, it is critical to test on Linux.
Another important detail about testing for Linux is the Tests/LinuxMain.swift
file. For versions prior to Swift 5.1, This file provides SwiftPM an index of all the tests it needs to run on Linux.
-
For version Swift 5.1 and later, this file is no longer required, and testing with
--enable-test-discovery
seamlessly discovers the tests. This will be the default behvior in future releases of Swift. Until this is the default, it can be useful to keepTests/LinuxMain.swift
around but with with the following code to remind you to use the flag:fatalError("Please use
swift test --enable-test-discoveryto run the tests instead")
-
For versions prior to Swift 5.1, it is critical to keep this file up-to-date as you add more unit tests. To regenerate this file, run
swift test --generate-linuxmain
after adding tests. It is also a good idea to include this command as part of your continuous integration setup.
-
For versions Swift 5.1 and later, always test with
--enable-test-discovery
to avoid forgetting tests on Linux. -
Generally, whilst testing, you may want to build using
swift build --sanitize=thread
. The binary will run slower and is not suitable for production, but you'll see many many threading issues before you deploy your software. Often threading issues are really hard to debug and reproduce and also cause random problems. TSan helps catch them early. -
Make use of the sanitizers. Before running code in production, do the following:
- Run your test suite with TSan (thread sanitizer):
swift test --sanitize=thread
- Run your test suite with ASan (address sanitizer):
swift test --sanitize=address
andswift test --sanitize=address -c release -Xswiftc -enable-testing
- Run your test suite with TSan (thread sanitizer):
-
If you have
--privileged
/--security-opt seccomp=unconfined
containers or are running in VMs or even bare metal, you can run your binary withlldb --batch -o "break set -n main --auto-continue 1 -C \"process handle SIGPIPE -s 0\"" -o run -k "image list" -k "register read" -k "bt all" -k "exit 134" ./my-program
instead of
./my-program
to get something something akin to a 'crash report' on crash. -
If you don't have
--privileged
(or--security-opt seccomp=unconfined
) containers (meaning you won't be able to uselldb
) or you don't want to use lldb, consider using a library likeswift-backtrace
to get stack traces on crash.
Once an application is built for production, it needs to be packaged for deployment. There are several strategies for packaging Swift applications for deployment, see the Packaging Guide for more information.
A separate guide for Ubuntu on DigitalOcean is also available.
TODO: add guides for popular public clouds like AWS, GCP, Azure, Heroku, etc.