Swift 交叉编译:在 macos 上构建 swift vapor 项目的 linux 可执行文件

301 阅读9分钟

几年前,我还用vapor写应用接口,但慢慢的我放弃了。很大一个阻力就是在linux编译vapor项目非常困难:

  • 首先就是服务器性能比较低,构建速度非常慢
  • 最痛的就是,拉取第三方库非常非常非常慢,导致得在服务器安装 vpn
  • 还有就是 vapor 的 orm 不成熟

其实除去打包编译的原因,我还是很喜欢用vapor的,毕竟很多东西很原始,你的手动实现,这样也不会有很多魔法特性,至少用完之后,你知道服务器 web 开发到底都涉及了哪些技术。很多其他语言的 web开发框架很好用,但是学习不到啥东西,因为它帮你做了很多东西,节省了很多步骤。如果你是为了学习,建议用vapor,如果你是为了快速开发,还是用其他语言的框架把,比如 springboot,django 等。

很久没有跟进 vapor 这块文章,最近秒了几眼 swift 的交叉编译,就迫不及待的想尝试下 vapor 项目的构建。

实践结果是可行的,不得不说swift在跨端方面确实做了工作。那么接下来一起来看看这个构建该如何实施。

Swift Static Linux SDK

在这之前我们先了解下 Swift Static Linux SDK,它 主要解决了 Linux 上部署 Swift 应用时的依赖问题。传统上,Swift 应用需要依赖系统中的动态库(如 libSwiftCore.solibc.so),这可能导致兼容性问题或复杂的部署过程。该 SDK 通过 静态链接,将所有依赖嵌入到一个独立的可执行文件中,避免了对目标系统上库的依赖。

举个例子:

假设你开发了一个 Swift 应用,并想部署到一个没有 Swift 环境的 Linux 服务器。使用 Swift Static Linux SDK 后,你可以构建一个 静态链接的可执行文件,直接在目标服务器上运行,而不需要安装任何 Swift 或系统库。

它解决的问题:

  • 避免动态库依赖:应用不依赖系统动态库,因为它唯一依赖的是 Linux 系统调用接口。
  • 减少兼容性问题:保证应用在不同版本的 Linux 系统上运行。
  • 简化部署:只需部署单个二进制文件,简化了安装过程。

Static vs Dynamic Linking

以下是关于 静态库动态库 的定义、差异、优缺点的对比表格:

类别静态库动态库
定义在编译时将库文件的内容嵌入到最终的可执行文件中。在运行时,程序依赖操作系统加载的外部共享库。
链接方式静态链接:在编译时将所有依赖的库打包到可执行文件中。动态链接:程序在运行时加载外部共享库。
文件大小可执行文件较大,因为库被直接嵌入其中。可执行文件较小,只包含程序的代码,库由系统加载。
内存使用每个程序都包含一份库,可能造成内存浪费。多个程序可以共享同一个动态库的内存,节省内存。
依赖管理不依赖系统的动态库,完全独立。依赖目标系统中的动态库,可能导致依赖缺失或版本不兼容。
启动时间启动时无需加载外部库,因此启动较快。程序启动时需要加载动态库,可能导致启动时间稍慢。
更新与维护更新库时需要重新编译整个程序。只需更新共享库,程序可自动加载新版本的库。
兼容性问题无兼容性问题,程序与库一起打包。可能会遇到版本不匹配或缺少库等兼容性问题。
部署复杂度部署时只需复制单个可执行文件,简单。部署时需要确保目标系统安装了正确的动态库,较复杂。
安全性更安全,因为所有依赖都在程序内,不依赖外部库。可能会受到外部库的安全漏洞影响。

优缺点总结

特性静态库动态库
优点1. 独立性强,无需依赖外部库。
2. 兼容性好,跨平台部署简单。
3. 部署简便,减少了外部环境配置的复杂性。
4. 更安全,所有依赖都被打包。
1. 文件小,节省磁盘空间。
2. 内存共享,多个程序共享同一库。
3. 更新库时,无需重新编译程序。
4. 程序启动较快。
缺点1. 文件较大。
2. 更新麻烦,必须重新编译程序。
3. 内存浪费,每个程序都包含一份库。
1. 依赖外部库,可能缺失或不兼容。
2. 兼容性差,可能引发“DLL Hell”。
3. 部署复杂,需要管理系统库。
4. 启动时间稍慢。

安装环境

首先我们无法使用 Xcode 提供的工具链来使用 SDK 构建程序,必须安装对应版本的工具链。

1. 安装Swiftly

curl -O https://download.swift.org/swiftly/darwin/swiftly.pkg && \
installer -pkg swiftly.pkg -target CurrentUserHomeDirectory && \
~/.swiftly/bin/swiftly init --quiet-shell-followup && \
. ~/.swiftly/env.sh && \
hash -r

Swiftly 是一个专门为 Swift 提供的工具和框架,旨在简化和加速开发者的工作流程。它可以用来帮助开发者更高效地构建、测试和调试 Swift 应用,特别是在某些特定场景下,如自动化构建、交叉编译、和使用 Swift 进行脚本化工作时。

以下是 Swiftly 的用途和适用场景的表格:

功能说明适用场景
自动化构建与管理提供自动化工具,简化构建和处理输出,适用于持续集成和持续交付(CI/CD)。持续集成、持续交付(CI/CD)流程
交叉编译支持支持在不同平台(如 Linux、Windows)之间进行交叉编译,简化多平台构建过程。跨平台开发、嵌入式设备、不同操作系统间迁移
命令行工具支持允许用 Swift 编写命令行工具,提供自动化和脚本化能力,适用于处理批量任务。自动化脚本、批量处理任务
开发者工具整合提供库和工具,帮助开发者集成第三方服务或工具,减少配置复杂度和手动操作。第三方服务集成、减少配置操作
增强的调试和测试提供增强的调试功能,如跨平台调试、模拟环境等,帮助快速发现和修复错误。调试跨平台应用、测试不同环境

安装完成后,我们可以用以下指令:

swiftly --version  # 查看版本
swiftly list # 查看已安装sdk列表
swiftly install 6.1.0 # 安装指定版本swfit
swiftly use 6.1.0 # 使用指定版swift
swiftly uninstall 6.1.0 # 卸载指定版本swift

更多可以前往:www.swift.org/swiftly/doc…

2. Static Linux SDK 安装

工具链必须与您安装的 Static Linux SDK 的版本匹配。静态 Linux SDK 在其文件名中包含相应的 Swift 版本,以帮助识别 SDK 的正确版本。

可以在 www.swift.org/install/mac…,找到相应资源。

$ xattr -d -r -s com.apple.quarantine "swift-6.1-RELEASE_static-linux-0.0.1.artifactbundle.tar.gz"
$ swift sdk install swift-6.1-RELEASE_static-linux-0.0.1.artifactbundle.tar.gz
$ swift sdk list #获取已安装 SDK 的列表
$ swift sdk remove <name-of-SDK>  # 删除指定sdk

官方的安装指南:www.swift.org/documentati…

构建可执行包

  1. 首先,我们创建一个vapor 项目:

    vapor new hello -n 
    
  2. 在本地建立和运行

    $ swift build
    $ .build/debug/hello
    

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

    $ swift build --swift-sdk x86_64-swift-linux-musl # debug 包
    $ swift build --swift-sdk x86_64-swift-linux-musl -c release # release 包
    $ file .build/x86_64-swift-linux-musl/debug/hello
    

其他

  1. 为了能在服务器端直接访问到项目,需要在项目配置下绑定0.0.0.0和端口。

    app.http.server.configuration.hostname = "0.0.0.0"
    app.http.server.configuration.port = 8188
    
  2. 打包完成后,会有两个linux 文件:x86_64-unknown-linux-gnux86_64-swift-linux-musl,怎么选?

    完全正确!你说的这句话抓住了重点:

    “Static Linux SDK 不使用 Glibc;相反,它是建立在顶部的 Linux 的替代 C 库称为 musl。”

    下面我帮你用一张清晰的表格总结一下相关概念,帮助你彻底理解它的意义、背景、优劣势以及使用场景:

    Static Linux SDKglibc vs musl 对比

    项目Static Linux SDK(musl)普通 Linux SDK(glibc)
    C 标准库muslglibc
    是否静态链接✅ 默认静态链接❌ 通常动态链接
    可执行文件是否自包含✅ 是,拷贝即用,无需依赖❌ 否,运行时需系统已安装 glibc
    兼容性更便携,适用于 Alpine、Docker、容器环境等只能在支持 glibc 的系统上运行
    文件体积相对小(单文件)相对大(动态链接多个库)
    适合什么场景?跨平台分发、容器、CI/CD、嵌入式正常部署到 Ubuntu/Debian/CentOS
    构建结果目录名x86_64-swift-linux-muslx86_64-unknown-linux-gnu

    📦 举个例子

    假设你用 swiftly build 编译出来两个版本:

    .build/x86_64-swift-linux-musl/MyApp   # ✅ 可直接部署,无需系统安装 Swift 或 glibc
    .build/x86_64-unknown-linux-gnu/MyApp  # ❌ 如果系统缺 glibc 可能运行失败
    

    你把 .build/x86_64-swift-linux-musl/MyApp 放到任何一个 Linux(甚至很老的)系统里,大概率都能跑;它几乎不依赖任何系统的运行库。

    Static Linux SDK = Swift + musl + 静态链接 → 最轻便、最可移植的 Linux 可执行文件构建方案。

    所以如果你在做跨平台 CLI 工具、容器部署、内网发布,推荐始终优先使用 musl 构建的版本

toolchain 与 static linux sdk 版本不匹配

这个是我在重启电脑重新运行构建脚本的时候发生:

swift build --swift-sdk x86_64-swift-linux-musl -c release

后面再多次阅读文档:

获得结论:当你运行 swift 时,你仍然会使用 Xcode 的 Swift 版本。你需要使用 xcrun 运行安装的特定工具链

xcrun --toolchain swift swift --version

综合命令,可以将打包命令综合

xcrun --toolchain swift swift build --swift-sdk x86_64-swift-linux-musl -c release

这才是正确的打包命令。

关注公众号:OldBird,获取最新文章推送。