创建 Swift Package 插件
Create Swift Package plugins
2022年6月6日
一句话判断
Swift Package Plugins 让你用 Swift 代码扩展 Xcode 和 SwiftPM 的构建流程——自定义命令、代码生成、自动化发布任务,这场 Session 通过三个 Demo 展示了从入门到进阶的完整路径。
这场 Session 讲了什么
Boris 主讲的这场 Session 系统性地介绍了 Swift Package Plugins 的创建方式。从基本的自定义命令插件(如从 Git 历史生成 CONTRIBUTORS 文件),到构建工具插件(分为 in-build 和 pre-build 两种类型),再到权限声明和沙盒机制。
核心概念:插件是用 Swift 编写的代码,使用 PackagePlugin API(类似 Package Manifest)。插件运行在沙盒中,网络访问受限,默认只能写临时目录。自定义命令可以选择声明写入 Package 根目录的权限。构建工具插件通过声明输入/输出关系,让构建系统自动判断是否需要重新执行。
值得深挖的点
In-build vs Pre-build 的选择。 In-build command 有明确的输出文件集合,构建系统会比较输入和输出的时间戳,只在输出过期时重新执行。Pre-build command 每次构建都会运行。如果你的工具有明确的输出(如代码生成),用 in-build;如果没有(如 lint 检查),用 pre-build,但要注意缓存策略避免性能问题。
沙盒限制和权限声明。 插件运行在受限环境中,网络访问被禁止。自定义命令可以通过 permission 声明需要写入 Package 根目录的权限,Xcode 会展示 reason string 让用户决定是否授权。这个设计类似 iOS 的权限弹窗,安全性考虑一致。
构建工具插件的三段式结构。 插件定义 executable(要运行的工具)、输入文件(如 .proto 文件)、输出文件(如生成的 .swift 文件)。构建系统根据这些信息自动调度执行时机。
代码片段
// Package.swift 中声明插件目标
// swift-tools-version: 5.6
.package(
name: "MyPackage",
targets: [
.plugin(
name: "GenerateContributors",
capability: .command(
intent: .custom(
verb: "generate-contributors",
description: "从 Git 历史生成 CONTRIBUTORS.txt"
),
permissions: [
.writeToPackageDirectory(
reason: "需要写入 CONTRIBUTORS.txt 文件到 Package 根目录"
)
]
)
)
]
)
// plugin.swift - 自定义命令插件实现
import PackagePlugin
import Foundation
@main
struct GenerateContributors: CommandPlugin {
func performCommand(
context: PluginContext,
arguments: [String]
) async throws {
// 执行 Git 命令获取提交历史
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/git")
process.arguments = ["log", "--format='%aN'", "--sort=name"]
let pipe = Pipe()
process.standardOutput = pipe
try process.run()
process.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8) ?? ""
// 去重并写入文件
let contributors = Set(output.components(separatedBy: "\n"))
.filter { !$0.isEmpty }
.sorted()
let content = contributors.joined(separator: "\n")
try content.write(
toFile: context.package.directoryURL
.appendingPathComponent("CONTRIBUTORS.txt").path,
atomically: true, encoding: .utf8
)
}
}
最佳实践
- Tools version 最低 5.6。 插件功能从这个版本开始支持。
- 谨慎使用 pre-build command。 每次构建都会执行,如果没有缓存机制,会严重拖慢构建速度。
- 为权限声明写清晰的 reason。 用户看到这段文字来决定是否信任你的插件,模糊的描述会导致用户拒绝授权。
- 利用
try process.run()执行外部工具。 插件可以通过 Process API 调用 Git、SwiftFormat 等命令行工具,但要注意工具必须在沙盒限制下正常工作。
还有什么值得关注
- 推荐先看 “Meet Swift Package Plugins” 了解基础概念
- 构建工具插件可以包装 SwiftLint、SwiftFormat、Sourcery 等现有工具
- 插件的输入输出声明帮助 Xcode 的增量构建系统正确判断是否需要重新执行
- 插件源码放在 Package 的
Plugins/目录下,与Sources/和Tests/平级