この記事は 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