普段あまり Go を使わない人が仕事で使ってるソースが突然 Go Modules になって困って調べたメモ。
問題の背景
Go では GOPATH という特殊な概念があって、Go のコードはライブラリも含めてすべて $GOPATH/src
以下に置くという約束になっている。そうするとプロジェクトごとにライブラリのバージョンを変えたい場合に困るので、プロジェクト固有のライブラリは $GOPATH/src/プロジェクト/vendor
の下に入れても良いことになっている。そうすると go コマンドは vendor 以下を優先して見てくれる。面白い事に、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 の説明のために作られたパッケージのようです。
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 のルールに従って 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" のように明示的に指定する必要がある。
package main
import (
"fmt"
"rsc.io/quote/v2"
)
func main() {
fmt.Println(quote.HelloV2())
}
別の言い方をすると、メジャーバージョンが違うモジュールは import name が異なるので全然別のモジュールとして扱われる。
明示的にインストールするライブラリのバージョンの決め方
一旦必要なバージョンが go.mod に記載されると、それ以降は go コマンドは勝手にライブラリのバージョンを上げない。これでバイナリ作成時の依存バージョンを特定出来る。
一方で、2つのライブラリが共通のライブラリを利用していて、マイナーバージョンやパッチバージョンが違う場合は高いバージョンが選択される。なのでライブラリ作成者の観点からすると、呼び出し元がより高いバージョンのライブラリを使ってリンクする事もあるという事になる(本当かな。。。。?)
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)
用語
- package とは、ソースコード内に
package hoge
のように指定する。だいたいディレクトリの事。 - module とは、go.mod ファイル内に
module example.com/user/hoge
のように指定する。複数の package を含むもの。