以前、XCFrameworkをバイナリターゲットとしてSwift Packageに埋め込んだライブラリを作った。
これを容易に利用するためには、ビルド済みのXCFrameworkをどこかにアップロードして、それを参照するSwift Packageを公開するとよい。XCFrameworkはGitHubリリースに成果物として置いておくことにする。
ということで、前回は概ねこれを手作業でやったのだけど、手作業したくないのでGitHub Actionsにした。
リリースが自動化されているソフトウェアは信頼性が高い。
GitHub Actionsでバイナリターゲットを含むSwift Packageをリリース
自動化にあたって手順を書き出してみると、次のようになる。ややこしい。
- XCFrameworkをビルドしてアーカイブする
- XCFrameworkのチェックサムを計算
- バージョン番号を決める
- Package.swiftを書き換える
- Package.swiftをコミット
- タグを打つ
- GitHubでリリースを作成
実際のActionはこうなる。
順を追ってやっていく。
XCFrameworkをビルドしてアーカイブする
これは普通にやったらいい。今回はgomobileを使っているのでちょっと変な感じではある。Makefileに書いてある。
.PHONY: install-tools
install-tools:
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
.PHONY: xcframework zip
xcframework: install-tools
gomobile bind -target=ios,iossimulator,macos,maccatalyst -iosversion 14 -prefix GOJQ -o Frameworks/GOJQBinding.xcframework github.com/cockscomb/swift-gojq/binding
zip: xcframework
zip -X -r Frameworks/GOJQBinding.xcframework.zip Frameworks/GOJQBinding.xcframework/
XCFrameworkのチェックサムを計算
Swift Packageのバイナリターゲットにはチェックサムが必要なので計算する。swift package compute-checksum
するだけ。
- name: Compute checksum
id: checksum
run: |
echo "checksum=$(swift package compute-checksum Frameworks/GOJQBinding.xcframework.zip)" >> $GITHUB_OUTPUT
バージョン番号を決める
mathieudutour/github-tag-action を使った。major
とか minor
とか patch
とかで一つ前のバージョン番号から決めることにして、workflow_dispatch
の input
で選べる感じにしている。
on:
workflow_dispatch:
inputs:
bump:
description: 'Bump version'
required: true
default: 'patch'
type: choice
options:
- major
- minor
- patch
- name: Calculate next version
id: next_version
uses: mathieudutour/github-tag-action@v6.1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
default_bump: ${{ github.event.inputs.bump }}
tag_prefix: ''
dry_run: true
バージョンを先に決めておかないとリリースのURLが決まらないので、先にバージョンを決める。dry_run
があって便利。
Package.swiftを書き換える
ここがいちばんトリッキー。リリースのURLとチェックサムを Package.swift
に書き込まないといけない。Package.swift
のテンプレートを用意してレンダリングするか、すでにある Package.swift
を書き換えるかだが、テンプレートを管理するのが面倒なので後者を選ぶ。
丁寧にSwift Package Pluginを作ってある。Swift Syntaxで Package.swift
を書き換える。
Swift SyntaxによるPackage.swiftの書き換え
Swift Syntaxでは、SyntaxRewriter
を継承して適当な visit(_:)
メソッドを実装すると、ASTを辿って必要な箇所を書き換えられる。Sources/swift-package-checksum-rewriter/BinaryTargetSourceRewriter.swift として実装している。書き換え前後で余計な差分が出にくいように Trivia
というやつで改行や空白を丁寧に調整している。(それもあってSwiftSyntaxBuilderがあまり思うように使えていない。)
これを executableTarget
にしておいて、Swift Package Pluginからは実行ファイルとして呼び出す。だからプラグインとしての実装は次の通りで、ただのラッパーになった。
import Foundation
import PackagePlugin
@main
struct RewriteChecksumCommandPlugin: CommandPlugin {
func performCommand(context: PluginContext, arguments: [String]) async throws {
let tool = try context.tool(named: "swift-package-checksum-rewriter")
let process = try Process.run(URL(fileURLWithPath: tool.path.string), arguments: arguments)
process.waitUntilExit()
}
}
使い方
これを使うには Package.swift
に依存を追加する。
dependencies: [
.package(url: "https://github.com/cockscomb/swift-package-checksum-rewriter", from: "0.1.0"),
],
あとは swift package
コマンドから呼び出すだけ。--allow-writing-to-package-directory
するのがコツ。
- name: Rewrite Package.swift
run: |
swift package \
--allow-writing-to-package-directory \
rewrite-package-binary-target \
--url=https://github.com/cockscomb/swift-gojq/releases/download/${{ steps.next_version.outputs.new_tag }}/GOJQBinding.xcframework.zip \
--checksum=${{ steps.checksum.outputs.checksum }} \
Package.swift \
GOJQBinding
ここが一番面倒だった。
Package.swiftをコミット
ここはどうやってもいい。今回は stefanzweifel/git-auto-commit-action を使った。簡単。
- uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: Bump version to ${{ steps.next_version.outputs.new_tag }}
タグを打つ
さっきは dry_run
したけど今回が本番。
- name: Bump version
id: bump_version
uses: mathieudutour/github-tag-action@v6.1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
default_bump: ${{ github.event.inputs.bump }}
tag_prefix: ''
actions/upload-release-asset がアーカイブされているので、案内に従って softprops/action-gh-release を使う。これも簡単。
- name: Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.next_version.outputs.new_tag }}
body: ${{ steps.next_version.outputs.changelog }}
files: Frameworks/GOJQBinding.xcframework.zip
完成
結構ややこしい感じだけど、これでバイナリターゲットを含むSwift Packageを自動的にリリースできるようになった。
ちょっとやったらできるだろうと思って作業し始めたけど、Package.swiftの書き換えがややこしくて思ったより大変だった。