Go Wiki: モジュールパスの変更によって生じる問題の解決

予期しないモジュールパス

プロジェクト my-go-project で作業しているユーザーは、go get -u の際に次のようなエラーに遭遇する可能性があります。

$ cd my-go-project
$ go get -u ./...
[...]
go: github.com/golang/lint@v0.0.0-20190313153728-d0100b6bd8b3: parsing go.mod: unexpected module path "golang.org/x/lint"
[...]
Exit code 1

golang.org/x/lint は、以前は github.com/golang/lint という git リポジトリとモジュール名を使用していましたが、その後 git リポジトリ golang.org/x/lint に移行し、モジュール名を golang.org/x/lint に変更したモジュールです。Go ツールは現在、新しい git リポジトリにある古いモジュール名を理解しようとしてつまずいています: golang/go#30831

この問題が my-go-project で表面化したのは、my-go-project またはその推移的な依存関係のいずれかに、モジュールグラフ内で古い github.com/golang/lint モジュール名へのルートがあるためです。

たとえば、my-go-project 自体が古い github.com/golang/lint モジュール名に依存している場合

$ GO111MODULE=on go mod graph
my-go-project github.com/golang/lint@v0.0.0-20180702182130-06c8688daad7

あるいは、my-go-project が古いバージョンの google.golang.org/grpc に依存しており、それが古い github.com/golang/lint モジュール名に依存している場合

$ GO111MODULE=on go mod graph
my-go-project google.golang.org/grpc@v1.16.0
google.golang.org/grpc@v1.16.0 github.com/golang/lint@v0.0.0-20180702182130-06c8688daad7

最後に、my-go-project が、古いバージョンの google.golang.org/grpc を必要とする別の依存関係に依存しており、それがさらに古い github.com/golang/lint モジュール名に依存している場合

$ GO111MODULE=on go mod graph
my-go-project some/dep@v1.2.3
...
another/dep@v1.4.2 google.golang.org/grpc@v1.16.0
google.golang.org/grpc@v1.16.0 github.com/golang/lint@v0.0.0-20180702182130-06c8688daad7

名前への参照の削除

Go ツールがモジュールパスを変更したモジュールを理解するように更新されるまで (golang/go#30831 で追跡中)、この問題の解決策は、古いモジュール名へのパスがなくなるようにグラフを更新することです。

上記の例を使用して、github.com/golang/lint へのパスがなくなるようにグラフを更新する方法を見ていきましょう。

最初の例の修正は簡単です。唯一のリンクは my-go-project からのもので、これはユーザーが制御できます! go.mod 内の古い場所 (github.com/golang/lint@v0.0.0-20180702182130-06c8688daad7) を新しい場所 (golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f) に置き換えることで、グラフからリンクが削除されます。

$ GO111MODULE=on go mod graph
my-go-project golang.org/x/lint@v0.0.0-20190301231843-5614ed5bae6f

2番目の例の修正にはより多くのステップが含まれますが、本質的には同じプロセスです。google.golang.org/grpc@v1.16.0github.com/golang/lint へのリンクを提供しているため、google.golang.org/grpcgo.modgithub.com/golang/lint@v0.0.0-20180702182130-06c8688daad7 から golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f に更新する必要があります (これは幸いにも v1.17.0 ですでに起こっています)。次に、my-go-project は、新しいバージョンの google.golang.org/grpc を含めるように go.mod を更新する必要があります。

$ GO111MODULE=on go mod graph
my-go-project google.golang.org/grpc@v1.17.0
google.golang.org/grpc@v1.17.0 golang.org/x/lint@v0.0.0-20181026193005-c67002cb31c3

3番目の例の修正は2番目の例と似ています。github.com/golang/lint への参照を含まない新しいバージョンの google.golang.org/grpc を取り込む、より新しいバージョンの another/dep に更新します。

万歳! 問題は解決しました。Go ツールが考慮すべき github.com/golang/lint へのパスはもう存在しないため、go get -u の際にこの問題につまずくことはありません。

より困難な問題: 履歴の削除

これはすべてうまくいき、ほとんどのユーザーの問題を解決するはずです。

しかし、モジュール依存関係グラフに循環がある場合、かなり複雑になる状況が1つあります。このモジュール依存関係グラフを考えてみましょう。

Module Dependency Graph With A Cycle

そして、some/lib がかつて github.com/golang/lint に依存していたと想像してみましょう。

バージョンを含むこのモジュール依存関係グラフを見てみましょう。

$ go mod graph
my-go-lib some/lib@v1.7.0
some/lib@v1.7.0 some-other/lib@v2.5.3
some/lib@v1.7.0 golang.org/x/lint@v0.0.0-20181026193005-c67002cb31c3
some-other/lib@v2.5.3 some/lib@v1.6.0
some/lib@v1.6.0 some-other/lib@v2.5.0
some/lib@v1.6.0 golang.org/x/lint@v0.0.0-20181026193005-c67002cb31c3
some-other/lib@v2.5.0 some/lib@v1.3.1
some/lib@v1.3.1 some-other/lib@v2.4.8
some/lib@v1.3.1 golang.org/x/lint@v0.0.0-20181026193005-c67002cb31c3
some-other/lib@v2.4.8 some/lib@v1.3.0
some/lib@v1.3.0 some-other/lib@v2.4.7
some/lib@v1.3.0 github.com/golang/lint@v0.0.0-20180702182130-06c8688daad7

golang.org/x/exp/cmd/modgraphviz で可視化

A Module Dependency Graph With Trailing History

ここでは、some/lib の最新のいくつかのバージョンが正しく golang.org/x/lint に依存しているにもかかわらず、some/libsome-other/lib が循環を共有しているという事実が、非常に昔にさかのぼるパスが存在する可能性が高いことを意味していることがわかります。

このようなパスが発生する理由は、バージョンアップのプロセスが通常個別にアトミックであるためです。some/libsome-other/lib のバージョンを上げて自身の新しいバージョンをリリースしても、some-other/lib の最新バージョンは依然として some/lib の以前のバージョンに依存しています。つまり、これらのライブラリのどちらかを個別にバージョンアップするだけでは、履歴への連鎖を削除するには不十分です。

履歴への連鎖を削除し、古い github.com/golang/lint 参照をグラフから完全に削除するには、両方のライブラリが互いのバージョンを同時に上げなければなりません。

2つのライブラリのアトミックなバージョンアップ

github.com/golang/lint を削除する解決策は、まず some/libgithub.com/golang/lint に依存しないことを確認し、次に some/libsome-other/lib の両方を互いの存在しない将来のバージョンにバージョンアップすることです。私たちはこのようなグラフを望んでいます。

my-go-lib some/lib@v1.7.1
some/lib@v1.7.1 some-other/lib@v2.5.4
some/lib@v1.7.1 golang.org/x/lint@v0.0.0-20181026193005-c67002cb31c3
some-other/lib@v2.5.4 some/lib@v1.7.1

A Module Dependency Graph Without Trailing History

some/libsome-other/lib が同じバージョンで相互に依存しているため、github.com/golang/lint が提供されていた時点に時間を遡るパスは存在しません。

some/libv1.7.0some-other/libv2.5.3 であると仮定して、このアトミックなバージョンアップを実現する手順を以下に示します。

  1. エラーが実際に存在することを確認する
    1. some/libsome-other/lib の両方で GO111MODULE=on go get -u ./... を実行します。
    2. 両方のリポジトリで、go: github.com/golang/lint@v0.0.0-20190313153728-d0100b6bd8b3: parsing go.mod: unexpected module path "golang.org/x/lint" というエラーが観察されるはずです。
  2. some/lib の最新バージョンが github.com/golang/lint ではなく golang.org/x/lint に依存していることを確認します。履歴の痕跡を削除する一方で、github.com/golang/lint への壊れた依存関係を残しておくのは残念なことです!
  3. アルファタグ (Go モジュールはモジュールの最新リリースバージョンを評価する際にアルファバージョンを新しいものとはみなさないため、より安全です) を使用して、両方のライブラリを互いの存在しない将来のバージョンにバージョンアップします。
    1. some/lib は、some-other/lib への依存関係を v2.5.3 から v2.5.4-alpha に変更します。
    2. some/lib はコミットに v1.7.1-alpha のタグを付け、コミットとタグをプッシュします。
    3. some-other/lib は、some/lib への依存関係を v1.6.0 から v1.7.1-alpha に変更します。
    4. some-other/lib はコミットに v2.5.4-alpha のタグを付け、コミットとタグをプッシュします。
  4. アルファ状態のまま結果を確認する
    1. some/libGO111MODULE=on go build ./... && go test ./... を実行します。
    2. some-other/libGO111MODULE=on go build ./... && go test ./... を実行します。
    3. 両方のリポジトリで GO111MODULE=on go mod graph を実行し、github.com/golang/lint へのパスがないことを確認します。
    4. 注: 上述のように、最新バージョンを評価する際にアルファバージョンは考慮されないため、go get -u はまだ機能しません。
  5. すべてが良好に見える場合は、互いの存在しない将来のバージョンに再度バージョンアップして続行します。
    1. some/lib は、some-other/lib への依存関係を v2.5.4-alpha から v2.5.4 に変更します。
    2. some/lib はコミットに v1.7.1 のタグを付け、コミットとタグをプッシュします。
    3. some-other/lib は、some/lib への依存関係を v1.7.1-alpha から v1.7.1 に変更します。
    4. some-other/lib はコミットに v2.5.4 のタグを付け、コミットとタグをプッシュします。
  6. エラーが解決されたことを確認する
    1. some/libsome-other/lib の両方で GO111MODULE=on go get -u ./... を実行します。
    2. go.mod: unexpected module path "golang.org/x/lint" のパースエラーは発生しないはずです。
  7. 現在、some/libsome-other/libgo.sum は不完全です。これは、将来の存在しないモジュールのバージョンに依存していたため、プロセスが完了するまで go.sum エントリを生成できなかったためです。では、これを修正しましょう。
    1. some/libGO111MODULE=on go mod tidy を実行します。
    2. コミットし、コミットに v1.7.2 のタグを付け、コミットとタグの両方をプッシュします。
    3. some-other/libGO111MODULE=on go mod tidy を実行します。
    4. コミットし、コミットに v2.5.5 のタグを付け、コミットとタグの両方をプッシュします。
  8. 最後に、my-go-project が、長い履歴の痕跡を持たないこれらの新しいバージョンの some/libsome-other/lib に依存していることを確認しましょう。
    1. my-go-projectgo.mod エントリを some/lib v1.7.0 から some/lib 1.7.2 に変更します。
    2. my-go-projectGO111MODULE=on go get -u ./... を実行してテストします。

ステップ 5.b と 5.d の間では、ユーザーは壊れた状態になることに注意してください。some/lib のバージョンがリリースされ、それが存在しないバージョンの some-other/lib に依存しています。したがって、このプロセスは理想的にはリアルタイムで行われるべきであり、ステップ 5.d がステップ 5.b の直後に完了するようにすることで、破損期間を可能な限り短くします。

より大きな循環

この例では、グラフ内に2つのパッケージが関与する循環が存在する場合の履歴の削除プロセスを説明しましたが、より多くのパッケージが関与する循環がある場合はどうでしょうか?たとえば、次のグラフを考えてみましょう。

Module Dependency Graph With Four Related Cycles

Module Dependency Graph With One Four Vertex Cycle

これらのグラフはそれぞれ、以前に見た単純な2つのモジュールの例とは異なり、4つのモジュールが関与する循環 (後者の例) または相互接続されたモジュール (前者の例) を含んでいます。プロセスはほとんど同じですが、今回はステップ3と5で、4つのモジュールすべてを互いの存在しない将来のバージョンにバージョンアップし、同様にステップ4と6で4つのモジュールすべてをテストし、ステップ7で4つのモジュールすべての go.sum を修正します。

より一般的には、上記のプロセスは、任意の n 個のモジュールが関与する相互接続されたモジュールの任意のグループに適用されます。各主要なステップは、n 個のモジュールが連携して動作するだけです。


このコンテンツはGo Wikiの一部です。