はじめに
この記事はシリーズの第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コマンドは、現在のディレクトリまたは親ディレクトリのいずれかにgo.mod
がある場合にモジュールの使用を有効にします。ただし、ディレクトリは$GOPATH/src
の外部である必要があります。($GOPATH/src
の内部では、互換性のために、goコマンドはgo.mod
が見つかっても古い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モジュールの主な動機は、他の開発者が書いたコードを使用する体験(つまり、依存関係を追加すること)を改善することでした。
``````bash
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
にリストされている特定の依存関係モジュールバージョンを使用してインポートを解決します。import
のパッケージがgo.mod
のいずれかのモジュールによって提供されていない場合、go
コマンドは自動的にそのパッケージを含むモジュールを検索し、最新バージョンを使用してgo.mod
に追加します。(「最新」とは、最新のタグ付き安定版(非[プレリリース](https://semver.org/#spec-item-9))バージョン、または最新のタグ付きプレリリースバージョン、または最新の未タグ付けバージョンを定義します。)私たちの例では、`````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
$
上記のように、1つの直接依存関係を追加すると、他の間接依存関係も持ち込まれることがよくあります。コマンド`````go list -m all`````は現在のモジュールとそのすべての依存関係をリストします:
``````bash
$ 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
の出力では、現在のモジュール、別名メインモジュールが常に最初の行にあり、その後にモジュールパスでソートされた依存関係が続きます。
`````go.mod`````に加えて、`````go`````コマンドは、特定のモジュールバージョンのコンテンツの期待される[暗号ハッシュ](https://golang.org/cmd/go/#hdr-Module_downloading_and_verification)を含む`````go.sum`````という名前のファイルを維持します:
``````bash
$ 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...
$
`
<a name="upgrading-dependencies"></a>
## 依存関係のアップグレード
Goモジュールでは、バージョンはセマンティックバージョンタグで参照されます。セマンティックバージョンは3つの部分から成ります:メジャー、マイナー、パッチ。たとえば、`````v0.1.2`````の場合、メジャーバージョンは0、マイナーバージョンは1、パッチバージョンは2です。いくつかのマイナーバージョンのアップグレードを見てみましょう。次のセクションでは、メジャーバージョンのアップグレードを考えます。
`````go list -m all`````の出力から、`````golang.org/x/text`````の未タグ付けバージョンを使用していることがわかります。最新のタグ付きバージョンにアップグレードし、すべてがまだ動作するかテストしてみましょう:
``````bash
$ 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
はGoの並行性の格言を返し、quote.Concurrency
を呼び出します。これはモジュールrsc.io/quote/v3
によって提供されます。まず、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
から始まり、パスはメジャーバージョンで終わる必要があります。この例では、v3
のrsc.io/quote
はもはやrsc.io/quote
ではなく、rsc.io/quote/v3
というモジュールパスによって識別されます。この規約はセマンティックインポートバージョニングと呼ばれ、互換性のないパッケージ(異なるメジャーバージョンを持つもの)に異なる名前を付けます。対照的に、v1.6.0
のrsc.io/quote
はv1.5.2
と後方互換性があるべきであり、rsc.io/quote
という名前を再利用します。(前のセクションでは、rsc.io/sampler
v1.99.99
はrsc.io/sampler
v1.3.0
と後方互換性があるはずでしたが、バグやモジュールの動作に関するクライアントの誤った仮定が発生することがあります。)
go
コマンドは、特定のモジュールパスのバージョンを1つだけ含むビルドを許可します。つまり、各メジャーバージョンの1つだけです:1つのrsc.io/quote
、1つのrsc.io/quote/v2
、1つのrsc.io/quote/v3
、などです。これにより、モジュールの著者は単一のモジュールパスの重複の可能性について明確なルールを持つことができます:プログラムがrsc.io/quote v1.5.2
とrsc.io/quote v1.6.0
の両方でビルドすることは不可能です。同時に、異なるメジャーバージョンのモジュールを許可すること(異なるパスを持つため)は、モジュールの消費者が新しいメジャーバージョンに段階的にアップグレードする能力を与えます。この例では、quote.Concurrency
をrsc/quote/v3 v3.1.0
から使用したいが、rsc.io/quote v1.5.2
の使用を移行する準備がまだできていないことを示しています。段階的に移行する能力は、大規模なプログラムやコードベースでは特に重要です。
依存関係を新しいメジャーバージョンにアップグレード
``````bash
$ 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
$
`
``````bash
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における依存関係管理の未来を形作るためにフィードバックを提供し、バグレポートや体験レポートを送信してください。
すべてのフィードバックとモジュール改善へのご協力に感謝します。