静态 Linux SDK 入门指南

众所周知,Swift 可以用于构建 Apple 平台(如 macOS 或 iOS)的软件,但 Swift 也支持其他平台,包括 Linux 和 Windows。

为 Linux 构建尤其有趣,因为从历史上看,用 Swift 编写的 Linux 程序需要确保目标系统上安装了 Swift 运行时及其所有依赖项的副本。此外,为特定发行版构建的程序,甚至为特定发行版的特定主要版本构建的程序,不一定能在任何其他发行版上运行,有时甚至在同一发行版的不同主要版本上都无法运行。

Swift 静态 Linux SDK 通过允许您将程序构建为完全静态链接的可执行文件来解决这两个问题,该可执行文件完全没有外部依赖项(甚至不依赖 C 库),这意味着它将在任何 Linux 发行版上运行,因为它唯一依赖的是 Linux 系统调用接口。

此外,静态 Linux SDK 可以从 Swift 编译器和包管理器支持的任何平台使用;这意味着您可以在 macOS 上开发和测试程序,然后再构建并将其部署到基于 Linux 的服务器,无论是在本地运行还是在云端运行。

静态链接与动态链接

链接是指获取计算机程序的不同部分并将这些部分之间的任何引用连接起来的过程。 对于静态链接,一般来说,这些部分是目标文件静态库(实际上只是目标文件的集合)。

对于动态链接,这些部分是可执行文件动态库(也称为 dylib、共享对象或 DLL)。

动态链接和静态链接之间有两个关键区别

后者很重要,因为传统上,静态链接器将包含命令行上显式列出的每个对象,但它在这样做可以解析未解析的符号引用时才包含来自静态库的对象。如果您静态链接了您实际上不使用的库,则传统的静态链接器将完全丢弃该库,并且不会在其最终二进制文件中包含任何来自该库的代码。

实际上,情况可能更复杂——静态链接器实际上可能基于目标文件中的单个原子工作,因此它实际上可能能够丢弃单个函数或数据片段,而不仅仅是整个对象。

静态链接的优点和缺点

静态链接的优点

静态链接的缺点

特别是在 Linux 上,也可以使用静态链接来完全消除对发行版提供的系统库的依赖,从而生成可在任何发行版上运行的可执行文件,并且可以通过简单地复制来安装。

安装 SDK

在开始之前,请务必注意

一旦解决了这个问题,实际安装静态 Linux SDK 就很容易了;在提示符下,输入

$ swift sdk install <URL-or-filename-here> [--checksum <checksum-for-archive-URL>]

给出 SDK 所在的 URL(和相应的校验和)或文件名。

例如,假设您已安装 swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-02-a 工具链,您需要输入

$ swift sdk install https://download.swift.org/swift-6.0-branch/static-sdk/swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-02-a/swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-02-a_static-linux-0.0.1.artifactbundle.tar.gz --checksum 42a361e1a240e97e4bb3a388f2f947409011dcd3d3f20b396c28999e9736df36

来安装相应的静态 Linux SDK。

Swift 将下载并在您的系统上安装 SDK。您可以使用以下命令获取已安装 SDK 的列表

$ swift sdk list

也可以使用以下命令删除它们

$ swift sdk remove <name-of-SDK>

您的第一个静态链接的 Linux 程序

首先,创建一个目录来保存您的代码

$ mkdir hello
$ cd hello

接下来,要求 Swift 为您创建一个新的程序包

$ swift package init --type executable

您可以在本地构建和运行它

$ swift build
Building for debugging...
[8/8] Applying hello
Build complete! (15.29s)
$ .build/debug/hello
Hello, world!

但是安装了静态 Linux SDK 后,您还可以为 x86-64 和 ARM64 机器构建 Linux 二进制文件

$ swift build --swift-sdk x86_64-swift-linux-musl
Building for debugging...
[8/8] Linking hello
Build complete! (2.04s)
$ file .build/x86_64-swift-linux-musl/debug/hello
.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
$ swift build --swift-sdk aarch64-swift-linux-musl
Building for debugging...
[8/8] Linking hello
Build complete! (2.00s)
$ file .build/aarch64-swift-linux-musl/debug/hello
.build/aarch64-swift-linux-musl/debug/hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, with debug_info, not stripped

这些文件可以复制到适当的基于 Linux 的系统并执行

$ scp .build/x86_64-swift-linux-musl/debug/hello linux:~/hello
$ ssh linux ~/hello
Hello, world!

软件包依赖项呢?

使用 Foundation 或 Swift NIO 的 Swift 软件包应该可以直接使用。但是,如果您尝试使用使用 C 库的软件包,则可能需要做一些工作。此类软件包通常包含带有如下代码的文件

#if os(macOS) || os(iOS)
import Darwin
#elseif os(Linux)
import Glibc
#elseif os(Windows)
import ucrt
#else
#error(Unknown platform)
#endif

静态 Linux SDK 不使用 Glibc;相反,它构建在名为 Musl 的 Linux 替代 C 库之上。我们选择这种方法有两个原因

  1. Musl 对静态链接有出色的支持。

  2. Musl 获得了宽松的许可,这使得静态链接到它的可执行文件易于分发。

如果您正在使用这样的依赖项,则需要调整它以导入 Musl 模块而不是 Glibc 模块

#if os(macOS) || os(iOS)
import Darwin
#elseif canImport(Glibc)
import Glibc
#elseif canImport(Musl)
import Musl
#elseif os(Windows)
import ucrt
#else
#error(Unknown platform)
#endif

有时,在 Musl 和 Glibc 之间导入 C 库类型的方式可能存在差异;如果有人添加了可空性注释,或者指针类型正在使用前向声明的 struct,但从未提供实际定义,则有时会发生这种情况。通常,问题会很明显——函数参数或结果在一个案例中是 Optional,而在另一个案例中是非 Optional,或者指针类型将被导入为 OpaquePointer 而不是 UnsafePointer<FOO>

如果您确实发现自己需要进行此类调整,您可以通过执行以下操作使软件包依赖项的本地副本可编辑

$ swift package edit SomePackage

然后编辑程序源代码目录中出现的 Packages 目录中的文件。 您可能希望考虑向上游提交 PR 以修复您可能进行的任何修复。