はじめに
自身で作った go package を公開し、別のプロジェクトで利用されるというケースで、バージョンがどのように扱われるのか調べました。
バージョン指定なし
githubにhello
というリポジトリを作り、そこにパッケージを作成します。
module github.com/username/hello
go 1.15
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)がわかります。
module hello_client
go 1.15
require github.com/username/hello v0.0.0-20210123030850-40fe1c06b3a8 // indirect
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に変更を加えてプッシュしたら、タグを作成します。
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は以下のようになります。
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)
パッケージ側
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
を作成してみます。
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
をみてみると別々のパッケージのように扱われていることがわかります。
module hello_client
go 1.15
require (
github.com/username/hello v1.1.0
github.com/username/hello/v2 v2.0.1
)
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
このようにメジャーバージョンが上がるとパスが別々になり、それぞれのバージョンごとにメンテナンスができるようになります。