はじめに
この記事はシリーズの第2部です。
- 第1部 — Goモジュールの使用
- 第2部 — Goモジュールへの移行(この記事)
- 第3部 — Goモジュールの公開
- 第4部 — Goモジュール:v2以降
- 第5部 — モジュールの互換性を保つ
注意: ドキュメントについては、依存関係の管理およびモジュールの開発と公開を参照してください。
Goプロジェクトは、さまざまな依存関係管理戦略を使用します。ベンダリングツールであるdepやglideは人気がありますが、動作に大きな違いがあり、常にうまく連携するわけではありません。一部のプロジェクトは、GOPATHディレクトリ全体を単一のGitリポジトリに保存します。他のプロジェクトは、単にgo get
に依存し、GOPATHに比較的新しいバージョンの依存関係がインストールされていることを期待します。
Goのモジュールシステムは、Go 1.11で導入され、go
コマンドに組み込まれた公式の依存関係管理ソリューションを提供します。この記事では、プロジェクトをモジュールに変換するためのツールと技術について説明します。
注意してください:プロジェクトがすでにv2.0.0以上でタグ付けされている場合、go.mod
ファイルを追加する際にモジュールパスを更新する必要があります。ユーザーを混乱させないようにする方法については、v2以降に焦点を当てた今後の記事で説明します。
プロジェクトにおけるGoモジュールへの移行
プロジェクトは、Goモジュールへの移行を開始する際に、次の3つの状態のいずれかにある可能性があります:
- 新しいGoプロジェクト。
- 非モジュール依存関係マネージャーを持つ確立されたGoプロジェクト。
- 依存関係マネージャーを持たない確立されたGoプロジェクト。
最初のケースはGoモジュールの使用で説明されています。残りの2つについてはこの記事で扱います。
依存関係マネージャーを使用している場合
依存関係管理ツールをすでに使用しているプロジェクトを変換するには、次のコマンドを実行します:
$ git clone https://github.com/my/project
[...]
$ cd project
$ cat Godeps/Godeps.json
{
"ImportPath": "github.com/my/project",
"GoVersion": "go1.12",
"GodepVersion": "v80",
"Deps": [
{
"ImportPath": "rsc.io/binaryregexp",
"Comment": "v0.2.0-1-g545cabd",
"Rev": "545cabda89ca36b48b8e681a30d9d769a30b3074"
},
{
"ImportPath": "rsc.io/binaryregexp/syntax",
"Comment": "v0.2.0-1-g545cabd",
"Rev": "545cabda89ca36b48b8e681a30d9d769a30b3074"
}
]
}
$ go mod init github.com/my/project
go: creating new go.mod: module github.com/my/project
go: copying requirements from Godeps/Godeps.json
$ cat go.mod
module github.com/my/project
go 1.12
require rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
$
go mod init
は新しいgo.modファイルを作成し、Godeps.json
、Gopkg.lock
、または他のサポートされている形式から依存関係を自動的にインポートします。go mod init
への引数はモジュールパスであり、モジュールが見つかる場所です。
これは、go build ./...
とgo test ./...
を実行する良いタイミングです。後のステップではgo.mod
ファイルが変更される可能性があるため、反復的なアプローチを好む場合、go.mod
ファイルはモジュール以前の依存関係仕様に最も近い状態になります。
$ go mod tidy
go: downloading rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
go: extracting rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
$ cat go.sum
rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca h1:FKXXXJ6G2bFoVe7hX3kEX6Izxw5ZKRH57DFBJmHCbkU=
rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
$
go mod tidy
は、モジュール内のパッケージによって間接的にインポートされたすべてのパッケージを見つけます。既知のモジュールによって提供されていないパッケージに対して新しいモジュール要件を追加し、インポートされたパッケージを提供しないモジュールに対する要件を削除します。モジュールが、まだモジュールに移行していないプロジェクトによってのみインポートされるパッケージを提供している場合、そのモジュール要件には// indirect
コメントが付けられます。go.mod
ファイルをバージョン管理にコミットする前にgo mod tidy
を実行することは常に良い習慣です。
コードがビルドされ、テストが通過することを確認して終了しましょう:
$ go build ./...
$ go test ./...
[...]
$
他の依存関係マネージャーは、個々のパッケージやリポジトリ全体(モジュールではなく)で依存関係を指定する場合があり、一般的に依存関係のgo.mod
ファイルに指定された要件を認識しません。そのため、以前とまったく同じバージョンのすべてのパッケージを取得できない可能性があり、破壊的な変更を超えてアップグレードするリスクがあります。したがって、上記のコマンドの後に結果の依存関係を監査することが重要です。そのためには、次のコマンドを実行します:
$ go list -m all
go: finding rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
github.com/my/project
rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
$
結果のバージョンを古い依存関係管理ファイルと比較して、選択したバージョンが適切であることを確認してください。望ましくないバージョンが見つかった場合は、go mod why -m
および/またはgo mod graph
を使用して理由を調べ、go get
を使用して正しいバージョンにアップグレードまたはダウングレードできます。(要求したバージョンが以前に選択されたバージョンよりも古い場合、go get
は互換性を維持するために他の依存関係を必要に応じてダウングレードします。)例えば、
$ go mod why -m rsc.io/binaryregexp
[...]
$ go mod graph | grep rsc.io/binaryregexp
[...]
$ go get rsc.io/binaryregexp@v0.2.0
$
依存関係マネージャーなしで
依存関係管理システムを持たないGoプロジェクトの場合、まずgo.mod
ファイルを作成します:
$ git clone https://go.googlesource.com/blog
[...]
$ cd blog
$ go mod init golang.org/x/blog
go: creating new go.mod: module golang.org/x/blog
$ cat go.mod
module golang.org/x/blog
go 1.12
$
以前の依存関係マネージャーからの構成ファイルがない場合、go mod init
はgo.mod
ファイルをmodule
およびgo
ディレクティブのみで作成します。この例では、モジュールパスをgolang.org/x/blog
に設定します。これはそのカスタムインポートパスです。ユーザーはこのパスでパッケージをインポートでき、変更しないように注意する必要があります。
次に、`````go mod tidy`````を実行してモジュールの依存関係を追加します:
``````bash
$ go mod tidy
go: finding golang.org/x/website latest
go: finding gopkg.in/tomb.v2 latest
go: finding golang.org/x/net latest
go: finding golang.org/x/tools latest
go: downloading github.com/gorilla/context v1.1.1
go: downloading golang.org/x/tools v0.0.0-20190813214729-9dba7caff850
go: downloading golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
go: extracting github.com/gorilla/context v1.1.1
go: extracting golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
go: downloading gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
go: extracting gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
go: extracting golang.org/x/tools v0.0.0-20190813214729-9dba7caff850
go: downloading golang.org/x/website v0.0.0-20190809153340-86a7442ada7c
go: extracting golang.org/x/website v0.0.0-20190809153340-86a7442ada7c
$ cat go.mod
module golang.org/x/blog
go 1.12
require (
github.com/gorilla/context v1.1.1
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
golang.org/x/text v0.3.2
golang.org/x/tools v0.0.0-20190813214729-9dba7caff850
golang.org/x/website v0.0.0-20190809153340-86a7442ada7c
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
)
$ cat go.sum
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
git.apache.org/thrift.git v0.0.0-20181218151757-9b75e4fe745a/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
[...]
$
`
go mod tidy
は、モジュール内のパッケージによって間接的にインポートされたすべてのパッケージに対してモジュール要件を追加し、特定のバージョンの各ライブラリのチェックサムを持つgo.sum
を構築しました。コードがまだビルドされ、テストが通過することを確認して終了しましょう:
$ go build ./...
$ go test ./...
ok golang.org/x/blog 0.335s
? golang.org/x/blog/content/appengine [no test files]
ok golang.org/x/blog/content/cover 0.040s
? golang.org/x/blog/content/h2push/server [no test files]
? golang.org/x/blog/content/survey2016 [no test files]
? golang.org/x/blog/content/survey2017 [no test files]
? golang.org/x/blog/support/racy [no test files]
$
go mod tidy
が要件を追加すると、モジュールの最新バージョンが追加されることに注意してください。GOPATH
に古いバージョンの依存関係が含まれていて、その後破壊的な変更が公開された場合、go mod tidy
、go 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
を明示的に設定する必要があります。
リリースの公開
最後に、新しいモジュールのリリースバージョンにタグを付けて公開する必要があります。まだバージョンをリリースしていない場合はオプションですが、公式のリリースがないと、下流のユーザーは擬似バージョンを使用して特定のコミットに依存することになり、サポートが難しくなる可能性があります。
$ git tag v1.2.0
$ git push origin v1.2.0
新しいgo.mod
ファイルは、モジュールの標準インポートパスを定義し、新しい最小バージョン要件を追加します。ユーザーがすでに正しいインポートパスを使用しており、依存関係に破壊的な変更がない場合、go.mod
ファイルを追加することは後方互換性がありますが、これは重要な変更であり、既存の問題を露呈する可能性があります。既存のバージョンタグがある場合は、マイナーバージョンをインクリメントする必要があります。バージョンをインクリメントして公開する方法については、Goモジュールの公開を参照してください。
インポートと標準モジュールパス
各モジュールは、go.mod
ファイル内でそのモジュールパスを宣言します。モジュール内のパッケージを参照する各import
ステートメントは、パッケージパスのプレフィックスとしてモジュールパスを持たなければなりません。ただし、go
コマンドは、さまざまなリモートインポートパスを介してモジュールを含むリポジトリに遭遇する可能性があります。例えば、golang.org/x/lint
とgithub.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の依存関係管理の未来を形作る手助けをするために、バグレポートや体験レポートをお送りください。
モジュールの改善に向けたすべてのフィードバックと支援に感謝します。