今更ながらGo Modulesについて簡単にまとめた。 そして今月(2019年2月)にリリースされる予定のGo1.12に含まれるGo Modules関連の仕様変更について調べた。
なお、この記事はGo 1.12 Release Party登壇に関する補足資料となる。
イベント名 | Go 1.12 Release Party in Tokyo w/ Fukuoka&Umeda |
---|---|
URL | https://gocon.connpass.com/event/118022/ |
会場 | 株式会社メルカリ 東京都港区六本木6-10-1(六本木ヒルズ森タワー18F) |
日時 | 2019/02/15(金) 19:30 〜 22:00 |
ハッシュタグ | #go112party |
TL;DR
- Go Modules(vgo)はGo1.11から導入され始めたGoの新しいバージョン管理
- Go1.12ではまだ有効にはなっていない(Go1.13からはデフォルトで有効になる)
- Go Modulesの概要とTipsなどを簡単にまとめた
- Go1.12のModules関連の変更をDockerを動かして確認してみた
GOPATHi
外でgo.mod
がなくてもgo run
可能replace
ディレクティブで依存パッケージをローカルのコードを使って解決- etc…
確認に利用したDockerfileやスクリプトは以下のリポジトリにある。
Modulesの概要
Go Modulesは2018年2月にRuss Coxさんが発表したGoのプロジェクトの依存パッケージに対するバージョン管理構想だ。
最初はvgo
と呼ばれ、Goとは独立したCLIツールとして提供されており、Go1.11からGo本体に試験的に導入された。
- Go & Versioning | research!rsc
vgo
発表前のGoのバージョン管理は以下のような手法が取られていた。
go get
でgo1
タグ・ブランチもしくは最新のmaster
ブランチを取得するglide
やdep
などのサードパーティ製ツールでバージョン管理をする
2017年、2018年に新規にプロジェクトを作るのならばdep
で管理することが多かったように思う。
(vvakameさんの記事を見たりして私はvgo
への移行は様子見していた。)
2019/02/16追記 Modulesはvgoよりはいい子になったとのこと。
vgo?go modulesはvgoに比べるといい子になったよ!って伝えておいて!
— わかめ@毎日猫がいる (@vvakame) 2019年2月15日
Minimal Version Selection
Go ModulesはSemantic Versioningに基づいてモジュールの管理を行なう。Semantic Versioningとは、vX.X.X
というようなバージョン番号の定義方法だ。
- Semantic Versioning 2.0.0
Semantic Import Versioningより引用
Go以外のプログラミング言語・フレームワークでも依存ライブラリのバージョン管理は行われているが、Go Modulesは可能な限り古いバージョンを採用するのが特徴的だ。
- Minimal Version Selection | research!rsc
2つのモード
- Module-aware go get
Go1.11、Go1.12のGo ModulesはGOPATH modeとmodule-aware modeという2つのモードを有している。 大まかな違いは以下の通り。
- GOPATH mode
- バージョン 1.10 までと同じ挙動をする。
- 標準pkg以外を全部
GOPATH
以下のディレクトリで管理する - パッケージの管理はリポジトリの最新リビジョンのみが対象となる
- module-aware mode
- 標準pkg以外の全てのパッケージをモジュールとして管理する
- モジュールの管理やビルドが任意のディレクトリで可能になる,
- モジュールはリポジトリのバージョンタグまたはリビジョン毎に管理される
2つのモードはGO111MODULE
という環境変数で切り替わる。
on
常にmodule-aware modeで動作するoff
常にGOPATH modeで動作するauto
$GOPATH
配下ではGOPATH modeで,それ以外のディレクトリではmodule-aware modeで動作する
Go1.11のときはデフォルトの挙動がauto
だったので、何もしなければ今までと同じ挙動をする。
Go1.12もGo1.11と同じようにauto
がデフォルト状態になっている。ただし半年後にリリースされるGo1.13ではGO111MODULE=on
がデフォルトとなる予定だ。
- Go Modules in 2019
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.
go modコマンド
Modulesの機能はモジュールのルートディレクトリに配置するgo.mod
ファイルとgo mod
コマンドから利用することができる。
すでに解説記事がいくつかあるのでここではgo.mod
ファイルについてだけ触れる。
go mod
コマンドの概要は以下の通り。
$ go help mod
Go mod provides access to operations on modules.
Note that support for modules is built into all the go commands,
not just 'go mod'. For example, day-to-day adding, removing, upgrading,
and downgrading of dependencies should be done using 'go get'.
See 'go help modules' for an overview of module functionality.
Usage:
go mod <command> [arguments]
The commands are:
download download modules to local cache
edit edit go.mod from tools or scripts
graph print module requirement graph
init initialize new module in current directory
tidy add missing and remove unused modules
vendor make vendored copy of dependencies
verify verify dependencies have expected content
why explain why packages or modules are needed
Use "go help mod <command>" for more information about a command.
依存関係はgo.mod
ファイルに書き出される。go.mod
ファイルはgo mod init
コマンドで作成することができる(go mod download
をするとGemfile.Lock
やpackage-lock.json
のようにgo.sum
ファイルも生成される)。
いくつかの依存関係を含んだgo.mod
ファイルは以下のようになる。
module example.com/m
replace (
example.com/a => ./a
example.com/a/b => ./b
)
require (
example.com/a/b v0.0.0-00010101000000-000000000000
example.com/v v1.12.0
example.com/x/v3 v3.0.0-00010101000000-000000000000
example.com/y v0.0.0-00010101000000-000000000000
)
go.mod
の中のディレクティブ(構成要素)の概要は以下の通り(Go1.12では後述するgo
ディレクティブも増える)。
module
ルートディレクトリのモジュール名require
必要なモジュール名とバージョン名を指定するexclude
明示的に除外するモジュールを指定するreplace
require
で指定したモジュール名を置き換える
go mod
コマンドの利用法は以下のブログが日本語で詳しい。
- モジュール対応モードへの移行を検討する
また、go/src/cmd/go/testdata/script
にgo
コマンド自体のテストスクリプトがあるのでそこからgo mod
コマンドの使い方を探ることもできる。
- go/src/cmd/go/testdata/script
Tips
Go Modulesに関するTipsは以下の通り。
go mod
で取得したバイナリなどは$GOPATH/pkg/mod/
以下にキャッシュされている- なのでCIの高速化でビルドキャッシュをするときはこのディレクトリをキャッシュする必要がある
- キャッシュを削除するときは
go clean -cache
コマンドで削除できる go mod vendor
コマンドでdep
のようにvendor
ディレクトリに依存関係を保存することができる
go mod tidy
でgo.mod
から不要な依存関係を削除できる- 不用意な
go get
で依存関係が増えてしまうことがあるのでツールをgo get
したときはやったほうがよい
- 不用意な
2019/02/16追記 tidy
は「タイディ」と読むとのこと(Perlでも使われている)。
ピピーッ、tidy タイディ警察です。dan the interruptされるぞ!
— songmu (@songmu) 2019年2月15日
CIでの設定例はちまきんさんやだっくさんが公開してくれている。
- timakin/go-module | CircleCI Orbs Registory
- GitHub Actions のワークフローで Go 1.11 Modules のキャッシュを扱う | This is my life.
Go 1.12の変更
Go1.12のリリースノートに記載されているModules関連の変更を確認しておく。
なお、全ての変更はmodule-aware mode(GO111MODULE=on
)の場合である。
- Go 1.12 Release Notes
確認(1.11との比較)はGo1.11.5とGo1.12rc1のDockerイメージ上で同じコマンドを実行することで行なった。
比較につかったDockerfileやスクリプトなどは以下のリポジトリにある。
モジュール管理外でもgo get
などができるようになった
1.11のときはgo.mod
がないディレクトリでgo get
をすると失敗していたが、1.12ではそれが解消された。
(ただ、go.mod
があるディレクトリで行なうとgo get
で取得した情報をgo.mod
に追加してしまうので注意が必要)。
# Go1.11 at outside module and GOPATH
$ GO111MODULE=on go get -u golang.org/x/lint/golint
go: cannot find main module; see 'go help modules'
make: *** [run111_go_get] Error 1
# Go1.12 at outside module and GOPATH
$ GO111MODULE=on go get -u golang.org/x/lint/golint
go: finding golang.org/x/lint/golint latest
go: finding golang.org/x/lint latest
go: downloading golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1
go: extracting golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1
go: finding golang.org/x/tools/go/gcexportdata latest
go: finding golang.org/x/tools/go/ast/astutil latest
go: finding golang.org/x/tools/go latest
go: finding golang.org/x/tools/go/ast latest
go: finding golang.org/x/tools latest
go: downloading golang.org/x/tools v0.0.0-20190214204934-8dcb7
go.mod
がなくてもどこでもgo run
ができるようになった。
# Go1.11 in moudle outside
$ GO111MODULE=on go run -v main.go
go: cannot find main module; see 'go help modules'
make: *** [run111_run_module_on_outside] Error 1
# Go1.12 in moudle outside
$ GO111MODULE=on go run -v main.go
Fetching https://gopkg.in/pressly/chi.v2?go-get=1
Parsing meta tags from https://gopkg.in/pressly/chi.v2?go-get=1 (status code 200)
get "gopkg.in/pressly/chi.v2": found meta tag get.metaImport{Prefix:"gopkg.in/pressly/chi.v2", VCS:"git", RepoRoot:"https://gopkg.in/pressly/chi.v2"} at https://gopkg.in/pressly/chi.v2?go-get=1
go: finding gopkg.in/pressly/chi.v2 v2.1.0
...
go env GOMOD
が/dev/null
を報告する
カレントディレクトリで参照しているgo.mod
ファイルを示すGOMOD
変数がある。
module-aware modeの時、Go1.11では参照するgo.mod
ファイルがないと空白文字列を返していたが、Go1.12は/dev/null
を返すようになった。
# Go1.11 with GO111MODULE=on
root@2d5cbf159edc:/tmp/test# GO111MODULE=on go env|grep GOMOD
GOMOD=""
# Go1.12 with GO111MODULE=on
root@a36b23164e3d:/tmp/test# GO111MODULE=on go env|grep GOMOD
GOMOD="/dev/null"
言語バージョンが保存される。
go.mod
にgo
ディレクティブが追加され、そのモジュール内のファイルで使用されている言語バージョンを示すようになった。
go.mod
に記載がない場合は、現在のリリースバージョンが設定される(1.12扱い)。
root@b83f6e3b9a00:/tmp/test# GO111MODULE=on go mod init test
go: creating new go.mod: module test
root@b83f6e3b9a00:/tmp/test# cat go.mod
module test
go 1.12
replace
でローカルのディレクトリのバージョン別に参照できるようになった
Go1.11でもreplace
ディレクティブを使ってルートディレクトリ以下のディレクトリに対して相対的にreplace
ディレクティブをおけるようになっていた。
Go1.12からは同じmodulesをバージョンごとに異なるディレクトリとして設定できるようになった。(発表後@hajimehoshiさんに指摘いただきました。ありがとうございます)。
バージョンの指定はv0.0.0-00010101000000-000000000000
のようになる。例えば以下のようなgo.mod
ファイルと適切なローカルディレクトリを用意しておく。
module example.com/m
replace (
example.com/a => ./a
example.com/a/b => ./b
)
replace (
example.com/x => ./x
example.com/x/v3 => ./v3
)
replace (
example.com/y => ./y
example.com/y/z/w => ./w
)
replace (
example.com/v => ./v
example.com/v v1.11.0 => ./v11
example.com/v v1.12.0 => ./v12
)
Go1.11で上記のgo.mod
はexample.com/v
の部分のパースに失敗してビルドできない。
$ GO111MODULE=on go build -o test.exe
go: errors parsing go.mod:
/tmp/replace_import/go.mod:19: invalid module path
/tmp/replace_import/go.mod:20: invalid module path
/tmp/replace_import/go.mod:21: invalid module path
Go1.12ではビルドに成功し、require
ディレクティブが自動生成される。
module example.com/m
replace (
example.com/a => ./a
example.com/a/b => ./b
)
replace (
example.com/x => ./x
example.com/x/v3 => ./v3
)
replace (
example.com/y => ./y
example.com/y/z/w => ./w
)
replace (
example.com/v => ./v
example.com/v v1.11.0 => ./v11
example.com/v v1.12.0 => ./v12
)
require (
example.com/a/b v0.0.0-00010101000000-000000000000
example.com/v v1.12.0
example.com/x/v3 v3.0.0-00010101000000-000000000000
example.com/y v0.0.0-00010101000000-000000000000
)
今後の動向を知るためには
ModulesはGo1.13からはデフォルトで有効になる。1.13に向けて準備をしたいとき、最新の仕様を確認したいときは以下を確認する。 まず、計画されているModulesの2019年のプランは以下に記載されている。
- Go Modules in 2019
また、未リリース版のGoの仕様は以下で確認できる。
- The Go Programming Language(tip)
Modulesの利用に関してissueなどでtipsが見つかったときは、以下のwikiの追加されている。
- Modules | golang/go wiki
さっとbeta版やrc版を試したいときはdockerイメージを使ってみれば良いだろう。
- golang images | docker hub
終わりに
depのファイルなどからGo Modulesへの移行は簡単に行える。
正直私はまだdep
を使っていたのだが、Go1.12を契機にそろそろModulesへの移行を始めようと思う(依存しているライブラリもちゃんとgo.mod
を配置していないと辛い模様)。
ただ、まだ理解できていないこともあるのでGo1.13までにはちゃんと使えるようになっておきたい。
Go1.13以降のGo Modulesについて(2019/11/02追記)
Go1.13からはGo Modulesに加えてプロキシのエコシステムが加わった。また、$GO111MODULE=auto
の挙動も若干変わっている。
くわしくは@ktrさんのブログ記事を読むといいだろう。
参考
- Go & Versioning | research!rsc
- Go Modules in 2019
- Modules | golang/go wiki
- Goにおけるバージョン管理の必要性 − vgoについて −
- モジュール対応モードへの移行を検討する
- Understand Go Modules / Go Modulesを理解する
- Semantic Versioning 2.0.0