最近のGo Modulesプラクティス ~ ghqユーザーの場合も添えて
最近Go Modulesを使っていて、だいたいプラクティスが定まってきたのでまとめてみる。
個人的な結論
- Go Modulesは積極的に使っていけばいい
- 幾つか課題はある
$GOPATH
から出る必要もない$GO111MODULE
を適宜設定すればよい- どうせ次のGo 1.13からはどこに置こうが関係なくなる
2つのモード
$GOPATH/src
にプロジェクトを置いていると、今(Go 1.12)の標準動作はGOPATHモードになる。これは、$GOPATH/src
以下からサードパーティパッケージを読み込むこれまでのGoと同様の動作になるということ。
それ以外の場所では go mod
コマンドを使ってGo Modulesを利用することができる。これをmodule-awareモードという。go.mod
と go.sum
を使って依存ライブラリを管理する方式になる。これらのファイルはgo mod
コマンドが大体いい感じに生成してくれるので別にファイルが増えて煩雑になるということはない。
module-awareモードの場合$GOPATH/pkg/mod
以下にパッケージのバージョンごとの実体が置かれるので、そこを眺めるとイメージが掴めるかも知れない。ただ、この位置は変更になる可能性があるし、そもそも普通に go mod
を利用する上では意識する必要はない。
この、GOPATHモードとmodule-awareモードの切り替わりはデフォルト挙動であり、 $GO111MODULE
環境変数を変更することで変更可能。また次のGo 1.13からは、一律module-awareモードになる。
Go Modules所感
普通に使い物になるし、vendorディレクトリとか使わなくていいので便利。積極的に使っていきましょう。その場合一律Go 1.12以降を使うことが望ましいとは思います。
使い方は、最近オフィシャルブログに Using Go Modules と言う記事が上がっていて、これがイメージを掴むためには良いと思います。
https://blog.golang.org/using-go-modules
依存定義のための go.mod
とロックのための go.sum
という依存管理システムに良くある2ファイル構成です。ただ、goコマンドがことある事にそれらを更新するのでそこは少し驚くかも知れません。
多くが自動生成されるため、覚えることは余りありませんが、網羅的な情報は公式のWikiにまとまっています。
https://github.com/golang/go/wiki/Modules
別に $GOPATH/src
からプロジェクトを動かす必要はない
$GOPATH
以下だと、前述の通りデフォルトでは go mod
が動かないが、明示的に $GO111MODULE=on
するだけで動くようになる。どうせ次のGo 1.13では、$GOPAH/src
だろうがなんだろうがデフォルトでgo mod
が動く。チュートリアルとかでは $GOPATH
の外でやることが案内されてるけど、そっちのほうがチュートリアル的には混乱がなく明確なのでそうしているだけで、別に出なくてもいいという理解をしている。
それに、現在移行期であることから、ツールチェイン類がまだmodule-awareモードに対応しておらず、GOPATHモードの方が嬉しい局面もある。具体的にはgoimportsなど。なので、逆に積極的にGOPATHから出る理由もないと考えています。
今のところは、開発時はGOPATHモードで良くて、CI/CD周りは、Go Modulesに寄せるのが良いと考えている。今は、Makefileに export GO111MODULE=on
ってべた書きしている。Dockerfileとかにも書くといいと思う。
https://github.com/Songmu/godzil/blob/master/Makefile#L8
ハマりどころや課題など
masterのHEADと最新のタグに乖離があるときに困る
Go Modulesは、そのパッケージのリポジトリにv1.2.3のようなsemverタグが打たれている場合は、masterのHEADではなく最新のsemverタグを取得する。これは良い仕様だと思う。
ただ、HEADと最新のタグの乖離が激しいときは困ることになる。実際、 github.com/octokit/go-octokit
でめちゃくちゃハマった。このライブラリの場合、最新のv0.4.0タグとHEADで依存パッケージまでことなるので、普通に go get
すると意図しない依存が書き出されて困ってしまった。
以下のように go get
で指定するパッケージに @master
を付与することで強制的にHEADに依存させることができるのでそれで凌いだ。
% GO111MODULE=on go get github.com/octokit/go-octokit@master
今後はライブラリ系のパッケージもsemverタグを付けていくのが望ましい。僕はビルドを提供したいツール類だけリリース時にタグを打っていたけど、ライブラリ系もちゃんと打っていきたい。godzilを作り始めた背景もそれ。
ちなみに、 go-octokit
はghch内部で使っているのですが、どうもメンテが止まっており、hub
コマンド内部でも使われなくなってしまったので、go-github
に乗り換えるのが良いかなーと思っています。
ツール類を go get
すると go.mod
が書き換わってしまう
module-awareモードの時に go get
をすると、それがツール類であっても無条件に go.mod
及び go.sum
を書き換えてしまう。例えば以下のように golint
を入れたい場合とかに困る。
% go get golang.org/x/lint/golint
これは今のところ以下のように強制的にmodule-awareモードを切ることで凌いでいる。
% GO111MODULE=off go get golang/x/lint/golint
https://github.com/Songmu/godzil/blob/master/Makefile#L16
議論はされていて、 go get -b <pkg>@<version>
のように -b
オプションを設けるのが有力のようです。
https://github.com/golang/go/issues/30515
依存先モジュールの編集をしたい場合に困る
バグ修正だったり挙動確認の為にログを差し込んだりとかのために依存先を変更したくなること、なんだかんだであると思います。
GOPATHモードの場合、依存先のコードを書きかえて動作を確認して、そのままシームレスにpull requestを送ったりもできて便利なのですが、module-awareモードの場合はそうもいきません。
これも以下で議論されていますが、go.modを一時的に書き換えてreplaceディレクティブを使う方法や、go mod vendor
でvendorを書き出してしまってそれを編集するなどのワークアラウンドが話されています。
https://github.com/golang/go/issues/27542
根本解決に関しては、優先度が下げられていて、方針も決まっていません。go.mod.local
を認識させる案も提案されていますが、個人的にはそれはあまり良い方法だとは思わないし、採用もされないだろうなと思っています。
これに関しては、GOPATHモードの方がやりやすさがあるので、今のところ個人的に開発時にGOPATHモードを利用したいモチベーションの一つにもなっています。
上記issueでも議論されていますが、mini CPANのようなローカルモジュールindexとしての $GOPATH/src
のわかりやすさが損なわれることについての対応策は今後の課題になりそうです。
完全な依存ツリーを作るために go mod tidy
を使う
僕も最近まで勘違いしていたのですが、 go mod tidy
は環境に関わらない完全な依存ツリーを構築するためのものと捉えたほうが良さそうです。「綺麗にする」「余計なものを消す」というイメージで使われていそうですが「整頓する」というのがイメージとしては正しそうです。
例えば、開発環境とは異なる特定OSのみ、例えばwindowsだけで利用されるパッケージを go.mod
に追加するには go mod tidy
を実行するだけです。go build
や go get
は、 GOOS、GOARCH、ビルドタグなどを見てしまうのでgo.mod
への最低限の依存の書き出ししかしませんが、go mod tidy
の場合は、それらを見ることなしに、つまり全てのgoファイルを読み込んで依存ツリーを構築してくれるようです。
これは少し分かりづらい挙動ですが、仕様であり明確にドキュメントされるべきだと以下で述べられています。
https://github.com/golang/go/issues/27633#issuecomment-420545604
go get
や go build
では go.mod
にtest dependencyが追加されず、go test
しないと追加されないことや、それなのに go mod tidy
では追加されてしまうことを不思議に思っていたのですが、先程書いたように、 go get
や go build
ではその環境での実行に必要な必要最低限の依存抽出しかせず、 go mod tidy
ではgoファイルの絞り込みを行わず全ての環境での依存を抽出してくれる仕様であるためだということがわかりました。
ghqを使っている場合
すでに書いたように $GOPATH/src
から無理に出る必要はないのでこれまで通り ghq
を使えば良いでしょう。逆に、Go 1.13以降は go get
の挙動が完全に変わってしまうため、これまでと同様の配置ルールでリポジトリ管理したい場合は、 ghq get
をより活用することになると思われる。
改めて個人的な結論
- Go Modulesは積極的に使っていけばいい
- 幾つか課題はある
$GOPATH
から出る必要もない$GO111MODULE
を適宜設定すればよい- どうせ次のGo 1.13からはどこに置こうが関係なくなる
課題についてはGitHub issueを探せば大体議論が出てくるので便利。必要なやつを適宜追いかけておけばよいでしょう。
ということで使っていきましょう。