これは、なにをしたくて書いたもの?
記事や書籍などで、以下のような記述を見かけます。
$ 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
であること、リテラルで指定された値であること、が条件のようですね。
覚えておきましょう。
※トップレベルでないとダメかどうかはちゃんと確認してはいませんが、まあいいでしょう…