Go 1.11 Modules でパッケージ管理を行う
これは Go4 Advent Calendar 2018 の 22 日目の記事です。
今年の 8 月に Go 1.11 がリリースされました。このバージョンでは Modules と呼ばれる新しいコンセプトが導入されています。これはバージョニングとパッケージ配布の仕組みで、 GOPATH 配下以外でのプロジェクト管理を可能にするものです。
これは Go 1.11 の時点では実験的な機能として提供されていて、環境変数 GO111MODULE
の設定やプロジェクトが GOPATH 配下にあるかどうかに応じて有効化されるようになっています。このバージョンでフィードバックを取り込みながら Go 1.12 で正式な機能としてリリースされる予定ということです。今回は現時点での Go Modules を試してみたいと思います。
Go 1.11 Modules で依存解決を行う
私は Web API の作成に goa を良く使っているので、その example である goa-cellar に Go 1.11 Modules を適用できるか試してみたいと思います。
1. GOPATH
外にプロジェクトを用意してそのルートに移動する
GOPATH
が ~/go
なので、その配下ではない ~/goa-cellar
に git clone
してプロジェクトルートに移動します。
$ git clone git@github.com:goadesign/goa-cellar.git ~/goa-cellar
$ cd ~/goa-cellar
2. Module 定義を初期化して go.mod
ファイルを作成する
go
コマンドには既にサブコマンドとして mod
が組み込まれているのでそれを使って go.mod
を作成します。
$ go mod init go: creating new go.mod: module github.com/goadesign/goa-cellar
3. Module をビルドする
Module をビルドします。 Go Modules ではビルドやテストのタイミングで必要な依存解決が行われます。
$ go build -o cellar go: finding github.com/goadesign/goa v1.4.0 go: finding github.com/satori/go.uuid v1.2.0 go: finding github.com/go-kit/kit v0.8.0 go: finding github.com/dimfeld/httptreemux v5.0.1+incompatible go: finding github.com/hashicorp/go-immutable-radix v1.0.0 go: finding github.com/go-logfmt/logfmt v0.4.0 go: finding github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da go: finding golang.org/x/net v0.0.0-20181217023233-e147a9138326 go: finding github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 go: finding github.com/hashicorp/go-uuid v1.0.0 go: finding github.com/hashicorp/golang-lru v0.5.0 go: downloading github.com/goadesign/goa v1.4.0 go: downloading github.com/go-kit/kit v0.8.0 go: downloading golang.org/x/net v0.0.0-20181217023233-e147a9138326 go: downloading github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da go: downloading github.com/dimfeld/httptreemux v5.0.1+incompatible go: downloading github.com/satori/go.uuid v1.2.0 go: downloading github.com/go-logfmt/logfmt v0.4.0 go: downloading github.com/hashicorp/go-immutable-radix v1.0.0 go: downloading github.com/hashicorp/golang-lru v0.5.0 # github.com/goadesign/goa/uuid ../go/pkg/mod/github.com/goadesign/goa@v1.4.0/uuid/uuid.go:18:23: not enough arguments in call to uuid.Must have (uuid.UUID) want (uuid.UUID, error)
おっと、エラーが出てしまいました。コードを見てみます。
https://github.com/goadesign/goa/blob/v1.4.0/uuid/uuid.go#L18
import "github.com/satori/go.uuid" // FromString Wrapper around the real FromString func FromString(input string) (UUID, error) { u, err := uuid.FromString(input) return UUID(u), err } // NewV4 Wrapper over the real NewV4 method func NewV4() UUID { return UUID(uuid.Must(uuid.NewV4())) }
これは https://github.com/satori/go.uuid の問題ですね。当初 uuid.Must() は uuid.UUID だけを返す関数だったのですが、あるとき破壊的変更が入って error
も返す形にシグネチャが変更されました。しかしその変更のあとに新たなバージョンのリリースは行われておらず master
ブランチだけに変更が入っている状態です。
goa はこの master
ブランチのコードを参照しているのですが、 Go Modules はデフォルトだとタグ付けされた最新のリリースバージョンを選択するため master
ではなく v1.2.0 が使われることになります。これがエラーの原因でした。
$ cat go.mod | grep github.com/satori/go.uuid github.com/satori/go.uuid v1.2.0 // indirect
このデフォルトのバージョン選択は @version
接尾辞を用いた go get
で変更することが出来るので、明示的に master
を参照するよう依存を変更します。
$ go get github.com/satori/go.uuid@master
go: finding github.com/satori/go.uuid master
go: downloading github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b
再度ビルドしてみます。
$ go build -o cellar go: finding github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b
今度は成功です。ではサーバを起動してリクエストを送信してみます。
$ ./cellar & [1] 27478 lvl=info msg=mount ctrl=Account action=Create route="POST /cellar/accounts" lvl=info msg=mount ctrl=Account action=Delete route="DELETE /cellar/accounts/:accountID" lvl=info msg=mount ctrl=Account action=List route="GET /cellar/accounts" lvl=info msg=mount ctrl=Account action=Show route="GET /cellar/accounts/:accountID" lvl=info msg=mount ctrl=Account action=Update route="PUT /cellar/accounts/:accountID" lvl=info msg=mount ctrl=Bottle action=Create route="POST /cellar/accounts/:accountID/bottles" lvl=info msg=mount ctrl=Bottle action=Delete route="DELETE /cellar/accounts/:accountID/bottles/:bottleID" lvl=info msg=mount ctrl=Bottle action=List route="GET /cellar/accounts/:accountID/bottles" lvl=info msg=mount ctrl=Bottle action=Rate route="PUT /cellar/accounts/:accountID/bottles/:bottleID/actions/rate" lvl=info msg=mount ctrl=Bottle action=Show route="GET /cellar/accounts/:accountID/bottles/:bottleID" lvl=info msg=mount ctrl=Bottle action=Update route="PATCH /cellar/accounts/:accountID/bottles/:bottleID" lvl=info msg=mount ctrl=Bottle action=Watch route="GET /cellar/accounts/:accountID/bottles/:bottleID/watch" lvl=info msg=mount ctrl=Public files=public/html/index.html route="GET /ui" lvl=info msg=mount ctrl=Js files=public/js route="GET /js/*filepath" lvl=info msg=mount ctrl=Js files=public/js/index.html route="GET /js/" lvl=info msg=mount ctrl=Swagger files=public/swagger/swagger.json route="GET /swagger.json" lvl=info msg=listen transport=http addr=:8081
$ http :8081/cellar/accounts req_id=pTlK94BRv4-1 lvl=info msg=started GET=/cellar/accounts from=::1 ctrl=Account action=list req_id=pTlK94BRv4-1 lvl=info msg=headers Accept=*/* Accept-Encoding="gzip, deflate" Connection=keep-alive User-Agent=HTTPie/1.0.2 req_id=pTlK94BRv4-1 lvl=info msg=completed status=200 bytes=114 time=203.016µs ctrl=Account action=list HTTP/1.1 200 OK Content-Length: 114 Content-Type: application/vnd.account+json; type=collection Date: Tue, 18 Dec 2018 13:58:15 GMT [ { "href": "/cellar/accounts/1", "id": 1, "name": "account 1" }, { "href": "/cellar/accounts/2", "id": 2, "name": "account 2" } ] $ kill 27478 [1]+ Terminated: 15 ./cellar
問題なさそうです。
依存としてのツール
goa はプロジェクトを構成するコードの大部分を自動生成する仕組みになっており、そのために goagen
という専用のツールを用意しています。 Go Modules は、そういったプロジェクトに必要となるツールの管理も出来るようになっています。 Go modules by example に詳しい手順が載っているので、これを参考に goagen
を管理してみます。
GOBIN
にツールのインストール先を設定する
Go はデフォルトだと go install
で作られたバイナリを $GOPATH/bin
に配置しますが、 $GOBIN
を設定することで対象のパスを変更することが出来ます。今回はツールもプロジェクトのディレクトリ配下に置くようにするため $PWD/bin
に設定します。
$ export GOBIN=$PWD/bin $ export PATH=$GOBIN:$PATH
ツールを import するためのファイルを作成する
ツールの package を import するためのファイルを作成します。先頭行のコメントは Build Constraints と呼ばれるもので、デフォルトのビルド対象から除外することが出来ます。
$ cat tools.go // +build tools package tools import ( _ "github.com/goadesign/goa/goagen" )
ツールをインストールする
ではツールをインストールしてみます。
$ go get github.com/goadesign/goa/goagen@master go: finding github.com/goadesign/goa/goagen master go: finding github.com/goadesign/goa master go: finding github.com/manveru/faker latest go: finding github.com/zach-klippenstein/goregen latest go: finding github.com/dimfeld/httppath latest go: finding github.com/spf13/pflag v1.0.3 go: finding github.com/spf13/cobra v0.0.3 go: finding golang.org/x/tools/go/ast/astutil latest go: downloading github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d go: downloading github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea go: finding gopkg.in/yaml.v2 v2.2.2 go: downloading github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 go: downloading github.com/spf13/pflag v1.0.3 go: downloading github.com/spf13/cobra v0.0.3 go: finding golang.org/x/tools/go/ast latest go: finding golang.org/x/tools/go latest go: finding golang.org/x/tools latest go: downloading golang.org/x/tools v0.0.0-20181218204010-d4971274fe38 go: downloading gopkg.in/yaml.v2 v2.2.2 go: finding gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405
$ tree ./bin/ ./bin/ └── goagen 0 directories, 1 file
$PWD/bin/
に goagen
が作成されました。
インストールしたツールを使う
このプロジェクトには Makefile
が含まれており、 make
することで以下のタスクが実行されるようになっています。
- 関連する package の
go get
goagen
によって前回生成されたコードの削除goagen
によるコードの再生成go build
go get
対象の goa のパッケージに @master
を付与しておきます。
diff --git a/Makefile b/Makefile index 9790e3a..80a82d7 100644 --- a/Makefile +++ b/Makefile @@ -14,9 +14,9 @@ # - all is the default target, it runs all the targets in the order above. # DEPEND= bitbucket.org/pkg/inflect \ - github.com/goadesign/goa \ - github.com/goadesign/goa/goagen \ - github.com/goadesign/goa/logging/logrus \ + github.com/goadesign/goa@master \ + github.com/goadesign/goa/goagen@master \ + github.com/goadesign/goa/logging/logrus@master \ github.com/sirupsen/logrus \ gopkg.in/yaml.v2 \ golang.org/x/tools/cmd/goimports
make
します。
$ make go: finding github.com/goadesign/goa/goagen master go: finding github.com/goadesign/goa/logging/logrus master go: finding github.com/goadesign/goa master go: finding github.com/goadesign/goa/logging master go: finding bitbucket.org/pkg/inflect latest go: finding golang.org/x/tools/cmd/goimports latest go: finding golang.org/x/tools/cmd latest go: finding golang.org/x/tools latest app app/contexts.go app/controllers.go app/hrefs.go app/media_types.go app/user_types.go app/test app/test/account_testing.go app/test/bottle_testing.go app/test/health_testing.go app/test/js_testing.go app/test/public_testing.go app/test/swagger_testing.go public/swagger public/swagger/swagger.json public/swagger/swagger.yaml public/schema public/schema/schema.json tool/cellar-cli tool/cellar-cli/main.go tool/cli tool/cli/commands.go client client/client.go client/account.go client/bottle.go client/health.go client/js.go client/public.go client/swagger.go client/user_types.go client/media_types.go public/js public/js/client.js public/js/axios.min.js public/js/index.html public/js/example.go
無事にビルドできました。
余談ですが、 goagen
は今まで GOPATH
を前提とした実装になっていたので Go Modules では動きませんでした。実は今回の記事を書くにあたって Go Modules をサポートする Pull request を出したので goa
をお使いの方は試してみてもらえると嬉しいです。 (記事の公開前にマージされて良かった😌)
依存解決でダウンロードされたパッケージ
実は今回の作業を始める前に $GOPATH/src
を空にしておいたので、念のため中身を確認してみます。
$ tree ~/go/src/ /Users/tchssk/go/src/ 0 directories, 0 files
何もないですね。
Go Modules は依存解決でダウンロードしたパッケージを $GOPATH/pkg/mod/
の配下にキャッシュするようです。
$ tree -L 2 ~/go/pkg/mod/ /Users/tchssk/go/pkg/mod/ ├── bitbucket.org │ └── pkg ├── cache │ ├── download │ └── vcs ├── github.com │ ├── armon │ ├── dimfeld │ ├── go-kit │ ├── go-logfmt │ ├── goadesign │ ├── hashicorp │ ├── manveru │ ├── satori │ ├── sirupsen │ ├── spf13 │ └── zach-klippenstein ├── golang.org │ └── x └── gopkg.in └── yaml.v2@v2.2.2 21 directories, 0 files
このキャッシュは go clean -modcache
で削除することが出来ます。
$ go clean -modcache $ tree -L 2 ~/go/pkg/mod/ /Users/tchssk/go/pkg/mod/ [error opening dir] 0 directories, 0 files
また、依存パッケージは go mod vendor
でプロジェクト内にコピーすることもできます。
$ go mod vendor go: finding github.com/hashicorp/go-immutable-radix v1.0.0 go: finding github.com/spf13/pflag v1.0.3 go: finding github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea go: finding github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da go: finding github.com/goadesign/goa v1.0.1-0.20181216194621-14b7b9950e87 go: finding github.com/sirupsen/logrus v1.2.0 go: finding github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d go: finding github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b go: finding golang.org/x/tools v0.0.0-20181220024903-92cdcd90bf52 go: finding golang.org/x/net v0.0.0-20181217023233-e147a9138326 go: finding github.com/go-logfmt/logfmt v0.4.0 go: finding github.com/pmezard/go-difflib v1.0.0 go: finding github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 go: finding golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 go: finding github.com/davecgh/go-spew v1.1.1 go: finding github.com/stretchr/testify v1.2.2 go: finding github.com/hashicorp/go-uuid v1.0.0 go: finding github.com/konsorten/go-windows-terminal-sequences v1.0.1 go: finding github.com/dimfeld/httptreemux v5.0.1+incompatible go: finding github.com/stretchr/objx v0.1.1 go: finding github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 go: finding github.com/go-kit/kit v0.8.0 go: finding github.com/spf13/cobra v0.0.3 go: finding github.com/hashicorp/golang-lru v0.5.0 go: finding golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 go: finding bitbucket.org/pkg/inflect v0.0.0-20130829110746-8961c3750a47 go: finding gopkg.in/yaml.v2 v2.2.2 go: finding gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 go: downloading github.com/goadesign/goa v1.0.1-0.20181216194621-14b7b9950e87 go: downloading golang.org/x/net v0.0.0-20181217023233-e147a9138326 go: downloading github.com/spf13/cobra v0.0.3 go: downloading github.com/go-kit/kit v0.8.0 go: downloading github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b go: downloading gopkg.in/yaml.v2 v2.2.2 go: downloading github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d go: downloading github.com/spf13/pflag v1.0.3 go: downloading github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 go: downloading github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da go: downloading github.com/dimfeld/httptreemux v5.0.1+incompatible go: downloading github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea go: downloading golang.org/x/tools v0.0.0-20181220024903-92cdcd90bf52 go: downloading github.com/hashicorp/go-immutable-radix v1.0.0 go: downloading github.com/hashicorp/golang-lru v0.5.0 go: finding github.com/inconshreveable/mousetrap v1.0.0 go: downloading github.com/go-logfmt/logfmt v0.4.0 go: downloading github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 go: finding google.golang.org/appengine v1.3.0 go: downloading github.com/inconshreveable/mousetrap v1.0.0 go: downloading google.golang.org/appengine v1.3.0 go: finding golang.org/x/net v0.0.0-20180724234803-3673e40ba225 go: finding github.com/golang/protobuf v1.2.0 go: finding golang.org/x/text v0.3.0 go: downloading github.com/golang/protobuf v1.2.0
$ tree -L 2 ./vendor/ ./vendor/ ├── github.com │ ├── armon │ ├── dimfeld │ ├── go-kit │ ├── go-logfmt │ ├── goadesign │ ├── golang │ ├── hashicorp │ ├── inconshreveable │ ├── kr │ ├── manveru │ ├── satori │ ├── spf13 │ └── zach-klippenstein ├── golang.org │ └── x ├── google.golang.org │ └── appengine ├── gopkg.in │ └── yaml.v2 └── modules.txt 20 directories, 1 file
Go Modules の今後
この記事を書いていたらちょうど The Go Blog で Go Modules の記事が公開されていました。
Go Modules in 2019 - The Go Blog
Go 1.12, scheduled for February 2019, will refine module support but still leave it in auto mode by default.
Our aim is for Go 1.13, scheduled for August 2019, to enable module mode by default (that is, to change the default from auto to on) and deprecate GOPATH mode.
2019 年 2 月予定の Go 1.12 は現在と同じようにデフォルトで auto
モードで動作するようですね。 Module モードをデフォルトにするのは 2019 年 8 月予定の Go 1.13 からの予定で、そのときには GOPATH モードを非推奨にするということです。楽しみですね。