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

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

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

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

このページはGo1.13以降の利用を前提とします。

モジュールとは

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 testコマンドはインポートパスがわからず、 ローカルのパスを基にした偽のインポートパス(筆者の環境では_/home/go/calc)を表示しています。

go test
PASS
ok      _/home/go/calc      0.100s

go mod initコマンドを使ってgo.modを作成し、現在のディレクトリをモジュールのルートとします。

go mod init example.com/calc
go: creating new go.mod: module example.com/calc

go.modには、モジュールの場所を示すモジュールパスが定義されます。

cat go.mod
module example.com/calc

go 1.15

モジュールを作成したので、もう一度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.5.4
go: found github.com/google/go-cmp/cmp in github.com/google/go-cmp v0.5.4
go: downloading golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543

go.modに依存関係が追加されたことを確認します。 バージョンは最新のものが使用されます。 github.com/google/go-cmp/cmpの依存関係としてgolang.org/x/xerrorsもダウンロードされましたが、 go.modには直接の依存関係のみが記録されます。

cat go.mod
module example.com/calc

go 1.15

require github.com/google/go-cmp v0.5.4

直接の依存関係と間接の依存関係の両方を確認するにはgo listコマンドを実行します。

go list -m all
example.com/calc
github.com/google/go-cmp v0.5.4
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543

goコマンドは、go.modに加えて、go.sumにモジュールのバージョンとハッシュ値を記録します。

cat go.sum
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

一度ダウンロードしたモジュールはローカルマシン上にキャッシュされます。 何らかの理由でキャッシュを削除するにはgo cleanコマンドを実行します。

go clean -modcache

これで依存関係の追加が完了しました。 gitなどのバージョン管理にはgo.modgo.sumの両方をコミットしてください。

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

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

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

アップグレード可能なバージョンが角括弧で表示されます。

example.com/calc
github.com/google/go-cmp v0.5.3 [v0.5.4]
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 [v0.0.0-20200804184101-5ec99f83aff1]

特定のモジュールをアップグレードするにはgo getコマンドでモジュールを指定して実行します。

go get github.com/google/go-cmp

モジュールパスの後に@を続けて、モジュールのバージョンを指定できます。

go get github.com/google/go-cmp@v0.5.4

すべてのモジュールをアップグレードするには./...と指定します。

go get -u ./...

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

モジュールは、メジャーバージョンが変わるとモジュールパスが変わります(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.15

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.15

replace example.com/calc => /home/go/calc

require example.com/calc v0.0.0-00010101000000-000000000000

プログラムを実行します。

go run .
2

これで自作モジュールを利用したプログラムを作成できました。

自作モジュールの利用(プライベートリポジトリ)

プライベートリポジトリ上のモジュールを利用する方法を説明します。

前提条件は次の通りです。

  • GitHubのプライベートリポジトリを利用

goコマンドはモジュールをダウンロードする際に、デフォルトでプロキシサーバを経由します。 プロキシサーバはプライベートリポジトリの認証情報を知らないため、モジュールをダウンロードできません。 これを回避するには、goコマンドに対してプロキシサーバを経由しないようにする設定が必要です。

環境変数GOPRIVATEにプライベートリポジトリのモジュールパス(前方一致)を記載することで、 そのモジュールはプロキシを経由せずにダウンロードできます。

go env -w GOPRIVATE=github.com/<user>

これで、goコマンドはプライベートリポジトリのモジュールを直接ダウンロードできるようになりました。 次はgoコマンドがプライベートリポジトリにアクセスするためのGitHub認証情報を設定します。

GitHubのパーソナルアクセストークンを生成し、生成したパーソナルアクセストークンをgitに設定します。

# xxxはパーソナルアクセストークンに読み替える
# <user>はユーザ名に読み替える
git config --global url."https://xxx:x-oauth-basic@github.com/".insteadOf "https://github.com/<user>/"

これでプライベートリポジトリにアクセスする準備が整いました。 あとは通常の外部モジュールを利用するように、go getgo mod tidyなどでモジュールを追加してください。

参考リンク