おそらくはそれさえも平凡な日々

最近の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.modgo.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-octokitghch内部で使っているのですが、どうもメンテが止まっており、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 buildgo get は、 GOOS、GOARCH、ビルドタグなどを見てしまうのでgo.modへの最低限の依存の書き出ししかしませんが、go mod tidy の場合は、それらを見ることなしに、つまり全てのgoファイルを読み込んで依存ツリーを構築してくれるようです。

これは少し分かりづらい挙動ですが、仕様であり明確にドキュメントされるべきだと以下で述べられています。

https://github.com/golang/go/issues/27633#issuecomment-420545604

go getgo build では go.modにtest dependencyが追加されず、go test しないと追加されないことや、それなのに go mod tidy では追加されてしまうことを不思議に思っていたのですが、先程書いたように、 go getgo 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を探せば大体議論が出てくるので便利。必要なやつを適宜追いかけておけばよいでしょう。

ということで使っていきましょう。

created at
last modified at

2019-06-05T11:10:14+0900

comments powered by Disqus