はじめに

この記事はシリーズの第1部です。

注: モジュールを使用した依存関係の管理に関する文書については、依存関係の管理を参照してください。

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を作成します:

  1. package hello
  2. func Hello() string {
  3. return "Hello, world."
  4. }

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

  1. package hello
  2. import "testing"
  3. func TestHello(t *testing.T) {
  4. want := "Hello, world."
  5. if got := Hello(); got != want {
  6. t.Errorf("Hello() = %q, want %q", got, want)
  7. }
  8. }

この時点で、ディレクトリにはパッケージは含まれていますが、go.modファイルがないためモジュールは含まれていません。/home/gopher/helloで作業していて、今go testを実行すると、次のようになります:

  1. $ go test
  2. PASS
  3. ok _/home/gopher/hello 0.020s
  4. $

最後の行は全体のパッケージテストを要約しています。$GOPATHの外で作業しており、またモジュールの外でもあるため、goコマンドは現在のディレクトリのインポートパスを知らず、ディレクトリ名に基づいて偽のものを作成します:_/home/gopher/hello

go mod initを使用して現在のディレクトリをモジュールのルートにし、再度go testを試してみましょう:

  1. $ go mod init example.com/hello
  2. go: creating new go.mod: module example.com/hello
  3. $ go test
  4. PASS
  5. ok example.com/hello 0.020s
  6. $

おめでとうございます!最初のモジュールを書いてテストしました。

go mod initコマンドはgo.modファイルを書きました:

  1. $ cat go.mod
  2. module example.com/hello
  3. go 1.12
  4. $

go.modファイルはモジュールのルートにのみ表示されます。サブディレクトリ内のパッケージは、モジュールパスとサブディレクトリへのパスからなるインポートパスを持ちます。たとえば、サブディレクトリworldを作成した場合、go mod initをそこで実行する必要はありません(またはしたくありません)。パッケージは自動的にexample.com/helloモジュールの一部として認識され、インポートパスはexample.com/hello/worldになります。

依存関係の追加

Goモジュールの主な動機は、他の開発者が書いたコードを使用する体験(つまり、依存関係を追加すること)を改善することでした。

  1. ``````bash
  2. package hello
  3. import "rsc.io/quote"
  4. func Hello() string {
  5. return quote.Hello()
  6. }
  7. `

もう一度テストを実行してみましょう:

  1. $ go test
  2. go: finding rsc.io/quote v1.5.2
  3. go: downloading rsc.io/quote v1.5.2
  4. go: extracting rsc.io/quote v1.5.2
  5. go: finding rsc.io/sampler v1.3.0
  6. go: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
  7. go: downloading rsc.io/sampler v1.3.0
  8. go: extracting rsc.io/sampler v1.3.0
  9. go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
  10. go: extracting golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
  11. PASS
  12. ok example.com/hello 0.023s
  13. $

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/samplergolang.org/x/textもダウンロードしました。go.mod`````ファイルには直接の依存関係のみが記録されます:

  1. $ cat go.mod
  2. module example.com/hello
  3. go 1.12
  4. require rsc.io/quote v1.5.2
  5. $

2回目のgo testコマンドはこの作業を繰り返しません。go.modは現在最新であり、ダウンロードされたモジュールはローカルにキャッシュされています($GOPATH/pkg/mod):

  1. $ go test
  2. PASS
  3. ok example.com/hello 0.020s
  4. $
  1. 上記のように、1つの直接依存関係を追加すると、他の間接依存関係も持ち込まれることがよくあります。コマンド`````go list -m all`````は現在のモジュールとそのすべての依存関係をリストします:
  2. ``````bash
  3. $ go list -m all
  4. example.com/hello
  5. golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
  6. rsc.io/quote v1.5.2
  7. rsc.io/sampler v1.3.0
  8. $
  9. `

go listの出力では、現在のモジュール、別名メインモジュールが常に最初の行にあり、その後にモジュールパスでソートされた依存関係が続きます。

  1. `````go.mod`````に加えて、`````go`````コマンドは、特定のモジュールバージョンのコンテンツの期待される[暗号ハッシュ](https://golang.org/cmd/go/#hdr-Module_downloading_and_verification)を含む`````go.sum`````という名前のファイルを維持します:
  2. ``````bash
  3. $ cat go.sum
  4. golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZO...
  5. golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:Nq...
  6. rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3...
  7. rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPX...
  8. rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/Q...
  9. rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9...
  10. $
  11. `
  1. <a name="upgrading-dependencies"></a>
  2. ## 依存関係のアップグレード
  3. Goモジュールでは、バージョンはセマンティックバージョンタグで参照されます。セマンティックバージョンは3つの部分から成ります:メジャー、マイナー、パッチ。たとえば、`````v0.1.2`````の場合、メジャーバージョンは0、マイナーバージョンは1、パッチバージョンは2です。いくつかのマイナーバージョンのアップグレードを見てみましょう。次のセクションでは、メジャーバージョンのアップグレードを考えます。
  4. `````go list -m all`````の出力から、`````golang.org/x/text`````の未タグ付けバージョンを使用していることがわかります。最新のタグ付きバージョンにアップグレードし、すべてがまだ動作するかテストしてみましょう:
  5. ``````bash
  6. $ go get golang.org/x/text
  7. go: finding golang.org/x/text v0.3.0
  8. go: downloading golang.org/x/text v0.3.0
  9. go: extracting golang.org/x/text v0.3.0
  10. $ go test
  11. PASS
  12. ok example.com/hello 0.013s
  13. $
  14. `

やった!すべてが合格しました。go list -m allgo.modファイルをもう一度見てみましょう:

  1. $ go list -m all
  2. example.com/hello
  3. golang.org/x/text v0.3.0
  4. rsc.io/quote v1.5.2
  5. rsc.io/sampler v1.3.0
  6. $ cat go.mod
  7. module example.com/hello
  8. go 1.12
  9. require (
  10. golang.org/x/text v0.3.0 // indirect
  11. rsc.io/quote v1.5.2
  12. )
  13. $

golang.org/x/textパッケージは最新のタグ付きバージョン(v0.3.0)にアップグレードされました。go.modファイルもv0.3.0を指定するように更新されました。indirectコメントは、このモジュールによって直接使用されていない依存関係が、他のモジュールの依存関係によってのみ間接的に使用されていることを示しています。詳細についてはgo help modulesを参照してください。

次に、rsc.io/samplerマイナーバージョンをアップグレードしてみましょう。同じ方法で始め、go getを実行してテストを実行します:

  1. $ go get rsc.io/sampler
  2. go: finding rsc.io/sampler v1.99.99
  3. go: downloading rsc.io/sampler v1.99.99
  4. go: extracting rsc.io/sampler v1.99.99
  5. $ go test
  6. --- FAIL: TestHello (0.00s)
  7. hello_test.go:8: Hello() = "99 bottles of beer on the wall, 99 bottles of beer, ...", want "Hello, world."
  8. FAIL
  9. exit status 1
  10. FAIL example.com/hello 0.014s
  11. $

ああ!テストの失敗は、rsc.io/samplerの最新バージョンが私たちの使用法と互換性がないことを示しています。そのモジュールの利用可能なタグ付きバージョンをリストしてみましょう:

  1. $ go list -m -versions rsc.io/sampler
  2. rsc.io/sampler v1.0.0 v1.2.0 v1.2.1 v1.3.0 v1.3.1 v1.99.99
  3. $

私たちはv1.3.0を使用していましたが、v1.99.99は明らかに良くありません。代わりにv1.3.1を使用してみましょう:

  1. $ go get rsc.io/sampler@v1.3.1
  2. go: finding rsc.io/sampler v1.3.1
  3. go: downloading rsc.io/sampler v1.3.1
  4. go: extracting rsc.io/sampler v1.3.1
  5. $ go test
  6. PASS
  7. ok example.com/hello 0.022s
  8. $

go get引数の明示的な@v1.3.1に注意してください。一般的に、go getに渡される各引数は明示的なバージョンを取ることができます。デフォルトは@latestで、これは前述のように最新バージョンに解決されます。

新しいメジャーバージョンへの依存関係の追加

パッケージに新しい関数を追加しましょう:func ProverbはGoの並行性の格言を返し、quote.Concurrencyを呼び出します。これはモジュールrsc.io/quote/v3によって提供されます。まず、hello.goを更新して新しい関数を追加します:

  1. package hello
  2. import (
  3. "rsc.io/quote"
  4. quoteV3 "rsc.io/quote/v3"
  5. )
  6. func Hello() string {
  7. return quote.Hello()
  8. }
  9. func Proverb() string {
  10. return quoteV3.Concurrency()
  11. }

次に、hello_test.goにテストを追加します:

  1. func TestProverb(t *testing.T) {
  2. want := "Concurrency is not parallelism."
  3. if got := Proverb(); got != want {
  4. t.Errorf("Proverb() = %q, want %q", got, want)
  5. }
  6. }

次に、コードをテストできます:

  1. $ go test
  2. go: finding rsc.io/quote/v3 v3.1.0
  3. go: downloading rsc.io/quote/v3 v3.1.0
  4. go: extracting rsc.io/quote/v3 v3.1.0
  5. PASS
  6. ok example.com/hello 0.024s
  7. $

私たちのモジュールは現在、rsc.io/quotersc.io/quote/v3の両方に依存していることに注意してください:

  1. $ go list -m rsc.io/q...
  2. rsc.io/quote v1.5.2
  3. rsc.io/quote/v3 v3.1.0
  4. $

Goモジュールの異なるメジャーバージョン(v1v2など)は異なるモジュールパスを使用します:v2から始まり、パスはメジャーバージョンで終わる必要があります。この例では、v3rsc.io/quoteはもはやrsc.io/quoteではなく、rsc.io/quote/v3というモジュールパスによって識別されます。この規約はセマンティックインポートバージョニングと呼ばれ、互換性のないパッケージ(異なるメジャーバージョンを持つもの)に異なる名前を付けます。対照的に、v1.6.0rsc.io/quotev1.5.2と後方互換性があるべきであり、rsc.io/quoteという名前を再利用します。(前のセクションでは、rsc.io/sampler v1.99.99rsc.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.2rsc.io/quote v1.6.0の両方でビルドすることは不可能です。同時に、異なるメジャーバージョンのモジュールを許可すること(異なるパスを持つため)は、モジュールの消費者が新しいメジャーバージョンに段階的にアップグレードする能力を与えます。この例では、quote.Concurrencyrsc/quote/v3 v3.1.0から使用したいが、rsc.io/quote v1.5.2の使用を移行する準備がまだできていないことを示しています。段階的に移行する能力は、大規模なプログラムやコードベースでは特に重要です。

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

  1. ``````bash
  2. $ go doc rsc.io/quote/v3
  3. package quote // import "rsc.io/quote/v3"
  4. Package quote collects pithy sayings.
  5. func Concurrency() string
  6. func GlassV3() string
  7. func GoV3() string
  8. func HelloV3() string
  9. func OptV3() string
  10. $
  11. `
  1. ``````bash
  2. package hello
  3. import quoteV3 "rsc.io/quote/v3"
  4. func Hello() string {
  5. return quoteV3.HelloV3()
  6. }
  7. func Proverb() string {
  8. return quoteV3.Concurrency()
  9. }
  10. `

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

  1. package hello
  2. import "rsc.io/quote/v3"
  3. func Hello() string {
  4. return quote.HelloV3()
  5. }
  6. func Proverb() string {
  7. return quote.Concurrency()
  8. }

すべてが正常に動作していることを確認するために、テストを再実行しましょう:

  1. $ go test
  2. PASS
  3. ok example.com/hello 0.014s

未使用の依存関係の削除

私たちはrsc.io/quoteのすべての使用を削除しましたが、go list -m allgo.modファイルにはまだ表示されています:

  1. $ go list -m all
  2. example.com/hello
  3. golang.org/x/text v0.3.0
  4. rsc.io/quote v1.5.2
  5. rsc.io/quote/v3 v3.1.0
  6. rsc.io/sampler v1.3.1
  7. $ cat go.mod
  8. module example.com/hello
  9. go 1.12
  10. require (
  11. golang.org/x/text v0.3.0 // indirect
  12. rsc.io/quote v1.5.2
  13. rsc.io/quote/v3 v3.0.0
  14. rsc.io/sampler v1.3.1 // indirect
  15. )
  16. $

なぜですか?単一のパッケージをビルドする場合、go buildgo testのように、何かが欠けていて追加する必要があることは簡単にわかりますが、何かを安全に削除できるかどうかはわかりません。依存関係を削除するには、モジュール内のすべてのパッケージとそれらのパッケージのすべてのビルドタグの組み合わせを確認する必要があります。通常のビルドコマンドはこの情報を読み込まず、したがって依存関係を安全に削除することはできません。

go mod tidyコマンドは、これらの未使用の依存関係をクリーンアップします:

  1. $ go mod tidy
  2. $ go list -m all
  3. example.com/hello
  4. golang.org/x/text v0.3.0
  5. rsc.io/quote/v3 v3.1.0
  6. rsc.io/sampler v1.3.1
  7. $ cat go.mod
  8. module example.com/hello
  9. go 1.12
  10. require (
  11. golang.org/x/text v0.3.0 // indirect
  12. rsc.io/quote/v3 v3.1.0
  13. rsc.io/sampler v1.3.1 // indirect
  14. )
  15. $ go test
  16. PASS
  17. ok example.com/hello 0.020s
  18. $

結論

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.modおよびgo.sumファイルを追加することをお勧めします。Goにおける依存関係管理の未来を形作るためにフィードバックを提供し、バグレポート体験レポートを送信してください。

すべてのフィードバックとモジュール改善へのご協力に感謝します。