GOPATHを掃除してGo Modulesに移行しよう

こんにちは! ソーシャルゲーム事業部の川添 (@acidlemon) です。

この記事は Tech KAYAC Advent Calendar Migration Trackの8日目です。 今年はみなさまどんな一年だったでしょうか。私もいろいろありましたが、最近ちょっと私がやってたマイグレーションというと自分の開発環境のGOPATHの掃除がありますので、今日はその話を書こうと思います。

2018年までのGOPATH

GOPATHというと、昔からGo言語をゴリゴリ書いていた人にはおなじみの、 GOPATH=$HOME/go みたいなのを設定して、go get github.com/kayac/ecspresso すると $GOPATH/src の下に $GOPATH/src/github.com/kayac/ecspresso みたいなディレクトリができてそこにソースコードがcloneされるみたいなやつです。

で、go modが主流になるまでは、go get github.com/kayac/ecspresso を実行すると ecspresso が 依存するいろんなGoのパッケージ、たとえば github.com/aws/aws-sdk-gogithub.com/mattn/go-isattygithub.com/morikuni/aecgithub.com/Songmu/prompter などなどが $GOPATH/src の下にcloneされます。

……というのを数年やっていると、気がつくと $GOPATH/src の下は大量のリポジトリであふれかえります。

ちょっと私の手元で du -h -d 2 してどのぐらい散らかっているか確認したのを一部抜粋すると

5.3M    ./github.com/jinzhu
356K    ./github.com/joho
3.8M    ./github.com/stamblerre
316K    ./github.com/cenkalti
1.3M    ./github.com/sirupsen
29M     ./github.com/sourcegraph
300K    ./github.com/philhofer
1.2M    ./github.com/soh335
1.1M    ./github.com/go-martini
175M    ./github.com/Microsoft
336K    ./github.com/konsorten
4.3M    ./github.com/Songmu
752K    ./github.com/mholt
1.6M    ./github.com/codegangsta
396K    ./github.com/Azure
508K    ./github.com/Masterminds
336K    ./github.com/methane
380K    ./github.com/mizkei
192M    ./github.com/aws
972K    ./github.com/nicksnyder
356K    ./github.com/uudashr
768K    ./github.com/BurntSushi
1.7M    ./github.com/pkg
1.3M    ./github.com/jmoiron
22M     ./github.com/hiryma
6.5M    ./github.com/ulikunitz
704K    ./github.com/sebest
27G     ./github.com/kayac
620K    ./github.com/nsf
24M     ./github.com/mattn
348K    ./github.com/pborman
632K    ./github.com/monochromegane
3.5M    ./github.com/tdewolff

こんなような感じで大量のリポジトリで溢れています。まぁしかたないよね。

2019年のGOPATH

2019年はGo 1.12(2月)、Go 1.13(8月)のリリースがあり、Go Modulesの動作を標準でいい感じにする GO111MODULE 環境変数のデフォルト値 が auto に設定されました。 元々きいてた話だと1.13からは on になって移行が促進されるような話だった気がしますが実際はautoのままで、GOPATHの下かどうかに関係なく基本的には go.mod があれば go get するときに module-aware modeになっているはずです(GOPATH以下のautoの挙動変更が1.13から入りました)。

module-aware modeだと、go getして一緒に降ってくる依存ライブラリは $GOPATH/pkg/mod の下に入るようになりました。これによって、 $GOPATH/src の下が散らからなくなりました。 また、地味にGOPATHモードと違ってgitをcloneするわけではないので $GOPATH/pkg/mod の下には .git ディレクトリが作られないため、今までよりもローカルディスクの使用量がエコになるわけです。

デフォルトでGo Modules環境に強制してしまう

Go 1.13からはgo envという環境変数を列挙するサブコマンドに -w オプションが追加されて、 go env -w GO111MODULE=on とやるとgoコマンドのデフォルトの環境変数を設定することが出来るようになりました。この設定は $HOME/.config/go/env ファイルに保存されるようです。

ということで、これを実行すると、今後のgo get などが全部基本module-aware modeになります。つまり、$GOPATH/src の下にはいつのまにか依存でcloneされたディレクトリが生えた〜 みたいなことがなくなります。今こそ、$GOPATH/src の掃除のときですね。

メキッ バキバキバキッ バサッ (rm -rfでたくさん削除する音)

さて、これでお掃除は完了です。.gitたくさん消えたので結構容量あいたはず! と思いきや意外と減ってない。たしかにゲーム開発で.gitが数百MB〜数GBのプロジェクトばっかり取り扱ってて感覚がマヒしてましたが、普通のgoのパッケージしか入ってないようなプロジェクトは大きくても数MBくらいなのでそんなに容量が空かないのでした。でも $GOPATH/src/github.com の下がサッパリしただけでも気持ちよい。「なんか $GOPATH/src の下が散らかってるよなぁ」という気持ちの問題は解決しました。

手持ちのプロジェクトをGo Modulesに移行しよう

ここからはおまけです(いちおうマイグレーションTrackなのでこれも書こうということで……)。

GO111MODULE=on になるとgo.modファイルのないプロジェクトで go get すると何が起きるのか、どうすればいいのかの話も書いておきます。

go.mod なしで go getするとエラーになる

(*'-')< project$ go get
go: cannot find main module, but found .git/config in /path/to/project
    to create a module there, run:
    go mod init

その通り、エラーになりますが、どうすればいいか書いてあるので大丈夫です。go mod init していきましょう。このときに注意点があって、起こることが2〜3パターンに分かれます。具体的にはどういうディレクトリで go mod init したかによります。

  1. $GOPATH/src/github.com/acidlemon/hoge のような GOPATHの下の元々の場所
  2. $GOPATH/src/projects/hoge のような GOPATHの下だけどちょっと移動させた場所
  3. GOPATHの下じゃない、 $HOME/projects/hoge のような場所

GOPATHの下で実行すると、 $GOPATH/src/ の下のパスがモジュール名として採用されるので、1の場合は module github.com/acidlemon/hoge になります。これはまぁ普通ですが、2の場合は自動で module projects/hoge というモジュール名になってしまいます。これはちょっと困ります。

GOPATHの外で実行すると、以下のようにエラーとなるので、go mod init に追加の引数としてモジュールパス名を追加する必要があります。

(*'-')< hoge$ go mod init
go: cannot determine module path for source directory /home/kawazoel/projects/hoge (outside GOPATH, module path must be specified)
Example usage:
    'go mod init example.com/m' to initialize a v0 or v1 module
    'go mod init example.com/m/v2' to initialize a v2 module
Run 'go help mod init' for more information.

ということで、go mod init するときはとりあえず追加引数でモジュールパスを必ず入れておくようにするのがお勧めという感じです。それさえやっとけばあとは go get してgo.modに依存関係を入れてgo.modファイルだけ git add すればGo Modulesへの移行は完了! という感じです(昔からGOPATHにおいてあったソースコードと比較して、エイヤーでgo getして全てが最新化されたときに互換性が壊れてないという前提です……)。

それでは、みなさまよいお年をお迎えください〜