Go Wiki: Go Modules

この Wiki ページは、使用方法とトラブルシューティングガイドとして機能します。

Go は、ここで提案されているバージョン管理されたモジュールに対するサポートを 1.11 以降に含んでいます。最初のプロトタイプである vgo は、2018 年 2 月に発表されました。2018 年 7 月には、バージョン管理されたモジュールが Go のメインリポジトリに取り込まれました。

Go 1.14 以降、モジュールサポートは本番環境で使用できる状態と見なされており、すべてのユーザーは他の依存関係管理システムからモジュールに移行することが推奨されています。Go ツールチェーンの問題のために移行できない場合は、その問題に未解決の課題が登録されていることを確認してください。(その問題が Go1.16 マイルストーンにない場合は、移行できない理由をコメントして、適切に優先順位を付けることができます)。より詳細なフィードバックについては、エクスペリエンスレポートを提供することもできます。

最近の変更

Go 1.16

詳細については、Go 1.16 リリースノートを参照してください。

Go 1.15

詳細については、Go 1.15 リリースノートを参照してください。

Go 1.14

詳細については、Go 1.14 リリースノートを参照してください。

Go 1.13

詳細については、Go 1.13 リリースノートを参照してください。

目次

「クイックスタート」セクションと「新しい概念」セクションは、モジュールを使い始める人にとって特に重要です。「使用方法」セクションでは、メカニズムに関する詳細を説明しています。このページで最も多くのコンテンツは、より具体的な質問に答える FAQ にあります。ここにリストされている FAQ のワンライナーをざっと目を通すだけでも価値があります。

クイックスタート

詳細は、このページの残りの部分で説明しますが、ここでは、モジュールを最初から作成する簡単な例を示します。

GOPATHの外部にディレクトリを作成し、必要に応じてVCSを初期化します。

$ mkdir -p /tmp/scratchpad/repo
$ cd /tmp/scratchpad/repo
$ git init -q
$ git remote add origin https://github.com/my/repo

新しいモジュールを初期化します。

$ go mod init github.com/my/repo

go: creating new go.mod: module github.com/my/repo

コードを記述します。

$ cat <<EOF > hello.go
package main

import (
    "fmt"
    "rsc.io/quote"
)

func main() {
    fmt.Println(quote.Hello())
}
EOF

ビルドして実行します。

$ go mod tidy
go: finding module for package rsc.io/quote
go: found rsc.io/quote in rsc.io/quote v1.5.2
$ go build -o hello
$ ./hello
Hello, world.

go.modファイルは、依存関係に明示的なバージョンを含めるように更新されました。ここで`v1.5.2`はsemverタグです。

$ cat go.mod
module github.com/my/repo

go 1.16

require rsc.io/quote v1.5.2

日々のワークフロー

1.16より前では、`go build -o hello`を実行する前に`go get`または`go mod tidy`は必要ありませんでした。go.modgo.sumファイルの暗黙的な変更は、1.16でデフォルトで無効化されました。

一般的な日々のワークフローは次のとおりです。

使用できる可能性のあるその他の一般的な機能の概要

次の4つの「新しい概念」に関するセクションを読んだ後、ほとんどのプロジェクトでモジュールを使い始めるのに十分な情報が得られます。また、上記の目次(そこに記載されているFAQのワンライナーを含む)を確認して、より詳細なトピックのリストに慣れておくことも役立ちます。

新しい概念

これらのセクションでは、主な新しい概念の概要を簡単に説明します。詳細と理由については、設計の背後にある哲学について説明するRuss Coxによる40分の紹介ビデオをご覧ください公式提案文書、またはより詳細な最初のvgoブログシリーズを参照してください。

モジュール

モジュールとは、単一のユニットとしてバージョン管理された、関連するGoパッケージのコレクションです。

モジュールは、正確な依存関係の要件を記録し、再現可能なビルドを作成します。

ほとんどの場合、バージョン管理リポジトリには、リポジトリルートに定義されたモジュールが1つだけ含まれています。(単一のリポジトリに複数のモジュールをサポートしていますが、通常、リポジトリごとに1つのモジュールよりも継続的な作業が増えることになります)。

リポジトリ、モジュール、パッケージ間の関係をまとめます。

モジュールは、semverに従ってセマンティックバージョン管理を行う必要があります。通常、v(major).(minor).(patch)形式(例:v0.1.0v1.2.3v1.5.0-rc.1)です。先頭のvが必要です。Gitを使用する場合は、タグを使用して、リリースされたコミットにバージョンを付けます。公開および非公開のモジュールリポジトリとプロキシが利用可能になりつつあります(下のFAQを参照)。

go.mod

モジュールは、ツリーのルートディレクトリにgo.modファイルを持つGoソースファイルのツリーによって定義されます。モジュールのソースコードはGOPATHの外部に配置できます。4つのディレクティブがあります:modulerequirereplaceexclude

モジュールgithub.com/my/thingを定義するgo.modファイルの例を次に示します。

module github.com/my/thing

require (
    github.com/some/dependency v1.2.3
    github.com/another/dependency/v4 v4.0.0
)

モジュールは、モジュールパスを提供するmoduleディレクティブを介して、そのgo.modでそのIDを宣言します。モジュール内のすべてのパッケージのインポートパスは、モジュールパスを共通のプレフィックスとして共有します。モジュールパスと、go.modからパッケージのディレクトリまでの相対パスを組み合わせることで、パッケージのインポートパスが決まります。

たとえば、インポートパスがgithub.com/user/mymod/foogithub.com/user/mymod/barの2つのパッケージを含むリポジトリgithub.com/user/mymodのモジュールを作成する場合、go.modファイルの最初の行には通常、module github.com/user/mymodとしてモジュールパスが宣言され、対応するディスク上の構造は次のようになります。

mymod
|-- bar
|   `-- bar.go
|-- foo
|   `-- foo.go
`-- go.mod

Goソースコードでは、モジュールパスを含む完全パスを使用してパッケージがインポートされます。たとえば、上記の例で、go.modmodule github.com/user/mymodとしてモジュールIDを宣言した場合、コンシューマーは次のように実行できます。

import "github.com/user/mymod/bar"

これにより、モジュールgithub.com/user/mymodからパッケージbarがインポートされます。

excludeおよびreplaceディレクティブは、現在の(「メイン」)モジュールでのみ機能します。メインモジュール以外のモジュール内のexcludeおよびreplaceディレクティブは、メインモジュールをビルドする際に無視されます。したがって、replaceおよびexcludeステートメントにより、メインモジュールは独自のビルドを完全に制御できますが、依存関係によって完全に制御されることはありません。(replaceディレクティブを使用する時期については、下のFAQを参照してください)。

バージョン選択

go.modrequireでまだカバーされていない新しいインポートをソースコードに追加すると、「go build」や「go test」などのほとんどのgoコマンドは、適切なモジュールを自動的に検索し、新しい直接依存関係の最高のバージョンをモジュールのgo.modrequireディレクティブとして追加します。たとえば、新しいインポートが、最新のタグ付きリリースバージョンがv1.2.3である依存関係Mに対応する場合、モジュールのgo.modにはrequire M v1.2.3が追加されます。これは、モジュールMがバージョン>= v1.2.3(v2はv1と互換性がないと見なされるため、< v2)が許可された依存関係であることを示しています。

最小バージョン選択アルゴリズムは、ビルドで使用されるすべてのモジュールのバージョンを選択するために使用されます。ビルド内の各モジュールについて、最小バージョン選択によって選択されたバージョンは、常に、メインモジュールまたはその依存関係のいずれかでrequireディレクティブによって明示的にリストされているバージョンのセマンティックに最も高いバージョンです。

例として、モジュールがrequire D v1.0.0を持つモジュールAに依存し、モジュールがrequire D v1.1.1を持つモジュールBにも依存する場合、最小バージョン選択では、ビルドにDのv1.1.1が選択されます(リストされているrequireバージョンの中で最も高いため)。このv1.1.1の選択は、後でDのv1.2.0が利用可能になっても変わりません。これは、モジュールシステムが100%再現可能なビルドを提供する方法の一例です。準備ができたら、モジュール作成者またはユーザーは、Dの最新バージョンにアップグレードするか、Dの明示的なバージョンを選択できます。

最小バージョン選択アルゴリズムの簡単な理由と概要については、公式提案の「High Fidelity Builds」セクション、またはより詳細なvgoブログシリーズを参照してください。

選択されたモジュールバージョン(間接依存関係を含む)のリストを表示するには、go list -m allを使用します。

下記の「依存関係のアップグレードとダウングレードの方法」セクションと、「バージョンが非互換としてマークされるのはどのようにしてですか?」のFAQも参照してください。

セマンティックインポートバージョン管理

長年にわたり、公式のGo FAQには、パッケージのバージョニングに関する次のアドバイスが含まれていました。

「公開用に意図されたパッケージは、進化する際に後方互換性を維持しようとします。Go 1互換性ガイドラインはここで良い参照となります。エクスポートされた名前を削除しないでください。タグ付き複合リテラルを推奨し、その他も同様です。異なる機能が必要な場合は、古い名前を変更する代わりに新しい名前を追加します。完全なブレークが必要な場合は、新しいインポートパスを持つ新しいパッケージを作成します。」

最後の文は特に重要です。互換性を壊す場合は、パッケージのインポートパスを変更する必要があります。Go 1.11モジュールでは、そのアドバイスがインポート互換性ルールに公式化されています。

古いパッケージと新しいパッケージが同じインポートパスを持つ場合、新しいパッケージは古いパッケージとの後方互換性を持たなければなりません。

semver を思い出してください。v1以上のバージョンで後方互換性のない変更を加えた場合は、メジャーバージョンの変更が必要になります。インポート互換性ルールとsemverの両方を遵守した結果を、セマンティックインポートバージョン管理と呼びます。メジャーバージョンはインポートパスに含まれ、互換性の破損によってメジャーバージョンが増加するたびにインポートパスが変更されることを保証します。

セマンティックインポートバージョン管理の結果として、Goモジュールを採用するコードは**これらのルールに従わなければなりません**

一般的に、インポートパスが異なるパッケージは異なるパッケージです。例えば、math/randcrypto/randとは異なるパッケージです。これは、インポートパスが異なるメジャーバージョンによる場合にも当てはまります。したがって、example.com/my/mod/mypkgexample.com/my/mod/v2/mypkgとは異なるパッケージであり、どちらも単一のビルドでインポートできます。これには、ダイヤモンド依存性の問題に対処するなどの利点があり、v1モジュールをv2の代替物で実装したり、その逆を行うこともできます。

セマンティックインポートバージョン管理の詳細については、goコマンドのドキュメントの「モジュールの互換性とセマンティックバージョン管理」セクションを参照してください。セマンティックバージョン管理の詳細についてはhttps://semver.orgを参照してください。

これまでのセクションでは、モジュールを採用し、他のモジュールをインポートするコードに焦点を当ててきました。しかし、v2以上のモジュールのインポートパスにメジャーバージョンを入れることで、古いバージョンのGoや、まだモジュールを採用していないコードとの間に互換性の問題が発生する可能性があります。これを解決するために、上記の動作とルールの3つの重要な移行時の特別なケースまたは例外があります。これらの移行時の例外は、より多くのパッケージがモジュールを採用するにつれて、時間の経過とともに重要性が低くなります。

3つの移行時の例外

  1. gopkg.in

    gopkg.inで始まるインポートパス(例:gopkg.in/yaml.v1gopkg.in/yaml.v2)を使用している既存のコードは、モジュールを採用した後も、それらの形式をモジュールパスとインポートパスに使用し続けることができます。

  2. 非モジュールv2+パッケージをインポートする場合の'+incompatible'

    モジュールは、それ自体がモジュールを採用していないv2+パッケージをインポートできます。有効なv2+ semver タグを持つ非モジュールv2+パッケージは、インポートするモジュールのgo.modファイルに+incompatibleサフィックスが付加されて記録されます。+incompatibleサフィックスは、v2+パッケージが有効なv2+ semver タグ(例:v2.0.0)を持っている場合でも、v2+パッケージが積極的にモジュールを採用していないことを示しています。したがって、v2+パッケージはセマンティックインポートバージョン管理の意味と、インポートパスでメジャーバージョンを使用する方法を理解せずに作成されたと想定されます。モジュールモードで動作している場合、goツールは非モジュールv2+パッケージをパッケージのv1バージョンシリーズの(非互換な)拡張として扱い、パッケージがセマンティックインポートバージョン管理を認識していないと仮定します。+incompatibleサフィックスは、goツールがそうしていることを示す指標です。

  3. モジュールモードが無効になっている場合の「最小限のモジュール互換性」

    後方互換性を向上させるために、Goバージョン1.9.7以降、1.10.3以降、および1.11は、これらのリリースでビルドされたコードが、既存のコードを変更せずにv2+モジュールを適切に利用できるようにするために更新されています。この動作は「最小限のモジュール互換性」と呼ばれ、goツールの完全なモジュールモードが無効になっている場合にのみ有効になります(例:Go 1.11でGO111MODULE=offを設定した場合、またはGoバージョン1.9.7以降または1.10.3以降を使用している場合)。Go 1.9.7以降、1.10.3以降、および1.11でこの「最小限のモジュール互換性」メカニズムに依存している場合、モジュールを採用していないパッケージは、インポートされたv2+モジュールのインポートパスにメジャーバージョンを含めません。対照的に、モジュールを採用しているパッケージは、セマンティックインポートバージョン管理を完全に認識した状態でgoツールが完全なモジュールモードで動作している場合に、v2+モジュールを正しくインポートするために、v2+モジュールをインポートするインポートパスにメジャーバージョンを含める必要があります。

v2以上のモジュールをリリースするために必要な正確なメカニズムについては、以下の「モジュールのリリース(v2以上)」セクションを参照してください。

モジュールの使用方法

モジュールサポートのインストールと有効化の方法

モジュールを使用するには、2つのインストールオプションがあります。

インストール後、次の2つの方法のいずれかでモジュールサポートを有効にできます。

モジュールの定義方法

既存のプロジェクトにgo.modを作成するには

  1. GOPATHの外側のモジュールのソースツリーのルートに移動します。

    $ cd <project path outside $GOPATH/src>         # e.g., cd ~/projects/hello
    

    GOPATHの外側では、モジュールモードを有効にするためにGO111MODULEを設定する必要はありません。

    または、GOPATHで作業する場合

    $ export GO111MODULE=on                         # manually active module mode
    $ cd $GOPATH/src/<project path>                 # e.g., cd $GOPATH/src/you/hello
    
  2. 最初のモジュール定義を作成し、go.modファイルに書き込みます。

    $ go mod init
    

    この手順では、既存のdep Gopkg.lockファイルまたはその他の合計9種類のサポートされている依存関係形式から変換され、既存の設定に一致するrequireステートメントが追加されます。

    go mod initは、多くの場合、補助データ(VCSメタデータなど)を使用して適切なモジュールパスを自動的に決定できますが、go mod initがモジュールパスを自動的に決定できないと表示された場合、またはパスを上書きする必要がある場合は、オプションの引数としてモジュールパスをgo mod initに提供できます。例:

    $ go mod init github.com/my/repo
    

    依存関係にv2以上のモジュールが含まれている場合、またはv2以上のモジュールを初期化している場合、go mod initを実行した後、上記の「セマンティックインポートバージョン管理」セクションで説明されているように、インポートパスとモジュールパスに/vNを追加するために、go.mod.goコードを編集する必要がある場合があります。これは、go mod initdepまたはその他の依存関係マネージャーから依存関係情報を自動的に変換した場合でも適用されます。(このため、go mod initを実行した後、通常はgo mod tidyを、go build ./...などを正常に実行するまで実行しないでください。これはこのセクションで示されているシーケンスです)。

  3. モジュールをビルドします。モジュールのルートディレクトリから実行された場合、./...パターンは現在のモジュール内のすべてのパッケージに一致します。go buildは、この特定のビルド呼び出しのインポートを満たすために必要な、欠落している依存関係または変換されていない依存関係を自動的に追加します。

    $ go build ./...
    
  4. 選択したバージョンで機能することを確認するために、モジュールをテストします。

    $ go test ./...
    
  5. (オプション) 非互換性を確認するために、モジュールとそのすべての直接的および間接的依存関係のテストを実行します。

    $ go test all
    

リリースにタグ付けする前に、以下の「リリースの準備方法」セクションを参照してください。

これらのトピックの詳細については、公式モジュールドキュメントの主要なエントリポイントはgolang.orgで利用可能です。

依存関係のアップグレードとダウングレードの方法

依存関係の日々のアップグレードとダウングレードは、「go get」を使用して行う必要があります。これにより、go.modファイルが自動的に更新されます。または、go.modを直接編集することもできます。

さらに、「go build」、「go test」、または「go list」などのgoコマンドは、インポートを満たすために必要な新しい依存関係を自動的に追加します(go.modを更新し、新しい依存関係をダウンロードします)。

依存関係を最新バージョンにアップグレードするには

go get example.com/package

依存関係とそのすべての依存関係を最新バージョンにアップグレードするには

go get -u example.com/package

すべての直接的および間接的依存関係で使用可能なマイナーおよびパッチアップグレードを表示するには

go list -u -m all

直接依存関係のみのマイナーおよびパッチアップグレードを表示するには、次を実行します。

go list -u -f '{{if (and (not (or .Main .Indirect)) .Update)}}{{.Path}}: {{.Version}} -> {{.Update.Version}}{{end}}' -m all 2> /dev/null

現在のモジュールのすべての直接的および間接的依存関係の最新バージョンにアップグレードするには、モジュールルートディレクトリから次を実行できます。

go get foofooの最新バージョンに更新します。go get foogo get foo@latestと同等です。つまり、@バージョンが指定されていない場合は@latestがデフォルトになります。

このセクションでは、「latest」はsemverタグを持つ最新バージョン、またはsemverタグがない場合は最新の既知のコミットです。プレリリースタグは、リポジトリに他のsemverタグがない場合を除き、「latest」として選択されません(詳細)。

よくある間違いは、go get -u foofooの最新バージョンのみを取得すると考えることです。実際には、go get -u fooまたはgo get -u foo@latest-uは、fooのすべての直接的および間接的依存関係の最新バージョンも取得することを意味します。fooをアップグレードする場合の一般的な出発点は、-uなしでgo get fooまたはgo get foo@latestを実行することです(動作したら、go get -u=patch foogo get -u=patchgo get -u foo、またはgo get -uを検討してください)。

より具体的なバージョンにアップグレードまたはダウングレードするには、「go get」を使用すると、@versionサフィックスまたは「モジュールクエリ」をパッケージ引数に追加することでバージョン選択を上書きできます。例:go get foo@v1.6.2go get foo@e3702bed2、またはgo get foo@'<v1.6.2'

go get foo@master(Mercurialの場合はfoo@default)のようなブランチ名を使用すると、semverタグの有無にかかわらず、最新のコミットを取得できます。

一般的に、semverタグに解決されないモジュールクエリは、go.modファイルに疑似バージョンとして記録されます。

詳細については、goコマンドのドキュメントの「モジュール対応go get」セクションと「モジュールクエリ」セクションを参照してください。

モジュールは、モジュールをまだ採用していないパッケージを使用できます。これには、利用可能なsemverタグをgo.modに記録し、それらのsemverタグを使用してアップグレードまたはダウングレードすることも含まれます。モジュールは、適切なsemverタグを持っていないパッケージも使用できます(その場合、疑似バージョンを使用してgo.modに記録されます)。

依存関係のアップグレードまたはダウングレード後、ビルド内のすべてのパッケージ(直接的および間接的な依存関係を含む)のテストを再度実行して、互換性の問題がないか確認することをお勧めします。

$ go test all

リリースの準備方法

モジュールのリリース(すべてのバージョン)

モジュールのリリースを作成するためのベストプラクティスは、初期のモジュール実験の一部として確立される予定です。これらの多くは、将来の「go release」ツールによって自動化される可能性があります。

タグ付けする前に考慮すべき、現在推奨されているベストプラクティスの一部

モジュールのリリース(v2以降)

v2以上のモジュールをリリースする場合は、まず上記「セマンティックインポートバージョン管理」セクションの議論を確認してください。そこには、v2以上のモジュールでメジャーバージョンがモジュールパスとインポートパスに含まれる理由、およびGoバージョン1.9.7以上と1.10.3以上がその移行を簡素化するために更新された方法が記載されています。

モジュールを初めて採用する前に、既存のリポジトリまたはパッケージのセットに対してv2.0.0以上がすでにタグ付けされている場合、推奨されるベストプラクティスは、モジュールを初めて採用する際にメジャーバージョンを増分することです。たとえば、fooの作成者であり、fooリポジトリの最新のタグがv2.2.2であり、fooがまだモジュールを採用していない場合、モジュールを採用するfooの最初のリリース(つまり、go.modファイルを含むfooの最初のリリース)にはv3.0.0を使用するのがベストプラクティスです。この場合、メジャーバージョンを増分すると、fooの利用者にとってより明確になり、必要に応じてv2シリーズのfooで追加の非モジュールパッチまたはマイナーリリースが可能になり、モジュールベースのfooの利用者に対して、import "foo"と対応するrequire foo v2.2.2+incompatible、およびimport "foo/v3"と対応するrequire foo/v3 v3.0.0で異なるメジャーバージョンが生成されるという強力なシグナルが提供されます。(モジュールを初めて採用する際にメジャーバージョンを増分するというこのアドバイスは、最新のバージョンがv0.x.xまたはv1.x.xである既存のリポジトリまたはパッケージには適用されません)。

v2以上のモジュールをリリースする代替メカニズムが2つあります。どちらの方法でも、モジュール作成者が新しいタグをプッシュすると、新しいモジュールリリースが利用者にとって利用可能になります。v3.0.0リリースの作成例では、2つのオプションがあります。

  1. メジャーブランチgo.modファイルを更新して、moduleディレクティブのモジュールパスの最後に/v3を含めます(例:module github.com/my/module/v3)。モジュール内のインポート文も/v3を使用するように更新します(例:import "github.com/my/module/v3/mypkg")。リリースにv3.0.0というタグを付けます。

    • Goバージョン1.9.7以上、1.10.3以上、および1.11は、モジュールをまだ採用していない利用者側のコードを更新することなく、このアプローチを使用して作成されたv2以上のモジュールを適切に消費してビルドできます(上記「セマンティックインポートバージョン管理」セクションで説明)。
    • コミュニティツールgithub.com/marwan-at-work/modは、この手順の自動化に役立ちます。概要については、リポジトリまたは以下のコミュニティツールに関するFAQを参照してください。
    • このアプローチによる混乱を避けるために、モジュールのv3.*.*コミットを別のv3ブランチに配置することを検討してください。
    • 注記:新しいブランチを作成する必要はありません。以前にmasterでリリースしており、masterにv3.0.0というタグを付ける方が良い場合は、それも有効なオプションです。(ただし、goツールがGo 1.11以前のsemverを認識していない場合、またはGo 1.11以降でモジュールモードが有効になっていない場合、masterで非互換なAPI変更を導入すると、非モジュールユーザーがgo get -uを実行した場合に問題が発生する可能性があることに注意してください)。
    • depなどの既存の依存関係管理ソリューションは、この方法で作成されたv2以上のモジュールを消費する際に問題が発生することがあります。dep#1962を参照してください。
  2. メジャーサブディレクトリ:新しいv3サブディレクトリ(例:my/module/v3)を作成し、そのサブディレクトリに新しいgo.modファイルを作成します。モジュールパスは/v3で終わる必要があります。コードをv3サブディレクトリにコピーまたは移動します。モジュール内のインポート文も/v3を使用するように更新します(例:import "github.com/my/module/v3/mypkg")。リリースにv3.0.0というタグを付けます。

    • これにより、より高い下位互換性が実現します。特に、1.9.7および1.10.3より古いGoバージョンでも、このアプローチを使用して作成されたv2以上のモジュールを適切に消費してビルドできます。
    • ここでは、より洗練されたアプローチとして、型エイリアス(Go 1.9で導入)と、異なるサブディレクトリにあるメジャーバージョン間の転送シムを利用することができます。これにより、追加の互換性を提供し、あるメジャーバージョンを別のメジャーバージョンで実装できますが、モジュール作成者にとってより多くの作業が必要になります。これを自動化する進行中のツールはgoforwardです。詳細と理由、および機能するgoforwardの初期バージョンについては、こちらを参照してください。
    • depなどの既存の依存関係管理ソリューションは、この方法で作成されたv2以上のモジュールを消費できるはずです。

これらの代替策の詳細については、https://research.swtch.com/vgo-moduleを参照してください。

リリースの公開

新しいモジュールバージョンは、モジュールソースコードを含むリポジトリにタグをプッシュすることで公開できます。タグは、プレフィックスバージョンという2つの文字列を連結して形成されます。

バージョンは、リリースのセマンティックインポートバージョンです。セマンティックインポートバージョン管理のルールに従って選択する必要があります。

プレフィックスは、リポジトリ内でモジュールが定義されている場所を示します。モジュールがリポジトリのルートで定義されている場合、プレフィックスは空であり、タグはバージョンだけです。ただし、マルチモジュールリポジトリでは、プレフィックスによって異なるモジュールのバージョンが区別されます。プレフィックスは、リポジトリ内でモジュールが定義されているディレクトリです。リポジトリが上記で説明したメジャーサブディレクトリパターンに従っている場合、プレフィックスにはメジャーバージョンのサフィックスは含まれません。

たとえば、モジュールexample.com/repo/sub/v2があり、バージョンv2.1.6を公開するとします。リポジトリルートはexample.com/repoに対応し、モジュールはリポジトリ内のsub/v2/go.modで定義されています。このモジュールのプレフィックスはsub/です。このリリースの完全なタグはsub/v2.1.6にする必要があります。

モジュールへの移行

このセクションでは、モジュールに移行する際に決定する必要がある主要な事項を簡単に列挙し、その他の移行関連のトピックをリストしようとします。詳細については、一般的に他のセクションへの参照が提供されています。

この資料は、主にモジュール実験の一部としてコミュニティから得られたベストプラクティスに基づいています。したがって、これは進行中のセクションであり、コミュニティがより多くの経験を積むにつれて改善されるでしょう。

概要

移行トピック

以前の依存関係マネージャーからの自動移行

旧バージョンのGoおよびモジュールを使用しないコンシューマーへの依存関係情報の提供

既存のインストール手順の更新

既存のインポートパスの破壊を回避する

モジュールは、moduleディレクティブ(例:module github.com/my/module)を使用してgo.modでその識別情報を宣言します。モジュール内のすべてのパッケージは、モジュールの宣言されたモジュールパスと一致するインポートパスを使用して、モジュール認識コンシューマーによってインポートする必要があります(ルートパッケージの場合は完全に一致するか、インポートパスのプレフィックスとしてモジュールパスを使用します)。インポートパスと対応するモジュールの宣言されたモジュールパスが一致しない場合、goコマンドはunexpected module pathエラーを報告します。

既存のパッケージセットにモジュールを採用する際には、メジャーバージョンの増分を行わない限り、既存のコンシューマーによって使用されている既存のインポートパスを壊さないように注意する必要があります。

たとえば、既存のREADMEでコンシューマーにimport "gopkg.in/foo.v1"を使用するように指示していて、v1リリースでモジュールを採用する場合、最初のgo.modはほぼ確実にmodule gopkg.in/foo.v1になります。gopkg.inの使用をやめたい場合、それは現在のコンシューマーにとって破壊的な変更になります。1つのアプローチは、後でv2に移行する場合、module github.com/repo/foo/v2のようなものに変更することです。

モジュールパスとインポートパスは大文字と小文字が区別されます。たとえば、モジュールをgithub.com/Sirupsen/logrusからgithub.com/sirupsen/logrusに変更することは、GitHubがリポジトリ名を新しいリポジトリ名に自動的に転送する場合でも、コンシューマーにとって破壊的な変更です。

モジュールを採用した後、go.modでモジュールパスを変更することは破壊的な変更です。

全体として、これは、“インポートパスコメント”(「インポートプラグマ」または「インポートパスエンフォースメント」と呼ばれることもあります)による標準的なインポートパスのモジュール以前の適用と似ています。例として、パッケージgo.uber.org/zapは現在github.com/uber-go/zapでホストされていますが、パッケージ宣言の横にインポートパスコメントを使用して、間違ったgithubベースのインポートパスを使用するモジュール以前のコンシューマーに対してエラーをトリガーします。

package zap // import "go.uber.org/zap"

インポートパスコメントは、go.modファイルのモジュールステートメントによって廃止されました。

v2以降のパッケージを含むモジュールを最初に採用する際のメジャーバージョンの増分

v2以降のモジュールでは、単一のビルド内に複数のメジャーバージョンを使用できます

非モジュールコードを使用するモジュール

モジュールを使用する非モジュールコード

既存のv2以降のパッケージの作者向けの戦略

モジュールへの参加を検討している既存のv2以降のパッケージの作者にとって、代替アプローチを要約する1つの方法は、3つの最上位戦略のいずれかを選択することです。各選択肢には、その後、決定事項とバリエーションが続きます(上記で概説されています)。これらの代替最上位戦略は次のとおりです。

  1. Goバージョン1.9.7以降、1.10.3以降、または1.11以降を使用するようクライアントに要求する.

    このアプローチは「メジャーブランチ」アプローチを使用し、1.9.7および1.10.3にバックポートされた「最小限のモジュール認識」に依存しています。詳細については、上記の“セマンティックインポートバージョン管理”および“モジュールのリリース(v2以降)”セクションを参照してください。

  2. Go 1.8などの古いGoバージョンを使用できるようにする.

    このアプローチは「メジャーサブディレクトリ」アプローチを使用し、/v2または/v3などのサブディレクトリを作成することを含みます。詳細については、上記の“セマンティックインポートバージョン管理”および“モジュールのリリース(v2以降)”セクションを参照してください。

  3. モジュールへの参加を待つ.

    この戦略では、モジュールをオプトインしたクライアントコードと、オプトインしていないクライアントコードの両方で動作し続けます。時間が経つにつれて、Goバージョン1.9.7+、1.10.3+、1.11+はますます長い期間利用可能になり、将来のある時点で、Goバージョン1.9.7+/1.10.3+/1.11+を必須とする方が自然で、クライアントにとって使いやすいものになります。その時点で、上記の戦略1(Goバージョン1.9.7+、1.10.3+、または1.11+を必須とする)または上記の戦略2(ただし、1.8のような古いGoバージョンをサポートするために最終的に上記の戦略2を使用する場合は、今すぐ実行できます)を実装できます。

追加のリソース

ドキュメントと提案

入門資料

追加資料

最初の Vgo 提案以降の変更

提案、プロトタイプ、ベータ版のプロセスの過程で、全体的なコミュニティによって400以上の課題が作成されました。引き続きフィードバックを提供してください。

以下は、より大きな変更と改善の一部を部分的にリストしたものです。ほとんどすべてが主にコミュニティからのフィードバックに基づいていました。

GitHub の課題

FAQ

バージョンが非互換としてマークされるのはどのような場合ですか?

requireディレクティブを使用すると、任意のモジュールが、依存関係Dのバージョン>= x.y.zでビルドする必要があることを宣言できます(モジュールDのバージョンこれはdepcargoで使用されている制約の支配的な形式であることが示唆されています。さらに、ビルドのトップレベルモジュールは、依存関係の特定のバージョンをexcludeしたり、他のモジュールを異なるコードでreplaceしたりできます。詳細と根拠については、提案全体を参照してください。

バージョン管理されたモジュール提案の主要な目標の1つは、ツールと開発者の両方にとって、Goコードのバージョンの共通の語彙とセマンティクスを追加することです。これにより、将来、追加の非互換性の形式を宣言するための基盤が築かれます。たとえば、

古い動作と新しいモジュールベースの動作はいつ取得されますか?

一般的に、モジュールはGo 1.11ではオプトインであるため、設計上、デフォルトで古い動作が保持されます。

古い1.10の現状の動作と、新しいオプトインのモジュールベースの動作がいつ得られるかをまとめます。

go getを使用してツールをインストールすると、エラーcannot find main moduleが発生するのはなぜですか?

これは、GO111MODULE=onを設定しているが、go getを実行したときにgo.modのあるファイルツリー内にいない場合に発生します。

最も簡単な解決策は、GO111MODULEを設定しない(または同等にGO111MODULE=autoを明示的に設定する)ことです。これにより、このエラーを回避できます。

モジュールが存在する主な理由の1つは、正確な依存関係情報を記録することです。この依存関係情報は、現在のgo.modに書き込まれます。go.modのあるファイルツリー内にいないにもかかわらず、GO111MODULE=onを設定することでgo getコマンドにモジュールモードで動作するように指示した場合、go getを実行すると、依存関係情報を記録できるgo.modがないため、エラーcannot find main moduleが発生します。

代替ソリューションには以下が含まれます。

  1. GO111MODULEを設定しない(デフォルト、または明示的にGO111MODULE=autoを設定する)ことで、よりフレンドリーな動作になります。モジュール外にいる場合はGo 1.10の動作になり、go getcannot find main moduleを報告することを回避します。

  2. GO111MODULE=onのままにし、必要に応じて一時的にモジュールを無効にして、go get中にGo 1.10の動作を有効にします(例:GO111MODULE=off go get example.com/cmd)。これは、alias oldget='GO111MODULE=off go get'などの簡単なスクリプトまたはシェルエイリアスに変換できます。

  3. @rogpeppeによる簡単なシェルスクリプトによって自動化されている一時的なgo.modファイルを作成し、後で破棄します。このスクリプトを使用すると、vgoget example.com/cmd[@version]を使用してバージョン情報をオプションで提供できます。(これは、エラーcannot use path@version syntax in GOPATH modeを回避するための解決策になる可能性があります)。

  4. gobinは、メインパッケージをインストールして実行するためのモジュール認識コマンドです。デフォルトでは、gobinは最初にモジュールを手動で作成する必要なくメインパッケージをインストール/実行しますが、-mフラグを使用すると、既存のモジュールを使用して依存関係を解決するように指示できます。詳細と追加のユースケースについては、gobinREADMEFAQを参照してください。

  5. グローバルにインストールされたツールをトラッキングするために使用するgo.mod(例:~/global-tools/go.mod)を作成し、グローバルにインストールされたツールのgo getまたはgo installを実行する前に、そのディレクトリにcdします。

  6. ~/tools/gorename/go.mod~/tools/goimports/go.modなど、個別のディレクトリにツールごとにgo.modを作成し、ツールのgo getまたはgo installを実行する前に、適切なディレクトリにcdします。

現在のこの制限は解消されます。ただし、主な問題は、モジュールが現在オプトインであることであり、完全な解決策は、GO111MODULE=onがデフォルトの動作になるまで待つ可能性が高いです。詳細な議論、およびこのコメントについては、#24250 を参照してください。

これは最終的に確実に動作する必要があります。私が確信していないのは、バージョンに関してこれが正確に何をするかということです。一時的なモジュールルートとgo.modを作成し、インストールしてから破棄するのでしょうか?おそらくそうです。しかし、私は完全に確信しておらず、今のところ、go.modツリーの外でvgoに動作させることで人々を混乱させたくありませんでした。確かに、最終的なgoコマンドの統合はこれをサポートする必要があります。

このFAQでは、グローバルにインストールされたツールの追跡について説明してきました。

代わりに、特定のモジュールに必要なツールを追跡する場合は、次のFAQを参照してください。

モジュールのツール依存関係をどのように追跡できますか?

もしあなたが

現在推奨されるアプローチの1つは、関心のあるツールのインポートステートメント(例:import _ "golang.org/x/tools/cmd/stringer")と//go:build toolsビルド制約を含むtools.goファイルをモジュールに追加することです。インポートステートメントにより、goコマンドはモジュールのgo.modにツールのバージョン情報を正確に記録できます。一方、//go:build toolsビルド制約により、通常のビルドでツールを実際にインポートすることはなくなります。

これを行う方法の具体的な例については、この「Go Modules by Example」のチュートリアルを参照してください。

このアプローチに関する議論と、これを行う方法の以前の具体的な例は、#25922のこのコメントにあります。

簡単な理由(#25922からも)

私は、tools.goファイルが、実際にはツール依存関係のベストプラクティスであり、Go 1.11では特にそうであると考えています。

新しいメカニズムを導入しないため、気に入っています。

既存のものを再利用するだけです。

また(Go 1.16以降)、go install tool@versionを使用すると特定のバージョンをインストールできます。また(Go 1.17以降)、#42088および#40276で実装されているように、go run tool@versionを使用すると、インストールせずにツールを実行できます。これにより、tools.goの必要性がなくなります。

IDE、エディター、goimports、gorenameなどの標準ツールにおけるモジュールサポートの状況は?

エディターやIDEへのモジュールサポートが導入され始めています。

例えば

goimports、guru、gorenameなどの他のツールの状況は、包括的な問題#24661で追跡されています。最新の状況については、この包括的な問題を参照してください。

特定のツールの追跡の問題には以下が含まれます。

一般的に、エディター、IDE、その他のツールがまだモジュールに対応していなくても、GOPATH内でモジュールを使用し、go mod vendorを実行する場合(適切な依存関係がGOPATHを介して取得されるため)、その機能の多くはモジュールで動作するはずです。

完全な修正は、パッケージをロードするプログラムをgo/buildからgolang.org/x/tools/go/packagesに移行することです。これは、モジュールを認識する方法でパッケージの場所を特定する方法を理解しています。これは最終的にgo/packagesになる可能性があります。

FAQ — 追加の制御

モジュールの操作に使用できるコミュニティツールは何ですか?

コミュニティは、モジュールの上にツールを構築し始めています。例えば

いつreplaceディレクティブを使用する必要がありますか?

上記の「go.mod」の概念に関するセクションで説明されているように、replaceディレクティブは、Goソースまたはgo.modファイルで見つかった依存関係を満たすために実際に使用されるものを、トップレベルのgo.modで追加的に制御します。一方、メインモジュール以外のモジュール内のreplaceディレクティブは、メインモジュールをビルドする際には無視されます。

replaceディレクティブを使用すると、VCS(GitHubなど)にある別のモジュール、または相対パスまたは絶対パスを持つローカルファイルシステムにある別のインポートパスを指定できます。実際のソースコードのインポートパスを更新する必要なく、replaceディレクティブからの新しいインポートパスが使用されます。

replaceを使用すると、トップレベルのモジュールで依存関係に使用される正確なバージョンを制御できます。例えば、

replaceを使用すると、フォークされた依存関係を使用することもできます。例えば、

ブランチを参照することもできます。例えば、

1つの使用例は、依存関係で何かを修正または調査する必要がある場合です。ローカルフォークを作成し、トップレベルのgo.modに次のようなものを追加できます。

replaceは、マルチモジュールプロジェクトのモジュールのディスク上の相対パスまたは絶対パスをgoツールに知らせるためにも使用できます。例えば、

注記replaceディレクティブの右辺がファイルシステムパスである場合、その場所にgo.modファイルが存在する必要があります。go.modファイルが存在しない場合は、go mod initを使用して作成できます。

一般的に、replaceディレクティブの=>の左側にバージョンを指定できますが、通常、省略した場合の方が変更の影響を受けにくいです(上記のすべてのreplaceの例のように)。

直接的な依存関係の各replaceディレクティブには、requireディレクティブが必要です。ファイルシステムパスから依存関係を置き換える場合、対応するrequireディレクティブのバージョンは本質的に無視されます。この場合、疑似バージョンv0.0.0はこれを明確にするために良い選択です。例:require example.com/module v0.0.0

go list -m allを実行すると、replaceステートメントを考慮してビルドで使用される実際の最終バージョンが表示されるため、期待どおりのバージョンを取得していることを確認できます。

詳細については、「go mod edit」のドキュメントを参照してください。

github.com/rogpeppe/gohackは、このようなワークフローをはるかに容易にします。特に、モジュールの依存関係の変更可能なチェックアウトを行うことを目的としている場合です。リポジトリまたはすぐ前のFAQを参照して概要を確認してください。

VCSの外部で完全に動作するreplaceの使用の詳細については、次のFAQを参照してください。

ローカルファイルシステム上の VCS の外部で完全に作業できますか?

はい。VCSは必須ではありません。

一度に編集する単一モジュールがVCSの外部にある場合(モジュールが合計で1つしかないか、他のモジュールがVCSにある場合)、これは非常に簡単です。この場合、単一のgo.modを含むファイルツリーを便利な場所に配置できます。単一のモジュールがVCSの外部にあっても(go.modreplaceを使用する必要なく)、go buildgo testなどコマンドは機能します。

ローカルディスク上に複数の相互関連するモジュールを同時に編集したい場合、`replace`ディレクティブが1つのアプローチです。 バージョン管理システム(VCS)に依存せずに、`hello`モジュールを`goodbye`モジュールのディスク上の場所を指す相対パスで置き換える`go.mod`のサンプルを以下に示します。

module example.com/me/hello

require (
  example.com/me/goodbye v0.0.0
)

replace example.com/me/goodbye => ../goodbye

実行可能な小さな例は、このスレッドに示されています。

モジュールでベンダーを使用するにはどうすればよいですか?ベンダーは廃止されますか?

初期の`vgo`ブログ記事シリーズでは、ベンダーディレクトリを完全に廃止することを提案していましたが、コミュニティからのフィードバックにより、ベンダーディレクトリのサポートは維持されました。

簡単に言うと、モジュールでベンダーディレクトリを使用するには

Go 1.10などの古いバージョンは、`go mod vendor`によって作成されたベンダーディレクトリを使用する方法を理解しており、モジュールモードが無効になっている場合、Go 1.11と1.12以降でも同様です。したがって、ベンダーディレクトリは、モジュールを完全に理解していない古いバージョンのGo、およびモジュール自体を有効にしていないコンシューマーに依存関係を提供する1つの方法です。

ベンダーディレクトリを使用することを検討している場合は、tipドキュメントの「モジュールとベンダーディレクトリ」セクションと「依存関係のベンダーコピーを作成する」セクションを読むことをお勧めします。

「常にオン」のモジュールリポジトリとエンタープライズプロキシはありますか?

公開ホストされている「常時稼働」の不変モジュールリポジトリと、オプションのプライベートホストプロキシおよびリポジトリが利用可能になりつつあります。

例えば

プロキシを実行する必要はないことに注意してください。 むしろ、1.11のgoツールは、GOPROXYを介してオプションのプロキシサポートを追加し、より多くのエンタープライズユースケース(より高度な制御など)を可能にし、「GitHubがダウンしている」などの状況や、ユーザーがGitHubリポジトリを削除する状況をより適切に処理できるようにしました。

go.mod の更新時期と、go ツールがネットワークを使用して依存関係を満たす時期を制御できますか?

デフォルトでは、`go build`などのコマンドは、必要に応じてネットワークにアクセスしてインポートを満たします。

一部のチームは、特定の時点でgoツールがネットワークに触れることを許可しない、またはgoツールが`go.mod`を更新するタイミング、依存関係の取得方法、ベンダーディレクトリの使い方についてより詳細な制御を望む場合があります。

goツールは、`-mod=readonly`、`-mod=vendor`、`GOFLAGS`、`GOPROXY=off`、`GOPROXY=file:///filesystem/path`、`go mod vendor`、`go mod download`などを使用して、これらのデフォルトの動作を調整または無効にするためのかなりの柔軟性を提供します。

これらのオプションの詳細については、公式ドキュメント全体に分散されています。 これらの動作に関連するノブの統合された概要に関するコミュニティによる試みはこちらにあり、詳細については公式ドキュメントへのリンクが含まれています。

Travis や CircleCI などの CI システムでモジュールを使用するにはどうすればよいですか?

最も簡単なアプローチはおそらく、ほとんどのCIシステムで動作する環境変数`GO111MODULE=on`を設定することです。

ただし、一部のユーザーはまだモジュールを有効にしていないことを考えると、モジュールを有効にした状態と無効にした状態でGo 1.11でCIでテストを実行することは有益です。ベンダーディレクトリも考慮すべき点です。

次の2つのブログ記事では、これらのトピックについて具体的に説明しています。

特定のパッケージやテストをビルドするために必要なモジュールをダウンロードするにはどうすればよいですか?

`go mod download`コマンド(または同等の`go mod download all`)は、ビルドリスト(`go list -m all`によって報告される)内のすべてのモジュールをダウンロードします。これらのモジュールの多くは、メインモジュールのパッケージをビルドするのに必要ありません。完全なビルドリストには、テスト依存関係や他のモジュールのツール依存関係など、さまざまなものが含まれているためです。 その結果、`go mod download`で準備されたDockerイメージは、必要以上に大きくなる可能性があります。

代わりに、`go list`を使用することを検討してください。たとえば、`go list ./...`は、`./...`(モジュールルートディレクトリから実行した場合のメインモジュールのパッケージのセット)をビルドするのに必要なモジュールをダウンロードします。

テスト依存関係もダウンロードするには、`go list -test ./...`を使用します。

デフォルトでは、`go list`は現在のプラットフォームに必要な依存関係のみを考慮します。 `GOOS`と`GOARCH`を設定して、`go list`に別のプラットフォームを考慮させることができます(例:`GOOS=linux GOARCH=amd64 go list ./...`)。`-tags`フラグを使用して、特定のビルドタグを持つパッケージを選択することもできます。

遅延モジュール読み込みが実装された将来(#36460を参照)には、モジュールパターン`all`に含まれるモジュールが少なくなるため、このテクニックはそれほど必要なくなる可能性があります。

FAQ — go.mod と go.sum

なぜ 'go mod tidy' は間接依存関係とテスト依存関係を 'go.mod' に記録するのですか?

モジュールシステムは、`go.mod`に正確な依存関係の要件を記録します。(詳細については、上記のgo.modの概念セクション、またはgo.mod tipドキュメントを参照してください)。

`go mod tidy`は、モジュールのテストに必要な依存関係を含めるように現在の`go.mod`を更新します。テストが失敗した場合、失敗を再現するために使用された依存関係を知る必要があります。

`go mod tidy`はまた、現在の`go.mod`が、OS、アーキテクチャ、ビルドタグのすべての可能な組み合わせの依存関係の要件を反映していることを保証します(こちらで説明されているとおり)。一方、`go build`や`go test`などの他のコマンドは、現在の`GOOS`、`GOARCH`、ビルドタグの下で要求されたパッケージによってインポートされたパッケージを提供するように`go.mod`を更新するだけです(これは、`go mod tidy`が`go build`などでは追加されなかった要件を追加する可能性のある理由の1つです)。

モジュールの依存関係自体に`go.mod`がない場合(たとえば、依存関係自体がまだモジュールを有効にしていないため)、またはその`go.mod`ファイルに1つ以上の依存関係がない場合(たとえば、モジュール作成者が`go mod tidy`を実行しなかったため)、不足している推移的な依存関係は、あなたのモジュールの要件に追加され、依存関係があなたのモジュール内の直接のインポートからではないことを示す`// indirect`コメントが追加されます。

これは、直接または間接的な依存関係から不足しているテスト依存関係も`go.mod`に記録されることを意味します。(これが重要な場合の例:`go test all`は、モジュールのすべての直接および間接的な依存関係のテストを実行し、現在のバージョンの組み合わせが連携して動作することを検証する1つの方法です。`go test all`を実行するときに依存関係の1つでテストが失敗した場合、再現可能な`go test all`動作を確保するために、完全なテスト依存関係情報セットを記録することが重要です)。

`go.mod`ファイルに`// indirect`依存関係があるもう1つの理由は、`go get -u`または`go get foo@1.2.3`を実行した場合など、直接的な依存関係で必要なものを超えて間接的な依存関係の1つをアップグレード(またはダウングレード)した場合です。goツールは、これらの新しいバージョンを記録する場所が必要であり、`go.mod`ファイルで行います(依存関係に到達してそれらの`go.mod`ファイルを修正することはありません)。

一般的に、上記で説明した動作は、正確な依存関係情報を記録することで、モジュールが100%再現可能なビルドとテストを提供する方法の一部です。

特定のモジュールが`go.mod`に表示されている理由を知りたい場合は、`go mod why -m <module>`を実行して、その質問に答えることができます。要件とバージョンを検査するための他の便利なツールには、`go mod graph`と`go list -m all`があります。

'go.sum' はロックファイルですか?なぜ 'go.sum' には、もはや使用していないモジュールバージョンの情報が含まれているのですか?

いいえ、`go.sum`はロックファイルではありません。ビルド内の`go.mod`ファイルには、100%再現可能なビルドに必要な情報が十分に含まれています。

検証目的で、`go.sum`には、特定のモジュールバージョンのコンテンツの期待される暗号化チェックサムが含まれています。`go.sum`の詳細(通常は`go.sum`をチェックインする理由など)については、下記のFAQ、およびtipドキュメントの「モジュールのダウンロードと検証」セクションを参照してください。

さらに、モジュールの`go.sum`には、ビルドで使用されるすべての直接的および間接的な依存関係のチェックサムが記録されます(したがって、`go.sum`には、`go.mod`よりも多くのモジュールがリストされていることがよくあります)。

'go.mod' ファイルと同様に 'go.sum' ファイルもコミットする必要がありますか?

通常、モジュールの`go.sum`ファイルは`go.mod`ファイルと一緒にコミットする必要があります。

依存関係がない場合でも 'go.mod' ファイルを追加する必要がありますか?

はい。これにより、GOPATHの外で作業することがサポートされ、モジュールをオプトインしていることをエコシステムに伝えるのに役立ちます。さらに、go.mod内のmoduleディレクティブは、コードのアイデンティティを明確に宣言する役割を果たします(これが、インポートコメントが最終的に非推奨となる可能性のある理由の1つです)。もちろん、モジュールはGo 1.11では純粋にオプトイン機能です。

FAQ — セマンティックインポートバージョン管理

なぜメジャーバージョン番号はインポートパスに表示される必要があるのですか?

上記の“セマンティックインポートバージョン管理”概念セクションで、セマンティックインポートバージョン管理とインポート互換性ルールに関する議論を参照してください。提案を発表したブログ記事も参照してください。そこでは、インポート互換性ルールの動機と正当性について詳しく説明されています。

メジャーバージョンv0、v1がインポートパスから省略されるのはなぜですか?

公式の提案に関する議論からの以前のFAQで、「メジャーバージョンv0、v1がインポートパスから省略されるのはなぜですか?」という質問を参照してください。

プロジェクトにメジャーバージョン v0、v1 でタグを付けたり、v2 以降で破壊的な変更を加えることの含意は何ですか?

「k8sはマイナーリリースを行うが、各マイナーリリースでGo APIを変更する」というコメントへの回答として、Russ Coxは、プロジェクトでv0、v1を選択すること、またはv2、v3、v4などで頻繁に破壊的変更を行うことの影響を強調した以下の回答を行いました。

k8sの開発サイクルなどは完全に理解していませんが、一般的にk8sチームは、安定性についてユーザーに何を保証するつもりなのかを決定/確認し、それに応じてバージョン番号を適用する必要があると思います。

  • API互換性に関する約束をする(これは最高のユーザーエクスペリエンスのように思えます!)のであれば、それを始め、1.X.Yを使用してください。
  • すべてのリリースで下位互換性のない変更を行う柔軟性を持ちながら、大規模プログラムの異なる部分が異なるスケジュールでコードをアップグレードできるようにする(つまり、異なる部分が1つのプログラムでAPIの異なるメジャーバージョンを使用できる)場合は、X.Y.0と、k8s.io/client/vX/fooのようなインポートパスを使用してください。
  • API互換性に関する約束をせず、すべてのビルドでk8sライブラリの1つのコピーのみを必要とし(すべての部分が準備ができていなくても、すべての部分が同じバージョンを使用することを暗黙的に強制する)、その場合は0.X.Yを使用してください。

関連事項として、Kubernetesにはいくつかの非標準的なビルドアプローチがあります(現在、godepの上にカスタムラッパースクリプトを含んでいます)。そのため、Kubernetesは他の多くのプロジェクトにとって完璧な例ではありませんが、KubernetesがGo 1.11モジュールを採用するにつれて、興味深い例になる可能性があります。

モジュールは、モジュールにオプトインしていないパッケージを使用できますか?

はい。

リポジトリがモジュールをオプトインしていないが、有効なsemverタグ(必須の先頭のvを含む)でタグ付けされている場合、これらのsemverタグはgo getで使用でき、対応するsemverバージョンはインポートするモジュールのgo.modファイルに記録されます。リポジトリに有効なsemverタグがない場合、リポジトリのバージョンはv0.0.0-20171006230638-a6e239ea1c69など、「擬似バージョン」で記録されます(タイムスタンプとコミットハッシュが含まれ、go.modに記録されたバージョン全体での全順序付けを容易にし、どの記録されたバージョンが別の記録されたバージョンよりも「後」であるかを判断しやすくするように設計されています)。

例えば、パッケージfooの最新バージョンがv1.2.3でタグ付けされているが、foo自体がモジュールをオプトインしていない場合、モジュールMの内側からgo get fooまたはgo get foo@v1.2.3を実行すると、モジュールMのgo.modファイルに以下のように記録されます。

require  foo  v1.2.3

goツールは、非モジュールパッケージの追加ワークフロー(モジュールの依存関係をパッチリリースで利用可能なものへアップグレードするgo list -u=patch、利用可能なアップグレードを表示するgo list -u -m allなど)でも、利用可能なsemverタグを使用します。

v2以降のパッケージでモジュールをオプトインしていないものに関する追加の詳細については、次のFAQを参照してください。

モジュールは、モジュールにオプトインしていない v2 以降のパッケージを使用できますか?'+incompatible' とは何を意味しますか?

はい、モジュールはモジュールをオプトインしていないv2以降のパッケージをインポートできます。インポートされたv2以降のパッケージに有効なsemverタグがある場合、+incompatibleサフィックス付きで記録されます。

追加の詳細

上記の“セマンティックインポートバージョン管理”セクションの資料をよく理解しておいてください。

一般的に役立つが、このFAQで説明されている動作について考える際に特に心に留めておくことが重要な、いくつかの基本原則を最初に確認することをお勧めします。

goツールがモジュールモード(例:GO111MODULE=on)で動作している場合、次の基本原則は常に当てはまります。

  1. パッケージのインポートパスは、パッケージのアイデンティティを定義します。
    • 異なるインポートパスを持つパッケージは、異なるパッケージとして扱われます。
    • 同じインポートパスを持つパッケージは、同じパッケージとして扱われます(VCSタグがパッケージのメジャーバージョンが異なることを示していても、これは当てはまります)。
  2. /vNを含まないインポートパスは、v1またはv0モジュールとして扱われます(インポートされたパッケージがモジュールをオプトインしておらず、VCSタグがメジャーバージョンが1より大きいことを示していても、これは当てはまります)。
  3. モジュールのgo.modファイルの先頭で宣言されたモジュールパス(例:module foo/v2)は、両方とも
    • そのモジュールのアイデンティティの明確な宣言
    • そのモジュールが消費コードによってインポートされる方法の明確な宣言

次のFAQでわかるように、これらの原則は、goツールがモジュールモードになっていない場合は必ずしも当てはまりませんが、goツールがモジュールモードになっている場合は常に当てはまります。

簡単に言うと、+incompatibleサフィックスは、次の場合に原則2が有効であることを示しています。

goツールがモジュールモードの場合、モジュールではないv2以降のパッケージは、セマンティックインポートバージョン管理を認識しておらず、パッケージのv1バージョンシリーズの(非互換な)拡張として扱われると仮定します(+incompatibleサフィックスは、goツールがそうしていることを示しています)。

以下を仮定します。

この場合、例えばモジュールMの内側からgo get oldpackage@latestを実行すると、モジュールMのgo.modファイルに以下が記録されます。

require  oldpackage  v3.0.1+incompatible

上記のgo getコマンドや記録されたrequireディレクティブのoldpackageの末尾に/v3は使用されていません。モジュールパスとインポートパスで/vNを使用することはセマンティックインポートバージョン管理の機能であり、oldpackageは、oldpackage自体内にgo.modファイルを持つことでモジュールをオプトインしていないため、セマンティックインポートバージョン管理の権利と責任を認められていません。言い換えれば、oldpackagev3.0.1semverタグを持っている場合でも、oldpackageセマンティックインポートバージョン管理(インポートパスに/vNを使用するなど)の権利と責任を付与されていません。なぜなら、oldpackageはまだそうする意思を表明していないからです。

+incompatibleサフィックスは、oldpackagev3.0.1バージョンがモジュールを積極的にオプトインしておらず、したがってoldpackagev3.0.1バージョンはセマンティックインポートバージョン管理やインポートパスでメジャーバージョンを使用する方法を理解していないと仮定されていることを示しています。モジュールモードで動作している場合、goツールは、モジュールではないoldpackagev3.0.1バージョンをoldpackageのv1バージョンシリーズの(非互換な)拡張として扱い、oldpackagev3.0.1バージョンはセマンティックインポートバージョン管理を認識していないと仮定します。+incompatibleサフィックスは、goツールがそうしていることを示しています。

セマンティックインポートバージョン管理によると、oldpackagev3.0.1バージョンがv1リリースシリーズの一部と見なされるということは、例えばバージョンv1.0.0v2.0.0v3.0.1はすべて同じインポートパスを使用して常にインポートされることを意味します。

import  "oldpackage"

繰り返しますが、oldpackageの末尾に/v3は使用されていません。

一般的に、インポートパスが異なるパッケージは異なるパッケージです。この例では、oldpackageのバージョンv1.0.0v2.0.0v3.0.1はすべて同じインポートパスを使用してインポートされるため、ビルドによって同じパッケージとして扱われます(oldpackageはまだセマンティックインポートバージョン管理をオプトインしていないため)。1つのoldpackageのコピーが、特定のビルドに含まれます。(使用されるバージョンは、任意のrequireディレクティブにリストされているバージョンのうち、セマンティック的に最も高いバージョンです。“バージョン選択”を参照してください)。

その後、モジュールを採用したoldpackageの新しいv4.0.0リリースが作成されたと仮定すると(したがって、go.modファイルが含まれます)、これはoldpackageがセマンティックインポートバージョン管理の権利と責任を理解していることを示す信号であり、したがってモジュールベースのコンシューマは、インポートパスで/v4を使用してインポートするようになります。

import  "oldpackage/v4"

そして、バージョンは以下のように記録されます。

require  oldpackage/v4  v4.0.0

oldpackage/v4は、oldpackageとは異なるインポートパスであるため、異なるパッケージです。ビルド内のコンシューマの一部がimport "oldpackage/v4"を使用し、同じビルド内の他のコンシューマがimport "oldpackage"を使用している場合、2つのコピー(インポートパスごとに1つ)がモジュール認識ビルドに含まれることになります。これは、モジュールの段階的な採用を可能にする戦略の一部として望ましいものです。さらに、モジュールが現在の移行段階を終了した後でも、この動作は、大規模なビルド内の異なるコンシューマが異なる速度で新しいバージョンにアップグレードすることを可能にする(例えば、大規模なビルド内の異なるコンシューマがoldpackage/v4から将来のoldpackage/v5へのアップグレードを異なる速度で選択できるようにする)という点で望ましいものです。

モジュールサポートが有効になっていない場合、ビルドで v2 以降のモジュールはどのように処理されますか?1.9.7 以降、1.10.3 以降、および 1.11 での「最小モジュール互換性」はどのように機能しますか?

以前のGoバージョンやモジュールをまだオプトインしていないGoコードを検討する場合、セマンティックインポートバージョン管理は、v2以降のモジュールに関連して重要な下位互換性の影響を及ぼします。

上記の“セマンティックインポートバージョン管理”セクションで説明されているように

ただし、エコシステムは、モジュールとセマンティックインポートバージョン管理の採用ペースが異なることが予想されます。

「v2+ モジュールのリリース方法」セクション(“v2+ モジュールのリリース方法”)でより詳細に説明されているように、「メジャーサブディレクトリ」アプローチでは、v2+ モジュールの作成者は、mymodule/v2mymodule/v3 などのサブディレクトリを作成し、適切なパッケージをこれらのサブディレクトリの下に移動またはコピーします。これは、従来のインポートパスロジック(Go 1.8 や 1.7 などの古い Go リリースでも)が、import "mymodule/v2/mypkg" などのインポート文を見ると適切なパッケージを見つけることを意味します。したがって、「メジャーサブディレクトリ」v2+ モジュールにあるパッケージは、モジュールサポートが有効になっていない場合でも(Go 1.11 を実行していてモジュールを有効にしていない場合、または完全なモジュールサポートがない Go 1.7、1.8、1.9、1.10 などの古いバージョンを実行している場合)検出され、使用されます。「メジャーサブディレクトリ」アプローチの詳細については、“v2+ モジュールのリリース方法”セクションを参照してください。

この FAQ の残りの部分は、「v2+ モジュールのリリース方法」セクション(“v2+ モジュールのリリース方法”)で説明されている「メジャーブランチ」アプローチに焦点を当てています。「メジャーブランチ」アプローチでは、/vN サブディレクトリは作成されず、代わりにモジュールバージョン情報は go.mod ファイルによって伝えられ、コミットに semver タグを適用することで伝達されます(多くの場合 master ブランチ上で行われますが、異なるブランチ上で行われることもあります)。

現在の移行期間中に支援するために、「最小限のモジュール互換性」が 導入 され、Go 1.11 により Go コードとの互換性が向上しました。この「最小限のモジュール互換性」は、Go 1.9.7 と 1.10.3 にもバックポートされました(これらのバージョンでは、古い Go バージョンには完全なモジュールサポートがないため、事実上常に完全なモジュールモードが無効になっています)。

「最小限のモジュール互換性」の主な目標は次のとおりです。

  1. 古い Go バージョン(1.9.7 以降および 1.10.3 以降)が、インポートパスに /vN を使用しているセマンティックインポートバージョンを使用するモジュールをより簡単にコンパイルできるようにし、モジュールモード が Go 1.11 で無効になっている場合にも同じ動作を提供します。

  2. 古いコードが、v2+ モジュールを使用する際に、古いコンシューマーコードが新しい /vN インポートパスを使用するようにすぐに変更する必要なく、v2+ モジュールを使用できるようにします。

  3. モジュール作成者が /vN サブディレクトリを作成することに依存せずに、これを実現します。

追加の詳細 – 「最小限のモジュール互換性」

「最小限のモジュール互換性」は、go ツールの完全なモジュールモードが無効になっている場合にのみ有効になります。たとえば、Go 1.11 で GO111MODULE=off を設定した場合、または Go 1.9.7 以降または 1.10.3 以降を使用している場合などです。

v2+ モジュールの作成者が /v2 または /vN サブディレクトリを作成しておらず、「最小限のモジュール互換性」メカニズムを Go 1.9.7 以降、1.10.3 以降、および 1.11 で使用している場合

go.mod を作成しても、リポジトリに semver タグを適用しなかった場合はどうなるでしょうか?

semver はモジュールシステムの基礎です。コンシューマーにとって最高のエクスペリエンスを提供するために、モジュール作成者は semver VCS タグ(例:v0.1.0 または v1.2.3-rc.1)を適用することを推奨していますが、semver VCS タグは厳密には必須ではありません。

  1. go コマンドがドキュメントどおりに動作するには、モジュールがsemver 仕様に従う必要があります。これには、破壊的変更が許可される方法とタイミングに関する semver 仕様に従うことが含まれます。

  2. semver VCS タグを持たないモジュールは、擬似バージョン形式の semver バージョンを使用してコンシューマーによって記録されます。通常、モジュール作成者が“メジャーサブディレクトリ”アプローチに従って v2+ モジュールを作成しない限り、これは v0 メジャーバージョンになります。

  3. したがって、semver VCS タグを適用しておらず、「メジャーサブディレクトリ」を作成していないモジュールは、事実上、semver v0 メジャーバージョンのシリーズにあると宣言しており、モジュールベースのコンシューマーはそれらを semver v0 メジャーバージョンとして扱います。

モジュールは、それ自体の異なるバージョンに依存できますか?

モジュールは、それ自体の異なるメジャーバージョンに依存できます。概して、これは異なるモジュールに依存することと比較できます。これは、モジュールのメジャーバージョンを別のメジャーバージョンをラップするシムとして実装するために役立ちます。

さらに、まったく異なる2つのモジュールがお互いに循環的に依存できるのと同じように、モジュールは循環的にそれ自体の異なるメジャーバージョンに依存できます。

ただし、モジュールがそれ自体の異なるバージョンに依存することを期待していない場合は、間違いの兆候である可能性があります。たとえば、v3 モジュールのパッケージをインポートすることを意図した .go コードは、インポート文に必要な /v3 が不足している可能性があります。その間違いは、v3 モジュールがそれ自体の v1 バージョンに依存しているという形で現れる可能性があります。

モジュールがそれ自体の異なるバージョンに依存していることに驚いた場合は、上記の“セマンティックインポートバージョン”セクションとFAQの“依存関係の期待されるバージョンが表示されない場合に確認できること”を確認する価値があります。

2つのパッケージがお互いに循環的に依存することはできないという制約は依然として残っています。

FAQ — 複数モジュールリポジトリ

マルチモジュールリポジトリとは何ですか?

複数モジュールリポジトリとは、それぞれ独自の go.mod ファイルを持つ複数のモジュールを含むリポジトリのことです。各モジュールは、その go.mod ファイルを含むディレクトリから始まり、そのディレクトリとそのサブディレクトリから再帰的にすべてのパッケージを含み、別の go.mod ファイルを含むサブツリーは除外します。

各モジュールには独自のバージョン情報があります。リポジトリのルートの下にあるモジュールのバージョンタグには、相対ディレクトリをプレフィックスとして含める必要があります。たとえば、次のリポジトリを考えてみてください。

my-repo
`-- foo
    `-- rop
        `-- go.mod

モジュール「my-repo/foo/rop」のバージョン1.2.3のタグは「foo/rop/v1.2.3」です。

通常、リポジトリ内の1つのモジュールのパスは、他のモジュールのパスへのプレフィックスになります。たとえば、次のリポジトリを考えてみてください。

my-repo
|-- bar
|-- foo
|   |-- rop
|   `-- yut
|-- go.mod
`-- mig
    |-- go.mod
    `-- vub

Fig. A top-level module’s path is a prefix of another module’s path.

図 A トップレベルモジュールのパスは、別のモジュールのパスのプレフィックスです。

このリポジトリには2つのモジュールが含まれています。ただし、モジュール「my-repo」はモジュール「my-repo/mig」のパスへのプレフィックスです。

単一のリポジトリに複数のモジュールを含める必要がありますか?

このような構成でモジュールを追加、削除、およびバージョン管理するには、相当な注意と熟考が必要です。そのため、既存のリポジトリに複数のモジュールを管理するよりも、単一モジュールリポジトリを管理する方がほぼ常に簡単でシンプルです。

Russ Cox は #26664 でコメントしました。

パワーユーザーを除くほとんどのユーザーにとって、1つのリポジトリを1つのモジュールとする通常の規約を採用することをお勧めします。リポジトリが複数のモジュールを含むことが可能であることは、コード保存オプションの長期的な進化にとって重要ですが、デフォルトでそれを行うことはほとんど間違いなく望ましいことではありません。

マルチモジュールがより多くの作業になる2つの例

しかし、これらの2つの例を超えた追加のニュアンスがあります。単一のリポジトリに複数のモジュールを含めることを検討している場合は、このサブセクションのFAQを注意深くお読みください。

リポジトリに複数のgo.modファイルがあることが理にかなう2つのシナリオの例

  1. 例自体が複雑な依存関係セットを持つ使用例がある場合(たとえば、小さなパッケージを持っているが、Kubernetesを使用してパッケージを使用する例を含める場合など)。その場合、こちらに示すように、独自のgo.modを持つexampleまたは_exampleディレクトリにリポジトリを持たせることが理にかなう場合があります。

  2. 複雑な依存関係セットを持つリポジトリがあり、依存関係のセットがより小さいクライアントAPIがある場合。場合によっては、独自のgo.modを持つapiまたはclientapiなどのディレクトリを作成するか、そのclientapiを別々のリポジトリに分離することが理にかなう場合があります。

ただし、これらの両方のケースにおいて、多数の非直接的な依存関係のパフォーマンスまたはダウンロードサイズのためにマルチモジュールリポジトリの作成を検討している場合は、まずGOPROXYを試すことを強くお勧めします。GOPROXYはGo 1.13でデフォルトで有効になります。GOPROXYを使用すると、マルチモジュールリポジトリを作成することによって得られるパフォーマンス上の利点や依存関係のダウンロードサイズ上の利点の大部分が得られます。

マルチモジュールリポジトリにモジュールを追加することはできますか?

はい。ただし、この問題には2つのクラスがあります。

最初のクラス:モジュールが追加されるパッケージはまだバージョン管理されていません(新しいパッケージ)。このケースは簡単です。パッケージとgo.modを同じコミットに追加し、コミットにタグを付け、プッシュします。

2番目のクラス:モジュールが追加されるパスはバージョン管理されており、1つ以上の既存のパッケージが含まれています。このケースでは、かなりの注意が必要です。例として、次のリポジトリ(現実の世界をよりよくシミュレートするためにgithub.comの場所に配置)をもう一度検討してください。

github.com/my-repo
|-- bar
|-- foo
|   |-- rop
|   `-- yut
|-- go.mod
`-- mig
    `-- vub

モジュール「github.com/my-repo/mig」を追加することを検討してください。上記と同じアプローチに従うと、パッケージ/my-repo/migは2つの異なるモジュールによって提供される可能性があります。「github.com/my-repo」の古いバージョンと、新しいスタンドアロンモジュール「github.com/my-repo/mig」です。両方のモジュールがアクティブな場合、「github.com/my-repo/mig」のインポートは、コンパイル時に「あいまいなインポート」エラーが発生します。

これを回避する方法は、新しく追加されたモジュールが、「切り出された」モジュールに、切り出された後のバージョンで依存するようにすることです。

「github.com/my-repo」が現在v1.2.3であると仮定して、上記のリポジトリでこの手順を実行してみましょう。

  1. github.com/my-repo/mig/go.modを追加

    cd path-to/github.com/my-repo/mig
    go mod init github.com/my-repo/mig
    
    # Note: if "my-repo/mig" does not actually depend on "my-repo", add a blank
    # import.
    # Note: version must be at or after the carve-out.
    go mod edit -require github.com/myrepo@v1.3
    
  2. git commit

  3. git tag v1.3.0

  4. git tag mig/v1.0.0

  5. 次に、これらをテストしましょう。goコマンドは各依存モジュールをモジュールキャッシュからフェッチしようと試みるため、go buildまたはgo testを単純に実行することはできません。そのため、replaceルールを使用して、goコマンドがローカルコピーを使用するようにする必要があります。

    cd path-to/github.com/my-repo/mig
    go mod edit -replace github.com/my-repo@v1.3.0=../
    go test ./...
    go mod edit -dropreplace github.com/my-repo@v1.3.0
    
  6. git push origin master v1.3.0 mig/v1.0.0コミットと両方のタグをプッシュ

将来、golang.org/issue/28835により、テスト手順がより簡単なエクスペリエンスになるはずです。

マイナーバージョンの間でモジュール「github.com/my-repo」からコードが削除されたことにも注意してください。これをメジャーな変更と見なさないと奇妙に思えるかもしれませんが、このインスタンスでは、推移的な依存関係は、削除されたパッケージの互換性のある実装を元のインポートパスで引き続き提供します。

マルチモジュールリポジトリからモジュールを削除することはできますか?

はい、上記と同じ2つのケースと同様の手順です。

モジュールは、別のモジュールの内部/にある`internal`パッケージに依存できますか?

はい。1つのモジュールのパッケージは、内部/パスコンポーネントまで同じパスプレフィックスを共有している限り、別のモジュールから内部パッケージをインポートできます。たとえば、次のリポジトリを考えてみましょう。

my-repo
|-- foo
|   `-- go.mod
|-- go.mod
`-- internal

ここでは、モジュール「my-repo/foo」がモジュール「my-repo」に依存している限り、パッケージfooは/my-repo/internalをインポートできます。同様に、次のリポジトリでは

my-repo
|-- foo
|   `-- go.mod
`-- internal
    `-- go.mod

ここでは、モジュール「my-repo/foo」がモジュール「my-repo/internal」に依存している限り、パッケージfooはmy-repo/internalをインポートできます。セマンティクスは両方とも同じです。my-repoはmy-repo/internalとmy-repo/fooの間の共有パスプレフィックスであるため、パッケージfooはパッケージinternalをインポートできます。

追加のgo.modで不要なコンテンツを除外できますか?モジュールには、.gitignoreファイルに相当するものがありますか?

単一のリポジトリに複数のgo.modファイルを持つもう1つのユースケースは、リポジトリにモジュールから削除する必要があるファイルが含まれている場合です。たとえば、リポジトリには、Goモジュールに必要のない非常に大きなファイルが含まれている場合や、複数言語のリポジトリには多くの非Goファイルが含まれている場合があります。

ディレクトリ内の空のgo.modは、そのディレクトリとそのすべてのサブディレクトリを最上位のGoモジュールから除外します。

除外されたディレクトリに.goファイルが含まれていない場合は、空のgo.modファイルを作成する以外に追加の手順は必要ありません。除外されたディレクトリに.goファイルが含まれている場合は、最初にこのマルチモジュールリポジトリセクションの他のFAQを注意深く確認してください。

よくある質問 — 最小バージョン選択

最小バージョン選択では、開発者が重要なアップデートを受け取ることができなくなりますか?

以前の公式提案の議論からのFAQにある「最小バージョン選択により、開発者は重要なアップデートを取得できなくなりますか?」という質問を参照してください。

よくある質問 — 発生する可能性のある問題

問題が発生した場合、スポットチェックできる一般的な事項は何ですか?

現在調べているエラーは、ビルドに特定のモジュールまたはパッケージの予想バージョンがないことが原因で発生する二次的な問題である可能性があります。したがって、特定のエラーの原因が明らかでない場合は、次のFAQで説明されているように、バージョンをスポットチェックすると役立つ場合があります。

依存関係の期待されるバージョンが表示されない場合、何をチェックできますか?

  1. 最初のステップとして、go mod tidyを実行することをお勧めします。これにより問題が解決する可能性がありますが、.goソースコードに関してgo.modファイルを整合性のある状態にするのに役立ち、その後の調査を容易にします。(go mod tidy自体が予期しない方法で依存関係のバージョンを変更する場合、最初に「go mod tidy」に関するこのFAQを読んでください。それで説明されない場合は、go.modをリセットしてからgo list -mod=readonly allを実行してみてください。これにより、バージョンを変更する必要があるものに関するより具体的なメッセージが表示される可能性があります)。

  2. 2番目のステップは通常、go list -m allをチェックして、ビルド用に選択された実際のバージョンのリストを確認することです。go list -m allは、間接的な依存関係についても、共有依存関係のバージョンを解決した後についても、最終的に選択されたバージョンを表示します。また、replaceおよびexcludeディレクティブの結果も表示されます。

  3. 次のステップとして、go mod graphまたはgo mod graph | grep <module-of-interest>の出力を調べることができます。go mod graphは、(置換を考慮して)モジュールの要件グラフを出力します。出力の各行には2つのフィールドがあります。最初の列は消費モジュールであり、2番目の列はそのモジュールの要件の1つです(その消費モジュールによって要求されるバージョンを含む)。これは、ビルドに特定の依存関係を要求しているモジュールをすばやく確認する方法です。これは、ビルドに異なる消費者が異なる必要なバージョンを持つ依存関係がある場合(その場合、「バージョン選択」セクションで説明されている動作に精通していることが重要です)。

go mod why -m <module> もここで役立つ可能性がありますが、これは通常、依存関係がなぜ含まれているのか(依存関係が特定のバージョンになる理由ではなく)を確認する場合に、より役立ちます。

go list は、必要に応じてモジュールを調査するために役立つクエリのさらに多くのバリエーションを提供します。1 つの例を以下に示します。これにより、テスト専用の依存関係を除外したビルドで使用されている正確なバージョンが表示されます。

go list -deps -f '{{with .Module}}{{.Path}} {{.Version}}{{end}}' ./... | sort -u

モジュールの調査に関するコマンドと例のより詳細なセットは、実行可能な「Go Modules by Example」のウォークスルーで確認できます。

予期しないバージョンの原因の1つは、意図されていなかった無効または予期しないgo.modファイルが作成されたこと、または関連するミス(例:モジュールのv2.0.1バージョンが、必要な/v2なしでmodule fooとして誤って宣言されている可能性があります。v3モジュールをインポートすることを目的とした.goコードのインポートステートメントに必要な/v3が不足している可能性があります。v4モジュールのgo.modrequireステートメントに必要な/v4が不足している可能性があります)です。したがって、表示されている特定の問題の原因が明らかでない場合は、まず上記の“go.mod”セクションと“セマンティックインポートバージョニング”セクションの資料を再読し(これらにはモジュールが従うべき重要なルールが含まれているため)、数分かけて最も関連性の高いgo.modファイルとインポートステートメントをスポットチェックすることをお勧めします。

「パッケージfooを提供するモジュールが見つかりません」というエラーが表示されるのはなぜですか?

これは、いくつかの異なる根本的な原因で発生する可能性のある一般的なエラーメッセージです。

場合によっては、このエラーは単にタイプミスされたパスが原因であるため、最初のステップはおそらく、エラーメッセージに記載されている詳細に基づいて、パスが間違っていないかどうかを二重に確認することです。

まだ行っていない場合は、go get -v fooまたはgo get -v -x fooを試すことが次のステップとして有効です。

その他の考えられる原因

「go mod init」で「ソースディレクトリのモジュールパスを決定できません」というエラーが表示されるのはなぜですか?

引数なしでgo mod initを実行すると、VCSメタデータなどのさまざまなヒントに基づいて、適切なモジュールパスを推測しようとします。ただし、go mod initが常に適切なモジュールパスを推測できるとは限りません。

go mod initでこのエラーが発生した場合は、ヒューリスティックが推測できず、モジュールパスを自分で指定する必要があります(例:go mod init github.com/you/hello)。

モジュールに対応していない複雑な依存関係に問題があります。その現在の依存関係マネージャーの情報を使用できますか?

はい。これにはいくつかの手動手順が必要ですが、より複雑な場合に役立つ場合があります。

独自のモジュールを初期化するときにgo mod initを実行すると、Gopkg.lockglide.lockvendor.jsonなどの構成ファイルを、対応するrequireディレクティブを含むgo.modファイルに変換することによって、以前の依存関係マネージャーから自動的に変換されます。たとえば、既存のGopkg.lockファイルの情報は、通常、直接および間接の依存関係すべてのバージョン情報を記述しています。

ただし、まだモジュールにオプトインしていない新しい依存関係を追加する場合は、新しい依存関係が使用していた可能性のある以前の依存関係マネージャーからの同様の自動変換プロセスはありません。その新しい依存関係自体が破壊的な変更があった非モジュール依存関係を持っている場合、場合によっては互換性の問題が発生する可能性があります。言い換えれば、新しい依存関係の以前の依存関係マネージャーは自動的に使用されず、場合によっては間接的な依存関係に問題が発生する可能性があります。

1つの方法は、問題のある非モジュール直接依存関係でgo mod initを実行して、現在の依存関係マネージャーから変換し、結果として得られる一時的なgo.modからのrequireディレクティブを使用して、モジュールのgo.modに情報を追加または更新することです。

たとえば、github.com/some/nonmoduleが現在別の依存関係マネージャーを使用しているモジュールの問題のある直接依存関係である場合、次のような操作を行うことができます。

$ git clone -b v1.2.3 https://github.com/some/nonmodule /tmp/scratchpad/nonmodule
$ cd /tmp/scratchpad/nonmodule
$ go mod init
$ cat go.mod

一時的なgo.modからの結果のrequire情報は、モジュールの実際のgo.modに手動で移動するか、このユースケースを対象としたコミュニティツールであるhttps://github.com/rogpeppe/gomodmergeの使用を検討できます。さらに、手動でクローンしたバージョンと一致するように、実際のgo.modrequire github.com/some/nonmodule v1.2.3を追加する必要があります。

Dockerに対してこのテクニックに従った具体的な例は、この#28489コメントにあり、github.com/sirupsen/logrusgithub.com/Sirupsen/logrusの間の大文字と小文字を区別する問題を回避するためのDocker依存関係の一貫したバージョンのセットの取得を示しています。

インポートパスと宣言されたモジュールIDの不一致によって発生する「go.modの解析エラー:予期しないモジュールパス」と「モジュール要件の読み込みエラー」を解決するにはどうすればよいですか?

なぜこのエラーが発生するのですか?

一般的に、モジュールはmoduleディレクティブ(例:module example.com/m)を使用して、go.modでそのアイデンティティを宣言します。これはそのモジュールの「モジュールパス」であり、goツールは、宣言されたモジュールパスと、コンシューマーによって使用されるインポートパスの間の整合性を強制します。モジュールのgo.modファイルにmodule example.com/mが記述されている場合、コンシューマーはそのモジュールから、そのモジュールパスで始まるインポートパス(例:import "example.com/m"またはimport "example.com/m/sub/pkg")を使用してパッケージをインポートする必要があります。

コンシューマーによって使用されるインポートパスと対応する宣言されたモジュールパスに不一致がある場合、goコマンドはparsing go.mod: unexpected module pathという致命的なエラーを報告します。さらに、場合によっては、goコマンドはその後、より一般的なerror loading module requirementsエラーを報告します。

このエラーの最も一般的な原因は、名前の変更(例:github.com/Sirupsen/logrusからgithub.com/sirupsen/logrus)があった場合、またはバニティインポートパスが原因でモジュールがモジュール化前に2つの異なる名前で時々使用されていた場合(例:github.com/golang/syncと推奨されるgolang.org/x/sync)です。

依存関係が古い名前(例:github.com/Sirupsen/logrus)または非標準名(例:github.com/golang/sync)でまだインポートされているが、その依存関係がその後モジュールを採用し、その標準名をgo.modで宣言している場合、問題が発生する可能性があります。このエラーは、モジュールのアップグレードされたバージョンが見つかり、標準モジュールパスが古いインポートパスと一致しなくなった場合のアップグレード中に発生する可能性があります。

問題のあるシナリオの例

go: github.com/Quasilyte/go-consistent@v0.0.0-20190521200055-c6f3937de18c: parsing go.mod: unexpected module path “github.com/quasilyte/go-consistent” go get: error loading module requirements

解決策

エラーの最も一般的な形式は次のとおりです。

go: example.com/some/OLD/name@vX.Y.Z: parsing go.mod: unexpected module path “example.com/some/NEW/name”

example.com/some/NEW/name(エラーの右側)のリポジトリにアクセスして、最新のリリースまたはmastergo.modファイルを確認し、最初の行にmodule example.com/some/NEW/nameとして宣言されているかどうかを確認できます。その場合、「古いモジュール名」と「新しいモジュール名」の問題が発生しているというヒントになります。

このセクションの残りの部分では、次の手順を順に実行することで、このエラーの「古い名前」と「新しい名前」の形式を解決することに重点を置いています。

  1. 独自のコードを確認して、example.com/some/OLD/nameを使用してインポートしているかどうかを確認します。その場合は、コードを更新してexample.com/some/NEW/nameを使用してインポートします。

  2. アップグレード中にこのエラーが発生した場合は、よりターゲットを絞ったアップグレードロジック(#26902) を備え、この問題を回避し、この状況に対するより良いエラーメッセージを提供することが多い、最新のGoバージョンを使用してアップグレードを試みる必要があります。最新のGoバージョンのgo get引数は、1.12のものとは異なります。最新のバージョンを取得して依存関係をアップグレードする例を以下に示します。

go get golang.org/dl/gotip && gotip download
gotip get -u all
gotip mod tidy

問題のある古いインポートは多くの場合間接的な依存関係にあるため、最新のバージョンでアップグレードしてからgo mod tidyを実行すると、多くの場合、問題のあるバージョンを超えてアップグレードし、go.modから不要になった問題のあるバージョンを削除できるため、日常的にGo 1.12または1.11を使用する場合に機能状態になります。たとえば、この方法でgithub.com/golang/lintgolang.org/x/lintの問題を超えてアップグレードする方法をこちらで確認できます。

  1. go get -u fooまたはgo get -u foo@latestの実行中にこのエラーが発生した場合は、-uを削除してみてください。これにより、fooの作者がfooのリリース時に機能していると検証した可能性のあるバージョンを超えてfooの依存関係をアップグレードすることなく、foo@latestで使用されている依存関係のセットが得られます。これは、fooの直接および間接の依存関係の一部がまだsemverまたはモジュールを採用していない可能性のあるこの移行期間中に特に重要です。(よくある間違いは、go get -u fooによってfooの最新バージョンのみを取得すると考えていることです。実際には、go get -u fooまたはgo get -u foo@latest-uは、fooの直接および間接の依存関係のすべての最新バージョンも取得することを意味します。それはあなたが望むものかもしれませんが、深い間接的な依存関係のために失敗している場合はそうではない可能性があります)。

  2. 上記の手順でエラーが解決しない場合は、次のアプローチはやや複雑ですが、多くの場合、「古い名前」と「新しい名前」の形式のこのエラーを解決するはずです。これは、エラーメッセージ自体からの情報のみと、いくつかのVCS履歴を簡単に確認したものを使用します。

    4.1. example.com/some/NEW/nameリポジトリに移動します。

    4.2. そこでgo.modファイルがいつ導入されたかを判断します(例:go.modのブレイムビューまたは履歴ビューを確認します)。

    4.3. そこでgo.modファイルが導入される直前のリリースまたはコミットを選択します。

    4.4. `go.mod`ファイルに、`replace`ステートメントを追加します。`replace`ステートメントの両側に古い名前を使用します。`replace example.com/some/OLD/name => example.com/some/OLD/name ` 以前の例では、`github.com/Quasilyte/go-consistent`が古い名前で、`github.com/quasilyte/go-consistent`が新しい名前です。`go.mod`が最初に導入されたのは、コミット00c5b0cf371aです。このリポジトリはsemverタグを使用していないため、直前のコミット00dd7fb039eを使用し、大文字の古いQuasilyte名を使用して両側に追加します。

replace github.com/Quasilyte/go-consistent => github.com/Quasilyte/go-consistent 00dd7fb039e

この`replace`ステートメントにより、「古い名前」と「新しい名前」の不一致という問題を回避できます。`go.mod`の存在下で古い名前が新しい名前にアップグレードされるのを効果的に防止します。通常、`go get -u`などによるアップグレードで、エラーを回避できるようになります。アップグレードが完了したら、古い名前をインポートしているものがまだあるかどうかを確認できます(例:`go mod graph | grep github.com/Quasilyte/go-consistent`)。なければ、`replace`を削除できます。(多くの場合、これが機能するのは、アップグレード自体が、古い問題のあるインポートパスが使用されている場合に失敗する可能性があるためです。最終結果では使用されていない場合でも、アップグレードが完了していれば、これは#30831で追跡されています)。

  1. 上記の手順で問題が解決しない場合は、問題のある古いインポートパスが、1つ以上の依存関係の最新バージョンでまだ使用されている可能性があります。この場合、問題のある古いインポートパスをまだ使用しているものを特定し、問題のあるインポーターが現在標準的なインポートパスを使用するように変更するよう依頼する問題を特定するか、開くことが重要です。ステップ2.で`gotip`を使用すると、問題のあるインポーターを特定できる可能性がありますが、すべての場合、特にアップグレードの場合にそうなるわけではありません(#30661)。問題のある古いインポートパスを使用しているインポーターが不明な場合は、クリーンなモジュールキャッシュを作成し、エラーをトリガーする操作を実行し、モジュールキャッシュ内で古い問題のあるインポートパスをgrepすることで、通常は確認できます。例えば
export GOPATH=$(mktemp -d)
go get -u foo               # perform operation that generates the error of interest
cd $GOPATH/pkg/mod
grep -R --include="*.go" github.com/Quasilyte/go-consistent
  1. これらの手順で問題が解決しない場合、または循環参照のために古い問題のあるインポートパスへの参照を削除できないプロジェクトの保守担当者である場合は、別のwikiページで、この問題についてより詳細な説明を参照してください。

最後に、上記の手順は、「古い名前」と「新しい名前」という根本的な問題の解決方法に焦点を当てています。ただし、同じエラーメッセージは、`go.mod`が間違った場所に配置されているか、単にモジュールパスが間違っている場合にも表示されることがあります。その場合は、そのモジュールのインポートは常に失敗します。新しく作成したモジュールをインポートしていて、これまで成功したことがない場合は、`go.mod`ファイルが正しく配置されていること、およびその場所に対応する適切なモジュールパスが設定されていることを確認する必要があります。(最も一般的なアプローチは、リポジトリごとに1つの`go.mod`を使用し、リポジトリのルートに1つの`go.mod`ファイルを配置し、`module`ディレクティブで宣言されたモジュールパスとしてリポジトリ名を使用することです)。詳細については、“go.mod”セクションを参照してください。

「go build」でgccが必要なのはなぜですか?また、net/httpなどのプリビルドパッケージが使用されないのはなぜですか?

要約すると

プリビルドパッケージはモジュールではないビルドであり、再利用できません。申し訳ありません。今のところcgoを無効にするか、gccをインストールしてください。

これは、モジュールを有効にしている場合(例:`GO111MODULE=on`)にのみ問題になります。#26988で、追加の議論を参照してください。

モジュールは、`import "./subdir"`のような相対インポートで動作しますか?

いいえ。#26645を参照してください。これには以下が含まれています。

モジュールでは、サブディレクトリの名前がついに付けられました。親ディレクトリが「module m」と言っている場合、サブディレクトリは「m/subdir」としてインポートされ、「./subdir」とはなりません。

移入されたvendorディレクトリに必要なファイルが存在しない場合があります。

`.go`ファイルのないディレクトリは、`go mod vendor`によって`vendor`ディレクトリ内にコピーされません。これは設計によるものです。

要約すると、特定のベンダーの動作を別にすれば、goビルドの全体的なモデルは、パッケージをビルドするために必要なファイルは`.go`ファイルのあるディレクトリに存在する必要があるということです。

cgoの例を使用すると、他のディレクトリにあるCソースコードを変更しても再ビルドはトリガーされず、代わりにビルドは古いキャッシュエントリを使用します。cgoのドキュメントには、現在含まれています

他のディレクトリのファイルの変更によってパッケージが再コンパイルされるわけではないため、パッケージのすべての非Goソースコードは、パッケージディレクトリに格納する必要があり、サブディレクトリに格納しないでください

コミュニティツールhttps://github.com/goware/modvendorを使用すると、モジュールから`.c`、`.h`、`.s`、`.proto`、その他のファイルを`vendor`ディレクトリに簡単にコピーできます。これは役立ちますが、パッケージをビルドするために必要なファイルが`.go`ファイルのあるディレクトリの外部にある場合、(ベンダーの有無にかかわらず)goビルドが適切に処理されていることを確認する必要があります。

#26366で追加の議論を参照してください。

従来のベンダーリングとは異なるアプローチとして、モジュールキャッシュをチェックインする方法があります。従来のベンダーリングと同様の利点を提供し、いくつかの点でより忠実度の高いコピーになります。このアプローチは、「Go Modules by Example」のチュートリアルとして説明されています。


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