這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
當我們編寫的Go代碼依賴特定平台或者cpu架構的時候,我們需要給出不同的實現
C語言有前置處理器,可以通過宏或者#define包含特定平台指定的代碼進行編譯
但是Go沒有前置處理器,他是通過 go/build包 裡定義的tags和命名規範來讓Go的包可以管理不同平台的代碼
這篇文章將講述Go的條件編譯系統是如何?的,並且通過執行個體來說明如何使用
1. 預備知識:go list命令的使用
在講條件編譯之前需要瞭解go list的簡單用法
go list訪問源檔案裡那些能夠影響編譯進程內部的資料結構
go list與go build ,test,install大部分的參數相同,但是go list不會執行編譯操作。使用-f參數可以讓我們提供的text/template裡的代碼在包含go/build.Package內容相關的環境裡正確執行(就是讓go/build.Package裡的上下文去格式化 text/template裡這種格式 '{{.GoFiles}}'裡的預留位置,寫過http server程式的同學看到應該很熟悉)
使用格式化參數,我們能通過go list擷取將會被編譯的檔案名稱
- % go list -f '{{.GoFiles}}' os/exec
- [exec.go lp_unix.go]
上面這個例子裡我們用go list來查看在Linux/arm平台下 os/exec包裡有哪些檔案將會被編譯。
結果顯示:exec.go包含了通用的代碼在所有的平台下可用,lp_unix.go包含了*nix系統裡的exec.LookPath
在windows系統下運行同樣的命令,結果如下:
- C:\go> go list -f '{{.GoFiles}}' os/exec
- [exec.go lp_windows.go]
上面這個例子是Go 條件編譯系統的兩個部分,稱之為:編譯約束,下面將詳細描述
2. 第一種條件編譯的方法:編譯標籤
在原始碼裡添加標註,通常稱之為編譯標籤( build tag)
編譯標籤是在盡量靠近原始碼檔案頂部的地方用注釋的方式添加
go build在構建一個包的時候會讀取這個包裡的每個源檔案並且分析編譯便簽,這些標籤決定了這個源檔案是否參與本次編譯
編譯標籤添加的規則(附上原文):
1. a build tag is evaluated as the OR of space-separated options
2. each option evaluates as the AND of its comma-separated terms
3. each term is an alphanumeric word or, preceded by !, its negation
1). 編譯標籤由空格分隔的編譯選項(options)以"或"的邏輯關係組成
2). 每個編譯選項由逗號分隔的條件項以邏輯"與"的關係組成
3). 每個條件項的名字用字母+數字表示,在前面加!表示否定的意思
例子(編譯標籤要放在源檔案頂部)
- // +build darwin freebsd netbsd openbsd
這個將會讓這個源檔案只能在支援kqueue的BSD系統裡編譯
一個源檔案裡可以有多個編譯標籤,多個編譯標籤之間是邏輯"與"的關係
- // +build linux darwin
- // +build 386
這個將限制此源檔案只能在 linux/386或者darwin/386平台下編譯
關於注釋的說明
剛開始使用編譯標籤經常會犯下面這個錯誤
- // +build !linux
- package mypkg // wrong
這個例子裡的編譯標籤和包的聲明之間沒有用空行隔開,這樣編譯標籤會被當做包聲明的注釋而不是編譯標籤從而被忽略掉
下面這個是正確的標籤的書寫方式,標籤的結尾添加一個空行這樣標籤就不會當做其他聲明的注釋
- // +build !linux
-
- package mypkg // correct
用go vet命令也可以檢測到這個缺少空行的錯誤,初期可以用這個命令來避免缺少空行的錯誤
- % go vet mypkg
- mypkg.go:1: +build comment appears too late in file
- exit status 1
作為參考,下面的例子將licence聲明,編譯標籤和包聲明放在一起,請大家注意分辨
- % head headspin.go
- // Copyright 2013 Way out enterprises. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
-
- // +build someos someotheros thirdos,!amd64
-
- // Package headspin implements calculates numbers so large
- // they will make your head spin.
- package headspin
3. 第二種條件編譯方法:檔案尾碼
這個方法通過改變檔案名稱的尾碼來提供條件編譯,這種方案比編譯標籤要簡單,go/build可以在不讀取源檔案的情況下就可以決定哪些檔案不需要參與編譯
檔案命名規範可以在go/build 包裡找到詳細的說明,簡單來說如果你的源檔案包含尾碼:_$GOOS.go,那麼這個源檔案只會在這個平台下編譯,_$GOARCH.go也是如此。這兩個尾碼可以結合在一起使用,但是要注意順序:_$GOOS_$GOARCH.go, 不能反過來用:_$GOARCH_$GOOS.go
例子如下:
- mypkg_freebsd_arm.go // only builds on freebsd/arm systems
- mypkg_plan9.go // only builds on plan9
源檔案不能只提供條件編譯尾碼,還必須有檔案名稱:
- _linux.go
- _freebsd_386.go
這兩個源檔案在所有平台下都會被忽略掉,因為go/build將會忽略所有以底線或者點開頭的源檔案
4. 編譯標籤和檔案尾碼的選擇
編譯標籤和檔案尾碼的功能上有重疊,例如一個檔案名稱:mypkg_linux.go包含了// +build linux將會出現冗餘
通常情況下,如果源檔案與平台或者cpu架構完全符合,那麼用檔案尾碼,例如:
- mypkg_linux.go // only builds on linux systems
- mypkg_windows_amd64.go // only builds on windows 64bit platforms
相反,如果這個源檔案可以在超過一個平台或者超過一個cpu架構下可以使用或者需要去除指定平台,那麼使用編譯標籤,例如下面的編譯標籤可以在所有*nix平台上編譯:
- % grep '+build' $HOME/go/src/pkg/os/exec/lp_unix.go
- // +build darwin dragonfly freebsd linux netbsd openbsd
下面是可以在除了windows的所有平台下編譯
- % grep '+build' $HOME/go/src/pkg/os/types_notwin.go
- // +build !windows
5. 總結
這篇文章主要關注所有可以被go tool編譯的go源檔案,編譯標籤和檔案尾碼名(也包括了.c 和.s檔案)
Go的標準庫裡包含了很多的範例,特別是runtime,syscall,os和net包,讀者可以通過這些包來學習
Test檔案也支援編譯標籤和檔案尾碼條件編譯,並且作用方式與go源檔案相同。可以在不同平台下有條件的包含一些測試範例。同樣,標準庫也包含了大量的例子
最後,這篇檔案是講如何用go tool來達到條件編譯,但是條件編譯不限於go tool,你可以用go/build包編寫自己的條件編譯工具