Hateburo: kazeburo hatenablog

Operations Engineer / 運用系小姑 / Perl Monger

cgoを利用した Go project を GitHub Actions でクロスコンパイル&リリースを行う

check-lastlogというmackerel監視pluginを作ってるのですが、

github.com

こちらは /var/log/lastlog を読むために一部cgoを使っています。mackerelのpluginなので一つ前の記事で紹介した方法で

kazeburo.hatenablog.com

リリースしたいところなのですが、GoReleaserでは、cgoへの依存があるプロジェクトのクロスコンパイルがサポートされていないため、ひと工夫必要になります。

goreleaser.com

github.com

check-lastlogではこちらのxgoを使う方法でクロスコンパイルし、GoReleaserでGitHub Releaseにアップロードをしています。

出来上がったGitHub Actionsのワークフローはこちら

https://github.com/kazeburo/check-lastlog/blob/master/.github/workflows/release.yml

xgoでのクロスコンパイル

xgo はGoのクロスコンパイルの環境がはいったDockerのイメージと各環境をターゲットとしたビルドを行うコマンドを提供しています。

github.com

check-lastlogではGitHub Actionが用意されているこちら

github.com

を使いました。こちらはforkしたxgoを使っているようです。

check-lastlogのワークフロー、.github/workflows/release.yml の前半ではxgoを使ってクロスコンパイルしています。

name: release
on:
  push:
    tags:
    - "v[0-9]+.[0-9]+.[0-9]+"
jobs:
  goreleaser:
    runs-on: ubuntu-latest
    env:
      BIN_NAME: check-lastlog
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Set tag to environment variable
        id: set-tag
        run: echo ::set-output name=version::${GITHUB_REF#refs/*/}

      - name: Build with xgo
        uses: crazy-max/ghaction-xgo@v1
        with:
          xgo_version: latest
          go_version: 1.15.x
          dest: build
          prefix: ${{ env.BIN_NAME }}
          targets: linux/amd64,linux/arm64
          v: true
          x: false
          ldflags: -s -w -X main.version=${{ steps.set-tag.outputs.version }}

      - name: Check build dir
        run: ls -lR build

ghaction-xgo のパラメータは次のようになってます。

  • dest: ビルドしたバイナリを格納するディレクト
  • prefix: バイナリのファイル名。後ろに -${OS}-${Arch} がつきます
  • targets: クロスコンパイルするターゲット。カンマで区切る
  • v: ビルドしたファイル名の表示
  • x: ビルドの際のコマンドなどの表示

また、GoReleaser ではldflagsをデフォルトでいくつか設定してくれますが、xgoはそうではないので、-w -s-X main.version を追加しています。

これでビルドが完了すると、dest で指定したディレクトリにバイナリができます。

> Run ls -lR build
build:
total 3428
-rwxr-xr-x 1 runner docker 1806024 Dec  2 09:12 check-lastlog-linux-amd64
-rwxr-xr-x 1 runner docker 1703448 Dec  2 09:12 check-lastlog-linux-arm64

アーカイブ作成とGitHub Releaseへのアップロード

あとは、GitHub Releaseへのアップロードだけではあるのですが、check-lastlog はmackerel監視プラグインなので、mkr plugin install 対応をさせます。

mkr plugin install 対応するためにはファイル名が {{ .ProjectName }}_{{ .Os }}_{{ .Arch }}.zip でzipでアーカイブする必要があります。なのでShellで頑張ってzipをつくりました。 

- name: Zip binaries
  run: |
    cd build
    for file in ./* ; do
      mkdir $(echo ${file}|awk -F- '{print "${{ env.BIN_NAME }}_"$(NF-1)"_"$(NF)}') &&
      cp ${file} $(echo ${file}|awk -F- '{print "${{ env.BIN_NAME }}_"$(NF-1)"_"$(NF)}')/${{ env.BIN_NAME }} &&
      zip $(echo ${file}|awk -F- '{print "${{ env.BIN_NAME }}_"$(NF-1)"_"$(NF)}').zip -j $(echo ${file}|awk -F- '{print "${{ env.BIN_NAME }}_"$(NF-1)"_"$(NF)}')/${{ env.BIN_NAME }} ../README.md ../LICENSE;
    done
    shasum -a 256 *.zip > ${{ env.BIN_NAME }}_${{ steps.set-tag.outputs.version }}_checksums.txt
    ls -lR ./

バイナリだけではなく、README.mdやLICENSEファイルもアーカイブに同梱しています。また、checksum用のファイルも作っています。GoReleaserのchecksumがsha256なので、同じようにshasumコマンドを使ってみました。

そして最後にGitHub Releaseにアップロードするのですが、CHANGESも作ってくれるGoReleaserを使います。

まず、GoReleaserの設定ファイル .goreleaser.yml

# Use for generating CHANGELOG
builds:
  - binary: check-lastlog
    skip: true
release:
  github:
    owner: kazeburo
    name: check-lastlog
  extra_files:
    - glob: "build/*.zip"
    - glob: "build/*_checksums.txt"

ビルド(builds)は skip:true で止めることができました。成果物がない場合、archiveの作成も行われないようでした。GoReleaserで作ったパッケージのかわりに、xgoとShellで作ったzipとchecksumファイルをextra_filesに指定してアップロードしてもらいます。

GitHub Actionsのワークフローは goreleaser/goreleaser-action を使いました。

- name: Run GoReleaser
  uses: goreleaser/goreleaser-action@v2
  with:
    version: latest
    args: release --rm-dist
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

GoReleaser 便利

まとめ

xgoを使うとGoReleaserのみでクロスコンパイルする場合にくらべて実行時間は伸びてしまいますが、cgoを使っているGo Projectでもlinuxamd64, arm64に対応したパッケージを作ることができました。