Goブログ
Goモジュールを使う
はじめに
この投稿はシリーズのパート1です。
- パート1 — Goモジュールを使う (この記事)
- パート2 — Goモジュールへの移行
- パート3 — Goモジュールの公開
- パート4 — Goモジュール:v2以降
- パート5 — モジュールの互換性を維持する
注意:モジュールを使用した依存関係の管理に関するドキュメントについては、依存関係の管理を参照してください。
Go 1.11および1.12には、モジュールのサポート、依存関係のバージョン情報を明示的にし、管理を容易にするGoの新しい依存関係管理システムが含まれています。このブログ投稿では、モジュールを使い始めるために必要な基本的な操作を紹介します。
モジュールは、ルートにgo.mod
ファイルがあるファイルツリーに保存されたGoパッケージのコレクションです。go.mod
ファイルは、モジュールのモジュールパス(ルートディレクトリに使用されるインポートパスでもある)と、ビルドを成功させるために必要な他のモジュールである依存関係の要件を定義します。各依存関係の要件は、モジュールパスと特定のセマンティックバージョンとして記述されます。
Go 1.11の時点では、現在のディレクトリまたは親ディレクトリにgo.mod
があり、ディレクトリが$GOPATH/src
の外にある場合、goコマンドはモジュールの使用を有効にします。($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/world
のexample.com/hello
モジュールの一部として自動的に認識されます。
依存関係の追加
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
$
2回目のgo test
コマンドは、go.mod
が最新であり、ダウンロードされたモジュールがローカルにキャッシュされている($GOPATH/pkg/mod
内)ため、この作業を繰り返しません
$ go test
PASS
ok example.com/hello 0.020s
$
go
コマンドを使用すると、新しい依存関係をすばやく簡単に追加できますが、コストがかからないわけではないことに注意してください。モジュールは、正しさ、セキュリティ、適切なライセンスなど、重要な領域で新しい依存関係に文字通り依存するようになりました。その他の考慮事項については、Russ Coxのブログ投稿「ソフトウェアの依存関係の問題」を参照してください。
上記で見たように、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
、rsc.io/quote/v2
、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開発者ネットワーク
ブログインデックス