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

上述指令執行結果如下,可以看到由於沒有指定 PlatformVersion 的值,所以輸出的值都是空字串:

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.Platformmain.Version 換成我們想要的值:

$ go build -ldflags "-X main.Platform=macOS -X main.Version=0.0.1" src/main.go

編譯完之後再執行一遍:

,/main

可以從結果看到就算原始 Go 程式碼裡什麼值都沒指定, main.go 裡的 PlatformVersion 已經被設定值囉:

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

對抗久坐職業傷害

研究指出每天增加 2 小時坐著的時間,會增加大腸癌、心臟疾病、肺癌的風險,也造成肩頸、腰背疼痛等常見問題。

然而對抗這些問題,卻只需要工作時定期休息跟伸展身體即可!

你想輕鬆改變現狀嗎?試試看我們的 PomodoRoll 番茄鐘吧! PomodoRoll 番茄鐘會根據你所設定的專注時間,定期建議你 1 項辦公族適用的伸展運動,幫助你打敗久坐所帶來的傷害!

追蹤新知

看完這篇文章了嗎?還意猶未盡的話,追蹤粉絲專頁吧!

我們每天至少分享 1 篇文章/新聞或者實用的軟體/工具,讓你輕鬆增廣見聞提升專業能力!如果你喜歡我們的文章,或是想了解更多特定主題的教學,歡迎到我們的粉絲專頁按讚、留言讓我們知道。你的鼓勵,是我們的原力!

贊助我們的創作

看完這篇文章了嗎? 休息一下,喝杯咖啡吧!

如果你覺得 MyApollo 有讓你獲得實用的資訊,希望能看到更多的技術分享,邀請你贊助我們一杯咖啡,讓我們有更多的動力與精力繼續提供高品質的文章,感謝你的支持!