|
| 1 | +--- |
| 2 | +layout: page |
| 3 | +date: 2024-06-04 12:00:00 |
| 4 | +title: Getting Started with the Static Linux SDK |
| 5 | +author: [al45tair] |
| 6 | +--- |
| 7 | + |
| 8 | +It's well known that Swift can be used to build software for Apple |
| 9 | +platforms such as macOS or iOS, but Swift is also supported on other |
| 10 | +platforms, including Linux and Windows. |
| 11 | + |
| 12 | +Building for Linux is especially interesting because, historically, |
| 13 | +Linux programs written in Swift needed to ensure that a copy of the |
| 14 | +Swift runtime---and all of its dependencies---was installed on the |
| 15 | +target system. Additionally, a program built for a particular |
| 16 | +distribution, or even a particular major version of a particular |
| 17 | +distribution, would not necessarily run on any other distribution or |
| 18 | +in some cases even on a different major version of the same |
| 19 | +distribution. |
| 20 | + |
| 21 | +The Swift Static Linux SDK solves both of these problems by allowing |
| 22 | +you to build your program as a _fully statically linked_ executable, |
| 23 | +with no external dependencies at all (not even the C library), which |
| 24 | +means that it will run on _any_ Linux distribution as the only thing |
| 25 | +it depends on is the Linux system call interface. |
| 26 | + |
| 27 | +Additionally, the Static Linux SDK can be used from any platform |
| 28 | +supported by the Swift compiler and package manager; this means that |
| 29 | +you can develop and test your program on macOS before building and |
| 30 | +deploying it to a Linux-based server, whether running locally or |
| 31 | +somewhere in the cloud. |
| 32 | + |
| 33 | +### Static vs Dynamic Linking |
| 34 | + |
| 35 | +_Linking_ is the process of taking different pieces of a computer |
| 36 | +program and wiring up any references between those pieces. For |
| 37 | +_static_ linking, generally speaking those pieces are _object files_, |
| 38 | +or _static libraries_ (which are really just collections of object |
| 39 | +files). |
| 40 | + |
| 41 | +For _dynamic_ linking, the pieces are _executables_ and _dynamic |
| 42 | +libraries_ (aka dylibs, shared objects, or DLLs). |
| 43 | + |
| 44 | +There are two key differences between dynamic and static linking: |
| 45 | + |
| 46 | +* The time at which linking takes place. Static linking happens when |
| 47 | + you build your program; dynamic linking happens at runtime. |
| 48 | + |
| 49 | +* The fact that a static library (or _archive_) is really a collection |
| 50 | + of individual object files, whereas a dynamic library is monolithic. |
| 51 | + |
| 52 | +The latter is important because traditionally, the static linker will |
| 53 | +include every object explicitly listed on its command line, but it |
| 54 | +will _only_ include an object from a static library if doing so lets |
| 55 | +it resolve an unresolved symbolic reference. If you statically link |
| 56 | +against a library that you do not actually use, a traditional static |
| 57 | +linker will completely discard that library and not include any code |
| 58 | +from it in your final binary. |
| 59 | + |
| 60 | +In practice, things can be more complicated---the static linker may |
| 61 | +actually work on the basis of individual _sections_ or _atoms_ from |
| 62 | +your object files, so it may in fact be able to discard individual |
| 63 | +functions or pieces of data rather than just whole objects. |
| 64 | + |
| 65 | +### Pros and Cons of Static Linking |
| 66 | + |
| 67 | +Pros of static linking: |
| 68 | + |
| 69 | +* No runtime overhead. |
| 70 | + |
| 71 | +* Only include code from libraries that is actually needed. |
| 72 | + |
| 73 | +* No need for separately installed dynamic libraries. |
| 74 | + |
| 75 | +* No versioning issues at runtime. |
| 76 | + |
| 77 | +Cons of static linking: |
| 78 | + |
| 79 | +* Programs cannot share code (higher overall memory usage). |
| 80 | + |
| 81 | +* No way to update dependencies without rebuilding program. |
| 82 | + |
| 83 | +* Larger executables (though this can be offset by not having to |
| 84 | + install separate dynamic libraries). |
| 85 | + |
| 86 | +On Linux in particular, it's also possible to use static linking to |
| 87 | +completely eliminate dependencies on system libraries supplied by the |
| 88 | +distribution, resulting in executables that work on any distribution |
| 89 | +and can be installed by simply copying. |
| 90 | + |
| 91 | +### Installing the SDK |
| 92 | + |
| 93 | +Before you start, it's important to note: |
| 94 | + |
| 95 | +* You will need to [install an Open Source toolchain from |
| 96 | + swift.org](https://www.swift.org/install/). |
| 97 | + |
| 98 | +* You cannot use the toolchain provided with Xcode to build programs |
| 99 | + using the SDK. |
| 100 | + |
| 101 | +* If you are using macOS, you will also need to ensure that you use |
| 102 | + the Swift compiler from this toolchain by [following the |
| 103 | + instructions |
| 104 | + here](https://www.swift.org/install/macos/#installation-via-swiftorg-package-installer). |
| 105 | + |
| 106 | +* The toolchain must match the version of the Static Linux SDK that |
| 107 | + you install. The Static Linux SDK includes the corresponding Swift |
| 108 | + version in its filename to help identify the correct version of the |
| 109 | + SDK. |
| 110 | + |
| 111 | +Once that is out of the way, actually installing the Static Linux SDK |
| 112 | +is easy; at a prompt, enter |
| 113 | + |
| 114 | +```console |
| 115 | +$ swift sdk install <URL-or-filename-here> |
| 116 | +``` |
| 117 | + |
| 118 | +giving the URL or filename at which the SDK can be found. |
| 119 | + |
| 120 | +For instance, assuming you have installed the |
| 121 | +`swift-6.0-DEVELOPMENT-SNAPSHOT-2024-06-06-a` toolchain, you would |
| 122 | +need to enter |
| 123 | + |
| 124 | +```console |
| 125 | +$ swift sdk install https://download.swift.org/development/static-sdk/swift-DEVELOPMENT-SNAPSHOT-2024-06-06-a/swift-DEVELOPMENT-SNAPSHOT-2024-06-06-a_static-linux-0.0.1.artifactbundle.tar.gz |
| 126 | +``` |
| 127 | + |
| 128 | +to install the corresponding Static Linux SDK. |
| 129 | + |
| 130 | +Swift will download and install the SDK on your system. You can get a |
| 131 | +list of installed SDKs with |
| 132 | + |
| 133 | +```console |
| 134 | +$ swift sdk list |
| 135 | +``` |
| 136 | + |
| 137 | +and it's also possible to remove them using |
| 138 | + |
| 139 | +```console |
| 140 | +$ swift sdk remove <name-of-SDK> |
| 141 | +``` |
| 142 | + |
| 143 | +### Your first statically linked Linux program |
| 144 | + |
| 145 | +First, create a directory to hold your code: |
| 146 | + |
| 147 | +```console |
| 148 | +$ mkdir hello |
| 149 | +$ cd hello |
| 150 | +``` |
| 151 | + |
| 152 | +Next, ask Swift to create a new program package for you: |
| 153 | + |
| 154 | +```console |
| 155 | +$ swift package init --type executable |
| 156 | +``` |
| 157 | + |
| 158 | +You can build and run this locally: |
| 159 | + |
| 160 | +```console |
| 161 | +$ swift build |
| 162 | +Building for debugging... |
| 163 | +[8/8] Applying hello |
| 164 | +Build complete! (15.29s) |
| 165 | +$ .build/debug/hello |
| 166 | +Hello, world! |
| 167 | +``` |
| 168 | + |
| 169 | +But with the Static Linux SDK installed, you can also build Linux |
| 170 | +binaries for x86-64 and ARM64 machines: |
| 171 | + |
| 172 | +```console |
| 173 | +$ swift build --sdk x86_64-swift-linux-musl |
| 174 | +Building for debugging... |
| 175 | +[8/8] Linking hello |
| 176 | +Build complete! (2.04s) |
| 177 | +$ file .build/x86_64-swift-linux-musl/debug/hello |
| 178 | +.build/x86_64-swift-linux-musl/debug/hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, with debug_info, not stripped |
| 179 | +``` |
| 180 | + |
| 181 | +```console |
| 182 | +$ swift build --sdk aarch64-swift-linux-musl |
| 183 | +Building for debugging... |
| 184 | +[8/8] Linking hello |
| 185 | +Build complete! (2.00s) |
| 186 | +$ file .build/aarch64-swift-linux-musl/debug/hello |
| 187 | +.build/aarch64-swift-linux-musl/debug/hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, with debug_info, not stripped |
| 188 | +``` |
| 189 | + |
| 190 | +These can be copied to an appropriate Linux-based system and executed: |
| 191 | + |
| 192 | +```console |
| 193 | +$ scp .build/x86_64-swift-linux-musl/debug/hello linux:~/hello |
| 194 | +$ ssh linux ~/hello |
| 195 | +Hello, world! |
| 196 | +``` |
| 197 | + |
| 198 | +### What about package dependencies? |
| 199 | + |
| 200 | +Swift packages that make use of Foundation or Swift NIO should just |
| 201 | +work. If you try to use a package that uses the C library, however, |
| 202 | +you may have a little work to do. Such packages often contain files |
| 203 | +with code like the following: |
| 204 | + |
| 205 | +```swift |
| 206 | +#if os(macOS) || os(iOS) |
| 207 | +import Darwin |
| 208 | +#elseif os(Linux) |
| 209 | +import Glibc |
| 210 | +#elseif os(Windows) |
| 211 | +import ucrt |
| 212 | +#else |
| 213 | +#error(Unknown platform) |
| 214 | +#endif |
| 215 | +``` |
| 216 | + |
| 217 | +The Static Linux SDK does not use Glibc; instead, it is built on top |
| 218 | +of an alternative C library for Linux called |
| 219 | +[Musl](https://musl-libc.org). We chose this approach for two |
| 220 | +reasons: |
| 221 | + |
| 222 | +1. Musl has excellent support for static linking. |
| 223 | + |
| 224 | +2. Musl is permissively licensed, which makes it easy to distribute |
| 225 | + executables statically linked with it. |
| 226 | + |
| 227 | +If you are using such a dependency, you will therefore need to adjust |
| 228 | +it to import the `Musl` module instead of the `Glibc` module: |
| 229 | + |
| 230 | +```swift |
| 231 | +#if os(macOS) || os(iOS) |
| 232 | +import Darwin |
| 233 | +#elseif canImport(Glibc) |
| 234 | +import Glibc |
| 235 | +#elseif canImport(Musl) |
| 236 | +import Musl |
| 237 | +#elseif os(Windows) |
| 238 | +import ucrt |
| 239 | +#else |
| 240 | +#error(Unknown platform) |
| 241 | +#endif |
| 242 | +``` |
| 243 | + |
| 244 | +Occasionally there might be a difference between the way a C library |
| 245 | +type gets imported between Musl and Glibc; this sometimes happens if |
| 246 | +someone has added nullability annotations, or where a pointer type is |
| 247 | +using a forward-declared `struct` for which no actual definition is |
| 248 | +ever provided. Usually the problem will be obvious---a function |
| 249 | +argument or result will be `Optional` in one case and non-`Optional` |
| 250 | +in another, or a pointer type will be imported as `OpaquePointer` |
| 251 | +rather than `UnsafePointer<FOO>`. |
| 252 | + |
| 253 | +If you do find yourself needing to make these kinds of adjustments, |
| 254 | +you can make your local copy of the package dependency editable by |
| 255 | +doing |
| 256 | + |
| 257 | +```console |
| 258 | +$ swift package edit SomePackage |
| 259 | +``` |
| 260 | + |
| 261 | +and then editing the files in the `Packages` directory that appears in |
| 262 | +your program's source directory. You may wish to consider raising PRs |
| 263 | +upstream with any fixes you may have. |
0 commit comments