📦

artifact bundle を作成する GitHub Actions

2022/12/08に公開

こちらは Swift Advent Calendar 2022 8日目の記事です
せっかくなので Advent Calendar に参加してみたいと思います

はじめに

以前 2022年 個人的おすすめ iOS プロジェクト構成 の記事において、 Swift Package Plugin はしばらく様子見と述べましたが、その要因の一つである artifactbundle の生成・配布を解決する GitHub Actions を作ったので紹介します

artifactbundle の構造

まず、 artifactbundle の構造について説明します

プロポーザル の引用になりますが、

protoc.artifactbundle
├── info.json
├── protoc-3.15.6-linux-gnu
│   ├── bin
│   │   └── protoc
│   └── include
│       └── etc.proto
├── protoc-3.15.6-macos
│   ├── bin
│   │   └── protoc
│   └── include
│       └── etc.proto
└── protoc-3.15.6-windows
    ├── bin
    │   └── protoc.exe
    └── include
        └── etc.proto

このように info.json と1つ以上の artifact (実行可能ファイル)を拡張子 .artifactbundle でまとめたものになります

info.json は 実行可能ファイルの path と対応するプラットフォームを示す supportedTriples を羅列した情報になります

{
    "schemaVersion": "1.0",
    "artifacts": {
        "protoc": {
            "type": "executable",
            "version": "3.15.6",
            "variants": [
                {
                    "path": "protoc-3.15.6-linux-gnu/bin/protoc",
                    "supportedTriples": ["x86_64-unknown-linux-gnu"]
                },
                {
                    "path": "protoc-3.15.6-macos/bin/protoc",
                    "supportedTriples": ["x86_64-apple-macosx", "arm64-apple-macosx"]
                },
                {
                    "path": "protoc-3.15.6-windows/bin/protoc.exe",
                    "supportedTriples": ["x86_64-unknown-windows"]
                },
            ]
        }
    }
}

GitHub Actions

上記のファイル群と JSON ファイルを作成できれば良さそうです
今回はカスタム GitHub Action のうち composite アクション を用います

作成したアクションは以下の2つからなります

アクション自体は Swift は登場せず shell script で泥臭く実現しています

使い方

使い方として2パターンを想定しています

  • A. ツール提供者が自リポジトリで artifactbundle を生成する
  • B. 第三者がいくつかのツールを artifactbundle にまとめて生成する

後者は artifactbundle が提供されていないツールを自分で artifactbundle 化させる、もしくは CI の都合でオフィシャルでは提供されていない linux 向けのバイナリを生成したい際に有用です
どちらの場合でも基本的にはアクションの内容は変わりません

バージョンタグが打たれたら GitHub Releases に artifactbundle をプッシュする例

name: Release artifactbundle

on:
  push:
    tags:
      - '[0-9]+.[0-9]+.[0-9]+'

jobs:
  build:
    strategy:
      matrix:
        os: [ ubuntu-latest, macos-latest ]

    runs-on: ${{ matrix.os }}

    steps:
    - uses: actions/checkout@v3
    - uses: swiftty/swiftpm-artifactbundle-builder@v1  # <-----
      with:
        swift-version: '5.7'
        depth: 1

  draft:
    needs: [ build ]

    runs-on: ubuntu-latest

    steps:
    - uses: swiftty/swiftpm-artifactbundle-bundler@v1  # <-----
      id: bundler
      with:
        variants-version: ${{ github.ref_name  }}
    - uses: softprops/action-gh-release@v1
      with:
        draft: true
        generate_release_notes: true
        body: | 
          ## Checksums
          - for `.binaryTarget(name:url:checksum:)`
            ```
            ${{ steps.bundler.outputs.checksums }}
            ```
          ---
        files: |
          ${{ steps.bundler.outputs.path }}/**

2つのジョブからなっていて各プラットフォーム毎に build ジョブを行い、それらが全て終わったら draft ジョブで xxx.artifactbundle.zip を作成し、アセットにアップロードしています

B. の場合の補足説明

B. の場合は、ツール管理用のリポジトリを作成し、 Package.swift と上記のような GitHub Action を設置します

// swift-tools-version:5.6
import PackageDescription

let package = Package(
    name: "cli",
    dependencies: [
        .package(url: "https://github.com/swiftty/XcodeGen", branch: "feature/resources")
    ]
)

あとは利用したい側の Package.swift に artifactbundle が保存されている URL と checksum を binaryTarget として設定すれば、通常の executableTarget の場合と同じく plugin 側で呼び出すことができます

.binaryTarget(
    name: "xxx",
    url: "https://path/to/xxx.artifactbundle.5.7.zip",
    checksum: "<swift package compute-checksum>"
)


その他苦労した点

GitHub Action のバージョン管理の扱い

actions/checkout@v3 のように v3v3.0.1v3.1.2 などのバージョンから適切に GitHub が選択してくれるものかと思ったら、その都度 v3 のタグを打ち直す必要があった

https://github.com/community/community/discussions/39519

Plugin で実行する際にハマった点

今までのツールの使い方をなるべく再現しようとすると、 Build Tool Plugin では権限がきつかったので Command Plugin 上での話になります

  • SwiftPM Plugin で利用する際の権限周り
    • 任意のディレクトリへの書き込みがある
      • --allow-writing-to-directory <dir>
        • 例)SwiftLint でのフォーマット、 XcodeGen のプロジェクト生成
    • ツール内部でプロセス呼び出しが発生する
      • --disable-sandbox
        • 例)SwiftLint 内部での SourceKit との通信周り(推測)、自ツール内でのプロジェクト構成解析時の swift package dump-package 呼び出し

例として SwiftLint のフォーマットを plugin 経由で呼び出す際のコマンド例を記載します

xcrun --sdk macosx swift package --disable-sandbox plugin --allow-writing-to-directory $(PROJECT_DIR) swiftlint --fix --config $(PROJECT_DIR)/.swiftlint.yml

そして Command Plugin の形になってしまっているので、これを呼び出すために結局 Xcode Build Phase のスクリプト等に記載しないといけないのが、イケてないです

おわりに

Swift Package Plugin はまだ制約が厳しく使い道が限られている印象ですが、 artifactbundle 対応されていると手軽に試しやすくなるので、自作ツールの artifactbundle を用意したい場合に是非お使いください ✨

Discussion