Go製CUIツールのバージョン表示に便利なパッケージを作りました
どういったものを作ったのか
今回作ったのは、curverというgoのパッケージです。
使い方は簡単で、以下のようにgo get
してimportすれば準備OKです。
$ go get github.com/harakeishi/curver@latest
package main
import (
"github.com/harakeishi/curver"
)
func main () {
curver.EchoVersion()
}
これで、以下のようにビルドするとEchoVersion()で指定したバージョンが表示されます。
$ go build -ldflags '-X github.com/harakeishi/curver.Version=v0.1.0' -o ./main
$ ./main
version: v0.1.0
git tag
をバージョンとして使用したければ以下のようにすれば、その時点で最新のtagがVersionとして使われます。
$ go build -ldflags "-X github.com/harakeishi/curver.Version=$(git describe --tags)" -o ./main
$ ./main
version: v0.1.0
goreleaserなどを使っている場合は、以下のように設定すればOKです。
builds:
- eldflags:
- -s -w -X github.com/harakeishi/curver.Version={{.Version}}
どうしてつくったのか
こんなパッケージ使わなくても普通にmain内で宣言した変数をldflagsで書き換えればいいじゃんと思った方もいるかと思います。
もちろんそれでもいいのですが、goreleaserを使ってGithub Actionsでリリースをするような場合などは意図したとおりに動かないと思います。
この方法をとっている場合、Github ReleasesのAssetsからバイナリを落としてきて使うと問題なくバージョンがldflagsで置換えたものが表示されると思います。
ですが、go install
を使用してインストールされたものはldflagsで設定したように置き換えられていないと思います。
これは(たぶんですが)go install
する際、Actionsで生成したバイナリをDLしてくるのではなく、ソースを手元に落としてきて手元でビルドしているためldflagsが使われずビルドされてしまうのではないかと思います。
なにをしてるか
では、go install
された場合のバージョン表示ってどうすればいいのでしょうか?
下記のようにruntime/debugパッケージを使えば良さそうです。
package main
import (
"fmt"
"runtime/debug"
)
func main () {
if buildInfo, ok := debug.ReadBuildInfo(); ok {
fmt.Println(buildInfo.Main.Version)
}
}
debug.ReadBuildInfo()
は、下記に書いてあるようにビルド時のデバック情報を返してくれるようです。
ReadBuildInfo returns the build information embedded in the running binary. The information is available only in binaries built with module support.
これすごく便利なのですが、カレントディレクトリがGoモジュール管理下でビルドする場合、初期値である(devel)
という値が入ってしまうみたいです。
つまり、Github Actionでビルドする際は(devel)
という値が入ってしまい、生成したバイナリでバージョンを確認すると(devel)
が表示されてしまうわけです。
ここまででわかったことは
-
ldflags
を使用する場合go install
するとバージョン表示が意図したとおりにならない -
debug.ReadBuildInfo()
を使用する場合、Github Actionを通し生成したバイナリはバージョン表示が意図したとおりにならない
これらを踏まえてどちらにも対応したい場合以下のようにする必要があります。
package main
import (
"fmt"
"runtime/debug"
)
var version string
func main () {
if version != "" {
// ldflagsでバージョンを埋め込めている場合はそれを表示
fmt.Println(version)
}
if buildInfo, ok := debug.ReadBuildInfo(); ok {
// ldflagsでバージョンを埋め込めていない場合(go installされた場合)
// debug.ReadBuildInfo()を使ってバージョンを表示
fmt.Println(buildInfo.Main.Version)
}
}
これでやっと、両方共バージョンを表示できるようになりました。
しかし、goでCUIツールを作るたびバージョンを表示するためだけにこの処理を書くのはめんどくさいですよね。
ということで、1行で上記の処理をできるようにパッケージ化してしまえばと思い今回パッケージ化しました。
もし、バージョンだけほしいんじゃ!!って場合は以下のようにするとバージョンを文字列で取得できます。
package main
import (
"github.com/harakeishi/curver"
)
func main () {
version := curver.GetVersion()
}
おわりに
今回、自作のコマンドにバージョン表示機能をつけようと思ったけどなかなかうまく行かず手こずったのでもっと簡単にできるようにしたいという思いからパッケージを作成しました。
もし、Goで作ったもののバージョン表示に困ったら使ってみてもらえると嬉しいです。
curverへのPR・issueも気軽にお待ちしています!!
おまけ
curverは、current version
を略して命名しました。
Discussion