Swift 包交叉编译到 Linux(Swift Package to Linux with SPM)
场景
MSP(Model Shell Protocol)仓库是 SwiftPM 包,主要面向 iOS,但核心逻辑纯 Swift、平台无关。目标:在 WSL Ubuntu 里把它编译成 Linux 原生可执行二进制(msp-shell,一个沙箱 shell REPL),用于后续接 LLM 的 agent 运行时。
为什么「iOS SDK」能直接在 Linux 编译
- 核心模块(虚拟文件系统、shell 解析器、命令运行时)0% 依赖 Apple,纯 Swift。
- 名字带 Apple 的
MSPApple底层只用Foundation.FileManager,Linux 可用。 - 少量 Apple 独占 API(
CryptoKit哈希、部分Foundation/URLSession细节)用#if os()/#if canImport(CryptoKit)守卫 + 纯 Swift 替代实现,同一份源码 iOS 和 Linux 都能编。 - PTY 的 C 包装在 Linux 编译为空实现(返回
ENOTSUP),不影响。 - 编译入口:
swift build --product <name> --disable-sandbox(--disable-sandbox在 /mnt 挂载的 WSL 上必需)。
关键坑(都踩过、已解决)
1. swift-cgit2 1.1.1 上游只有 Apple xcframework,Linux 编不了
上游 Package.swift 无条件声明 OpenSSL/libgit2/libssh2 三个 binaryTarget(Apple 二进制),Linux 无法使用。
解法:用仓库内自带的最小 Linux shim 包替代——Cgit2 target 的 umbrella header 只写 #include <git2.h>,linkerSettings 链系统 git2/ssh2/ssl/crypto/iconv/z(需要 libgit2-dev libssh2-1-dev libssl-dev zlib1g-dev)。构建脚本用 sed 把主包 Package.swift 的依赖 URL 临时替换成 shim 路径,结束 trap 还原。
2. SPM 路径依赖的「包身份」= 目录名,不是 manifest 里的 name
.package(path: "...") 后,包身份由目录名决定。若目录叫 vendor-cgit2-linux,target 里 .product(name: "Cgit2", package: "swift-cgit2") 会报 unknown package 'swift-cgit2'。
解法:目录名就叫 swift-cgit2,与 .product(package:) 引用一致。
3. 路径依赖的包必须显式声明 products
只写 targets: 不够,.product(name: "Cgit2", package: ...) 找不到产品。必须加 products: [.library(name: "Cgit2", targets: ["Cgit2"])]。
4. Swift 类型检查器对长位运算表达式「超时」
w[i] = (UInt64(b[0]) << 56) | (UInt64(b[1]) << 48) | ... 8 项 以及 [a,b,c,d].flatMap { word in [...] } 会报 unable to type-check this expression in reasonable time(debug + 慢文件系统更易触发)。
解法:拆成循环逐字节拼装 word = (word << 8) | UInt64(block[j]);数组字节输出改 for 循环逐 append。
5. fputs/fflush 要 C 的 stderr(UnsafeMutablePointer<FILE>),不是 FileHandle.standardError
CLI 里打印提示符/横幅用 stderr(import Glibc 后直接可用),命令输出用 FileHandle.standardOutput/Error.write。
6. REPL 提示符要按 isatty(STDIN_FILENO) 门控
管道模式(echo 'pwd' | msp-shell)下 stdin 非 TTY,不该打印 banner/msp$ 提示符,否则污染 stdout。用 Glibc.isatty(0) 判断,交互才显示。
7. 纯 Swift 哈希接口要跟 CryptoKit 调用点对齐
CryptoKit 的 SHA256.update(data:) 是带标签的;已有纯 Swift 的 MSPSHA224.update(_:) 是不带标签的。补丁里两种调用点混用,新实现必须按各调用点签名提供 update(data:) 或 update(_:)。
构建步骤(WSL Ubuntu)
sudo apt install -y libgit2-dev libssh2-1-dev libssl-dev zlib1g-dev # 系统库
bash Tools/msp-shell/build-linux.sh # sed 换依赖 + swift build -c release + 冒烟 + 还原产物:.build/x86_64-pc-linux-gnu/release/<product>。
验证方法
msp-shell 'echo hi'单命令;echo 'pwd' | msp-shell管道;交互msp$+exit()。- 沙箱隔离:
cat /etc/hostname应失败(命令跑在「以 workspace 为根的虚拟文件系统」)。 - digest 正确性:与系统
md5sum/sha256sum/sha384sum交叉比对同一文件。
相关
- 仓库内实现:
Tools/msp-shell/(构建脚本 + cgit2 shim + README)、Implementations/Swift/Sources/MSPFakeShellCLI/。 - 概念关联:MSP (Model Shell Protocol) 沙箱 Shell
本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/Programming