プロジェクト作成・依存関係管理(Go Modules)

Goではコードのバージョン管理や依存関係管理の仕組みとしてモジュール(Go Modules)があります。

ここではモジュールを使って次の内容を説明します。

  • Goのプロジェクト作成(=モジュールの作成)
  • モジュールの依存関係の管理
  • ローカルマシン上にある自作モジュールの利用
  • プライベートリポジトリ上にある自作モジュールの利用

このページのコマンド出力例はGo 1.26.3で確認しています。 Go ModulesはGo 1.11で導入され、Go 1.16以降はモジュールモードが標準になりました。 古いGoを使っている場合、go.modgoディレクティブや一部のコマンド出力がこのページと異なることがあります。

モジュールとは

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.modgo.sumの両方をコミットしてください。

依存関係の更新(メジャーバージョン以外)

go listコマンドに-uオプションを指定して、アップグレード可能なモジュールを確認します。

cd $HOME/calc
go list -m -u all

アップグレード可能なバージョンが角括弧で表示されます。 次の例では、説明のためgithub.com/google/go-cmpv0.6.0で入っている状態を想定しています。

example.com/calc
github.com/google/go-cmp v0.6.0 [v0.7.0]

特定のモジュールをアップグレードするにはgo getコマンドでモジュールを指定して実行します。 現在のGoでは、go getgo.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 getgo mod tidyなどでモジュールを追加してください。

参考リンク