オフトゥン大好き。

惰眠系プログラマの作業ログで( ˘ω˘ ) スヤァ…

簡単にできる!Goで書いたCLIツールを配布する方法

どうも、id:nukosukeです。

さっそくですがみなさん、パッとCLIツールを作るとなったらやっぱりGoですか?
Goですよね?

でも、「作ったツールを配布したい」となったときにちょっと困ったことがあります。

  • 開発マシンにGoの環境を作ってる人が少なかった (構築方法説明するのは面倒)
  • Go環境入れてもらったもののバージョンが違ってビルドで死ぬ
  • depやvgoのようなバージョンマネージャを使ってなくて、依存ライブラリがアップグレードしててビルドで死ぬ

とかとか。

前のEmacsの記事でも同じようなことを言いましたが、環境再現性ってやっぱり大事ですよね。この辺りの煩雑さは周辺ツールではなくGo本体のバージョンアップで今後対応が進むようですが、そもそもツールを使うだけの人にビルド環境を用意させるのはどうなんでしょう。バイナリで配布すれば面倒な環境構築も必要ないわけでして。

というわけで、今回の記事ではGoで書いたツールのテストと配布 (CI・CD*1 )環境をサクッと作ります。

要件

  • お金をかけずに誰でもダウンロードできる場所に置いておきたい
  • gitでバージョンタグを打ったら自動でビルドして配布用のアーカイブがアップロードされるようにしたい
  • OSやCPUアーキテクチャごとにクロスコンパイルしたバージョンを用意したい

使用するツール・サービス

GitHub

言わずと知れたGitホスティングサービスですね。今回はツールのバイナリ配布のためにreleaseという機能を使います。releaseでは特定のタグやリビジョンに対してソースコードアーカイブ(.zip, .tar.gz)を自動で作成してくれます。加えて、任意のファイルを追加することができますのでここにバイナリをzipに固めたものをアップロードしていきます。

TravisCI

OSS開発においてお金をかけずにCIを導入したいというときにTravisCIとCircleCIが選択肢に上がるかと思います。(他にもたくさんCIサービスがありますね。おすすめがあればぜひ教えてください)

CI ビルド環境 id:nukosukeの所感
TravisCI VM あんまり複雑なことを考えずサクッと導入したい人向け。TravisCIがサポートしている機能だけで要件を満たせるなら導入がすごくラクGitHub releaseを自動で作る機能など便利機能が満載。
CircleCI コンテナ(v2.0より) テスト環境をカスタマイズしてステップごとに色々やりたい人向け。Dockerイメージを使用してとことんカスタマイズできるため複雑な要件の場合はこちらを選択した方がいい。ステップごとにイメージの切り替えが可能。

今回はTravisCIの機能で要件を満たせるのでこちらを使ってスピーディーにCI環境を作ります。ちなみに今回は触れませんが、WindowsでテストしたいときはAppVeyorが便利です。

gox

Goは GOOS, GOARCH 環境変数を指定することで様々なプラットフォーム向けにビルドすることができます。しかし、いちい環境変数を指定してプラットフォームごとにgo buildを繰り返すのは面倒です。一発で複数のプラットフォーム向けのバイナリを作りたいですよね。

はい、そんなときにgoxが便利です。デフォルトではサポートしているOSとアーキテクチャの全組み合わせでバイナリを出力します。ターゲットを絞りたい場合は、OSとアーキテクチャのリストをオプションに渡すとその分だけビルドすることができます。

使用方法の詳細については後ほど見ていきます。

手順

GitHub: Personal Tokenの発行

releaseページにバイナリをアップロードするのに使います。場所が少し分かりづらいのですが、SettingsDeveloper settingsPersonal access tokens から発行できます。

Generate new tokenで発行できたらTravisCIの環境変数GITHUB_TOKEN という名前で保存しておきましょう。

この際、Display value in build logがオフになっていることを確認しておきましょう。これがオンになっていると、テストのログに$GITHUB_TOKENの中身が表示されてしまうためトークンが漏洩してしまいます。

TravisCI: go testの実行

TravisCIでgo testを走らせる設定です。この記事の本旨とは関係ないですが、パッケージマネージャにdepを、テストカバレッジの計測にCoverallsを使っています。

# 言語を指定します
language: go

# テストを回すGoのバージョンをリストで指定します
go:
  - "1.11.x"
  - master

# 依存ライブラリのインストール処理前に走らせる処理を書きます
#   ここではdepパッケージマネージャとテストカバレッジ計測のためにgoverallsを入れています
before_install:
  - curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
  - go get github.com/mattn/goveralls # for profiling coverage

# Gopkg.toml, Gopkg.lockをもとに依存ライブラリをインストールします
#   installを指定しないとデフォルトではgo getが使用されるため、
#   依存ライブラリのアップデートで突然壊れる可能性があります
install:
  - dep ensure

# テストを実行します
#  2つ目はカバレッジ計測のコマンドです
script:
  - go test -v ./...
  - goveralls -service=travis-ci

自動テスト(CI)だけが目的であればこれで完了ですが、今回は配布(CD)もやりたいので続いてその設定をやっていきましょう。

TravisCI: goxでクロスコンパイル

先ほどの.travis.ymlにビルドの設定を追記します。デフォルトではgoxはカレントディレクトリに {{.Dir}}_{{.OS}}_{{.Arch}} (Windowsの場合は.exeがつく) の形式の名前でバイナリを吐き出しますが、これだとLICENSEやREADMEなど同梱したいファイルがある場合に不便です。そこでディレクトリを切ってその中にバイナリを出力するように -output オプションを指定しています。ターゲットのOSやアーキテクチャはスペース区切りの文字列で渡します。 -osarch="linux/amd64" のように一緒に指定することもできます。

詳しくは gox -h で確認してみてください。

before_deploy:
  # goxをインストール
  - go get github.com/mitchellh/gox
  # Linux, macOS, Windowsに対して、
  # 386(32bit), amd64(64bit)CPU向けのバイナリをビルドします
  - gox -os="linux darwin windows" -arch="386 amd64" -output="{{.Dir}}-{{.OS}}-{{.Arch}}/{{.Dir}}"
  # バイナリを出力したディレクトリをそれぞれzipに固めます
  - |
      for os in linux darwin windows; do
        for arch in 386 amd64; do
          cp README.md LICENSE terraform-provider-zendesk-$os-$arch
          zip -r terraform-provider-zendesk-$os-$arch.zip terraform-provider-zendesk-$os-$arch
        done
      done

TravisCI: GitHub releaseページにバイナリをアップロード

さらに、GitHub releaseページにバイナリをアップロードする設定を追記します。

deploy:
  provider: releases
  api_key: $GITHUB_TOKEN
  file_glob: true
  file: terraform-provider-zendesk-*-*.zip
  skip_cleanup: true
  # tagが打たれたときのみGo 1.11でビルドしたバイナリをアップロードします
  on:
    tags: true
    go: "1.11.x"

ではさっそくgit tagでバージョンタグを打ってみます。しばらくして、releaseページに以下のように.zipがアップロードされていたら成功です。

それでは、楽しいGoライフを!

*1:CI = Continuous Integration, CD = Continuous Delivery