前言
條件編譯 (conditional compilation) 是指針對不同平台 (platform) 在編譯時期選擇性地引入特定的程式碼,藉以封裝不同平台間的差異性,達到跨平台的效果。條件編譯最常見的情境是 C 或 C++ 的前置處理器 (preprocessor);雖然 Go 語言 (golang) 沒有 C 或 C++ 的前置處理器,但 Go 語言也可透過 build constraints 來達到類似的功能。
如果使用英文的相關關鍵字去查詢,可以查到幾個國外的部落客寫到 Go 語言的這項特性;但若使用中文的相關關鍵字去查詢,則幾乎清一色是來自對岸的簡體中文文章,代表這項特性算是相對冷門的。官方文件中關於 build constraints 的描述在 go/build 套件中,而本文會用中文介紹這項特性,讀者可以相互參照著看。
在 Go 語言中要達到條件編譯的特性,有兩種方式:
- 檔案後綴
- build tags
本文將分別說明。
檔案後綴
檔案後綴比較容易理解,就是在檔名中加入代表特定平台的後綴 (suffix);加入後綴的 Go 程式碼檔案只有在特定平台才會引入,在其他平台則會忽略。檔案後綴可限定作業系統 (operating systems) 或 CPU 架構 (CPU architecture),其語法如下:
- _GOOS
- _GOARCH
- _GOOS_GOARCH
以下是實例:
- mypkg_windows.go :只在 Windows 系統上引入
- mypkg_amd64.go :只在 Intel 相容的 64 位元架構下引入
- mypkg_linux_arm.go :只在 ARM 架構下的 GNU/Linux 系統上引入
一個實例是用檔案後綴來宣告字串行尾 (EOL, end of line)。以下是 const_windows.go 的內容:
package main
const LineBreak = "\r\n"
以下則是 const_linux.go 和 const_darwin.go (Mac 系統) 的內容:
package main
const LineBreak = "\n"
使用方式如下:
msg := fmt.Sprintf("Some message%s", LineBreak)
透過這個手法,Go 編譯器會在不同平台塞入相對應的字串行尾,藉此達到跨平台的效果。
國外有強者整理出 GOOS 和 GOARCH 的清單,甚至連 go/build 的原始碼都挖出來了,相當具有求知精神。
此外,可以用 go tool dist list
指令在終端機中顯示所有支援的平台和架構。
Build Tag
上一節介紹的方法相當簡單,但每個檔案只能用在單一平台或架構中,比較不靈活;因此,Go 語言引入 build tag,使用起來會比檔案後綴來得靈活一些。
Build tag 以註解 (comments) 的形式撰寫,一定要放在 Go 程式碼檔案的第一行,以下是實例:
// +build windows
package mylib
// More code here.
上述範例程式碼只在 Windows 平台下才會引入。我們接下來會省略其他部分,只留 build tag。
以下程式只會在 Intel 相容 64 位元架構下的 GNU/Linux 系統上引入:
// +build linux,amd64
Build tags 可以納入多個架構或平台,如下例:
// +build linux darwin
上述程式碼會在 GNU/Linux 和 Mac 平台上納入。
Build tag 可以多行敘述,如下例:
// +build linux darwin
// +build amd64
上述程式碼會在 Intel 相容 64 位元架構下的 GNU/Linux 和 Mac 平台上納入。
也可以使用否定敘述:
// +build !windows
上述程式碼會在 Windows 以外的平台上納入。
最後,我們用 build tag 改寫先前的例子。在 const_unix.go 中加入以下內容:
// +build linux darwin freebsd
package main
const LineBreak = "\n"
由於 _unix 不是真正的檔案後綴,剛好可以用來命名檔案。藉由使用 build tag,我們可以減少重複的程式碼。
後記
雖然檔案後綴和 build tag 只有一小部分的前置處理器的功能,但前置處理器缺乏型別安全且難以除錯,故 Go 語言未引入整個前置處理器。透過 build constraint,我們可以在 Go 語言中使用條件編譯。