Edited at

The Go Blog - Using Go Modules / Go Modulesを使う(和訳)

この記事は 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 を作成します:


hello.go

package hello

func Hello() string {
return "Hello, world."
}


hello_test.go にテストも書きましょう:


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 の実装に使用しましょう:


hello.go

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/samplergolang.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.modgo.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 allgo.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 を更新して新しい関数を追加します:


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 にテストを追加します:


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/quotersc.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.2rsc.io/quote v1.6.0 の両方でビルドすることは不可能です。同時に、モジュールのメジャーバージョンが異なると(パスが異なるため)、モジュール利用者は新しいメジャーバージョンに段階的にアップグレードすることができます。この例では、rsc/quote/v3 v3.1.0quote.Concurrency を使用したいのですが、rsc.io/quote v1.5.2 の使用を移行する準備はまだできていません。段階的に移行する機能は、大規模なプログラムやコードベースでは特に重要です。


依存関係の新しいメジャーバージョンへのアップグレード

rsc.io/quote から rsc.io/quote/v3 のみの使用への変換を完了させましょう。 メジャーバージョンの変更により、一部のAPIは削除、名前変更、または互換性のない方法で変更された可能性があります。 ドキュメントを読むと、HelloHelloV3 になったことがわかります。

$ 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.goquote.Hello() の使用部分を quoteV3.HelloV3() に更新できます。


hello.go

package hello

import quoteV3 "rsc.io/quote/v3"

func Hello() string {
return quoteV3.HelloV3()
}

func Proverb() string {
return quoteV3.Concurrency()
}


この時点で、名前を変更したインポートはもう必要ないので、元に戻すことができます:


hello.go

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 buildgo 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 buildgo test、およびその他のパッケージビルドを行うコマンドは、必要に応じて go.mod に新しい依存関係を追加します。


  • go list -m all は現在のモジュールの依存関係を表示します。


  • go get は依存関係の、必要なバージョンを更新(または追加)します。


  • go mod tidy は未使用の依存関係を削除します。

私達は、あなたがローカルでの環境でモジュールを使い始め、go.modgo.sum を追加することをお勧めします。 Goの依存関係管理の将来を形作るのを助けるために、ぜひバグレポートや経験レポートを送ってください。

フィードバックを提供し、モジュールの改善にご協力いただき、ありがとうございます。

By Tyler Bui-Palsulich and Eno Compton