The Go Blog
Go Modules の利用
はじめに
この記事は連載のパート1です。
- パート1 — Go Modules の利用 (この記事)
- パート2 — Go Modules への移行
- パート3 — Go Modules の公開
- パート4 — Go Modules: v2 以降
- パート5 — モジュールの互換性を保つ
注: モジュールで依存関係を管理する方法については、依存関係の管理を参照してください。
Go 1.11 および 1.12 では、Go の新しい依存関係管理システムであるモジュールの予備的なサポートが含まれており、依存関係のバージョン情報を明示的かつ管理しやすくなっています。このブログ記事は、モジュールを使い始めるために必要な基本的な操作の紹介です。
モジュールとは、go.mod ファイルをルートとするファイルツリーに保存されたGo パッケージの集まりです。go.mod ファイルは、モジュールの_モジュールパス_ (ルートディレクトリに使用されるインポートパスでもあります) と、正常なビルドに必要な他のモジュールである_依存関係要件_を定義します。各依存関係要件は、モジュールパスと特定のセマンティックバージョンとして記述されます。
Go 1.11 以降、go コマンドは、現在のディレクトリまたは親ディレクトリに go.mod が存在し、かつそのディレクトリが $GOPATH/src の_外側_にある場合に、モジュールの使用を有効にします。($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 のどのモジュールにも提供されていないパッケージの import を検出すると、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
$
go.mod が最新であり、ダウンロードされたモジュールがローカルにキャッシュされている ($GOPATH/pkg/mod に) ため、2回目の go test コマンドはこの作業を繰り返しません。
$ go test
PASS
ok example.com/hello 0.020s
$
go コマンドが新しい依存関係の追加を迅速かつ簡単にする一方で、それにはコストがかかることに注意してください。あなたのモジュールは、正確性、セキュリティ、適切なライセンスなど、重要な分野で文字通り新しい依存関係に_依存_しています。さらに検討が必要な点については、Russ Cox のブログ記事「Our Software Dependency Problem」を参照してください。
上で見たように、1つの直接の依存関係を追加すると、他の間接的な依存関係も持ち込むことがよくあります。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 で、以前に定義された最新バージョンに解決されます。
新しいメジャーバージョンへの依存関係の追加
パッケージに新しい関数を追加しましょう: func 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つです。rsc.io/quote が1つ、rsc.io/quote/v2 が1つ、rsc.io/quote/v3 が1つなどです。これにより、モジュール作成者は単一のモジュールパスの重複に関する明確なルールを得られます。プログラムが 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/v3"
Package quote collects pithy sayings.
func Concurrency() string
func GlassV3() string
func GoV3() string
func HelloV3() string
func OptV3() string
$
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 の依存関係管理の将来についてフィードバックを提供し、その形成に協力するために、バグレポートまたは体験レポートを私たちに送ってください。
モジュールの改善にご協力いただいたすべてのフィードバックに感謝します。
次の記事: Go 1.12 でデプロイするものをデバッグする
前の記事: 新しい Go 開発者ネットワーク
ブログインデックス