Go 用 -ldflags -X 參數在編譯時自動設定程式碼內的字串變數值
Posted on Dec 7, 2023 in Go 程式設計 - 中階 by Amo Chen ‐ 4 min read
為了方便除錯,通常我們會在程式裡留下一些方便除錯的資訊,包含是哪個 commit 或者哪個系統編譯、哪個版本號等等⋯⋯。
你是手動更改這些資訊的嗎?
本文將透過實際範例教導如何自動化帶入這些資訊到 Go 的執行檔中,讓你徹底解脫手動之苦,效率 up up!
本文環境
- Go 1.20
- macOS
Go 專案結構
以下是本文的 Go 專案結構:
.
├── Makefile
├── go.mod
└── src
├── main.go
└── lib
└── value.go
執行本文的範例須設定 GOPATH
環境變數(請記得將 <your_go_path>
換成你的設定):
$ export GOPATH=<your_go_path>
建立 go.mod
的指令如下(請記得將 <your_username>/<your_go_repo>
換成你的設定):
$ go mod init <your_username>/<your_go_repo>
例如:
$ go mod init github/name/repo_name
main.go
的內容如下:
package main
import "fmt"
var Platform string
var Version string
func main() {
fmt.Printf("platform=%s | version=%s", Platform, Version)
}
上述是 1 個列印作業系統平台以及程式版本的 Go 程式碼,十分簡單。
編譯(compile)該 Go 程式的指令如下:
$ go build src/main.go
編譯完成之後,會產生 1 個 main
可執行檔,用以下指令執行它:
./main
上述指令執行結果如下,可以看到由於沒有指定 Platform
與 Version
的值,所以輸出的值都是空字串:
platform= | version=
-ldflags 介紹
通常為了方便除錯,我們會在程式裡留下一些方便除錯的資訊,例如本文範例中的 Platform
, Version
,有些還會加上編譯日期、時間、更詳細的作業系統版本等等。
但是這些資訊如果每次都要手動修改程式碼再進行編譯的話,我敢保證一定時有出錯,畢竟手動執行肯定會時有紕漏,不是忘記改就是漏了改⋯⋯,那麼這種方便除錯的設計,就形同虛設,因為不僅資訊存在不正確的機會,還可能造成除錯時的誤導⋯⋯。
最好的方式還是自動化,在執行編譯時自動寫進去!
要做到自動化的話,必須先認識 Go 提供的 1 個 -ldflags
參數 -X
,可以讓開發者在編譯時設定字串型態變數的值,其使用方法如下。
$ go build -ldflags "-X importpath.name=value" go_files...
-X 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.
所以我們可以用 -ldflags
參數,在編譯時把 main.Platform
與 main.Version
換成我們想要的值:
$ go build -ldflags "-X main.Platform=macOS -X main.Version=0.0.1" src/main.go
編譯完之後再執行一遍:
,/main
可以從結果看到就算原始 Go 程式碼裡什麼值都沒指定, main.go
裡的 Platform
與 Version
已經被設定值囉:
platform=macOS | version=0.0.1
記住,想透過 -ldflags "-X importpath.name=value"
設定的變數,必須是字串型態的變數,否則執行編譯時就會出現類似以下的錯誤(官方文件也有說明必須是 string variable ):
cannot set with -X: not a var of type string (type:int)
更改其他 package 內的值
前文提到的只有改 package main
內的值,假設我們想改 package lib
底下的值,做法也類似,但是要注意 importpath 寫法要正確,才能夠更改值。
以下是 lib/value.go
的內容:
package lib
var Value string
以下是 main.go
引用 package lib
的程式碼(請記得將 <your_username>/<your_go_repo>
換成你的設定):
package main
import (
"fmt"
"<your_username>/<your_go_repo>/src/lib"
)
var Platform string = "linux"
var Version string
func main() {
fmt.Printf("platform=%s | version=%s | lib.Value=%s", Platform, Version, lib.Value)
}
如果要更改 lib.go
裡的 Value
就必須指定 -X <your_username>/<your_go_repo>/src/lib.Value
才能正確運作,完整的編譯指令如下:
$ go build -ldflags "-X main.PLATFORM=macos -X main.VERSION=0.0.1 -X <your_username>/<your_go_repo>/src/lib.Value=123" src/main.go
搭配 uname, git, make 等指令進行自動化
學會 -ldflags
搭配 -X
如何使用之後,接下來要把自動化的部分整合到編譯流程之中。
首先,用 git
將程式碼提交後並設定版號:
$ git init .
$ git add .
$ git commit -m 'first commit'
$ git tag -a '0.1.1' -m 'my 1st tag'
然後可以把 Go 的編譯指令改為:
$ go build -ldflags "-X main.Platform=$(uname) -X main.Version=$(git describe --tags --abbrev=0)" src/main.go
上述指令的 uname
是取得作業系統名稱的指令,使用 $(uname)
代表執行 uname
指令之後並將執行結果取代掉 $(uname)
,假設 uname
執行結果是輸出文字 Darwin
,那麼 main.Platform=$(uname)
執行結果就會變成 main.Platform=Darwin
。
指令 git describe --tags --abbrev=0
則是取得 Git 最近的版號,同樣用 $()
包起來是將其執行結果帶入到指令之中。
上述指令執行完之後,執行檔的執行結果如下,可以看到不用手動修改任何值,就能夠自動帶入相關資訊:
platform=Darwin | version=0.1.1 | lib.Value=
有可以自動取得相關資訊的編譯指令之後,我們可以進一步把編譯指令放到 Makefile
之中,例如(要將 $()
改為 ``
):
build:
go build \
-ldflags "\
-X main.Platform=`uname` \
-X main.Version=`git describe --tags --abbrev=0`\
" src/main.go
如此一來,其他開發團隊的成員只要輸入指令 make build
就能夠自動化執行編譯囉!再也不用怕忘記改或漏改什麼資料了!
p.s. 如果你有什麼資訊想透過指令取得,詢問 ChatGPT 是個不錯的選擇
總結
Go 的 -ldflags
與 -X
參數真的十分方便,可以在編譯時更改字串型態的變數值,這給編譯的自動化帶來更多彈性與可能性,是值得學習的 1 個技術!
以上!
Enjoy!
References
link command - cmd/link - Go Packages