首页 / Swift 编程语言教程 / 包的依赖管理

Swift 编程语言教程

包的依赖管理

本教程共 93 篇 · 第 89 篇 · 更新于 2026-08-08 · 约 7 分钟阅读

SwiftSwift 编程语言教程Swift Package Manager依赖管理Package.swift版本区间依赖解析

本节目标:能在 Package.swift 里添加外部依赖,说清各种版本区间写法的区别,并把依赖正确链接到自己的 target。

上一章我们把包搭起来了,但 dependencies 还是空的。真实开发里,你几乎一定会用到别人写好的包。这一章就讲怎么声明依赖、版本号怎么写、以及 target 怎么”接上”这些依赖。

1-1 依赖长什么样

依赖写在 Package.swift 顶层 Packagedependencies 数组里。每一项用 .package(...) 描述从哪来、要哪个版本。

// swift-tools-version:6.0
import PackageDescription

let package = Package(
    name: "hello-pkg",
    products: [
        .executable(name: "hello-pkg", targets: ["hello-pkg"]),
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.0"),
    ],
    targets: [
        .executableTarget(
            name: "hello-pkg",
            dependencies: [
                .product(name: "ArgumentParser", package: "swift-argument-parser")
            ]
        )
    ]
)

注意一个容易混淆的点:顶层 dependencies 说的是”我要用这个包”,而 target 自己的 dependencies 说的是”这个 target 具体用到包的哪个 product”。两处都得写,缺一不可。

1-2 用 URL 指定来源

.package(url:from:) 里的 url 是包仓库地址。最常见的是 GitHub 的 https 链接。SPM 支持 Git 仓库,所以只要是能被 git clone 的地址都行,包括公司内网的私有仓库。

Note

SPM 拉取的是 Git 仓库的某个版本(tag 或分支)。它不会去下载 zip 包,而是走 Git 协议,所以本地通常需要配置好 git 凭据。

1-3 版本区间:from 的语义

from: "1.2.0" 是最常用的写法。它的意思是”至少 1.2.0,但只升到下一个大版本之前”。具体说,它允许 1.2.0、1.3.0、1.9.9……但不允许 2.0.0。

这套规则叫”向上兼容到下一个 major”。因为语义化版本(SemVer)约定:大版本号变了就意味着可能不兼容。所以 from 默认停在 1.x 内,既让你拿到新功能,又避免被破坏性更新坑到。

1-4 更精细的版本控制

除了 from,还有几种写法,按需要挑。

// 精确到某个版本
.package(url: "...", exact: "1.2.3")

// 指定一个闭合/半开区间
.package(url: "...", "1.2.0"..<"2.0.0")
.package(url: "...", "1.2.0"..."1.5.0")

// 跟随某个分支(不推荐用于正式发布)
.package(url: "...", branch: "main")

// 锁定到某次提交
.package(url: "...", revision: "a1b2c3d")

exact 最死板,只有这一个版本能用,适合你对兼容性极度敏感时。..<... 是标准区间写法,左闭右开和闭合区间,你能精确框死范围。branchrevision 适合依赖还在疯狂迭代、没发正式版本的情况,但正式项目里尽量少用,因为它不稳定、别人也难复现。

Warning

branch: "main" 看似省事,可 main 随时在变,今天能编明天可能就挂。正式依赖优先用带 tag 的版本区间。

1-5 依赖冲突与版本解析

当你依赖 A、A 又依赖 B,而你直接也依赖 B,SPM 会做”版本解析”:找出一个能让所有要求都满足的 B 版本。如果找不到,就会报冲突错误。

解析结果会被记进 Package.resolved 文件。它锁定了每个依赖实际用到的版本,保证你和队友、和你三个月后跑出来的结果一致。

$ swift package resolve   # 按 Package.swift 重新解析并下载
$ swift package update    # 在允许区间内升级到最新
Tip

Package.resolved 提交进版本控制(git)。它能让团队所有人、CI 机器拿到一模一样的依赖版本,避免”在我电脑上能跑”的尴尬。

1-6 在 target 里链接 product

顶层声明了依赖,还得在 target 里”点名”要用哪个 product。看回开头的例子:

.executableTarget(
    name: "hello-pkg",
    dependencies: [
        .product(name: "ArgumentParser", package: "swift-argument-parser")
    ]
)

name 是那个包对外暴露的 product 名(通常和库名一致),package 是顶层 .package 里写的包名(仓库对应的名字)。两者不是一回事,初学者常写反。

一旦链接好,源码里就能 import ArgumentParser 了。

1-7 依赖一个本地包

除了远程仓库,你也能依赖本机另一个目录里的包,常用于把大项目拆成多个子包、在本地联调。

.package(path: "../MyLocalLib")

path 指向本地文件夹。注意它不走版本区间——本地包就是当前磁盘上的样子。等子包发布到远程后,再把 path 换成 url 即可。

1-8 平台与 Swift 版本限制

有时某个依赖只在特定平台或 Swift 版本可用。你可以在清单里加条件,让解析更聪明。

.product(
    name: "ArgumentParser",
    package: "swift-argument-parser",
    condition: .when(platforms: [.macOS])
)

这表示这个 product 仅在 macOS 平台链接。SPM 会据此跳过不兼容平台的依赖,避免构建报错。

1-9 语义化版本到底怎么读

理解依赖区间前,得先懂版本号 主.次.补(如 1.2.3)的含义。第一位大版本变了,意味着可能不兼容旧用法;第二位小版本是向后兼容的新功能;第三位补丁只修 bug、绝不改行为。

SPM 的 from 规则正是建立在这套约定上:它敢自动升到 1.9.9,是因为按约定小版本不会破坏你;它停在 2.0.0 之前,是因为大版本可能不兼容。你发布自己的包时,也该遵守这套约定,别在补丁版本里偷偷删 API。

1-10 传递依赖与版本打架

你的包依赖 A,A 又依赖 B,B 还依赖 C——这一串叫传递依赖。你通常只在顶层写 A,B/C 由 SPM 顺着 A 的清单自动拉。你不用(也不该)手动把每一层都写进自己的 dependencies

麻烦出在版本冲突:假设你的包要 B 的 1.0.0,而 A 要 B 的 2.0.0,且这俩不兼容,SPM 解析失败,报 “conflicting dependencies”。解法通常是:升级你这边的要求到兼容版本,或者联系 A 的作者放宽它对 B 的限制。这种”依赖地狱”在大型项目里常见,所以尽量别把版本写死成 exact

1-11 Package.resolved 长什么样

resolve 之后生成的 Package.resolved 里,记录了每个依赖最终锁定的 commit 或版本号。它本质是个 JSON,列出仓库地址、解析到的版本、对应的 Git 哈希。

提交它进 git 的意义在于可复现:队友 git pullswift build,拿到的依赖版本和你完全一致,不会因为”上游刚好发了新版本”而表现不同。CI 机器同理。想故意升级,再跑 swift package update,它会按区间挑最新并刷新 resolved 文件。

1-12 选版本的实用建议

给正式项目加依赖,优先 .package(url:from:) 让补丁和小版本自动进。只有当你明确知道某 API 在 1.3 才出现、又在 2.0 被改,才用 ..< 收窄到 1.3.0..<2.0.0exact 留给那些你对兼容性零容忍、且上游确实稳定的极少数场景。branchrevision 当作应急通道,进了正式代码就尽快换成带 tag 的版本。

1-13 小结

依赖管理的核心就三处:顶层 dependencies.package 按 URL 和版本区间声明;版本区间里 from 最常用、exact/..< 更精确、branch/revision 应急用;target 里用 .product 把需要的库接进来。记得提交 Package.resolved 锁定版本。下两章我们讲怎么给包加测试和做基础调试。

Tip

加完依赖后第一次 swift build 若卡很久,多半是在下载并编译依赖本身(尤其像 swift-argument-parser 这类还带 transitive 依赖的)。耐心等一次,后续有缓存就快了。