Dartでコマンドラインツールを作ったものをGitHub Actionsでビルドして公開する


やりたいこと

Dartは dart compile exe すればシングルバイナリを出力してくれるので、社内向けのコマンドラインツールを作って配布するなどがしやすい。

GitHub Releasesで↓こういう感じにしたい。

GitHub ActionsでビルドしてReleasesにアップロードする

Goで書けばクロスコンパイルもできるしgoreleaserでGitHub Releasesへのアップロードも楽勝なのだが、Dartではそこまで整った仕組みはない。

https://github.com/dart-lang/sdk/issues/28617

クロスコンパイルできないまま2年くらい経っていて、そろそろみんな諦め始めている。代わりに、GitHub Actionsのwindows-latest, macos-latest, ubuntu-latestを使うというのが定石になりつつある。

上のissueには実際にそれぞれのビルド環境でビルドしてアーティファクトにアップロードするサンプルがあるが、GitHub Releasesにそれをまとめるスクリプトはない。

コンパイル

各OSで dart compile exe するだけなのだが、出力ファイル名がWindowsだけ .exe をつけないといけない。以下のようにmatrix/includeを使用するとよい。

jobs:
  compile:
    name: dart compile exe
    strategy:
      matrix:
        include:
          - runs-on: ubuntu-latest
            binary-name: mycli_linux_amd64
          - runs-on: macos-latest
            binary-name: mycli_macos_amd64
          - runs-on: windows-latest
            binary-name: mycli_windows.exe
    runs-on: ${{ matrix.runs-on }}
    steps:
      - uses: actions/checkout@v2
      - uses: dart-lang/setup-dart@v1
      - run: dart pub get
      - run: mkdir ${{ matrix.runs-on }}
      - run: dart compile exe bin/mycli.dart -o ${{ matrix.runs-on }}/${{ matrix.binary-name }}

リリースの作り方

https://docs.github.com/ja/rest/reference/repos#releases

を見るに、2つの作業が必要らしい。

  • リリースというものをつくる
    • POST /repos/{owner}/{repo}/releases
  • リリースにassetをアップロードする
    • POST /repos/{owner}/{repo}/releases/{release_id}/assets
    • api.github.comではなくuploads.github.com

最初は頑張ってcurlでやってみようとしたが、GitHub Actionsのマーケットプレイスをみると既存でいくらでもあるようなので、それを拝借することにした。

action-gh-release を使ってリリースを作る

actions/create-release@v1actions/upload-release-asset@v1 はもうメンテされていないらしいので、代わりに softprops/action-gh-release@v1 を使用する。

https://github.com/softprops/action-gh-release

リリースの作成とファイルのアップロードを1つのアクションでやってくれるので、けっこう便利。

いろいろオプションは指定できるが、とりあえず下書きでリリースを作ってそこにファイルをアップロードしたいだけなら、以下の内容だけでよい。

      - uses: softprops/action-gh-release@v1
        with:
          draft: true
          files: out/*

filesはglob形式で指定することもできるし、以下のように複数行指定もできるようだ。

          files: |
	    out/mycli_linux
	    out/mycli_macos
	    out/mycli_windows

まとめ

こんなかんじでいける。バイナリ名だけ調整すれば、けっこういろんなCLIアプリで使えるんじゃないかな。

.github/workflows/deploy.yml
name: Deploy

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

jobs:
  compile:
    name: dart compile exe
    strategy:
      matrix:
        include:
          - runs-on: ubuntu-latest
            binary-name: mycli_linux_amd64
          - runs-on: macos-latest
            binary-name: mycli_macos_amd64
          - runs-on: windows-latest
            binary-name: mycli_windows.exe
    runs-on: ${{ matrix.runs-on }}
    steps:
      - uses: actions/checkout@v2
      - uses: dart-lang/setup-dart@v1
      - run: dart pub get
      - run: mkdir ${{ matrix.runs-on }}
      - run: dart compile exe bin/mycli.dart -o ${{ matrix.runs-on }}/${{ matrix.binary-name }}
      - uses: actions/upload-artifact@v2
        with:
          name: bin-${{ matrix.runs-on }}
          path: ${{ matrix.runs-on }}

  release:
    needs: compile
    name: github release
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v2
        with:
          name: bin-ubuntu-latest
          path: bin-linux
      - uses: actions/download-artifact@v2
        with:
          name: bin-macos-latest
          path: bin-macos
      - uses: actions/download-artifact@v2
        with:
          name: bin-windows-latest
          path: bin-windows
      - uses: softprops/action-gh-release@v1
        with:
          draft: true
          files: bin-*/*

これで実際にtag pushしてみると、

3並列コンパイルからの・・・リリース作成で

思っていた通りのものが作れた\(^o^)/