Go Modules

普段あまり Go を使わない人が仕事で使ってるソースが突然 Go Modules になって困って調べたメモ。

## 問題の背景

Go では GOPATH という特殊な概念があって、Go のコードはライブラリも含めてすべて `$GOPATH/src` 以下に置くという約束になっている。そうするとプロジェクトごとにライブラリのバージョンを変えたい場合に困るので、プロジェクト固有のライブラリは `$GOPATH/src/プロジェクト/vendor` の下に入れても良いことになっている。そうすると go コマンドは [vendor 以下を優先して見てくれる](https://golang.org/cmd/go/#hdr-Vendor_Directories)。面白い事に、`vendor` ディレクトリの管理は govendor 等色々なサードパーティツールを使う。

将来の Go 1.12 からこのやり方を改め、`$GOPATH/src` や `vendor` は廃止となり Go Modules という物を採用する事になった。現在の Go 1.11 は移行期だが、環境変数 GO111MODULE により Go Modules を試す事が出来る。Go 1.11 で Go Module mode にするには次のようにする。

    export GO111MODULE=on 

## Go Modules の使い方

https://github.com/golang/go/wiki/Modules#quick-start-example のサンプルを試す。

GOPATH を指定しない状態で空のディレクトリを作り、中で

    go mod init example.com/hoge/hello

を実行すると go.mod ファイルが出来る。続けて以下のようなファイル hello.go を作る。`rsc.io/quote` は Go Modules の説明のために作られたパッケージのようです。


```go
package main

import (
	"fmt"
	"rsc.io/quote"
)

func main() {
	fmt.Println(quote.Opt())
}
```

    go build

でビルドすると、なんと勝手にライブラリ "rsc.io/quote" とその依存ファイルをダウンロードしてビルドしてくれる! ライブラリは ~/go/pkg/mod/ に保存されてました。試しに GOPATH を別の場所に設定すると `$GOPATH/pkg/mod/` に保存されてました。また、go install するとやはり $GOPATH/bin にバイナリがコピーされました。ということは次の事が言えます。

> Go Modules ではプロジェクトを GOPATH の外に置けるが、ライブラリのキャッシュや go install のコピー先として GOPATH (デフォルトで ~/go) が使われる。

知ってる人にとっては当たり前なんでしょうが、ドキュメントを読んでも GOPATH を使わないという事ばかり強調されてるので理解してませんでした。

## デフォルトでインストールするライブラリのバージョンの決め方

自動生成された go.mod の内容はこんな感じ。依存ライブラリのバージョンが勝手に指定されている。

```
module example.com/hoge/hello

require rsc.io/quote v1.5.2
```

`go build` コマンドはどうやって使うバージョンを v1.5.2 と決めたのだろうか?

まず [Remote import paths](https://golang.org/cmd/go/#hdr-Remote_import_paths) のルールに従って import path からレポジトリの位置を特定する。この例では https://rsc.io/quote にアクセスして rcs.io/quote のありかを meta タグで探す。

```
% curl 'https://rsc.io/quote'
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="go-import" content="rsc.io/quote git https://github.com/rsc/quote">
<meta http-equiv="refresh" content="0; url=https://godoc.org/rsc.io/quote">
</head>
<body>
Redirecting to docs at <a href="https://godoc.org/rsc.io/quote">godoc.org/rsc.io/quote</a>...
</body>
</html>
```

ここで https://github.com/rsc/quote が見つかった。

このレポジトリにはいくつかの tag があるが、同一メジャーバージョンの中でいちばん高いバージョンが選ばれる。この例 `rsc.io/quote` ではバージョン指定が無いので v1 が指定されたと見なす。v2 以上を指定したい時は import "rsc.io/quote/v2" のように明示的に指定する必要がある。

```go
package main

import (
	"fmt"
	"rsc.io/quote/v2"
)

func main() {
	fmt.Println(quote.HelloV2())
}
```

別の言い方をすると、メジャーバージョンが違うモジュールは import name が異なるので全然別のモジュールとして扱われる。

## 明示的にインストールするライブラリのバージョンの決め方

一旦必要なバージョンが go.mod に記載されると、それ以降は go コマンドは勝手にライブラリのバージョンを上げない。これでバイナリ作成時の依存バージョンを特定出来る。

一方で、２つのライブラリが共通のライブラリを利用していて、マイナーバージョンやパッチバージョンが違う場合は高いバージョンが選択される。なのでライブラリ作成者の観点からすると、呼び出し元がより高いバージョンのライブラリを使ってリンクする事もあるという事になる(本当かな。。。。?)

## [Go get](thttps://golang.org/cmd/go/#hdr-Module_aware_go_get)

    go get -u

で go.mod を更新し、最新のマイナーバージョン、パッチバージョンをキャッシュにダウンロードする。

今までと同様 go get でパッケージのビルドとインストールを行う事も出来る。`$GOPATH/bin` にインストールされる事も同じ。`$GOPATH/src` は使われない。

    go get -u github.com/gogo/protobuf/protoc-gen-gogoslick

`go get` で明示的にパッケージをインストールすると go.mod に使わないライブラリのカスが貯まるので、以下のコマンドで使うやつだけに整理する。

    go mod tidy

## デフォルト動作

`GO111MODULE` が未定義の時は次のような動作になる。

* `GOPATH` の中にいる時: 昔ながらの動作になる。`GO111MODULE=off` の動作
    * `go get` した物は `$GOPATH/src` に最新版が保存される。
* `GOPATH` の外かつ go.mod が存在するツリーの中にいる時: Go Modules が有効になる。`GO111MODULE=on` の動作
    * `GOPATH` が未定義の時は `$HOME/go` が `GOPATH` になる。
    * `go get` した物は `$GOPATH/pkg` にバージョンごとに保存される。

## protobuf で使う時の落とし穴

Go Modules が有効だと `$GOPATH/src` にソースコードが置かれないので、protobuf からインクルード出来ない。protobuf 用には `GO111MODULE=off go get なんとか` のようにする必要がある。(参考: [README.md: fix install instructions when used with modules](https://github.com/golang/protobuf/issues/751))

## 用語

* package とは、ソースコード内に `package hoge` のように指定する。だいたいディレクトリの事。
* module とは、go.mod ファイル内に `module example.com/user/hoge` のように指定する。複数の package を含むもの。