この記事は Go 1.16 より上のバージョン向けです。
Go アプリのバージョン表示 with go install
Go 言語(以下 Golang)の俺様アプリをバージョン情報表示に対応させたい。
自家製 Golang アプリでバージョン情報を表示するには、一般的にソースにバージョン情報を埋め込むか、ビルド時にオプションで渡す必要があります。
ソースにバージョン情報を埋め込む場合、更新するのが面倒だったり更新忘れがあったりします。そのため、git tag で打ったタグを、ビルド用スクリプトなどで読み込んでビルド時の引数に渡すなどの工夫をします。
しかし、ビルド時に引数でバージョンを渡すと、今度は go install でインストールされた場合は反映されないのが問題となります。
つまり、go install でインストールされたとしても、アプリのバージョン情報が git tag に追随するようにしたいのです。
ちなみに、go install でインストールされた Golang アプリを一括アップデートしたい場合は gup コマンドを使うと便利です。
- github.com/nao1215/gup @ GitHub
TL; DR (今北産業)
-
debug.ReadBuildInfoで、ビルド時のgitタグを取得できる。 -
サンプル
このサンプルは、git tagなどでバージョンをタグ付けしておけば、アプリをgo installされてもリポジトリのバージョンに追随します。(go build除く)main.gopackage main import ( "flag" "fmt" "runtime/debug" ) // Version はビルド時に `-ldflags` 経由で渡されるアプリのバージョン情報です。 var Version string func main() { var versionFlag = flag.Bool("version", false, "") flag.Parse() if *versionFlag { // "go build" された時用のバージョン対応(従来の手法。要 LDFLAGS) if Version != "" { fmt.Println(Version) return } // "go install" された時用のバージョン対応 if buildInfo, ok := debug.ReadBuildInfo(); ok { // clone された VCS の最終タグから取得するので、ローカルの場合は // デフォルトの "(devel)" がセットされる。 fmt.Println(buildInfo.Main.Version) return } fmt.Println("(unknown)") return } fmt.Println("Hello, world!") }- サンプルリポジトリ: https://github.com/KEINOS/go-version-opt-example/ @ GitHub
-
オンラインで上記を
go installしてバージョン表示する結果を見てみる @ paiza.IO
-
必須条件
-
gitなどの VCS で、バージョン用のタグを "vX.Y.Z" 形式で付けておくこと。 - ソースをモジュールモードに対応させておくこと。(
go.modとgo.sumを置くこと) -
go installでパッケージがインストールされた場合のみ反映される。
そのため、リリース用のバイナリ作成やテストなど、go buildする場合は反映されない。go buildでは、従来通りLDFLAGSオプションを指定(go build -ldflags="-X main.Version=$(git describe --tags)")してビルドする基本は変わらない。(Go 1.18 で改善される可能性があるそうです) - 対応 Go バージョン
-
go.modでgo 1.16以上に設定したパッケージで使う方がいい。 -
go.modがgo 1.17なら確実なのでオススメ。
-
-
🐒 この仕組みのポイントは Go 1.12 で実装された debug.readBuildInfo() を使うところです。readBuildInfo()は、ビルド時に埋め込まれたデバッグ情報のオブジェクトを返すのですが、obj.Main.Version フィールドに、main.go に付いていた最終タグが入っています(デフォルトは (devel))。サンプルでは、これを Version 変数が空の場合に利用しています。
しかし、実際には Go のモジュール・モードと go install に依存します。そのため、go get -u でパッケージをバイナリとしてインストールする習慣がまだある(go install を使わない)ユーザのことを考えると、 go.mod のバージョンを go 1.17 以上に設定している場合に、この方法を使うのが確実だと思います。(Go 1.17 は go install が必須になったため)
TIPS
-
gitタグによるバージョン情報の取得コマンドgit describe --tag -
gitのコミット ID の取得コマンドgit rev-parse --short HEAD -
バージョン情報をビルド時に渡す例
TAG_APP=$(git describe --tag) REV_APP=$(git rev-parse --short HEAD) VER_APP="${TAG_APP}+${REV_APP}" go build -ldflags="-s -w -extldflags \"-static\" -X 'main.versionApp=${VER_APP}'"
参考文献
- ReadBuildInfo() | debug | runtime @ pkg.go.dev
- #29228: runtime/debug: document BuildInfo.Main.Version == "(devel)" | Issue | golang @ GitHub
- #29814: cmd/go: use version control to discover the main module's version? | Issue | golang @ GitHub
- #172: Add go1.12 versioning support | Pull Request | golang-migrate @ GitHub