透過一個編譯報錯,總結兩個Go程序編譯的重要知識
本文轉載自微信公眾號「網管叨bi叨」,作者網管。轉載本文請聯系網管叨bi叨公眾號。
最近調研了一下某個做 APM 的廠商的 Go 探針程序,說是引入一個包,全程不用再修改其他代碼就能在項目里引入探針。沒想到在剛引入包試著構建了一下就翻車了。
- main.go:10:2: build constraints exclude all Go files in /xxx/github.com/xxx/agnet/xxxx
編譯器編譯的時候直接排除了某個包下的所有文件,啥意思呢?就是這個包下沒有能在當前構建環境下構建的 Go 文件。猜測應該是這個包源碼的構建標簽上聲明了不允許在Mac 環境下構建。打開源碼看了看,確實是,所有文件的構建標簽都是這么聲明的。
- // +build linux
- // +build amd64
這個叫做條件編譯,或者約束編譯。那想在Mac下編譯linux上才能運行的執行文件該怎么辦呢?Go 里邊還支持一個特性叫做交叉編譯,就是干跨平臺編譯這個事兒的。具體怎么用呢,比如這個例子里是需要在Mac環境下編譯能在Linux系統amd64架構下運行的執行文件,就得醬嬸進行編譯:
- CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go
不過我后來想研究下為啥不讓在 Mac 上編譯,看了看這個包的探針是用CGO實現的調用了linux系統下的一個C語言實現的工具命令??吹竭@我已經不想繼續研究這個包了,那么為了讓此篇文章水的不那么明顯:),接下來咱們就把Go語言的交叉編譯和條件編譯這兩個知識點再復習一遍吧。
交叉編譯
交叉編譯是用來在一個平臺上生成另一個平臺的可執行程序。Go 的命令集是原生支持交叉編譯的,使用方法也很簡單,比如上面已經演示過的
- CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go
參數說明
- CGO_ENABLED : CGO 表示golang中的工具,CGO_ENABLED 表示CGO禁用,交叉編譯中不能使用CGO的
- GOOS: 目標平臺
- mac 對應 darwin
- linux 對應 linux
- windows 對應 windows
- GOARCH :目標平臺的體系架構【386,amd64,arm】, 目前市面上的個人電腦一般都是amd64架構的
- 386 也稱 x86 對應 32位操作系統
- amd64 也稱 x64 對應 64位操作系統
- arm 這種架構一般用于嵌入式開發。比如 Android , IOS , Win mobile , TIZEN 等
了解完這幾個參數后,我們在看下Mac、Linux、Windows這三個平臺上執行交叉編譯的例子,Windows的因為家境貧寒,條件不允許我沒有試過,命令網上找的,如果有錯誤還請同學們在評論里留言幫我改正一下。
Mac 下編譯, Linux 或者 Windows 的可執行程序
- # linux可執行程序
- CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go
- # Windows 可執行程序
- CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go
Linux 下編譯 , Mac 或者 Windows 下去執行
- # Mac 平臺可執行程序
- CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go
- # Windows可執行程序
- CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go
Windows 下執行 , Mac 或 Linux 下去執行
需要寫一個批處理程序,在里面去設置,因為windows 下的 terminal 不支持shell , 這跟 Mac 和 Linux下的有點不同
- # Mac 下執行
- SET CGO_ENABLED=0
- SET GOOS=darwin
- SET GOARCH=amd64
- go build main.go
- # Linux 去執行
- SET CGO_ENABLED=0
- SET GOOS=linux
- SET GOARCH=amd64
- go build main.go
條件編譯
交叉編譯只是為了能在一個平臺上編譯出其他平臺可運行的程序,Go 作為一個跨平臺的語言,它提供的類庫勢必也是跨平臺的,比如說程序的系統調用相關的功能,能根據所處環境選擇對應的源碼進行編譯。讓編譯器只對滿足條件的代碼進行編譯,將不滿足條件的代碼舍棄,這就是另外一個概念叫---條件編譯。
在 Go 中,也稱之為Build Constraints 編譯約束,添加編譯約束的方式有2種分別:
- 編譯標簽(build tag)
- 文件后綴
編譯標簽
編譯標簽是一種通過在源碼文件頂部添加注釋,來決定文件是否參與編譯的約束方式。其格式如下:
- // +build <tags>
注意:// +build的下一行必須是空行,否則會被解析為包注釋。
- // +build linux
- // main package comment
- package main
tags說明:
- 以空格分開表示 AND
- 以逗號分開表示 OR
- !表示 NOT
標簽可以指定為以下內容:
- 操作系統,環境變量中GOOS的值,如:linux、darwin、windows等等。
- 操作系統的架構,環境變量中GOARCH的值,如:arch64、x86、i386等等。
- 使用的編譯器,gc或者gccgo。
- 是否開啟CGO,cgo。
- golang版本號:比如Go Version 1.1為go1.1,Go Version 1.12版本為go1.12,以此類推。
- 其它自定義標簽,通過go build -tags指定的值。
例如,編譯條件為(linux AND 386) OR (darwin AND (NOT cgo))
- // +build linux,386 darwin,!cgo
另外一個文件可以有多個編譯約束,比如條件為(linux OR darwin) AND amd64
- // +build linux darwin
- // +build amd64
也可以使用ignore標簽將一個文件從編譯中排除。
- // +build ignore
文件后綴
除了編譯標簽,第二種添加編譯約束的方法是通過源碼文件的文件名實現的,這種方案比構造標簽方案更簡單。編譯器也會根據文件后綴來自動選擇編譯文件:
- $filename_$GOOS.go
- $filename_$GOARCH.go
- $filename_$GOOS_$GOARCH.go
- $filename: 源文件名稱。
- $GOOS: 表示操作系統,從環境變量中獲取。
- $GOARCH: 表示系統架構,從環境變量中獲取。
后綴的順序記住不要顛倒,后綴中同時出現系統和架構名時,需要保持$filename_$GOOS_$GOARCH.go的順序。
在 Go 的每個內置庫里都有很多以不同系統名結尾的文件。下面是Go的os內置庫源代碼的部分截圖:
文件后綴添加編譯約束
兩種添加編譯限制的方式該如何選擇
構建標簽和文件名后綴在功能上是重疊的。比如,一個名為mypkg_linux.go的文件,再包含構建標簽// +build linux會顯得多余。
通常來說,當只有一個特定平臺需要指定時,我們選擇文件名后綴的方式。比如:
- mypkg_linux.go // 只在 linux 系統編譯
- mypkg_windows_amd64.go // 只在 windows amd 64位 平臺編譯
相反,如果你的文件需要指定給多個平臺或體系架構使用,或者你需要排除某個特定平臺時,我們選擇構建標簽的方式。比如:
- // 在所有類unix平臺編譯
- // +build darwin dragonfly freebsd linux netbsd openbsd
- // 在非Windows平臺編譯
- // +build !windows
一個編譯器報錯,居然水了一篇文章....啊...(咳嗽聲)引出來的交叉編譯和條件編譯(編譯約束)這兩個非常重要的知識點,其實這兩個知識點在很早之前我也寫過篇文章,這次相當于從實際遇到問題帶出從頭開始再分析一遍,希望大家能喜歡。
參考鏈接
http://www.oskip.com/post/golang/golang-build/
https://juejin.cn/post/6844903944808824845
https://mp.weixin.qq.com/s/Ys8o4arwIFYB6DPCdiGNNQ