これは、なにをしたくて書いたもの?
記事や書籍などで、以下のような記述を見かけます。
$ go build -ldflags '-X main.xxxx=....'
この-ldflagsと-Xの指定でプログラム内の値を変えているようなのですが、「変えられます」という情報以外のことを
あまり見かけないので調べてみることにしました。
環境
今回の環境は、こちらです。
$ go version go version go1.16 linux/amd64
Go 1.16ですね。
まずは試してみる
Goのプロジェクトを作成。
$ go mod init sample go: creating new go.mod: module sample
こんなプログラムを用意。
main.go
package main import ( "fmt" ) var ( Message = "Hello World!!" ) func main() { fmt.Printf("Message = %s\n", Message) }
ビルド。
$ go build
実行。
$ ./sample Message = Hello World!!
このMessage変数の値を、ビルド時に変更してみましょう。
$ go build -ldflags '-X main.Message=Wow'
確かに変わりました。
$ ./sample Message = Wow
ソースコードは変えていないのに、ビルド時のフラグ指定だけで変わりましたね。
スペースを含めたい場合は、変数名ごとクォートで囲えばよさそうです。
$ go build -ldflags '-X "main.Message=Hello Go!!!"' $ ./sample Message = Hello Go!!!
で、変えられることはわかったのですが、このフラグ、-ldflags自体の意味をもう少し調べたい、と。
go buildのヘルプを見る
まずは、go buildコマンドのヘルプを見てみます。
$ go help build usage: go build [-o output] [build flags] [packages] 〜省略〜
-ldflagsについて見てみましょう。
-ldflags '[pattern=]arg list'
arguments to pass on each go tool link invocation.
go tool linkの呼び出しに渡す、と書かれています。
ところで、似たような考えでgo runのヘルプも見てみましょう。
$ go help run usage: go run [build flags] [-exec xprog] package [arguments...] 〜省略〜 For more about build flags, see 'go help build'. For more about specifying packages, see 'go help packages'. See also: go build.
build flagsについてはgo buildを見て、と言っています。
ということは、-ldflagsも指定できそうですね。試してみましょう。
$ go run -ldflags '-X "main.Message=Hello Go!!!"' main.go Message = Hello Go!!!
やっぱりできましたね。なるほど。
go testでも使えそうですね。今回は、確認まではしませんが…。
$ go help test usage: go test [build/test flags] [packages] [build/test flags & test binary flags] 〜省略〜 For more about build flags, see 'go help build'. For more about specifying packages, see 'go help packages'. See also: go build, go vet.
以降は簡単のため、go runコマンドで-ldflagsフラグと-Xフラグを使っていこうと思います。
ドキュメントを見る
次は、goコマンドのドキュメントを見てみましょう。
go - The Go Programming Language
The -asmflags, -gccgoflags, -gcflags, and -ldflags flags accept a space-separated list of arguments to pass to an underlying tool during the build.
フラグは、スペースで区切ったリストで渡します、と。
To embed spaces in an element in the list, surround it with either single or double quotes.
要素自体にスペースを含めたい場合は、シングルクォートまたはダブルクォートで囲ってください。
The argument list may be preceded by a package pattern and an equal sign, which restricts the use of that argument list to the building of packages matching that pattern (see 'go help packages' for a description of package patterns).
ヘルプには、このフラグはgo tool linkに渡すとも書かれていました。
というわけで、go tool linkのヘルプを見てみます。
link - The Go Programming Language
こちらに、オプションの意味が書かれていました。
-Xの意味は、以下になります。
-X importpath.name=value
Set the value of the string variable in importpath named name to value.
This is only effective if the variable is declared in the source code either uninitialized
or initialized to a constant string expression. -X will not work if the initializer makes
a function call or refers to other variables.
Note that before Go 1.5 this option took two separate arguments.
importpath内のnameで指定された変数の値を設定します。ソースコード内で宣言された"変数"(variable)に対してのみ、
効果があるそうです。
他には、たとえばgdbを使ったドキュメントについては-ldflags=-wを使うことが書かれていますが、
Debugging Go Code with GDB - The Go Programming Language
これはDWARFシンボルテーブルをバイナリに含めないフラグです。要するに、デバッグ情報を含めません、と。
-w
Omit the DWARF symbol table.
DWARFについては、こちら。
dwarf - The Go Programming Language
ちなみに、これらの説明はgo doc cmd/linkでも表示できます。
$ go doc cmd/link
go tool linkでなにも指定しないで実行すると、フラグは見れますがちょっと説明が簡易ですね。
$ go tool link
usage: link [options] main.o
〜省略〜
-X definition
add string value definition of the form importpath.name=value
〜省略〜
とりあえず、情報の見方はわかりました。
バリエーションを試してみる
では、いくつかバリエーションを試してみましょう。-ldflagsフラグと-Xの組み合わせを試してみたいと思います。
変数を2つ定義してみます。
main.go
package main import ( "fmt" ) var ( Message1 = "Message1" Message2 = "Message2" ) func main() { fmt.Printf("Message1 = %s\n", Message1) fmt.Printf("Message2 = %s\n", Message2) }
これを1度に変えるには…?
-ldflagsの中に、2回書けばOKですね。
$ go run -ldflags '-X main.Message1=Hello -X main.Message2=World' main.go Message1 = Hello Message2 = World
-ldflags自体を繰り返すのはダメなようです。
$ go run -ldflags '-X main.Message1=Hello' -ldflags '-X main.Message2=World' main.go Message1 = Message1 Message2 = World
次は、こうしてみましょう。
main.go
package main import ( "fmt" ) var ( ExportedStringVar = "ExportedStringVar" unxportedStringVar = "unxportedStringVar" ExportedIntVar = 10 unxportedIntVar = 20 ) const ( ExportedStringConst = "ExportedStringConst" unexportedStringConst = "unexportedStringConst" ExportedIntConst = 100 unexportedIntConst = 200 ) func main() { fmt.Printf(` ExportedStringVar = %s unxportedStringVar = %s ExportedIntVar = %d unxportedIntVar = %d ExportedStringConst = %s unexportedStringConst = %s ExportedIntConst = %d unxportedIntConst = %d `, ExportedStringVar, unxportedStringVar, ExportedIntVar, unxportedIntVar, ExportedStringConst, unexportedStringConst, ExportedIntConst, unexportedIntConst, ) }
stringおよびintなvarおよびconst、そしてそれぞれエクスポートされているもの、されていないものを用意して、変更を試みます。
var ( ExportedStringVar = "ExportedStringVar" unxportedStringVar = "unxportedStringVar" ExportedIntVar = 10 unxportedIntVar = 20 ) const ( ExportedStringConst = "ExportedStringConst" unexportedStringConst = "unexportedStringConst" ExportedIntConst = 100 unexportedIntConst = 200 )
最初に、varを指定してみましょう。
$ go run -ldflags '-X main.ExportedStringVar=Foo -X main.unxportedStringVar=Bar -X main.ExportedIntVar=50 -X main.unxportedIntVar=60' main.go
すると、intの部分についてはエラーになります。
# command-line-arguments main.ExportedIntVar: cannot set with -X: not a var of type string (type.int) main.unxportedIntVar: cannot set with -X: not a var of type string (type.int)
ドキュメントを見た時から予想はついていましたが、変更できるのはstringのみのようですね。
修正版。
$ go run -ldflags '-X main.ExportedStringVar=Foo -X main.unxportedStringVar=Bar' main.go
ExportedStringVar = Foo
unxportedStringVar = Bar
ExportedIntVar = 10
unxportedIntVar = 20
ExportedStringConst = ExportedStringConst
unexportedStringConst = unexportedStringConst
ExportedIntConst = 100
unxportedIntConst = 200
stringであれば、エクスポートの有無に関わず変更できそうです。
続いて、const。せっかく用意しましたが、先ほどの結果でintは変えられないことはわかったのでstringのみ指定します。
$ go run -ldflags '-X main.ExportedStringConst=Foo -X main.unexportedStringConst=Bar' main.go
ExportedStringVar = ExportedStringVar
unxportedStringVar = unxportedStringVar
ExportedIntVar = 10
unxportedIntVar = 20
ExportedStringConst = ExportedStringConst
unexportedStringConst = unexportedStringConst
ExportedIntConst = 100
unxportedIntConst = 200
こちらは、変更できません。
説明の時点で、「変数」と書いていましたからね。やっぱりそうですか。
Set the value of the string variable in importpath named name to value.
サブパッケージでも試してみましょう。
$ mkdir sub
sub/message.go
package sub var ( Message = "Hello World" ) func GetMessage() string { return Message }
mainパッケージ側は、これを呼ぶだけにします。
main.go
package main import ( "fmt" "sample/sub" ) func main() { fmt.Printf("sub.GetMessage = %s\n", sub.GetMessage()) }
まずは、ふつうに実行。
$ go run main.go sub.GetMessage = Hello World
varを変更。
$ go run -ldflags '-X sample/sub.Message=Foo' main.go sub.GetMessage = Foo
サブパッケージのものも、変更できましたね。
最後は、関数呼び出しの結果を使うvar。
main.go
package main import ( "fmt" ) var ( Message = GetMessage() ) func GetMessage() string { return "Hello World" } func main() { fmt.Printf("Message = %s\n", Message) }
さすがに、これはムリですね。
$ go run -ldflags '-X main.Message=Foo' main.go Message = Hello World
説明にもこう書いていましたからね。
-X will not work if the initializer makes
a function call or refers to other variables.
つまり、パッケージのトップレベルに定義されたvarであること、リテラルで指定された値であること、が条件のようですね。
覚えておきましょう。
※トップレベルでないとダメかどうかはちゃんと確認してはいませんが、まあいいでしょう…