我最喜歡的 Go 構建選項
學習一門新的編程語言最令人欣慰的部分之一,就是最終運行了一個可執行文件,並獲得預期的輸出。當我開始學習 Go 這門編程語言時,我先是閱讀一些示常式序來熟悉語法,然後是嘗試寫一些小的測試程序。隨著時間的推移,這種方法幫助我熟悉了編譯和構建程序的過程。
Go 的構建選項提供了更好地控制構建過程的方法。它們還可以提供額外的信息,幫助把這個過程分成更小的部分。在這篇文章中,我將演示我所使用的一些選項。注意:我使用的「 構建 」和「 編譯 」這兩個詞是同一個意思。
開始使用 Go
我使用的 Go 版本是 1.16.7。但是,這裡給出的命令應該也能在最新的版本上運行。如果你沒有安裝 Go,你可以從 Go 官網 上下載它,並按照說明進行安裝。你可以通過打開一個命令提示符,並鍵入下面的命令來驗證你所安裝的版本:
$ go version
你應該會得到類似下面這樣的輸出,具體取決於你安裝的版本:
go version go1.16.7 linux/amd64
基本的 Go 程序的編譯和執行方法
我將從一個在屏幕上簡單列印 「Hello World」 的 Go 程序示例開始,就像下面這樣:
$ cat hello.go
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
在討論更高級的選項之前,我將解釋如何編譯這個 Go 示常式序。我使用了 build
命令,後面跟著 Go 程序的源文件名,本例中是 hello.go
,就像下面這樣:
$ go build hello.go
如果一切工作正常,你應該看到在你的當前目錄下創建了一個名為 hello
的可執行文件。你可以通過使用 file
命令驗證它是 ELF 二進位可執行格式(在 Linux 平台上)。你也可以直接執行它,你會看到它輸出 「Hello World」。
$ ls
hello hello.go
$ file ./hello
./hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
$ ./hello
Hello World
Go 提供了一個方便的 run
命令,以便你只是想看看程序是否能正常工作,並獲得預期的輸出,而不想生成一個最終的二進位文件。請記住,即使你在當前目錄中沒有看到可執行文件,Go 仍然會在某個地方編譯並生成可執行文件並運行它,然後把它從系統中刪除。我將在本文後面的章節中解釋。
$ go run hello.go
Hello World
$ ls
hello.go
更多細節
上面的命令就像一陣風一樣,一下子就運行完了我的程序。然而,如果你想知道 Go 在編譯這些程序的過程中做了什麼,Go 提供了一個 -x
選項,它可以列印出 Go 為產生這個可執行文件所做的一切。
簡單看一下你就會發現,Go 在 /tmp
內創建了一個臨時工作目錄,並生成了可執行文件,然後把它移到了 Go 源程序所在的當前目錄。
$ go build -x hello.go
WORK=/tmp/go-build1944767317
mkdir -p $WORK/b001/
<< snip >>
mkdir -p $WORK/b001/exe/
cd .
/usr/lib/golang/pkg/tool/linux_amd64/link -o $WORK
/b001/exe/a.out -importcfg $WORK/b001
/importcfg.link -buildmode=exe -buildid=K26hEYzgDkqJjx2Hf-wz/
nDueg0kBjIygx25rYwbK/W-eJaGIOdPEWgwC6o546
/K26hEYzgDkqJjx2Hf-wz -extld=gcc /root/.cache/go-build /cc
/cc72cb2f4fbb61229885fc434995964a7a4d6e10692a23cc0ada6707c5d3435b-d
/usr/lib/golang/pkg/tool/linux_amd64/buildid -w $WORK
/b001/exe/a.out # internal
mv $WORK/b001/exe/a.out hello
rm -r $WORK/b001/
這有助於解決在程序運行後卻在當前目錄下沒有生成可執行文件的謎團。使用 -x
顯示可執行文件確實在 /tmp
工作目錄下創建並被執行了。然而,與 build
命令不同的是,可執行文件並沒有移動到當前目錄,這使得看起來沒有可執行文件被創建。
$ go run -x hello.go
mkdir -p $WORK/b001/exe/
cd .
/usr/lib/golang/pkg/tool/linux_amd64/link -o $WORK/b001
/exe/hello -importcfg $WORK/b001/importcfg.link -s -w -buildmode=exe -buildid=hK3wnAP20DapUDeuvAAS/E_TzkbzwXz6tM5dEC8Mx
/7HYBzuaDGVdaZwSMEWAa/hK3wnAP20DapUDeuvAAS -extld=gcc
/root/.cache/go-build/75/
7531fcf5e48444eed677bfc5cda1276a52b73c62ebac3aa99da3c4094fa57dc3-d
$WORK/b001/exe/hello
Hello World
模仿編譯而不產生可執行文件
假設你不想編譯程序併產生一個實際的二進位文件,但你確實想看到這個過程中的所有步驟。你可以通過使用 -n
這個構建選項來做到這一點,該選項會列印出通常的執行步驟,而不會實際創建二進位文件。
$ go build -n hello.go
保存臨時目錄
很多工作都發生在 /tmp
工作目錄中,一旦可執行文件被創建和運行,它就會被刪除。但是如果你想看看哪些文件是在編譯過程中創建的呢?Go 提供了一個 -work
選項,它可以在編譯程序時使用。-work
選項除了運行程序外,還列印了工作目錄的路徑,但它並不會在這之後刪除工作目錄,所以你可以切換到該目錄,檢查在編譯過程中創建的所有文件。
$ go run -work hello.go
WORK=/tmp/go-build3209320645
Hello World
$ find /tmp/go-build3209320645
/tmp/go-build3209320645
/tmp/go-build3209320645/b001
/tmp/go-build3209320645/b001/importcfg.link
/tmp/go-build3209320645/b001/exe
/tmp/go-build3209320645/b001/exe/hello
$ /tmp/go-build3209320645/b001/exe/hello
Hello World
其他編譯選項
如果說,你想手動編譯程序,而不是使用 Go 的 build
和 run
這兩個方便的命令,最後得到一個可以直接由你的操作系統(這裡指 Linux)運行的可執行文件。那麼,你該怎麼做呢?這個過程可以分為兩部分:編譯和鏈接。你可以使用 tool
選項來看看它是如何工作的。
首先,使用 tool compile
命令產生結果的 ar
歸檔文件,它包含了 .o
中間文件。接下來,對這個 hello.o
文件執行 tool link
命令,產生最終的可執行文件,然後你就可以運行它了。
$ go tool compile hello.go
$ file hello.o
hello.o: current ar archive
$ ar t hello.o
__.PKGDEF
_go_.o
$ go tool link -o hello hello.o
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
$ ./hello
Hello World
如果你想進一步查看基於 hello.o
文件產生可執行文件的鏈接過程,你可以使用 -v
選項,它會搜索每個 Go 可執行文件中包含的 runtime.a
文件。
$ go tool link -v -o hello hello.o
HEADER = -H5 -T0x401000 -R0x1000
searching for runtime.a in /usr/lib/golang/pkg/linux_amd64/runtime.a
82052 symbols, 18774 reachable
1 package symbols, 1106 hashed symbols, 77185 non-package symbols, 3760 external symbols
81968 liveness data
交叉編譯選項
現在我已經解釋了 Go 程序的編譯過程,接下來,我將演示 Go 如何通過在實際的 build
命令之前提供 GOOS
和 GOARCH
這兩個環境變數,來允許你構建針對不同硬體架構和操作系統的可執行文件。
這有什麼用呢?舉個例子,你會發現為 ARM(arch64)架構製作的可執行文件不能在英特爾(x86_64)架構上運行,而且會產生一個 Exec 格式錯誤。
下面的這些選項使得生成跨平台的二進位文件變得小菜一碟:
$ GOOS=linux GOARCH=arm64 go build hello.go
$ file ./hello
./hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, not stripped
$ ./hello
bash: ./hello: cannot execute binary file: Exec format error
$ uname -m
x86_64
你可以閱讀我之前的博文,以更多了解我在 使用 Go 進行交叉編譯 方面的經驗。
查看底層彙編指令
源代碼並不會直接轉換為可執行文件,儘管它生成了一種中間彙編格式,然後最終被組裝為可執行文件。在 Go 中,這被映射為一種中間彙編格式,而不是底層硬體彙編指令。
要查看這個中間彙編格式,請在使用 build
命令時,提供 -gcflags
選項,後面跟著 -S
。這個命令將會顯示使用到的彙編指令:
$ go build -gcflags="-S" hello.go
# command-line-arguments
"".main STEXT size=138 args=0x0 locals=0x58 funcid=0x0
0x0000 00000 (/test/hello.go:5) TEXT "".main(SB), ABIInternal, $88-0
0x0000 00000 (/test/hello.go:5) MOVQ (TLS), CX
0x0009 00009 (/test/hello.go:5) CMPQ SP, 16(CX)
0x000d 00013 (/test/hello.go:5) PCDATA $0, $-2
0x000d 00013 (/test/hello.go:5) JLS 128
<< snip >>
你也可以使用 objdump -s
選項,來查看已經編譯好的可執行程序的彙編指令,就像下面這樣:
$ ls
hello hello.go
$ go tool objdump -s main.main hello
TEXT main.main(SB) /test/hello.go
hello.go:5 0x4975a0 64488b0c25f8ffffff MOVQ FS:0xfffffff8, CX
hello.go:5 0x4975a9 483b6110 CMPQ 0x10(CX), SP
hello.go:5 0x4975ad 7671 JBE 0x497620
hello.go:5 0x4975af 4883ec58 SUBQ $0x58, SP
hello.go:6 0x4975d8 4889442448 MOVQ AX, 0x48(SP)
<< snip >>
分離二進位文件以減少其大小
Go 的二進位文件通常比較大。例如, 一個簡單的 「Hello World」 程序將會產生一個 1.9M 大小的二進位文件。
$ go build hello.go
$
$ du -sh hello
1.9M hello
$
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
$
為了減少生成的二進位文件的大小,你可以分離執行過程中不需要的信息。使用 -ldflags
和 -s -w
選項可以使生成的二進位文件略微變小為 1.3M。
$ go build -ldflags="-s -w" hello.go
$
$ du -sh hello
1.3M hello
$
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped
$
總結
我希望這篇文章向你介紹了一些方便的 Go 編譯選項,同時幫助了你更好地理解 Go 編譯過程。關於構建過程的其他信息和其他有趣的選項,請參考 Go 命令幫助:
$ go help build
題圖由 Ashraf Chemban 在 Pixabay 上發布。
via: https://opensource.com/article/22/4/go-build-options
作者:Gaurav Kamathe 選題:lkxed 譯者:lkxed 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive