go module のバージョニング

# はじめに

自身で作った go package を公開し、別のプロジェクトで利用されるというケースで、バージョンがどのように扱われるのか調べました。


# バージョン指定なし

githubに`hello`というリポジトリを作り、そこにパッケージを作成します。

```go.mod

module github.com/username/hello

go 1.15
```

```go:hello.go

package hello

import "fmt"

func Hello() {
	fmt.Println("this is version v0.0.0")
}
```

#### パッケージを呼び出す

パッケージを呼び出すためのクライアントを用意します。

```
$ go mod init hello_client
$ go get -u github.com/username/hello
```

以下のようなgo.modができます。バージョン(v0.0.0)と参照しているコミット(40fe1c06b3a8)がわかります。

```go.mod
module hello_client

go 1.15

require github.com/username/hello v0.0.0-20210123030850-40fe1c06b3a8 // indirect
```

```go:main.go
package main

import (
	m "github.com/username/hello"
)

func main() {
	m.Hello()
}
```

```
$ go run main.go 
this is version v0.0.0
```
意図通りに実行できました。

特にバージョンについて考慮しないと、`master`ブランチを参照することがわかりました。例えばこのあと`master`に更新がありその最新版を取得したい場合は、再び`go get -u github.com/username/hello`を実行することで最新のコミットを参照するように`go.mod`が更新されます。

補足: masterブランチの参照

「`master`ブランチを参照する」というのは多少の語弊があって、正しくは「デフォルトブランチを参照」します。例えば`master`ブランチを起点に`main`ブランチを作り、`main`ブランチをデフォルトブランチにするように設定が変更された場合、`go get -u github.com/username/hello`は`main`ブランチを参照します。

# v1

バージョンを指定しない場合の問題点は、`go get -u`を実行したタイミングによって参照される資材が異なってしまう点です。そこで常に同じ資材を参照できるようにバージョン管理をします。

hello.goに変更を加えてプッシュしたら、タグを作成します。

```go:hello.go
package hello

import "fmt"

func Hello() {
	fmt.Println("this is version v1.0.0")
}
```

```
$ git tag -a v1.0.0 -m "Version 1.0.0"
$ git push --tags
Enumerating objects: 1, done.
Counting objects: 100% (1/1), done.
Writing objects: 100% (1/1), 167 bytes | 167.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To https://github.com/username/hello.git
 * [new tag]         v1.0.0 -> v1.0.0
```

#### パッケージを呼び出す

先ほど作成したクライアントで、パッケージをアップデートします。

```
$ go get -u github.com/username/hello
```

go.modは以下のようになります。

```go.mod
module hello_client

go 1.15

require github.com/username/hello v1.0.0
```

実行してみます。

```
$ go run main.go 
this is version v1.0.0
```

タグを打つことで、`master`ブランチではなく`v1.0.0`タグを参照するようになりました。
これで`master`ブランチで開発を続けても、利用者は常に`v1.0.0`を参照することができるようになります。

# v1.1

マイナーバージョンを一つ上げてタグを作成してみます。手順は、v1の時と同様ですので省きます。

#### パッケージを呼び出す

クライアントも同じようにパッケージをアップデートして実行してみます。

```
$ go get -u github.com/username/hello
$ go run main.go 
this is version v1.1.0
```

参照先が`v1.1.0`に切り替わったことがわかります。

このようにマイナーアップデートやパッチアップデートが行われたら、それらが最新版として認識されることがわかりました。

// プレフィックスとしての`v`は必須のようです。例えば`1.2.0`のようなタグの切り方はv1の最新版として認識されませんでした。

`v1.0.0`を参照したいときは明示的にバージョンを指定します。

```
go get github.com/username/hello@v1.0.0
```

# v2

セマンティックバージョニングに従うと、メジャーバージョンのアップデートは互換性のない変更が行われた時に発生します。つまり、v1とv2は自動的にアップデートされるべきでもないし、別物として扱うべきです。

そのため、v1の時と同じようなバージョンのアップデートの仕方をしてもうまくいきません。
(`v0 / v1` と `v2 ~` で扱いが変わります。詳しくは、公式ブログを参照:https://blog.golang.org/v2-go-modules)

__パッケージ側__

```go:hello.go
package hello

import "fmt"

func Hello() {
	fmt.Println("this is version v2.0.0")
}
```
```
$ git tag -a v2.0.0 -m "Version 2.0.0"
```

__クライアント側__

```
$ go get -u github.com/username/hello
$ go run main.go 
this is version v1.1.0
```
ここで `-u` オプションについて再確認します。メジャーアップデートはされないことがわかります。

> The -u flag instructs get to update modules providing dependencies of packages named on the command line to use newer minor or patch releases when available. 

また、`@2.0.0`とサフィックスを指定してもエラーになります。

```
$ go get github.com/username/hello@v2.0.0
go get github.com/username/hello@v2.0.0: github.com/username/hello@v2.0.0: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2
```

ということで、v2以降のバージョンを認識させるためにパッケージの`go.mod`を修正して、`v2.0.1`を作成してみます。

```go.mod
module github.com/username/hello/v2

go 1.15
```

```
$ git tag -a v2.0.1 -m "Version 2.0.1"
```

#### パッケージを呼び出す

パスが変わりましたので、クライアントも呼び出し方を合わせて変更します。

```
go get -u github.com/username/hello/v2
```

`go.mod`をみてみると別々のパッケージのように扱われていることがわかります。

```go.mod
module hello_client

go 1.15

require (
	github.com/username/hello v1.1.0
	github.com/username/hello/v2 v2.0.1
)
```

```go:main.go
package main

import (
	m1 "github.com/username/hello"
	m2 "github.com/username/hello/v2"
)

func main() {
	m1.Hello()
	m2.Hello()
}
```

実行してみると以下のようになります。

```
$ go run main.go 
this is version v1.1.0
this is version v2.0.1
```

このようにメジャーバージョンが上がるとパスが別々になり、それぞれのバージョンごとにメンテナンスができるようになります。