詩と創作・思索のひろば

ドキドキギュンギュンダイアリーです!!!

Fork me on GitHub

最近のGoプロジェクトのMakefile

最近は仕事でも新しくGoのプロジェクトをイチからはじめることが増えてきて、コピペ元が欲しくなるので、スナップショットとして残しておきます。とくに Go でウェブアプリケーションを書くような場合を想定していて、npm エコシステムにも乗っていきます。

大まかな方針としては、

  • self-contained である
  • グローバルな環境を汚染しない
  • コマンド一発で開発環境が再現できる

……というところを目指します。

motemen/prchecklist がこれを達成しているつもりなので、以下、これを例に見ていきます。

依存ライブラリは dep なり何かしらのツールと Go 標準の vendoring で管理すればよい一方、そのツール自体であったり、他の開発中に必要なツール(golint とか gobump とか)であったりのインストールをどうするかという話。

npm であれば devDependencies というやつでうまいことやってくれますが、現行の Go の依存管理ツールではデファクトがない、というか依存管理ツールのデファクトが(少なくとも自分の中では)まだ定まってないので、これを Makefile でやる必要がある。

かといって依存ツールを単純に go get してはグローバルな環境を汚染してしまうことになるので、npm を真似て Go 製ツールのバイナリを .bin/ 以下に置くことにします。以下のように GOBIN 環境変数を指定すれば、バイナリを作業ディレクトリ以下にインストールできます*1

.PHONY: setup-go
setup-go:
   GOBIN=$(abspath .bin) go get -v \
        gobin.cc/go-bindata \
        gobin.cc/reflex \
        gobin.cc/mockgen \
        github.com/motemen/go-generate-jsschema/cmd/gojsschemagen

gobin.cc を使えばすっきり。

最初に make setup-go しなくてもうまいこと動くように、依存関係をきちんと書いてやります。

.PHONY: test
test: lib/web/web_mock_test.go
    ...

lib/web/web_mock_test.go: lib/web/web.go .bin/mockgen
    .bin/mockgen -package web -source $< GitHubGateway > $@

.bin/%: Makefile
    @$(MAKE) setup-go
    @touch $@

テストがモックのソースコードに依存し、それがさらに .bin/mockgen に依存するように宣言しています。そして .bin/ 以下のファイルは先ほどの setup-go タスクで生成するよう指定します。.bin/% の依存先として Makefile を書いているのは、go get するパッケージの一覧が Makefile に書いてあるから。

ここに書いたのとまったく同じ方法で npm パッケージ向けの設定も書けますが、その場合は

node_modules/.bin/%: package.json

のように書けばいいわけです。

このようにして、gonpm または yarn さえ入っていればだいたい再現可能な開発環境が作れました(だいたいというのは、Go ツールに非互換な変更が入ったときに壊れる、という懸念があるよね、というところです)。

余談になるけれど、Go ソースコードの watch & build にこれまでよく entr を使っていて、これが大変便利なんですが、パッケージマネージャによるユニバーサルなインストール方法というのがなく、このエントリに書いたような再現可能な環境を作るために Go 製の reflex を使ってみました。

ls **/*.go | entr -r sh -c '...'

reflex -r '\.go\z' -s -- sh -c '...'

になる感じで、汎用的に使えるツールです。わりとよさそう。

*1:$GOPATH/pkg は汚染されますが。これは自分にとっては問題にならない

prchecklist でリリース Pull Request のチェックフローをスムーズに行う

背景

GitHub を使った開発では、

  • master ブランチがいつでも本番に出せる状態として、
  • master から切った develop ブランチを開発のベースとし、
  • 各フィーチャは develop から切って develop にマージし、
  • リリースのタイミングで develop を master にマージ、リリース

……という流れを pull request ベースで行うのがよくあるパターンのひとつだと思います。リリースの際、ステージングや QA という名前のついた本番前環境でそれぞれの機能が正しく動いているか確認するのもよくあるフローです。

このチェックを pull request 本文のチェックボックスを使って行おう、というアイデアを実装したのが git-pr-release で、もともと id:hitode909 がチーム向けにこしらえたものをパクった汎用化したものでした。この仕組はとても便利で、いまも社内で使われているのですが、GitHub 上のチェックボックスを利用するのはすこし困るところがありました:

  • (最近はないけど)Firefox で挙動がおかしいことがあった
  • 誰がチェックしたかわからない
  • 複数人でチェックできない
  • チェックが完了したかどうか、リリース担当者が確認する必要がある

などなど。GitHub の機能追加でチェックボックスまわりが今後リッチになっていく可能性は十分ありますが、それがいつのことかは当然わかりません。チームのプロセスの進歩のためには、自分たちで拡張可能な仕組みにする必要があります。

prchecklist

そういうわけでこのチェックリストをウェブアプリケーション化したのが prchecklist です。

GitHub - motemen/prchecklist: Provides checklists based on release PR

git-pr-release と同様に、複数のフィーチャ PR をまとめて master にマージする「リリース PR」に対してチェックリストを生成します(pull request の作成はおこないません)。チェックリストは、リポジトリへのアクセス権限のある GitHub ユーザによってチェックされ、設定によっては Slack への通知も行えます。

動作を大づかみするには、Heroku にデプロイしているデモを見るのが早そうです。サンプルのリリース PR である https://github.com/motemen/test-repository/pull/2 のチェックリストが 、prchecklist 上では https://prchecklist.herokuapp.com/motemen/test-repository/pull/2 になります(GitHub における URL のパスが prchecklist のパスに対応しています)。ログイン必須で右上から GitHub ログインする必要がありますが、repo 権限が必要なのでちょっと厳しいかも……。という人はあとの説明をみて、手元で立てて確かめてみてください。

f:id:motemen:20170926092823p:plain

設定

先のデモでは、qa と production の2ステージが選択できるようになっていました(右上のドロップダウン)。このような設定は、リポジトリ直下の prchecklist.yml でカスタマイズできます。

例はステージの設定のみですが、通知を設定することもできて、以下はチェックリストのアイテムがチェックされた場合と、チェックリストがすべてチェックされた場合に別々の Slack チャンネルに通知するような例です。

stages:
  - qa
  - production
notification:
  events:
    on_check:
      - ch_check
    on_complete:
      - ch_complete
  channels:
    ch_complete:
      url: https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX
    ch_check:
      url: https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX

通知は、こんな感じのが来ます。

f:id:motemen:20170926093024p:plain

ビルド/デプロイ

https://github.com/motemen/prchecklist/releases にビルド済みのバイナリをアップロードしています。go get github.com/motemen/prchecklist/cmd/prchecklist または git clone して make でも取得可能です。

利用するには GitHub にアプリケーションを登録する必要があります(-github-client-id-github-client-secret)。また、チェック情報の保存に別途ストレージが必要で、デフォルトでは Bolt を利用しますが、-datasource redis://u:<password>@<host> のような形で Redis も指定可能です。

% prchecklist -h
Usage of prchecklist:
  -behind-proxy
        prchecklist is behind a reverse proxy (PRCHECKLIST_BEHIND_PROXY)
  -datasource string
        database source name (default "bolt:./prchecklist.db")
  -github-client-id string
        GitHub client ID (GITHUB_CLIENT_ID)
  -github-client-secret string
        GitHub client secret (GITHUB_CLIENT_SECRET)
  -github-domain string
        GitHub domain (GITHUB_DOMAIN) (default "github.com")
  -licenses
        show license notifications
  -listen address
        address to listen (default "localhost:8080")
  -session-secret string
        session secret (PRCHECKLIST_SESSION_SECRET)
  -version
        show version information

おわり

便利なウェブアプリ、prchecklist の紹介でした。

今のところ社内でも順調に使われています。git-pr-release と組み合わせるとより便利でしょう。

ためしに Gitter ルームも用意してみました。make develop で開発環境も簡単に立ち上がりますし、PR ウェルカムです!

go get gobin.cc/PROGNAME でプログラムをゲットできる URL shortener っぽいサイトを作った

夏休みの自由研究です。

ghr を go get したいんだけど作者の e の数って何個だったっけ……と悩んでいたらそもそも違う ID でした、けどそれも思い出せない! って経験ありませんか? たいていの場合、プログラム名だけは覚えていて、その正確なパスまでは覚えていないものです。

そこでその名前だけから欲しいプログラムを go get できる gobin.cc というサイトを作りました。一種の URL shortener サービスだと思ってもらえればよさそうです。

go get gobin.cc/gore

などとすると、github.com/motemen/gore と同じものが go get されます。おー、便利!

実装

プログラム名からその完全なパッケージパスを自動的に再現するのは難しいのもあり、対応しているプログラムは手動管理のファイルである SOURCES に記載されています。そういう観点では、一種の curated list であるとも言えます。

gobin.cc のソースリポジトリは https://github.com/motemen/gobin.cc です。go get gobin.cc/gorego get github.com/motemen/gobin.cc/gore をしたのと同じソースコードをダウンロードします。

gobin.cc サイト自体は Cloudflare + GitHub Pages で構成されていて、404.html にあとで触れるメタタグを埋め込むことでどのようなリクエストでも github.com を参照するように設定しています。

リポジトリ直下に main パッケージがある場合

github.com/motemen/gore など。

このような場合、GitHub のリポジトリ側で行っているのは、それぞれのプログラムを git submodule として追加することだけです。go get は submodule も含めて clone するので、このように外部リポジトリの内容を取り込むことができるのでした。そのぶん、初回の go get は重くなってしまいますが。

リポジトリのサブディレクトリ以下に main パッケージがある場合

github.com/golang/lint/golint など。

この場合はやや面倒です。Git に外部リポジトリのサブディレクトリを取り込む標準的な方法はないので、git read-tree などを使って gobin.cc のリポジトリに当該プログラムの main パッケージに相当するコードを直接コミットしてしまっています。依存パッケージは go get がダウンロードしてくれるので、main だけ考えておけばよいです。

この際、特別にリポジトリ直下の LICENSE ファイルもあわせてコミットしています。

どちらの場合も本体の更新に自動で追随はしないので、リポジトリを更新していく必要はあります(CI でできる予定)。

その他に検討した実装

go get は、VCS を利用してソースコードをダウンロードします。パッケージの示す先が github.com などの有名なホストでない場合、一旦 HTTP(S) でアクセスし、以下の形の meta タグから利用すべき VCS や URL を判別します(Remote import paths)。

<meta name="go-import" content="import-prefix vcs repo-root">

golang.org/x/ 以下のパッケージが有名ですね。gobin.cc 自体もこの仕組みを使っています。

最初は git submodule など使わずに、すべて go-import メタタグによる一種のリダイレクトのみで実現するつもりで、一見、以下のような方法でうまく行ったのですが、

<meta name="go-import" content="gobin.cc/gore git github.com/motemen/gore">

この仕組みはあくまでリポジトリのホストを go に指し示すもので、パッケージパスを指すものではなく、パッケージ直下にないプログラムへの対応を以下のように実現することはできません。

<!-- github.com/golang/lint/golint はリポジトリの URL ではないので意図通りに動かない -->
<meta name="go-import" content="gobin.cc/golint git github.com/golang/lint/golint">

なのでパッケージ直下にないプログラムの対応をするには結局自前のリポジトリを用意するほかなく、そうすると手元の GOPATH ではリポジトリがネスト(例えば $GOPATH/src/gobin.cc$GOPATH/src/gobin.cc/gore)してしまうことになり、取り回しが困難なので諦めました。

ライセンスについて

MIT、Apache、Go 本体のライセンス(BSD 3-clause)のソースコードにおいてはこの配布方法は問題ないという認識ですが、詳しい人の指摘はウェルカムです。

おわりに

PoC 的に gobin.cc という go-gettable な shoturl/curated サイトを作りました。

SOURCES にはさしあたり自分がよく使うものをリストしてますが、ほかに追加したらいいものがあったら教えてくださいね。

はてなで一緒に働きませんか?