以前、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: ''
GitHubでリリースを作成
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の書き換えがややこしくて思ったより大変だった。