プロジェクト作成・依存関係管理(Go Modules)
Goではコードのバージョン管理や依存関係管理の仕組みとしてモジュール(Go Modules)があります。
ここではモジュールを使って次の内容を説明します。
- Goのプロジェクト作成(=モジュールの作成)
- モジュールの依存関係の管理
- ローカルマシン上にある自作モジュールの利用
- プライベートリポジトリ上にある自作モジュールの利用
このページのコマンド出力例はGo 1.26.3で確認しています。
Go ModulesはGo 1.11で導入され、Go 1.16以降はモジュールモードが標準になりました。
古いGoを使っている場合、go.modのgoディレクティブや一部のコマンド出力がこのページと異なることがあります。
モジュールとは
Goのモジュールは、ルートディレクトリにgo.modファイルを含む、Goのパッケージの集合です。
モジュールはパッケージ群のバージョン管理や配布をする単位となります。
バージョンの付け方はセマンティックバージョニングに従います。
モジュールの管理単位としては、基本的に1リポジトリ1モジュールで管理します(Go開発者の一人であるRuss Coxさんが推奨)。 このため、Goのプロジェクトを作成することは、モジュールを作成することと捉えることができます。
モジュールの作成
モジュールを作成する前に、モジュールが包含するパッケージを作成します。
任意のディレクトリを作成します。
mkdir $HOME/calc
cd $HOME/calc
calc.goを作成します。
package calc
func Max(x, y int) int {
if x < y {
return y
}
return x
}
calc_test.goにテストを書きます。
package calc
import "testing"
func TestMax(t *testing.T) {
want := 2
got := Max(1, 2)
if want != got {
t.Errorf("Max(1, 2) == %d, want %d", got, want)
}
}
この時点では、このディレクトリは単なるパッケージであり、モジュールではありません。
go.modがまだないためです。
そのことを確認するために、次の通りテストを実行します。
Go 1.16以降はモジュールモードが標準のため、go.modがない状態ではモジュールのルートがわからずエラーになります。
go test
go: go.mod file not found in current directory or any parent directory; see 'go help modules'
補足: Go 1.15以前、または
GO111MODULE=offの環境では、_/home/go/calcのようなローカルパス由来のインポートパスが表示される例を見かけることがあります。
go mod initコマンドを使ってgo.modを作成し、現在のディレクトリをモジュールのルートとします。
go mod init example.com/calc
go: creating new go.mod: module example.com/calc
go.modには、モジュールの場所を示すモジュールパスと、利用するGoのバージョンが定義されます。
cat go.mod
module example.com/calc
go 1.26.3
モジュールを作成したので、もう一度go testを実行します。
すると、先ほど定義したモジュールパスexample.com/calcがパッケージパスとして表示されます。
go test
PASS
ok example.com/calc 0.100s
これでモジュールの作成は完了です。
今回、モジュールの場所を示すモジュールパスはexample.com/calcとしました。
モジュールを外部に公開する場合は、公開先のURLにします。
例としてGitHubに公開する場合、github.com/<user>/<repo>のようにします。
モジュールを公開しない場合は、calcのようにシンプルな名前でも大丈夫です。
モジュールには複数のパッケージを配置できます。
パッケージを追加するにはサブディレクトリを作成します。
サブディレクトリのインポートパスは、モジュールパスとサブディレクトリパスを結合したものです。
次のディレクトリ構成の場合、pkg1パッケージのインポートパスはexample.com/calc/pkg1になります。
サブディレクトリに別途go.modを作成する必要はありません。
モジュールパス:example.com/calc
calcディレクトリ(パッケージ:calc、インポートパス:example.com/calc)
├── go.mod(モジュールのバージョンを管理するファイル)
├── go.sum(利用モジュールのsum値を管理するファイル)
├── calc.go(calcパッケージのソースファイル)
├── calc_test.go(calcパッケージのソースファイル)
└── pkg1ディレクトリ(パッケージ:pkg1、インポートパス:example.com/calc/pkg1)
├── pkg1.go(pkg1パッケージのソースファイル)
└── pkg1_test.go(pkg1パッケージのソースファイル)
依存関係の追加
見出し『モジュールの作成』で作成したモジュールに依存関係を追加する方法を説明します。
calc.goを次のように変更します。
package calc
import (
"github.com/google/go-cmp/cmp"
)
func Max(x, y int) int {
if x < y {
return y
}
return x
}
func Equal(x, y int) bool {
return cmp.Equal(x, y)
}
go mod tidyコマンドを使って必要な依存関係(github.com/google/go-cmp/cmp)を追加します。
このコマンドは、次の処理をします。
- ソースコードのimport文を参照し、必要なモジュールを
go.modに追加する - 追加されたモジュールをダウンロードする
- コードの変更に伴い、不要なモジュールを
go.modから削除する
cd $HOME/calc
go mod tidy
go: finding module for package github.com/google/go-cmp/cmp
go: downloading github.com/google/go-cmp v0.7.0
go: found github.com/google/go-cmp/cmp in github.com/google/go-cmp v0.7.0
go.modに依存関係が追加されたことを確認します。
バージョンはその時点で解決された最新のものが使用されます。
cat go.mod
module example.com/calc
go 1.26.3
require github.com/google/go-cmp v0.7.0
現在のモジュールが利用する依存関係の一覧を確認するにはgo listコマンドを実行します。
go list -m all
example.com/calc
github.com/google/go-cmp v0.7.0
goコマンドは、go.modに加えて、go.sumにモジュールのバージョンとハッシュ値を記録します。
cat go.sum
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
一度ダウンロードしたモジュールはローカルマシン上にキャッシュされます。
何らかの理由でキャッシュを削除するにはgo cleanコマンドを実行します。
go clean -modcache
これで依存関係の追加が完了しました。
gitなどのバージョン管理にはgo.modとgo.sumの両方をコミットしてください。
依存関係の更新(メジャーバージョン以外)
go listコマンドに-uオプションを指定して、アップグレード可能なモジュールを確認します。
cd $HOME/calc
go list -m -u all
アップグレード可能なバージョンが角括弧で表示されます。
次の例では、説明のためgithub.com/google/go-cmpがv0.6.0で入っている状態を想定しています。
example.com/calc
github.com/google/go-cmp v0.6.0 [v0.7.0]
特定のモジュールをアップグレードするにはgo getコマンドでモジュールを指定して実行します。
現在のGoでは、go getはgo.modの依存関係を追加・更新・削除するためのコマンドです。
go get github.com/google/go-cmp
モジュールパスの後に@を続けて、モジュールのバージョンを指定できます。
go get github.com/google/go-cmp@v0.7.0
依存パッケージも含めてアップグレード候補を広げるには-uを指定します。
go get -u ./...
Go 1.17以降: 外部コマンドをインストールする用途では、
go getではなくgo install example.com/cmd@versionを使います。
Go 1.18以降:
go getは主にgo.modの依存関係を編集するコマンドとして扱います。
依存関係の更新(メジャーバージョン)
モジュールは、メジャーバージョンが変わるとモジュールパスが変わります(v0からv1は例外で変わりません)。 セマンティックバージョニングではメジャーバージョンが変わると、後方互換性がなくなるためです。
モジュールパスは末尾にメジャーバージョンをつけます(v0とv1は例外でつけません)。
執筆時点では存在しませんが、仮にgithub.com/google/go-cmpをv1からv2にする場合は、モジュールパスの末尾にv2を付加します。
import (
"github.com/google/go-cmp/v2/cmp"
)
go getもモジュールパスにv2をつけます。
cd $HOME/calc
go get github.com/google/go-cmp/v2
モジュールパスの後に@を続けて、モジュールのバージョンを指定できます。
go get github.com/google/go-cmp/v2@v2.0.1
メジャーバージョンが異なるモジュールは同時に利用できます。 たとえば、v1からv2に徐々に移行する場合は、次のように両方をインポートします。
import (
"github.com/google/go-cmp/cmp"
cmpV2 "github.com/google/go-cmp/v2/cmp"
)
まとめると次の通りです。
- メジャーバージョンが変わるとモジュールパスが変わる(v0からv1は例外で変わらない)
- モジュールパスは末尾にメジャーバージョンをつける(v0とv1は例外でバージョンをつけない)
- メジャーバージョンが異なる場合は同時に利用(インポート)できる
自作モジュールの利用(ローカル)
見出し『モジュールの作成』で作成したローカルマシン上のモジュールを利用する方法を説明します。
任意のディレクトリを作成して、モジュールを作成します。
mkdir $HOME/myapp
cd $HOME/myapp
go mod init example.com/myapp
main.goを作成します。
package main
import (
"fmt"
"example.com/calc"
)
func main() {
fmt.Println(calc.Max(1, 2))
}
公開されているモジュールはモジュールパスが公開先のURLであることから、goコマンドが自動的に参照できました。
しかし、ローカルマシン上のモジュールは場所がわかりません。
そのため、goコマンドにモジュールの場所を知らせる必要があります。
go mod editコマンドを使ってgo.modにモジュールの場所を定義します。
-replaceオプションの値はモジュールパス=ディレクトリの形式で指定します。
筆者の環境ではモジュールパスexample.com/calcの場所は/home/go/calcディレクトリと定義しました。
利用している環境にあわせて読み替えてください。
go mod edit -replace "example.com/calc=/home/go/calc"
cat go.mod
module example.com/myapp
go 1.26.3
replace example.com/calc => /home/go/calc
go mod tidyコマンドを使って依存関係にexample.com/calcを追加します。
go mod tidy
cat go.mod
module example.com/myapp
go 1.26.3
replace example.com/calc => /home/go/calc
require example.com/calc v0.0.0-00010101000000-000000000000
require github.com/google/go-cmp v0.7.0 // indirect
プログラムを実行します。
go run .
2
これで自作モジュールを利用したプログラムを作成できました。
自作モジュールの利用(プライベートリポジトリ)
プライベートリポジトリ上のモジュールを利用する方法を説明します。
前提条件は次の通りです。
- GitHubのプライベートリポジトリを利用
goコマンドはモジュールをダウンロードする際に、デフォルトでプロキシサーバを経由します。
プロキシサーバはプライベートリポジトリの認証情報を知らないため、モジュールをダウンロードできません。
これを回避するには、goコマンドに対してプロキシサーバを経由しないようにする設定が必要です。
環境変数GOPRIVATEにプライベートリポジトリのモジュールパス(前方一致)を記載することで、
そのモジュールはプロキシを経由せずにダウンロードできます。
go env -w GOPRIVATE=github.com/<user>
これで、goコマンドはプライベートリポジトリのモジュールを直接ダウンロードできるようになりました。
次はgoコマンドがプライベートリポジトリにアクセスするためのGitHub認証情報を設定します。
GitHubへの認証は、トークンをURLに埋め込むのではなく、SSHまたはGitの認証ヘルパーに任せます。 おすすめは次のどちらかです。
- SSHでリポジトリにアクセスする。GitHubにSSH keyを登録し、
git@github.com:<user>/<repo>.gitの形式でアクセスします。 - HTTPSを使う場合は、GitHub CLIまたはGit Credential Managerで認証情報を保存します。GitHub CLIなら
gh auth login、Git Credential Managerなら各OSの安全な資格情報ストアを利用します。
パーソナルアクセストークンを使う場合も、git config --global url."https://TOKEN@github.com/"...のようにURLへ直接埋め込む設定は避けてください。
設定ファイルやログに秘密情報が残りやすく、漏えい時の影響が大きくなります。
必要な場合はfine-grained personal access tokenを使い、対象リポジトリと権限を最小限にして、Git Credential Managerなどのcredential helperに保存します。
使わなくなったトークンや漏えいした可能性があるトークンは、GitHubの設定画面からすぐに失効してください。
これでプライベートリポジトリにアクセスする準備が整いました。
あとは通常の外部モジュールを利用するように、go getやgo mod tidyなどでモジュールを追加してください。