はじめに

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

注意: ドキュメントについては、依存関係の管理およびモジュールの開発と公開を参照してください。

Goプロジェクトは、さまざまな依存関係管理戦略を使用します。ベンダリングツールであるdepglideは人気がありますが、動作に大きな違いがあり、常にうまく連携するわけではありません。一部のプロジェクトは、GOPATHディレクトリ全体を単一のGitリポジトリに保存します。他のプロジェクトは、単にgo getに依存し、GOPATHに比較的新しいバージョンの依存関係がインストールされていることを期待します。

Goのモジュールシステムは、Go 1.11で導入され、goコマンドに組み込まれた公式の依存関係管理ソリューションを提供します。この記事では、プロジェクトをモジュールに変換するためのツールと技術について説明します。

注意してください:プロジェクトがすでにv2.0.0以上でタグ付けされている場合、go.modファイルを追加する際にモジュールパスを更新する必要があります。ユーザーを混乱させないようにする方法については、v2以降に焦点を当てた今後の記事で説明します。

プロジェクトにおけるGoモジュールへの移行

プロジェクトは、Goモジュールへの移行を開始する際に、次の3つの状態のいずれかにある可能性があります:

  • 新しいGoプロジェクト。
  • 非モジュール依存関係マネージャーを持つ確立されたGoプロジェクト。
  • 依存関係マネージャーを持たない確立されたGoプロジェクト。

最初のケースはGoモジュールの使用で説明されています。残りの2つについてはこの記事で扱います。

依存関係マネージャーを使用している場合

依存関係管理ツールをすでに使用しているプロジェクトを変換するには、次のコマンドを実行します:

  1. $ git clone https://github.com/my/project
  2. [...]
  3. $ cd project
  4. $ cat Godeps/Godeps.json
  5. {
  6. "ImportPath": "github.com/my/project",
  7. "GoVersion": "go1.12",
  8. "GodepVersion": "v80",
  9. "Deps": [
  10. {
  11. "ImportPath": "rsc.io/binaryregexp",
  12. "Comment": "v0.2.0-1-g545cabd",
  13. "Rev": "545cabda89ca36b48b8e681a30d9d769a30b3074"
  14. },
  15. {
  16. "ImportPath": "rsc.io/binaryregexp/syntax",
  17. "Comment": "v0.2.0-1-g545cabd",
  18. "Rev": "545cabda89ca36b48b8e681a30d9d769a30b3074"
  19. }
  20. ]
  21. }
  22. $ go mod init github.com/my/project
  23. go: creating new go.mod: module github.com/my/project
  24. go: copying requirements from Godeps/Godeps.json
  25. $ cat go.mod
  26. module github.com/my/project
  27. go 1.12
  28. require rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
  29. $

go mod initは新しいgo.modファイルを作成し、Godeps.jsonGopkg.lock、または他のサポートされている形式から依存関係を自動的にインポートします。go mod initへの引数はモジュールパスであり、モジュールが見つかる場所です。

これは、go build ./...go test ./...を実行する良いタイミングです。後のステップではgo.modファイルが変更される可能性があるため、反復的なアプローチを好む場合、go.modファイルはモジュール以前の依存関係仕様に最も近い状態になります。

  1. $ go mod tidy
  2. go: downloading rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
  3. go: extracting rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
  4. $ cat go.sum
  5. rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca h1:FKXXXJ6G2bFoVe7hX3kEX6Izxw5ZKRH57DFBJmHCbkU=
  6. rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
  7. $

go mod tidyは、モジュール内のパッケージによって間接的にインポートされたすべてのパッケージを見つけます。既知のモジュールによって提供されていないパッケージに対して新しいモジュール要件を追加し、インポートされたパッケージを提供しないモジュールに対する要件を削除します。モジュールが、まだモジュールに移行していないプロジェクトによってのみインポートされるパッケージを提供している場合、そのモジュール要件には// indirectコメントが付けられます。go.modファイルをバージョン管理にコミットする前にgo mod tidyを実行することは常に良い習慣です。

コードがビルドされ、テストが通過することを確認して終了しましょう:

  1. $ go build ./...
  2. $ go test ./...
  3. [...]
  4. $

他の依存関係マネージャーは、個々のパッケージやリポジトリ全体(モジュールではなく)で依存関係を指定する場合があり、一般的に依存関係のgo.modファイルに指定された要件を認識しません。そのため、以前とまったく同じバージョンのすべてのパッケージを取得できない可能性があり、破壊的な変更を超えてアップグレードするリスクがあります。したがって、上記のコマンドの後に結果の依存関係を監査することが重要です。そのためには、次のコマンドを実行します:

  1. $ go list -m all
  2. go: finding rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
  3. github.com/my/project
  4. rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
  5. $

結果のバージョンを古い依存関係管理ファイルと比較して、選択したバージョンが適切であることを確認してください。望ましくないバージョンが見つかった場合は、go mod why -mおよび/またはgo mod graphを使用して理由を調べ、go getを使用して正しいバージョンにアップグレードまたはダウングレードできます。(要求したバージョンが以前に選択されたバージョンよりも古い場合、go getは互換性を維持するために他の依存関係を必要に応じてダウングレードします。)例えば、

  1. $ go mod why -m rsc.io/binaryregexp
  2. [...]
  3. $ go mod graph | grep rsc.io/binaryregexp
  4. [...]
  5. $ go get rsc.io/binaryregexp@v0.2.0
  6. $

依存関係マネージャーなしで

依存関係管理システムを持たないGoプロジェクトの場合、まずgo.modファイルを作成します:

  1. $ git clone https://go.googlesource.com/blog
  2. [...]
  3. $ cd blog
  4. $ go mod init golang.org/x/blog
  5. go: creating new go.mod: module golang.org/x/blog
  6. $ cat go.mod
  7. module golang.org/x/blog
  8. go 1.12
  9. $

以前の依存関係マネージャーからの構成ファイルがない場合、go mod initgo.modファイルをmoduleおよびgoディレクティブのみで作成します。この例では、モジュールパスをgolang.org/x/blogに設定します。これはそのカスタムインポートパスです。ユーザーはこのパスでパッケージをインポートでき、変更しないように注意する必要があります。

  1. 次に、`````go mod tidy`````を実行してモジュールの依存関係を追加します:
  2. ``````bash
  3. $ go mod tidy
  4. go: finding golang.org/x/website latest
  5. go: finding gopkg.in/tomb.v2 latest
  6. go: finding golang.org/x/net latest
  7. go: finding golang.org/x/tools latest
  8. go: downloading github.com/gorilla/context v1.1.1
  9. go: downloading golang.org/x/tools v0.0.0-20190813214729-9dba7caff850
  10. go: downloading golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
  11. go: extracting github.com/gorilla/context v1.1.1
  12. go: extracting golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
  13. go: downloading gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
  14. go: extracting gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
  15. go: extracting golang.org/x/tools v0.0.0-20190813214729-9dba7caff850
  16. go: downloading golang.org/x/website v0.0.0-20190809153340-86a7442ada7c
  17. go: extracting golang.org/x/website v0.0.0-20190809153340-86a7442ada7c
  18. $ cat go.mod
  19. module golang.org/x/blog
  20. go 1.12
  21. require (
  22. github.com/gorilla/context v1.1.1
  23. golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
  24. golang.org/x/text v0.3.2
  25. golang.org/x/tools v0.0.0-20190813214729-9dba7caff850
  26. golang.org/x/website v0.0.0-20190809153340-86a7442ada7c
  27. gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
  28. )
  29. $ cat go.sum
  30. cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
  31. cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
  32. git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
  33. git.apache.org/thrift.git v0.0.0-20181218151757-9b75e4fe745a/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
  34. github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
  35. [...]
  36. $
  37. `

go mod tidyは、モジュール内のパッケージによって間接的にインポートされたすべてのパッケージに対してモジュール要件を追加し、特定のバージョンの各ライブラリのチェックサムを持つgo.sumを構築しました。コードがまだビルドされ、テストが通過することを確認して終了しましょう:

  1. $ go build ./...
  2. $ go test ./...
  3. ok golang.org/x/blog 0.335s
  4. ? golang.org/x/blog/content/appengine [no test files]
  5. ok golang.org/x/blog/content/cover 0.040s
  6. ? golang.org/x/blog/content/h2push/server [no test files]
  7. ? golang.org/x/blog/content/survey2016 [no test files]
  8. ? golang.org/x/blog/content/survey2017 [no test files]
  9. ? golang.org/x/blog/support/racy [no test files]
  10. $

go mod tidyが要件を追加すると、モジュールの最新バージョンが追加されることに注意してください。GOPATHに古いバージョンの依存関係が含まれていて、その後破壊的な変更が公開された場合、go mod tidygo build、またはgo testでエラーが表示される可能性があります。この場合、go getを使用して古いバージョンにダウングレードするか(例えば、go get github.com/broken/[email protected])、各依存関係の最新バージョンにモジュールを互換性を持たせるための時間をかける必要があります。

モジュールモードでのテスト

Goモジュールに移行した後、一部のテストは調整が必要な場合があります。

テストがパッケージディレクトリ内にファイルを書き込む必要がある場合、パッケージディレクトリが読み取り専用のモジュールキャッシュ内にあると失敗する可能性があります。特に、これによりgo test allが失敗する可能性があります。テストは、書き込む必要のあるファイルを一時ディレクトリにコピーする必要があります。

テストが相対パス(../package-in-another-module)に依存して他のパッケージ内のファイルを見つけて読み取る場合、パッケージが別のモジュールにあると失敗します。これは、モジュールキャッシュのバージョン付きサブディレクトリまたはreplaceディレクティブで指定されたパスにあります。この場合、テスト入力をモジュールにコピーするか、テスト入力を生ファイルから.goソースファイルに埋め込まれたデータに変換する必要があります。

テストがGOPATHモードで実行されることを期待しているgoコマンドがある場合、失敗する可能性があります。この場合、テストされるソースツリーにgo.modファイルを追加するか、GO111MODULE=offを明示的に設定する必要があります。

リリースの公開

最後に、新しいモジュールのリリースバージョンにタグを付けて公開する必要があります。まだバージョンをリリースしていない場合はオプションですが、公式のリリースがないと、下流のユーザーは擬似バージョンを使用して特定のコミットに依存することになり、サポートが難しくなる可能性があります。

  1. $ git tag v1.2.0
  2. $ git push origin v1.2.0

新しいgo.modファイルは、モジュールの標準インポートパスを定義し、新しい最小バージョン要件を追加します。ユーザーがすでに正しいインポートパスを使用しており、依存関係に破壊的な変更がない場合、go.modファイルを追加することは後方互換性がありますが、これは重要な変更であり、既存の問題を露呈する可能性があります。既存のバージョンタグがある場合は、マイナーバージョンをインクリメントする必要があります。バージョンをインクリメントして公開する方法については、Goモジュールの公開を参照してください。

インポートと標準モジュールパス

各モジュールは、go.modファイル内でそのモジュールパスを宣言します。モジュール内のパッケージを参照する各importステートメントは、パッケージパスのプレフィックスとしてモジュールパスを持たなければなりません。ただし、goコマンドは、さまざまなリモートインポートパスを介してモジュールを含むリポジトリに遭遇する可能性があります。例えば、golang.org/x/lintgithub.com/golang/lintの両方が、go.googlesource.com/lintでホストされているコードを含むリポジトリに解決されます。そのリポジトリに含まれるgo.modファイルは、そのパスをgolang.org/x/lintと宣言しているため、そのパスのみが有効なモジュールに対応します。

Go 1.4は、// importコメントを使用して標準インポートパスを宣言するメカニズムを提供しましたが、パッケージの著者は常にそれを提供していませんでした。その結果、モジュール以前に書かれたコードは、エラーを表示せずにモジュールの非標準インポートパスを使用している可能性があります。モジュールを使用する場合、インポートパスは標準モジュールパスと一致する必要があるため、importステートメントを更新する必要があるかもしれません。例えば、import "github.com/golang/lint"import "golang.org/x/lint"に変更する必要があるかもしれません。

モジュールの標準パスがリポジトリパスと異なる別のシナリオは、メジャーバージョン2以上のGoモジュールに発生します。メジャーバージョンが1を超えるGoモジュールは、モジュールパスにメジャーバージョンのサフィックスを含める必要があります。例えば、バージョンv2.0.0/v2のサフィックスを持たなければなりません。ただし、importステートメントは、そのサフィックスなしでモジュール内のパッケージを参照している可能性があります。例えば、github.com/russross/blackfriday/v2の非モジュールユーザーはv2.0.1でそれをgithub.com/russross/blackfridayとしてインポートしているかもしれず、インポートパスを/v2サフィックスを含むように更新する必要があります。

結論

Goモジュールへの移行は、ほとんどのユーザーにとって簡単なプロセスであるはずです。非標準インポートパスや依存関係内の破壊的な変更により、時折問題が発生する可能性があります。今後の記事では、新しいバージョンの公開、v2以降、奇妙な状況をデバッグする方法について探ります。

フィードバックを提供し、Goの依存関係管理の未来を形作る手助けをするために、バグレポート体験レポートをお送りください。

モジュールの改善に向けたすべてのフィードバックと支援に感謝します。