Github ActionsでクロスコンパイルしてGithub Releaseにアップロードする

追記: GoReleaserのbabarotさんの記事があったので紹介。GoReleaser使ったほうが手軽でよさそう。 Go で書いた CLI ツールのリリースは GoReleaser と GitHub Actions で個人的には決まり | tellme.tokyo

追記: GoReleaserのプロジェクトがGithub Actionを公開したようなのでこちら使ってみるといいかもしれません。 https://github.com/goreleaser/goreleaser-action

この前kube-promptのリリースでミスをしてしまいissueが立て続けに2件上がったことがあったのですが、Github Actionsが自分のリポジトリで使えるようになったのでリリースを自動化することにしました。

触ってみてわかったのですが、↓の記事を書いたときとは多くの変更がありました。workflowの記述フォーマットがyamlに変更されていたり、Azure Pipelinesを裏側で使うようになったことでSupported virtual environmentsにmacOSWindowsが増えたり、その影響でCustom actionはDockerコンテナベースではなくJavaScriptアプリケーションとして記述できるようになったりしています。JavascriptでCustom actionを記述するのはまた今度にして、とりあえず今回はyaml形式のworkflowを使っていきます。

nwpct1.hatenablog.com

ロスコンパイルとGithub releasesへのアップロード

kube-promptではこんな感じでいくつかバイナリをGithub Releaseに含めています。今回は tagが作成されたタイミングでフックしてこれらのbinaryをcross compile, zipで圧縮してGithub Release画面を作成します。

f:id:nwpct1:20190820063438p:plain
kube-prompt github release画面

まずworkflowのevent trigger指定します。 今回からcronのように定期実行もできるようになっていますが、基本的には GithubのWebhook event を起動トリガーにすることがほとんどかと思います。git tagのcreateイベントをトリガーにしたいので次のようになります 1

on:
  create:
    tags:
    - v*.*.*

次にjobs propertyより実際にGithub Actionsで行いたい操作を記述します。 そんなに長くなくて↓のような感じです。

jobs:
  release:  # job_id
    name: Build
    runs-on: ubuntu-latest
    steps:

    - name: Set up Go 1.12
      uses: actions/setup-go@v1
      with:
        version: 1.12
      id: go

    - name: Check out code into the Go module directory
      uses: actions/checkout@master

    - name: Build
      env:
        GO111MODULE: on
        GOPATH: /home/runner/work/
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |
        export CREATE_EVENT_REF_TYPE=$(jq --raw-output .ref_type "$GITHUB_EVENT_PATH")
        export TAGNAME=$(jq --raw-output .ref "$GITHUB_EVENT_PATH")
        if [ "$CREATE_EVENT_REF_TYPE" != "tag" ]; then echo "ref_type is not a tag: ${CREATE_EVENT_REF_TYPE}" && exit 78; fi
        make cross
        go get -u github.com/tcnksm/ghr
        $GOPATH/bin/ghr -n ${TAGNAME} -b "ʕ◔ϖ◔ʔ Release ${TAGNAME}" -draft ${TAGNAME} pkg/
  • jobs.<job_id>.runs-on に実行環境を指定することができて、今回からmacOSWindowsが追加されました。kube-promptはCGOの依存とかもなく普通にクロスコンパイルできるので、一番速度や動作が安定してそうなubuntuにしています。他のCIサービスと同様に、matrixも組めるのでテストとか必要に応じてやっておくといいかと思います。
  • GITHUB_TOKENは以前までだと自分でsecretsに登録する必要がありましたが、今回からはその必要も無いようです。yaml内で ${{secrets.GITHUB_TOKEN}} とするだけで取り出せます。
    • どこまでが古いドキュメントにしか書かれてなくて、どこまでが新しいドキュメントに書かれてるのか分からずこれに気づくの少し時間がかかってしまいました。
  • $GITHUB_EVENT_PATH にはWebhook eventのJSONファイルがそのまま入っています。今回は create イベントにフックしているので、 こちら にあるように、 ref_type フィールドからtag名、 ref フィールドからrevision hashが取り出せます。
  • make cross の処理はこんな感じです https://github.com/c-bata/kube-prompt/blob/4e31373f5a10443746b4ab93d584ea0ec17d4e61/Makefile#L31-L43
  • 最後にghrをgo getしてきてアップロードします。Githubのtokenは GITHUB_TOKEN 環境変数を見てくれているので特にコマンド実行時には指定する必要がありません。
  • ~ちなみに actions/setup-go$GOPATH が空で $GOPATH/bin にもパスが通っていないようなので、明示的に指定しました。直したほうがいい気がしますが、JavaScriptベースのCustom actionとして定義されているらしく、まだ自分も調べてないところだったので今度必要になって調べることがあればついでに提案してみたいとおもいます。~
    • uses で何もDockerイメージが指定されていないstepは、 runs-on で指定したホスト上でそのまま動いているかもです。 setup-go はそのホスト上にGoの環境を構築してくれているということな気がします。ただ環境変数とかは引き継げないのかな?
    • https://github.com/actions/setup-go

全体像はこんな感じです。試しに v0.0.1 みたいなタグをpushして無事にバイナリ付きでGithub Releaseが作成されることは確認しました。

https://github.com/c-bata/kube-prompt/blob/master/.github/workflows/release.yml

おまけ: golangci-lintとgo testの実行

Goptunaという最近開発してるプロジェクトはまだCIを導入していなかったので、Lintとテストを実行できるようにしました。 他のプロジェクトでもほとんどそのまま使える点も多いと思うのでよければ参考にしてみてください。

name: Run tests and lint checks
on:
  pull_request:
    branches:
    - master
jobs:
  lint:
    name: Lint checking on Ubuntu
    runs-on: ubuntu-latest

    steps:
    - name: Set up Go 1.12
      uses: actions/setup-go@v1
      with:
        version: 1.12
      id: go

    - name: Check out code into the Go module directory
      uses: actions/checkout@master

    - name: Running golangci-lint
      env:
        GO111MODULE: on
        GOPATH: /home/runner/work/
      run: |
        go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
        GOCILINT=${GOPATH}/bin/golangci-lint make lint
  test:
    name: Test on ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macOS-latest]

    steps:
    - name: Set up Go 1.12
      uses: actions/setup-go@v1
      with:
        version: 1.12
      id: go

    - name: Check out code into the Go module directory
      uses: actions/checkout@master

    - name: Running go tests
      run: make test

https://github.com/c-bata/goptuna/blob/master/.github/workflows/run-tests.yml

追記:

Go関係ないですが、ついでにSphinxのドキュメントをビルドしてGCSにアップロードするscript書いたのでgcloudとかgsutil使いたい方とか参考にしたい方はどうぞ。本当は https://github.com/actions/gcloud を使おうと思ったのですが柔軟性なくて今回の用途には使えなかったのでやめました。

Github Actions Workflow to build your sphinx documentation and upload it to Google Cloud Storage. https://gist.github.com/c-bata/ed5e7b7f8015502ee5092a3e77937c99

おわりに

travis CIを長く使ってきましたが、Windowsの動作が不安定だったり苦労している点が結構ありました。 ActionsはAzure Pipelinesがバックエンドにあることもあり、立ち上がりがかなり速い印象です。 forkしたらそのままActionsが使えるというメリットもあるので、OSSでは積極的に使っていこうと思います。


  1. ちなみに複数のイベントにトリガーさせることもできて、 この例 がわかりやすかったです。