artifactbundle の作り方

良さそうな Swift 製のコマンドラインツールをあったとして、それを CI 上でだけで動かすだけなら極力ビルド済みのバイナリで動かしたい(円安ですし)。artifactbundle に対応すればそれが実現できるよというお話。artifactbundle の詳細は SE-0305: Package Manager Binary Target Improvements を読むといいんだけど、自分の理解としても残しておきたい。

arifactbundle とは

artifactbundle はマニフェストファイル( info.json )とアーティファクト(実行可能ファイル)を拡張子 .artifactbundle でまとめたもの。 artifactbundle は Package.swift で以下のように binaryTarget にビルド済みの実行ファイルが含まれた zip ファイルの URL とその checksum を指定して、Swift Package Plugin を通して利用することができる。

.binaryTarget(
  name: "your-tool",
  url: "....",
  checksum: "...."
)

前述した CI 上でのビルドの時間はもちろん、Build Tool Plugin の prebuildCommand だとそもそも実行がプラグイン自体のビルド前に行われるためそこで利用するツールはビルド済みである必要があるのでそういった問題も解決できる。もともと SPM の binaryTarget は XCFramework 形式のみサポートされていたんだけど、SE-0305.arifactbundle を含む zip ファイルへの URL 参照を許可できるようにしたという経緯。zip を解凍した時に .xcframework がなければ .arifactbundle をみてくれるようになっているっぽい。

ということで、さっそく作ってみる。

arifactbundle の作り方

実際にビルド済みバイナリを使える状態にするまでにやることとしてはおおまかに以下の通り。

  1. 利用したい環境向け(macOS, Linux, Windows etc)のビルドを作成する
    • macOSswift build --configuration release --arch arm64 --arch x86_64
    • Linuxswift build --configuration release --static-swift-stdlib
    • Windowsswift build --configuration release -Xswiftc -gnone
  2. ビルドで得たバイナリとマニフェストファイルを .artifactbundle ファイルにまとめる
  3. .artifactbundle をzip形式で圧縮して GitHub のリリースの Assets にアップロードする
  4. Zipアーカイブの Checksum を取得し GitHub のリリース等に併記する

.artifactbundle ファイルは以下のような構造。

<name>.artifactbundle
├ info.json
├ <artifact>
│  ├ <variant>
│  │  ├ <executable>
│  │  └ <other files>
│  └ <variant>
│     ├ <executable>
│     └ <other files>
├ <artifact>
│  └ <variant>
│     ├ <executable>
│     └ <other files>
│ <artifact>
┆  └┄

そこに含まれる info.json というマニフェストファイルには実行可能ファイルのパスと対応するプラットフォームを示す supportedTriples を羅列した情報を含める。以下は macOS のみ対応した場合:

{
    "schemaVersion": "1.0",
    "artifacts": {
        "your-tool": {
            "version": "__VERSION__",
            "type": "executable",
            "variants": [
                {
                    "path": "your-tool/bin/your-tool",
                    "supportedTriples": ["x86_64-apple-macosx", "arm64-apple-macosx"]
                }
            ]
        }
    }
}

この形式の json ファイルをテンプレートファイル( .template )としておき、バージョン情報などを書き換えた上で最終的に info.json として .artifactbundle ファイルに含まれるようにする。

// .artifactbundle の箱を用意
mkdir your-tool.artifactbundle

// LICENSE などバンドルに含めたいファイルはコピーしておくみたいなことはご自由に
cp LICENSE your-tool.artifactbundle

// バージョンを書き換えた上で info.json を  .artifactbundle の中に含める
sed 's/__VERSION__/'"v1.0.0"'/g' artifact-bundle-info.template > "your-tool.artifactbundle/info.json"

そして swift build でできた実行ファイルを .artifactbundle ファイルの中にコピーしていく。追加する場所は先ほどの info.json の中で指定していた path の箇所。ビルドでできた実行ファイルは環境によって生成される場所が違うので注意が必要。以下も macOS に対応する場合:

mkdir -p your-tool.artifactbundle/your-tool/bin
# macOS のビルドは .build/apple/Products/Release 配下
cp .build/apple/Products/Release/your-tool your-tool.artifactbundle/your-tool/bin

ここまでのステップで .artifactbundle ファイルが作成できる。

実際に利用する時は zip ファイルのURLを参照するので、 .artifactbundle ファイルを Zip アーカイブして GitHub リリースの Artifact にアップロードすればOK。

binaryTarget で指定する時は前述した checksum の値も必要なので作成した zip ファイルに対して swift package compute-checksum your-tool.artifactbundle.zip を実行して得られるので、アップロードと同時にリリースに併記すればやることは以上!

リリースフローにここまでの一連の処理の流れを乗せたい場合は、スクリプトを用意したりCIを作り込まないといけないのでもう一踏ん張りが必要。実際に未対応だったOSSにPR出してみたので、 リリースフローを自動化したいという方の参考になれば嬉しいです。

github.com

Swift Evolutions