說起條件編譯,大部分開發人員都會聯想到交叉編譯。但是 條件編譯 和 交叉編譯 完全是兩回事, 解決的問題也是不一樣的。總結區分一下:
- 交叉編譯,解決的是目標程序問題, 是目的。
- 條件編譯,解決的是代碼適配問題, 是過程。
交叉編譯
交叉編譯解決目標程序問題,即在一臺具體的系統環境下編譯出不同系統或不同語言環境的目標程序。舉個簡單的例子,在一臺任意操作系統的機器上,編譯出不同系統的目標程序:
# 以平臺 linux 作爲目標編譯平臺
$: GOOS=linux go build
# 以平臺 darwin 作爲目標編譯平臺
$: GOOS=darwin go build
# 以平臺 windows 作爲目標編譯平臺
$: GOOS=windows go build
當然交叉編譯不僅僅是可以根據目的操作系統進行編譯,還可以區分系統的CPU架構類型,以及不同的語言版本,甚至可以指定用戶自定義的編譯標籤,按自定義的方式編譯目標程序。
更復雜的交叉編譯參數如下:
$: GOOS=${GOOS} GOARCH=${GOARCH} go build -tags ...
交叉編譯程序是否是按照預期進行編譯,可以通過go list
命令進行驗證。不防先熟悉下該命令:
# linux
$: GOOS=linux go list -f '{{.GoFiles}}' os/exec
[exec.go exec_unix.go lp_unix.go]
# darwin
$: GOOS=darwin go list -f '{{.GoFiles}}' os/exec
[exec.go exec_unix.go lp_unix.go]
# windows
$: GOOS=windows go list -f '{{.GoFiles}}' os/exec
[exec.go exec_windows.go lp_windows.go]
可以看出,這個命令能夠快速回答我們按當前的目標平臺編譯時,編譯所需要的代碼文件。有了這個利器,就可以很方便的開始條件編譯的話題了。
條件編譯
條件編譯解決的是一份代碼在不同的編譯平臺以及不同的語言版本的兼容性問題,即一份代碼處處都可以編譯。Go 語言中的條件編譯的方式,可直接官方提供的文檔: Build Constraints.總結下來就是兩種方式:
文件名後綴方式
go build
在不讀取源文件的情況下可以通過文件名後綴以決定哪些文件參與編譯,哪些不需要。文件名後綴的形式,主要有:
_$GOOS.go
_$GOARCH.go
_$GOOS_$GOARCH.go
最後一種組合後綴,順序不能顛倒。
編譯標籤標註方式
更加靈活的條件編譯方式,是通過在文件頭增加條件編譯標籤。條件標籤在使用上需要和常規註釋進行區分。
條件編譯標籤的格式: // +build
前綴開始,條件編譯標籤必須和普通的註釋以及代碼通過空行分隔開來。否則,編譯器就無法認出。
條件編譯具體條件組合的規則,總結出來就是:
空格 ' ' = OR
逗號 ',' = AND
感嘆號 '!' = NOT
換行 = OR
官網例子:
// +build linux,386 darwin,!cgo
條件編譯組合結果是: (linux AND 386) OR (darwin AND (NOT cgo))
// +build linux darwin
// +build 386
條件編譯組合結果是:(linux OR darwin) AND 386
具體條件則可以有:
- 操作系統, 值可以通過
runtime.GOOS
獲取 - CPU架構, 值可以通過
runtime.GOARCH
獲取 - 編譯器,如
gc
,gccgo
- 是否開啓Cgo,
cgo
- 語言版本, Go版本如
go1.1
,...,go1.12
- 自定義標籤, 任意標籤,可以是發佈版本號,開發版本等等
現實問題
不論是交叉編譯
還是條件編譯
,終歸是爲了解決問題。交叉編譯
解決發佈問題,條件編譯
解決代碼問題。兩種都很重要,此節僅就代碼問題進行說明,即如何保證一份代碼在不同編譯條件下能夠通用。
其實這是一個編程領域的經典問題,即多態。既然是多態問題,就可以通過常說的OOP中的多態來實現, 即對多態進行抽象,再進行具體實現。
看一個例子pkg/profile:
在代碼實現中有兩個 trace 實現文件, 分別是: trace.go
與 trace16.go
. 其中用到了 runtime/trace
包,而該包是在 Go 1.7
版本中新引入的。所以作者定義了一個統一的抽象函數,具體實現則通過條件編譯,在不同的版本中提供具體的實現。對Go 1.7
以下的版本則採用空實現的方式。
貼一下代碼看看,trace.go
針對 Go 1.7
版本以及後續版本的實現:
// +build go1.7
package profile
import "runtime/trace"
var startTrace = trace.Start
var stopTrace = trace.Stop
再看看 trace16.go
的實現:
// +build !go1.7
package profile
import "io"
// mock trace support for Go 1.6 and earlier.
func startTrace(w io.Writer) error { return nil }
func stopTrace() {}
問題怎麼解決的,不用解釋了。