Highlight
Xcode 14 引入 Swift Package Plugins,让开发者可以用 Swift 脚本对 Swift Package 或 Xcode 项目执行自定义操作,包括命令插件(Command Plugins)用于格式化、生成贡献者列表等开发工作流任务,以及构建工具插件(Build Tool Plugins)用于在编译时生成源代码和资源。
核心内容
开发过程中总有一些重复性任务:代码格式化、根据数据文件生成代码、更新版权声明、统计代码指标。以前这些通常用 shell 脚本或 Makefile 解决,散落在项目各处,维护困难。
Swift Package Plugins 把这些任务纳入了 Swift Package 的体系。插件本身就是 Swift 包,可以依赖其他工具包,通过 Xcode 或命令行调用,运行在沙箱中。
Xcode 14 支持两种插件:
命令插件(Command Plugin):随时运行的自定义操作。比如调用 SwiftFormat 格式化代码,或根据 Git 历史生成贡献者列表。需要写文件系统的插件会请求用户授权。
构建工具插件(Build Tool Plugin):扩展编译系统的依赖图。在编译时生成源代码或资源,只在输入变化时重新运行,支持增量编译。
在 Xcode 中使用命令插件
假设你有一个几何图形 iOS App,由 App 项目和一个本地 Package 组成。你想把 Package 拆成独立仓库,需要生成一个贡献者列表文件。
你可以在 Package 的 manifest 中添加一个依赖:
// Package.swift
dependencies: [
.package(url: "https://github.com/example/SourceCodeUtilities", from: "1.0.0")
]
Xcode 获取依赖后,右键点击 Package,菜单中会出现插件提供的命令。比如:
- “Format with SwiftFormat” — 格式化代码
- “Generate Contributors” — 根据 Git 历史生成贡献者列表
- “Update Copyright Dates” — 更新版权声明年份
选择命令后,如果插件需要修改文件,Xcode 会弹出权限确认。你可以查看插件源码确认安全后授权执行。
命令行调用
插件也可以在终端中使用(08:49):
cd CoreLibs
swift package plugin --list
swift package generate-contributors --allow-writing-to-package-directory
swift package generate-contributors --verbose
--allow-writing-to-package-directory 用于 CI 环境,跳过交互式确认。
详细内容
插件的工作原理
插件是 Swift 脚本,在需要时编译并作为独立进程运行(05:56)。
插件获得的信息:
- Package 的精简表示,包括源文件路径
- Package 的依赖信息
- 构建输出目录的写入权限
插件运行在沙箱中:
- 禁止网络访问
- 只允许写入构建输出目录
- 命令插件可请求写入 Package 源码目录的权限
插件可以:
- 调用命令行工具
- 创建文件和目录
- 向 Xcode 发送警告和错误
- 构建工具插件可以定义编译命令让 Xcode 执行
命令插件的实现
命令插件的主类型遵循 CommandPlugin 协议(08:33):
import PackagePlugin
@main
struct MyPlugin: CommandPlugin {
/// 操作 Swift Package 时的入口点
func performCommand(context: PluginContext, arguments: [String]) throws {
debugPrint(context)
}
}
#if canImport(XcodeProjectPlugin)
import XcodeProjectPlugin
extension MyPlugin: XcodeCommandPlugin {
/// 操作 Xcode 项目时的入口点
func performCommand(context: XcodePluginContext, arguments: [String]) throws {
debugPrint(context)
}
}
#endif
关键点:
@main标记入口点PluginContext提供 Package 信息和工具路径- 通过条件编译同时支持 Swift Package 和 Xcode 项目
arguments接收用户传入的自定义参数
构建工具插件的实现
构建工具插件遵循 BuildToolPlugin 协议(11:13):
import PackagePlugin
@main
struct MyPlugin: BuildToolPlugin {
/// 操作 Swift Package 时的入口点
func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
debugPrint(context)
return []
}
}
#if canImport(XcodeProjectPlugin)
import XcodeProjectPlugin
extension MyPlugin: XcodeBuildToolPlugin {
/// 操作 Xcode 项目时的入口点
func createBuildCommands(context: XcodePluginContext, target: XcodeTarget) throws -> [Command] {
debugPrint(context)
return []
}
}
#endif
关键点:
- 返回
[Command]告诉 Xcode 要执行哪些构建命令 - 不直接执行操作,而是生成命令让 Xcode 在适当时候运行
- 支持增量编译:Xcode 根据输入输出文件时间戳决定是否需要重新运行
两种构建命令
普通构建命令(BuildCommand):指定输入和输出路径,只在输出缺失或输入变化时运行(11:40)。
预构建命令(PrebuildCommand):在编译开始前运行,用于输出文件名事先未知的情况。每次构建都会运行,所以要尽量减少重复工作。
在 Package 中声明插件依赖
SwiftPM 5.6+ 在 target 定义中增加了 plugins 参数(12:14):
// Package.swift
targets: [
.target(
name: "CoreLibs",
dependencies: [],
plugins: [
.plugin(name: "SourceGenerator", package: "SourceCodeUtilities")
]
)
]
添加后,Xcode 会在编译对应 target 时自动调用插件。生成的代码存放在构建目录中,不需要提交到版本控制。
实际案例:代码生成
假设你有一些数据文件,需要根据它们生成类型安全的 Swift 访问器。以前的做法是:
- 手动运行代码生成工具
- 把生成的代码提交到仓库
- 数据文件变化时重复上述步骤
用构建工具插件后:
- 在 Package 中添加插件依赖
- 在 target 的
plugins参数中引用插件 - 删除仓库中的生成代码
- Xcode 在编译时自动调用代码生成工具
这样生成的代码始终与数据文件保持同步,仓库也更干净。
核心启发
1. 自动代码格式化工作流
做什么:为团队配置 SwiftFormat 命令插件,在提交前自动格式化代码。
为什么值得做:插件可以集成到 Git 的 pre-commit hook 中,确保团队代码风格一致,不再需要人工审查格式问题。
怎么开始:添加 SwiftFormat 插件依赖,写一个包装插件调用 swift package plugin --allow-writing-to-package-directory format。
2. 从 API 定义生成客户端代码
做什么:写一个构建工具插件,根据 OpenAPI/Swagger 定义文件自动生成 Swift 网络请求代码。
为什么值得做:API 变化时自动重新生成客户端代码,避免手写请求代码与后端不同步的问题。
怎么开始:用 PrebuildCommand 监听 .yaml 文件变化,调用代码生成工具输出 Swift 文件到构建目录。
3. 本地化资源生成器
做什么:根据 Excel/CSV 翻译表自动生成 Localizable.strings 和类型安全的 Swift 字符串枚举。
为什么值得做:翻译人员修改表格后,编译时自动更新所有平台的本地化文件,避免漏翻译或键名拼写错误。
怎么开始:解析 CSV 文件,为每种语言生成 .strings 文件,同时生成 Swift 枚举提供编译期键名检查。
4. 图标和颜色资源生成
做什么:从 Figma/Sketch 导出的 SVG 文件自动生成 Xcode Asset Catalog 和 SwiftUI Image/Color 扩展。
为什么值得做:设计师更新图标后,开发者只需重新编译就能获得最新资源,不需要手动拖拽文件。
怎么开始:用 BuildCommand 监听 SVG 文件变化,生成 @2x/@3x PNG 和对应的 Swift 代码。
5. 测试覆盖率报告插件
做什么:写一个命令插件,运行测试后生成 HTML 覆盖率报告,并高亮未覆盖的代码区域。
为什么值得做:Xcode 的覆盖率视图功能有限,自定义报告可以按模块、按文件统计,更适合团队汇报。
怎么开始:调用 xcodebuild test 获取 .xcresult,解析覆盖率数据,用 Swift 生成 HTML 报告。
关联 Session
- Create Swift Package plugins — 深入学习如何从零创建自定义 Swift Package 插件
- Swift Package Manager — Swift Package Manager 5.6+ 的新特性和 manifest 语法
- Xcode — Xcode 14 的新功能,包括与 Package Plugins 的集成
评论
GitHub Issues · utterances