最近在 vibe coding 一款 macOS App,名叫 “Hassan”,具体做什么的暂且按下不表。项目在从 Xcode 16.4 升级到 26.1 后遇到了一个编译错误,花费几个小时研究明白后决定水一篇文章,整理一下这几个小时的收获。

问题描述


开发环境

先交代一些我的开发环境:

  • macOS Tahoe 26.1 (25B78)
  • Xcode 26.1 (17B55)
  • Tuist 4.90.0
  • swift-collections 1.3.0

问题表现

Hassan 使用了 Swift Collections 1.3.0。其在 Xcode 16.4 (16F6) 下编译一切正常,但切换到 Xcode 26.1 后,编译失败并报错:

/Users/rakuyo/Hassan/Tuist/.build/checkouts/swift-collections/Sources/InternalCollectionsUtilities/autogenerated/LifetimeOverride.swift:104:2 '@_lifetime' attribute is only valid when experimental feature Lifetimes is enabled
/Users/rakuyo/Hassan/Tuist/.build/checkouts/swift-collections/Sources/InternalCollectionsUtilities/autogenerated/LifetimeOverride.swift:107:6 A function cannot return a ~Escapable result

问题分析


根本原因

看了一下代码,发现报错的地方用了 #if compiler(>=6.2) 做条件编译:

  • Xcode 16.4 的编译器版本小于 6.2,所以这部分代码根本不会编译
  • Xcode 26.1 的编译器达到了 6.2,就触发了这部分代码的编译

swift-collections 的 Package.swift 文件

翻了一下 swift-collections 的源码,返现它有两个 Package.swift 文件:

  1. 默认的 Package.swift
  2. Swift 6.0 版本使用的 Package@swift-6.0.swift

关键的区别在于:

  • Package.swift 文件中包含了 .enableExperimentalFeature("Lifetimes") 配置
  • Package@swift-6.0.swift 文件中没有这个配置

关于 @_lifetime 特性

@_lifetime 是 Swift 6.2 引入的实验性特性,用于处理生命周期依赖关系。虽然 6.2 里已经有了,但它还不是正式的语言特性,所以需要显式开启。

官方论坛里有讨论:Experimental support for lifetime dependencies in Swift 6.2 and beyond

解决问题


最初的思路

一开始,我认为问题出在 SPM 或 Tuist 错误地选择了 Package@swift-6.0.swift 文件。因为我的项目设置的是 Swift 5.9,Build Settings 里也是 5.9,我认为应该使用默认的 Package.swift(包含了 Lifetimes 特性的启用),这样就不会有编译错误。

深入调查

但是继续往下查,发现这事儿比想的复杂:

SPM 使用工具链的 Swift 版本

SPM 在下载依赖时,使用的是 工具链 的 Swift 版本,也就是 swift --version 的版本,而不是项目中 Build Settings 配置的 Swift 版本,它只影响项目的编译,和 SPM 用哪个 Package.swift 无关。

macOS 的 Swift 工具链限制

macOS 上没法抛开 Xcode 单独装或配置 Swift 工具链。Swift 工具链是跟 Xcode 绑定的。

macOS Tahoe 与 Xcode 版本的限制

  • macOS Tahoe 无法使用 Xcode 15
  • Xcode 16.14 就已经是 Swift 6.0+ 的版本
  • 如果想在 Swift 5.x 的基础上使用 SPM,就必须使用 Xcode 15.0,但这在 macOS Tahoe 上是不可能的

swift-tools-version 的实际作用

项目的 Package.swift 文件顶部的 // swift-tools-version: 5.9 并不能决定用哪个 Package.swift 文件。这个声明只是指定了解析 Package.swift 所需的最低 SPM 工具版本。

那么单独安装 Swift 5.10.1 toolchain 行不行?

既然 Xcode 自带的工具链是 Swift 6.0+,那能不能单独装个 Swift 5.10.1 的 toolchain?

使用 swiftly 安装 toolchain

swiftly 是 Swift 官方的工具链管理工具,可以装不同版本的 Swift toolchain。

试了一下,用 Swift 5.10.1 toolchain 跑 SPM 下载依赖,结果又报错了:

Computing version for https://github.com/devxoul/Then.git
error: Invalid manifest (compiled with: ["/Users/rakuyo/Library/Developer/Toolchains/swift-5.10.1-RELEASE.xctoolchain/usr/bin/swiftc", "-vfsoverlay", "/var/folders/56/hxctgzds50b8p0fw6myh2y500000gn/T/TemporaryDirectory.qkPC2f/vfs.yaml", "-L", "/Users/rakuyo/Library/Developer/Toolchains/swift-5.10.1-RELEASE.xctoolchain/usr/lib/swift/pm/ManifestAPI", "-lPackageDescription", "-Xlinker", "-rpath", "-Xlinker", "/Users/rakuyo/Library/Developer/Toolchains/swift-5.10.1-RELEASE.xctoolchain/usr/lib/swift/pm/ManifestAPI", "-target", "arm64-apple-macosx12.0", "-sdk", "/Applications/Xcode-26.1.0.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.1.sdk", "-F", "/Applications/Xcode-26.1.0.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks", "-I", "/Applications/Xcode-26.1.0.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/lib", "-L", "/Applications/Xcode-26.1.0.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/lib", "-swift-version", "5", "-I", "/Users/rakuyo/Library/Developer/Toolchains/swift-5.10.1-RELEASE.xctoolchain/usr/lib/swift/pm/ManifestAPI", "-sdk", "/Applications/Xcode-26.1.0.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.1.sdk", "-package-description-version", "5.0.0", "/Package.swift", "-Xfrontend", "-disable-implicit-concurrency-module-import", "-Xfrontend", "-disable-implicit-string-processing-module-import", "-o", "/var/folders/56/hxctgzds50b8p0fw6myh2y500000gn/T/TemporaryDirectory.EKOhFy/then-manifest"])
error: fatalError
error: compile command failed due to signal 6 (use -v to see invocation)
/Applications/Xcode-26.1.0.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.1.sdk/System/Library/Frameworks/Foundation.framework/Modules/Foundation.swiftmodule/arm64e-apple-macos.swiftinterface:1114:149: error: expected ',' separator
  public mutating func transform<E>(updating range: inout Swift.Range<Foundation.AttributedString.Index>, body: (inout Foundation.AttributedString) throws(E) -> Swift.Void) throws(E) where E : Swift.Error

看报错信息就知道了,虽然用了 Swift 5.10.1 的编译器,但 SPM 还是在链接 Xcode 26.1 的 SDK。Swift 5.10.1 的编译器看不懂 macOS 26.1 SDK 里的新语法(比如 throws(E) 这种类型化抛出),所以编译失败。

结论

单独装 Swift 5.10.1 toolchain 也不行:

  • SPM 虽然用了旧版本的 Swift 编译器
  • 但它还是链接到了 Xcode 26.1 的 SDK
  • 旧版本编译器处理不了新版本 SDK 的语法
  • Swift toolchain 和 Xcode SDK 之间有版本兼容性问题

官方 Issues 的发现

调查的时候翻了下 swift-collections 的 Issues,找到了相关讨论:#546

看起来社区里也有人遇到类似问题,但官方并没有在 Package@swift-6.0.swift 里默认加上 .enableExperimentalFeature("Lifetimes") 配置。

解决方案

折腾了一圈,最后的结论就是:

  1. 只能用 Package@swift-6.0.swift:因为工具链是 Swift 6.0+,SPM 会自动选这个文件
  2. 必须显式启用 Lifetimes 特性:得在项目配置里加上 -enable-experimental-feature Lifetimes 编译参数

具体配置看你用什么构建工具:

使用 Tuist

在 Tuist 的项目配置中,需要为依赖 swift-collections 的 target 添加编译参数:

.target(
    name: "YourTarget",
    // ... 其他配置
    settings: .settings(
        base: [
            "OTHER_SWIFT_FLAGS": "$(inherited) -enable-experimental-feature Lifetimes"
        ]
    )
)

使用 Xcode 项目

在 Build Settings 中,找到 "Other Swift Flags",添加:

-enable-experimental-feature Lifetimes

意外收获:Tuist 的 Swift 版本默认配置

调查的时候还发现了个 Tuist 相关的事情。

Tuist 默认用的是 SWIFT_VERSION = 5.0。想用 Swift 6.0 的话,也得显式指定。

相关信息:

用 Tuist 并且想用 Swift 6.0 的话,得在配置里明确指定:

.target(
    name: "YourTarget",
    // ... 其他配置
    settings: .settings(
        base: [
            "SWIFT_VERSION": "6.0"
        ]
    )
)

总结

这次折腾下来,对 SPM 的工作机制算是有了更深的理解:

  1. SPM 用的是工具链版本:SPM 根据 swift --version 的版本(也就是工具链版本)来选 Package.swift 文件,不是项目配置的 Swift 版本
  2. Package@swift-X.X.swift 的优先级:工具链版本满足条件时,SPM 会优先用带版本后缀的 Package.swift 文件
  3. 实验性特性得显式启用:即使代码里用了实验性特性,Package.swift 里没启用的话,也得在项目级别显式启用
  4. macOS 的工具链限制:macOS 上 Swift 工具链跟 Xcode 是绑定的,不像 Linux 那样能灵活切换
  5. Toolchain 与 SDK 的兼容性:单独装了旧版本 Swift toolchain 也没用,它可能跟新版本 Xcode SDK 不兼容