My External Storage

Feb 15, 2019 - 10 minute read - Comments - go

Go Modulesの概要とGo1.12に含まれるModulesに関する変更 #golangjp #go112party

今更ながら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本体に試験的に導入された。

vgo発表前のGoのバージョン管理は以下のような手法が取られていた。

2017年、2018年に新規にプロジェクトを作るのならばdepで管理することが多かったように思う。 (vvakameさんの記事を見たりして私はvgoへの移行は様子見していた。)

2019/02/16追記 Modulesはvgoよりはいい子になったとのこと。

Minimal Version Selection

Go ModulesはSemantic Versioningに基づいてモジュールの管理を行なう。Semantic Versioningとは、vX.X.Xというようなバージョン番号の定義方法だ。

Semantic Versioning Semantic Import Versioningより引用

Go以外のプログラミング言語・フレームワークでも依存ライブラリのバージョン管理は行われているが、Go Modulesは可能な限り古いバージョンを採用するのが特徴的だ。

2つのモード

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 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.Lockpackage-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/scriptgoコマンド自体のテストスクリプトがあるのでそこからgo modコマンドの使い方を探ることもできる。

Tips

Go Modulesに関するTipsは以下の通り。

  • go modで取得したバイナリなどは$GOPATH/pkg/mod/以下にキャッシュされている
    • なのでCIの高速化でビルドキャッシュをするときはこのディレクトリをキャッシュする必要がある
    • キャッシュを削除するときはgo clean -cacheコマンドで削除できる
    • go mod vendorコマンドでdepのようにvendorディレクトリに依存関係を保存することができる
  • go mod tidygo.modから不要な依存関係を削除できる
    • 不用意なgo getで依存関係が増えてしまうことがあるのでツールをgo getしたときはやったほうがよい

2019/02/16追記 tidyは「タイディ」と読むとのこと(Perlでも使われている)。

CIでの設定例はちまきんさんやだっくさんが公開してくれている。

Go 1.12の変更

Go1.12のリリースノートに記載されているModules関連の変更を確認しておく。 なお、全ての変更はmodule-aware mode(GO111MODULE=on)の場合である。

確認(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.modgoディレクティブが追加され、そのモジュール内のファイルで使用されている言語バージョンを示すようになった。 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.modexample.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の利用に関してissueなどでtipsが見つかったときは、以下のwikiの追加されている。

さっとbeta版やrc版を試したいときはdockerイメージを使ってみれば良いだろう。

終わりに

depのファイルなどからGo Modulesへの移行は簡単に行える。 正直私はまだdepを使っていたのだが、Go1.12を契機にそろそろModulesへの移行を始めようと思う(依存しているライブラリもちゃんとgo.modを配置していないと辛い模様)。 ただ、まだ理解できていないこともあるのでGo1.13までにはちゃんと使えるようになっておきたい。

参考

関連記事