如何使用 Swift Package Manager(SPM)
开发一个命令行工具,并使用 GitHub Action
自动构建发布到 Homebrew
为了达到上述要求,我们需要做到以下:
- 本地使用
SPM
开发一个命令行工具 CLI (Package) - 配置 github workflow,使用 GitHub Action 配置自动构建发布二进制文件
- 自动发布到 Homebrew
Build a Command-line Tool
这里我们实现一个读取文件内容的命令行工具,功能类似于 linux cat
命令,就叫它 swiftcat
吧。
SPM 工程
创建 SPM 工程:
bash
mkdir swiftcat
cd swiftcat
swift package init --type executable
执行完上述命令后会得到 SPM 工程,工程文件目录如下:
bash
├── Package.swift
└── Sources
└── main.swift
Package.swift
Package.swift
是该 package 的配置文件,可以直接使用 Xcode 打开,运行该 SPM
工程,进行代码编写、调试。
Xcode 如何设置命令行参数进行调试?
Edit Scheme
>Run
>Arguments
>Arguments Passed On Launch
然后添加参数。
默认内容如下:
swift
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "swiftcat",
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.executableTarget(
name: "swiftcat"),
]
)
添加依赖
Package 可以添加依赖,使用别人已经开发好的包,这里我们添加苹果官方的 swift-argument-parser(后面会用到),这样我们的 Package.swift
就更新为如下内容: 一般添加完依赖后,Xcode 会自动解析拉取依赖,如果没有或者因为网络拉取出错,我们可以手动在 Xcode 状态栏,点击 File
> Packages
> Resolve Package Versions
手动触发一下解析。
swift
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "swiftcat",
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0")
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.executableTarget(
name: "swiftcat", dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser")
]),
]
)
main.swift
程序入口,你也可以删除该文件,另建 swift 文件,然后配置 @main 入口,实现 static main()
函数,main.swift
与 @main
只能存在一个。 这里我们删除了 main.swift
,新建了 ReadFile.swift
文件,并且使用上面依赖的swift-argument-parser
直接写命令行实现相关代码。 如下 文件所示:
swift
import Foundation
import ArgumentParser
@main
struct SwiftCat: ParsableCommand {
static var configuration: CommandConfiguration {
let conf = CommandConfiguration(commandName: "swiftcat")
return conf
}
@Argument(help: "文件路径")
var filePath: String
// 读取文件内容
func run() throws {
let fileContents = try String(contentsOfFile: filePath, encoding: .utf8)
print(fileContents)
}
}
filePath
是唯一的参数,传入待读取的文件路径,并且使用@Argument
包装, @Argument
是直接赋值的位置参数,跟在参数最后位置,如果有多个,按声明顺序赋值。其他还有 标记 @Flag
可选@Option
类型参数。
ParsableCommand 内部实现了
static main()
函数,并且调用了run()
函数,我们只需要在 `run()`` 函数内部实现逻辑就行。
ReadFile
run()
函数就是我们这个 Package 核心代码了,代码很简陋,仅仅就是演示一下。
run cli
最后在终端来试一下 ,如果能正常读取文件内容,那么该命令行就算编码完成了。
bash
# 读取文件内容
swift run swiftcat <filepath>
也可以直接在本地尝试编译,或者直接使用 Xcode run
bash
# 命令行编译
swift build -c release
最后在 GitHub 新建仓库,并将此工程代码 push 到该仓库。
GitHub Action
当我们完成代码编写,接下来就是 GitHub workflow 配置了。
生成 yml 配置文件
在 GitHub 仓库主页点击 Actions > New workflow > Swift Configuration ,保存生成的 yml 格式文件,提交到仓库。
或者直接在本地仓库根目录新建一个 ci.yml 文件,保存在 .github/workflows
,push 到远程仓库。
自动构建二进制文件并发布
将上述的 yml
配置文件内容更新为如下
yml
name: "swiftcat CI"
on:
push:
tags:
- "*"
jobs:
build:
runs-on: macos-13
permissions:
contents: write
steps:
- name: Swift version setup
uses: swift-actions/setup-swift@v1
- name: Get Swift version
run: swift --version
- name: Checkout
uses: actions/checkout@v4
- name: Build
run: swift build -c release --arch arm64 --arch x86_64 --product swiftcat
- name: Compress archive
run: tar -czf ${{ github.ref_name }}.tar.gz -C .build/apple/Products/Release swiftcat
- name: Relese
uses: softprops/action-gh-release@v1
with:
files: ${{ github.ref_name }}.tar.gz
上述配置,当我们推送任何 tag 到该仓库时,就会起一个 build
job 进行编译构建,生成一个兼容 arm64 和 x86_64 的二进制文件,然后压缩该二进制文件,发布到仓库的 release 界面。
这个时候我们可以手动打个 tag 0.0.1,推到远程,
build
就会执行,不出意外的话就会发布成功。发布成功后进入到该仓库 Release 界面,找到二进制压缩文件 tar.gz 的下载链接,复制该链接,下文需要用。
Homebrew
The Missing Package Manager for macOS (or Linux)
homewbrew 官方仓库有审核,像我们这种小众的demo式软件,不一定让我们进入他们的官方源, 所以这里我们新建一个自己的仓库。
Homebrew 仓库
-
github 创建 homebrew 仓库,命名格式为
homebrew-<repo>
,仓库名字需要以homebrew-
作为前缀,这里我直接命名为homebrew-tap
-
将仓库添加到本地
bashbrew tap jacinzhang/homebrew-tap
-
切到仓库目录下
bashcd $(brew --repo)/Library/Taps/jacinzhang/homebrew-tap
-
仓库根目录下创建
Formula
文件夹bashmkdir Formula
这里需要创建一个 Formula 文件夹,否则后续的 Github Action 自动上传 Homebrew 会报错找不到目录。
构建 Formula
- 创建一个新的 formula
bash
# 创建一个新的 Formula,链接就是上述构建成功二进制文件链接
brew create --tap jacinzhang/homebrew-tap https://github.com/jacinzhang/swiftcat/releases/download/0.0.1/0.0.1.tar.gz
上述命令会生成一个 swiftcat.rb
文件,并且会自动打开。
使用brew audit --new swiftcat
检验一下该文件,提示我们删掉注释,再修改更新一下内容,得到如下:
swift
class Swiftcat < Formula
desc "CLI"
homepage "https://github.com/jacinzhang/swiftcat"
url "https://github.com/jacinzhang/swiftcat/releases/download/0.0.1/0.0.1.tar.gz"
version "0.0.1"
sha256 "6502c461d6e8547c5566ebd1d5f48745a26f17e06ebde6094efaebbf6fa5fc2d"
license ""
def install
bin.install "swiftcat"
end
test do
system "false"
end
end
- 保存文件,提交到 GitHub 远程仓库
bash
cd $(brew --repo)/Library/Taps/jacinzhang/homebrew-tap
git add --all
git commit -m 'add swiftcat'
git push
- 安装
bash
# 安装
brew tap jacinzhang/homebrew-tap && brew install jacinzhang/homebrew-tap/swiftcat
至此,我们已经能够在任意一台电脑使用 swiftcat 这个命令行工具了。
手动更新 Formula
- 更新新版本信息:
bash
brew edit jacinzhang/homebrew-tap/swiftcat
- 获取新版本的
sha256
bash
brew fetch jacinzhang/homebrew-tap/swiftcat --build-from-sourc
-
更新 swiftcat.rb 中
sha256
值 -
提交到远程仓库
至此我们已可以:
- cli 的自动构建发布
- 手动更新到 homebrew
但是,我们的期望是 swiftcat 有版本更新时,能自动推送到 jacinzhang/homebrew-tap 仓库,然后随时能在本地通过 homebrew 更新。
自动更新 Formula
自动更新的话,我们需要在 swiftcat 这个仓库完成构建发布后,有权限将信息更新到 jacinzhang/homebrew-tap 这个仓库。
为此,我们需要用到 bump-homebrew-formula-action 这个 github action,fastlane 也使用这个工具更新到 homebrew
按照其教程所示,我们需要进行如下:
-
添加到 swiftcat 仓库的 secrets 中,注意是
Repository secrets
中
- 在上述命令行源码仓库的 ci.yml 配置文件中添加 Action
yml
- name: Bump Homebrew formula
uses: mislav/bump-homebrew-formula-action@v3
with:
formula-name: swiftcat
homebrew-tap: jacinzhang/homebrew-tap
download-url: https://github.com/jacinzhang/swiftcat/releases/download/${{ github.ref_name }}/${{ github.ref_name }}.tar.gz
env:
COMMITTER_TOKEN: ${{ secrets.COMMITTER_TOKEN }}
其中 COMMITTER_TOKEN
是步骤2中添加 token 时起的名字。
- 最后提交一下,push 到远程仓库。
最后我们打一个 tag,推到远程,测试一下 build
job 是成功执行了。
如果成功执行,我们就可以直接通过 homebrew 安装、更新 swiftcat
了。
bash
# 更新
brew upgrade swiftcat