この記事は The Go Blog - Using Go Modules の和訳です。
はじめに
Go 1.11および1.12には、依存バージョン情報を明示的で管理しやすくする、Goの 新しい依存管理システム である モジュール の予備サポートが含まれています。このブログ記事は、モジュールを使い始めるために必要な基本的な操作を紹介するチュートリアルです。この次の記事では、他の人が使用するためのモジュールのリリース方法について説明します。
モジュールは、そのルートに go.mod
ファイルを持つファイルツリーに格納された Goパッケージ の集まりです。 go.mod
ファイルは、モジュールのモジュールパス(ルートディレクトリに使用されるインポートパス)とその依存関係の要件(ビルドを成功させるために必要な他のモジュール)も定義します。各依存関係の要件は、モジュールパスと特定の セマンティックバージョン として記述されています。
Go 1.11以降、goコマンドは、カレントディレクトリまたは任意の親ディレクトリに $GOPATH/src
の外側にある go.mod
がある場合にモジュールを使用できるようにします。 ($GOPATH/src
内では、互換性のため、go.mod
が見つかってもgoコマンドは古いGOPATHモードで実行されます。詳細については goコマンドのドキュメント を参照してください。)Go 1.13以降は、モジュールモードがデフォルトになります。
この記事では、モジュールを使用してGoコードを開発するときに発生する一連の一般的な操作について説明します。
新しいモジュールの作成
新しいモジュールを作成しましょう。
$GOPATH/src
の外に新しい空のディレクトリを作成し、そのディレクトリに cd
して、新しいファイル hello.go
を作成します:
package hello
func Hello() string {
return "Hello, world."
}
hello_test.go
にテストも書きましょう:
package hello
import "testing"
func TestHello(t *testing.T) {
want := "Hello, world."
if got := Hello(); got != want {
t.Errorf("Hello() = %q, want %q", got, want)
}
}
この時点では、ディレクトリにはパッケージが含まれていますが、モジュールは含まれていません。これは、go.mod
ファイルがないためです。/home/gopher/hello
で作業していて、go test
を実行したとしたら、次のようになります:
$ go test
PASS
ok _/home/gopher/hello 0.020s
$
最後の行はパッケージ全体のテストのサマリです。私たちは $GOPATH
の外で作業しており、どのモジュール内にも入っていないため、goコマンドは現在のディレクトリのインポートパスを知らず、ディレクトリ名から見せかけのパス _/home/gopher/hello
を作成します。
go mod init
を使用して現在のディレクトリをモジュールのルートにしてから、もう一度 go test
を試してみましょう:
$ go mod init example.com/hello
go: creating new go.mod: module example.com/hello
$ go test
PASS
ok example.com/hello 0.020s
$
おめでとうございます!はじめてのモジュールの作成とテストができました。
go mod init
コマンドは go.mod
ファイルを生成します:
$ cat go.mod
module example.com/hello
go 1.12
$
go.mod
ファイルはモジュールのルートにのみ現れます。 サブディレクトリ内のパッケージには、モジュールパスとサブディレクトリへのパスで構成されるインポートパスがあります。 たとえば、サブディレクトリworldを作成した場合、そこに go mod init
を実行する必要はありません(むしろ、するべきではありません)。パッケージは自動的に example.com/hello
モジュールの一部として認識され、インポートパスは example.com/hello/world
になります。
依存関係の追加
Goモジュールの主な動機は、他の開発者によって書かれたコードを使用する(つまり、依存関係を追加する)経験を向上させることでした。
hello.go
を更新して rsc.io/quote
をインポートし、Hello
の実装に使用しましょう:
package hello
import "rsc.io/quote"
func Hello() string {
return quote.Hello()
}
テストを再実行しましょう:
$ go test
go: finding rsc.io/quote v1.5.2
go: downloading rsc.io/quote v1.5.2
go: extracting rsc.io/quote v1.5.2
go: finding rsc.io/sampler v1.3.0
go: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
go: downloading rsc.io/sampler v1.3.0
go: extracting rsc.io/sampler v1.3.0
go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
go: extracting golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
PASS
ok example.com/hello 0.023s
$
goコマンドは、go.mod
にリストされている特定の依存関係モジュールのバージョンを使用してインポートを解決します。 go.mod
のどのモジュールからも提供されていないパッケージのインポートを検出すると、goコマンドは自動的にそのパッケージを含むモジュールを検索し、最新バージョンを使ってそれを go.mod
に追加します。 (「最新」は、最新のタグ付き安定版(プレリリース版ではない)、または最新のタグ付きプレリリース版、または最新のタグなし版として定義されています。)
この例では、go test
によって新しいインポート rsc.io/quote
がモジュール rsc.io/quote v1.5.2
に解決されました。 また、rsc.io/quote
によって使用される2つの依存関係、すなわち rsc.io/sampler
と golang.org/x/text
もダウンロードしました。
直接の依存関係だけが go.mod
ファイルに記録されます:
$ cat go.mod
module example.com/hello
go 1.12
require rsc.io/quote v1.5.2
$
2回目の go test
コマンドでは、この作業は繰り返されません。 go.mod
が最新のものであり、ダウンロードされたモジュールがローカル($GOPATH/pkg/mod
)にキャッシュされているためです:
$ go test
PASS
ok example.com/hello 0.020s
$
goコマンドを使用すると新しい依存関係をすばやく簡単に追加できますが、コストがかからないわけではありません。あなたのモジュールは、正当性、セキュリティ、適切なライセンスなどの重要な分野で新しい依存関係に依存しています。その他の考慮事項については、Russ Coxのブログ投稿 Our Software Dependency Problem を参照してください。
上で見たように、一つの直接の依存関係を追加することはしばしば他の間接的な依存関係ももたらします。 go list -m all
コマンドは、現在のモジュールとそのすべての依存関係を一覧表示します:
$ go list -m all
example.com/hello
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0
$
go list
の出力では、現在のモジュールは メインモジュール とも呼ばれ、常に最初の行に表示され、その後にモジュールパスでソートされた依存関係が続きます。
golang.org/x/text
のバージョン v0.0.0-20170915032832-14c0d48ead0c
は、特定のタグなしコミットに対する goコマンドのバージョン構文である、疑似バージョンの例です。
go.mod
に加えて、goコマンドは特定のモジュールバージョンの内容の予想される暗号化ハッシュを含む go.sum
という名前のファイルを保持します。
$ cat go.sum
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZO...
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:Nq...
rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3...
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPX...
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/Q...
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9...
$
goコマンドは go.sum
ファイルを使用して、これらのモジュールの将来のダウンロードが最初のダウンロードと同じビットを取得するようにして、プロジェクトが依存しているモジュールが悪意、偶然、またはその他の理由で予期せず変更されないようにします。go.mod
と go.sum
の両方をバージョン管理するべきでしょう。
依存関係の更新
Goモジュールでは、バージョンはセマンティックバージョンタグで参照されます。 セマンティックバージョンには、メジャー、マイナー、パッチの3つの部分があります。 たとえば v0.1.2の場合、メジャーバージョンは0、マイナーバージョンは1、パッチバージョンは2です。いくつかのマイナーバージョンアップグレードを見てみましょう。 次のセクションでは、メジャーバージョンのアップグレードを検討します。
go list -m all
の出力から、タグなしバージョンの golang.org/x/text
を使用していることがわかります。 最新のタグ付きバージョンにアップグレードして、すべてがまだ機能することをテストしましょう。
$ go get golang.org/x/text
go: finding golang.org/x/text v0.3.0
go: downloading golang.org/x/text v0.3.0
go: extracting golang.org/x/text v0.3.0
$ go test
PASS
ok example.com/hello 0.013s
$
やったね! ʕ◔ϖ◔ʔ すべて通りました。 go list -m all
と go.mod
ファイルをもう一度見てみましょう:
$ go list -m all
example.com/hello
golang.org/x/text v0.3.0
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0
$ cat go.mod
module example.com/hello
go 1.12
require (
golang.org/x/text v0.3.0 // indirect
rsc.io/quote v1.5.2
)
$
golang.org/x/text
パッケージが最新のタグ付きバージョン(v0.3.0)にアップグレードされました。 go.mod
ファイルもv0.3.0を指定するように更新されました。 indirect
コメントは、依存関係がこのモジュールによって直接使用されず、他のモジュール依存関係によって間接的にのみ使用されることを示します。 詳細は go help modules
を見てください。
それでは、rsc.io/sampler
マイナーバージョンをアップグレードしてみましょう。同じように、 go get
を実行してテストを実行します:
$ go get rsc.io/sampler
go: finding rsc.io/sampler v1.99.99
go: downloading rsc.io/sampler v1.99.99
go: extracting rsc.io/sampler v1.99.99
$ go test
--- FAIL: TestHello (0.00s)
hello_test.go:8: Hello() = "99 bottles of beer on the wall, 99 bottles of beer, ...", want "Hello, world."
FAIL
exit status 1
FAIL example.com/hello 0.014s
$
うっ。テスト失敗は rsc.io/sampler
の最新版が私達の用法と互換性がないことを示します。 このモジュールの利用可能なタグ付きバージョンをリストしましょう:
$ go list -m -versions rsc.io/sampler
rsc.io/sampler v1.0.0 v1.2.0 v1.2.1 v1.3.0 v1.3.1 v1.99.99
$
私たちは以前v1.3.0を使っていました。v1.99.99はダメそうです。ためしにv1.3.1を使ってみましょう:
$ go get rsc.io/sampler@v1.3.1
go: finding rsc.io/sampler v1.3.1
go: downloading rsc.io/sampler v1.3.1
go: extracting rsc.io/sampler v1.3.1
$ go test
PASS
ok example.com/hello 0.022s
$
go get
引数の明示的な @v1.3.1
に注意してください。 一般的に go get
に渡される各引数は明示的なバージョンを取ることができます。 デフォルトは @latest
で、これは以前に定義されたとおりの最新バージョンに解決されます。
新しいメジャーバージョンへの依存関係の追加
パッケージに新しい関数を追加しましょう。Proverb
関数は、rsc.io/quote/v3
モジュールで提供されるquote.Concurrency
を呼び出すことによって、Goの並行処理に関する金言を返します。 まず、hello.go
を更新して新しい関数を追加します:
package hello
import (
"rsc.io/quote"
quoteV3 "rsc.io/quote/v3"
)
func Hello() string {
return quote.Hello()
}
func Proverb() string {
return quoteV3.Concurrency()
}
次に hello_test.go
にテストを追加します:
func TestProverb(t *testing.T) {
want := "Concurrency is not parallelism."
if got := Proverb(); got != want {
t.Errorf("Proverb() = %q, want %q", got, want)
}
}
テストしてみましょう:
$ go test
go: finding rsc.io/quote/v3 v3.1.0
go: downloading rsc.io/quote/v3 v3.1.0
go: extracting rsc.io/quote/v3 v3.1.0
PASS
ok example.com/hello 0.024s
$
今回のモジュールは rsc.io/quote
と rsc.io/quote/v3
の両方に依存していることに注意してください:
$ go list -m rsc.io/q...
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.1.0
$
Goモジュールの異なるメジャーバージョン(v1、v2など)はそれぞれ異なるモジュールパスを使用します。v2以降は、パスはメジャーバージョンで終わる必要があります。この例では、rsc.io/quote
のv3は rsc.io/quote
ではなくなり、rsc.io/quote/v3
で識別されます。この規則はセマンティックインポートのバージョン管理と呼ばれ、互換性のないパッケージ(異なるメジャーバージョンを持つもの)には異なる名前を付けます。対照的に、rsc.io/quote
のv1.6.0はv1.5.2と後方互換性があるはずなので、rsc.io/quote
という名前を再利用します。(前のセクションでは、rsc.io/sampler
v1.99.99は rsc.io/sampler
v1.3.0と下位互換性があるはずでしたが、モジュールの動作に関するバグやクライアントの誤った仮定が発生することもあります。)
goコマンドでは、ビルドで特定のモジュールパスから最大1つ(各メジャーバージョンから最大1つ)を含めることができます: io/quote
を一つ、io/quote/v2
を一つ、io/quote/v3
を一つ... など。これにより、モジュール作成者は単一のモジュールパスの重複の可能性について明確な規則を得ることができます。プログラムが rsc.io/quote v1.5.2
と rsc.io/quote v1.6.0
の両方でビルドすることは不可能です。同時に、モジュールのメジャーバージョンが異なると(パスが異なるため)、モジュール利用者は新しいメジャーバージョンに段階的にアップグレードすることができます。この例では、rsc/quote/v3 v3.1.0
の quote.Concurrency
を使用したいのですが、rsc.io/quote v1.5.2
の使用を移行する準備はまだできていません。段階的に移行する機能は、大規模なプログラムやコードベースでは特に重要です。
依存関係の新しいメジャーバージョンへのアップグレード
rsc.io/quote
から rsc.io/quote/v3
のみの使用への変換を完了させましょう。 メジャーバージョンの変更により、一部のAPIは削除、名前変更、または互換性のない方法で変更された可能性があります。 ドキュメントを読むと、Hello
が HelloV3
になったことがわかります。
$ go doc rsc.io/quote/v3
package quote // import "rsc.io/quote"
Package quote collects pithy sayings.
func Concurrency() string
func GlassV3() string
func GoV3() string
func HelloV3() string
func OptV3() string
$
(出力では既知のバグにより、表示されたインポートパスより /v3
が誤って削除されています。)
hello.go
で quote.Hello()
の使用部分を quoteV3.HelloV3()
に更新できます。
package hello
import quoteV3 "rsc.io/quote/v3"
func Hello() string {
return quoteV3.HelloV3()
}
func Proverb() string {
return quoteV3.Concurrency()
}
この時点で、名前を変更したインポートはもう必要ないので、元に戻すことができます:
package hello
import "rsc.io/quote/v3"
func Hello() string {
return quote.HelloV3()
}
func Proverb() string {
return quote.Concurrency()
}
テストを再実行して、すべてが機能していることを確認しましょう:
$ go test
PASS
ok example.com/hello 0.014s
未使用の依存関係の削除
rsc.io/quote
の使用をすべて削除しましたが、それでも go list -m all
および go.mod
には表示されます。
$ go list -m all
example.com/hello
golang.org/x/text v0.3.0
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1
$ cat go.mod
module example.com/hello
go 1.12
require (
golang.org/x/text v0.3.0 // indirect
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.0.0
rsc.io/sampler v1.3.1 // indirect
)
$
なぜでしょう?それは go build
や go test
のように単一のパッケージをビルドする際、何かが足りなくなって追加する必要があるときは簡単にわかりますが、安全に何かを削除できるときはわからないからです。 依存関係の削除は、モジュール内のすべてのパッケージ、およびそれらのパッケージに対して可能なすべてのビルドタグの組み合わせを確認した後にのみ実行できます。 通常のビルドコマンドではこの情報は読み込まれないため、依存関係を安全に削除することはできません。
go mod tidy
コマンドは、これらの未使用の依存関係をクリーンアップします:
$ go mod tidy
$ go list -m all
example.com/hello
golang.org/x/text v0.3.0
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1
$ cat go.mod
module example.com/hello
go 1.12
require (
golang.org/x/text v0.3.0 // indirect
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1 // indirect
)
$ go test
PASS
ok example.com/hello 0.020s
$
まとめ
GoモジュールはGoの依存関係管理の未来です。 モジュール機能は現在、サポートされているすべてのGoバージョン(つまり、Go 1.11およびGo 1.12)で使用可能です。
この記事では、Goモジュールを使用してこれらのワークフローを紹介しました。
-
go mod init
は新しいモジュールを作成し、それを記述するgo.mod
ファイルを初期化します。 -
go build
やgo test
、およびその他のパッケージビルドを行うコマンドは、必要に応じてgo.mod
に新しい依存関係を追加します。 -
go list -m all
は現在のモジュールの依存関係を表示します。 -
go get
は依存関係の、必要なバージョンを更新(または追加)します。 -
go mod tidy
は未使用の依存関係を削除します。
私達は、あなたがローカルでの環境でモジュールを使い始め、go.mod
と go.sum
を追加することをお勧めします。 Goの依存関係管理の将来を形作るのを助けるために、ぜひバグレポートや経験レポートを送ってください。
フィードバックを提供し、モジュールの改善にご協力いただき、ありがとうございます。
By Tyler Bui-Palsulich and Eno Compton