Go 1.18 で新しく追加される debug/buildinfo パッケージについて調べました。
※ 特に記載がない限り 2022 年 2 月 20 日時点 (Go 1.18 rc.1) の情報になります。
概要
従来からビルドに使用された Go のバージョンや依存パッケージの情報はバイナリに含まれており go version -m で出力できましたが内部パッケージとして実装されているため外部からは利用できませんでした。 1 2
これらの機能を外部から利用できるようにしたのが debug/buildinfo です。
また、 Go 1.18 ではビルドしたソースコードのバージョン管理情報やビルドオプションもバイナリに追加されるようになりました。
これによって従来は ldflag などで埋め込んでいた git の commit ハッシュなどを runtime から取得できるようになります。
取得できる情報
Go 1.18 では runtime/debug.ReadBuildInfo() で取得できる debug.BuildInfo の情報が増えました。
また、 debug/buildinfo パッケージを使うことで自分自身だけではなく、バイナリファイルまたは io.Reader から debug.BuildInfo を取得できるようになりました。
| 項目名 | 型 | 内容 | 
|---|---|---|
| GoVersion | string | ビルドに使用された Go のバージョン文字列 | 
| Path | string | main パッケージパス (既存項目) | 
| Main | Module | main パッケージが含まれるモジュールの情報 (既存項目) | 
| Deps | []*Module | 依存パッケージのモジュール情報 (既存項目) | 
| Settings | []BuildSetting | ビルドに関する情報 (Key - Value 形式) | 
Go 1.18 で追加されたのは GoVersion と Settings で他の項目に変更はありません。
手元の環境だと Settings には次の情報が出力されました。
| Key | Value | 内容 | 
|---|---|---|
| -compiler | gc | ビルドに使用されたコンパイラ ( gcとかgccgo) | 
| CGO_ENABLED | 1 | ビルドに使用される環境変数 | 
| CGO_CFLAGS | ビルドに使用される環境変数 | |
| CGO_CPPFLAGS | ビルドに使用される環境変数 | |
| CGO_CXXFLAGS | ビルドに使用される環境変数 | |
| CGO_LDFLAGS | ビルドに使用される環境変数 | |
| GOARCH | amd64 | ビルドターゲットの CPU アーキテクチャ | 
| GOOS | windows | ビルドターゲットの OS 名 | 
| GOAMD64 | v1 | ビルドターゲット amd64のマイクロアーキテクチャレベル | 
| vcs | git | バージョン管理システムの名前 ( git,hg,bzr,fossilをサポート) | 
| vcs.revision | (略) | ソースコードのリビジョンを示す情報 (git の場合は commit ハッシュ) | 
| vcs.time | (略) | ソースコードのリビジョン更新時刻 (git の場合は commit 時刻) | 
| vcs.modified | true | commit されていない変更があるかどうか | 
GOAMD64 は Go 1.18 で追加される環境変数で amd64 のマイクロアーキテクチャレベルを指定します。 3
デフォルトは v1 で v2, v3, v4 と世代が上がると利用できる命令が増えてビルドされるバイナリが最適化されます。
git tag のような情報はバージョンがあいまいになり予測できない動作を引き起こすという理由で含まれていません。 4
個人的に入れてほしい情報ですが commit ハッシュと違って tag は付け替えもできるし複数設定もできるので確かにその通りではあります。
また、バージョン管理システムの情報を含めないオプションを用意するのことでしたが見つけられませんでした。 5
また、ビルド時に -buildvcs=false を指定することでバージョン管理システムの情報をバイナリに含めないようにできます。
Go 1.18 beta.1 までは -buildinfo=false を指定することでビルドオプションもバイナリに含めないようにできましたがこのオプションは Go 1.18 beta.2 で廃止されました。 6
ちなみに Main.Version には (devel) という文字列が設定されます。
従来からの動作ですが少し残念な感じです。
go version コマンドで出力する
従来の go version -m は引き続き利用できます。
Go 1.18 でビルドしたバイナリだと表示される情報が増えますが、 Go 1.18 でビルドしたバイナリは Go 1.17 で表示できないようです。
Go 1.17 で go version -m を使った場合の出力例
$ go version -m testdata/hello.go117.exe
testdata/hello.go117.exe: go1.17.5
        path    github.com/sg0hsmt/try-goapp/hello
        mod     github.com/sg0hsmt/try-goapp    (devel)
        dep     github.com/google/uuid  v1.3.0  h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
        dep     github.com/maxence-charriere/go-app/v9  v9.2.1  h1:wP+A7zMDc1DIxXvhZQvWVq+vzlb8Hmo75AEVMChoVRg=
$ go version -m testdata/hello.go118.exe
testdata/hello.go118.exe: go version not found
Go 1.18 rc.1 で go version -m を使った場合の出力例
$ go1.18rc1 version -m testdata/hello.go117.exe
testdata/hello.go117.exe: go1.17.5
        path    github.com/sg0hsmt/try-goapp/hello
        mod     github.com/sg0hsmt/try-goapp    (devel)
        dep     github.com/google/uuid  v1.3.0  h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
        dep     github.com/maxence-charriere/go-app/v9  v9.2.1  h1:wP+A7zMDc1DIxXvhZQvWVq+vzlb8Hmo75AEVMChoVRg=
$ go1.18rc1 version -m testdata/hello.go118.exe
testdata/hello.go118.exe: go1.18beta1
        path    github.com/sg0hsmt/try-goapp/hello
        mod     github.com/sg0hsmt/try-goapp    (devel)
        dep     github.com/google/uuid  v1.3.0  h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
        dep     github.com/maxence-charriere/go-app/v9  v9.2.1  h1:wP+A7zMDc1DIxXvhZQvWVq+vzlb8Hmo75AEVMChoVRg=
        build   -compiler=gc
        build   CGO_ENABLED=1
        build   CGO_CFLAGS=
        build   CGO_CPPFLAGS=
        build   CGO_CXXFLAGS=
        build   CGO_LDFLAGS=
        build   GOARCH=amd64
        build   GOOS=windows
        build   GOAMD64=v1
        build   vcs=git
        build   vcs.revision=619f4a18485903816d91d53a6d1618d5024d47d1
        build   vcs.time=2021-12-19T07:51:57Z
        build   vcs.modified=true
Go 1.18 でもビルド時に -buildvcs=false オプションを指定することでバージョン管理システムの情報が含まれないように制限できます。
-buildvcs=false と -buildinfo=false を両方指定するとバイナリに含まれる情報は Go 1.17 と同等になります。
$ go1.18rc1 build -buildvcs=false -o hello.go118.exe .
$ go1.18rc1 version -m hello.go118.exe
hello.go118.exe: go1.18rc1
        path    github.com/sg0hsmt/try-goapp/hello
        mod     github.com/sg0hsmt/try-goapp    (devel)
        dep     github.com/google/uuid  v1.3.0  h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
        dep     github.com/maxence-charriere/go-app/v9  v9.2.1  h1:wP+A7zMDc1DIxXvhZQvWVq+vzlb8Hmo75AEVMChoVRg=
        build   -compiler=gc
        build   CGO_ENABLED=1
        build   CGO_CFLAGS=
        build   CGO_CPPFLAGS=
        build   CGO_CXXFLAGS=
        build   CGO_LDFLAGS=
        build   GOARCH=amd64
        build   GOOS=windows
        build   GOAMD64=v1
なお Go 1.18 rc.1 時点では go version の対象が実行ファイルに制限されており Windows で Linux 向けバイナリを表示させようとすると失敗します。 7
$ go1.18rc1 version -m testdata/hello.go118.linux
testdata/hello.go118.linux: not executable file
debug/buildinfo パッケージを使う
debug/buildinfo.ReadFile() にファイルパスを渡すと *debug.BuildInfo を返してくれるので利用は簡単です。
大雑把に次のようなコードで出力できます。
(Go 1.18 rc.1 で String() が実装され MarshalText() が廃止されました 8)
func main() {
	var path string
	flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	flags.StringVar(&path, "path", "", "path of target binary")
	flags.Parse(os.Args[1:])
	info, err := buildinfo.ReadFile(path)
	if err != nil {
		log.Fatalln("can not read build info from file:", err)
	}
	fmt.Println(info)
}
$ go1.18rc1 run main.go -path testdata/hello.go117.exe
go      go1.17.5
path    github.com/sg0hsmt/try-goapp/hello
mod     github.com/sg0hsmt/try-goapp    (devel)
dep     github.com/google/uuid  v1.3.0  h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
dep     github.com/maxence-charriere/go-app/v9  v9.2.1  h1:wP+A7zMDc1DIxXvhZQvWVq+vzlb8Hmo75AEVMChoVRg=
$ go1.18rc1 run main.go -path testdata/hello.go118.exe
go      go1.18beta1
path    github.com/sg0hsmt/try-goapp/hello
mod     github.com/sg0hsmt/try-goapp    (devel)
dep     github.com/google/uuid  v1.3.0  h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
dep     github.com/maxence-charriere/go-app/v9  v9.2.1  h1:wP+A7zMDc1DIxXvhZQvWVq+vzlb8Hmo75AEVMChoVRg=
build   -compiler=gc
build   CGO_ENABLED=1
build   CGO_CFLAGS=
build   CGO_CPPFLAGS=
build   CGO_CXXFLAGS=
build   CGO_LDFLAGS=
build   GOARCH=amd64
build   GOOS=windows
build   GOAMD64=v1
build   vcs=git
build   vcs.revision=619f4a18485903816d91d53a6d1618d5024d47d1
build   vcs.time=2021-12-19T07:51:57Z
build   vcs.modified=true
自分自身の情報を取得したい場合は従来通り debug.ReadBuildInfo() を呼べば *debug.BuildInfo を取得できます。
また debug/buildinfo パッケージを使用した場合は Windows でも Linux 向けバイナリの表示が可能です。
(さすがに wasm はだめでした)
$ go1.18rc1 run main.go -path testdata/hello.go118.linux
go      go1.18beta1
path    github.com/sg0hsmt/try-goapp/hello
mod     github.com/sg0hsmt/try-goapp    (devel)
dep     github.com/google/uuid  v1.3.0  h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
dep     github.com/maxence-charriere/go-app/v9  v9.2.1  h1:wP+A7zMDc1DIxXvhZQvWVq+vzlb8Hmo75AEVMChoVRg=
build   -compiler=gc
build   CGO_ENABLED=0
build   GOARCH=amd64
build   GOOS=linux
build   GOAMD64=v1
build   vcs=git
build   vcs.revision=619f4a18485903816d91d53a6d1618d5024d47d1
build   vcs.time=2021-12-19T07:51:57Z
build   vcs.modified=true
ユースケース
主なユースケースとして次の使い方が想定されているようです。
Go で実装されたアプリケーションを運用していくうえで重要な機能になりそうです。
コンテナ内のバイナリに対して脆弱性なパッケージが組み込まれていないかチェックする。
SBOM (ソフトウェア部品表) を作成する。
