當我們編寫的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裡這種格式 '}'裡的佔位符,寫過http server程式的同學看到應該很熟悉)
使用格式化引數,我們能通過go list獲取將會被編譯的檔名
% go list -f '}' 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 '}' 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
exit status 1
% head headspin.go
// 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包編寫自己的條件編譯工具
使用跟蹤和除錯進行條件編譯
當在開發過程中除錯應用程式時,跟蹤和除錯輸出都會出現在 visual studio 的 輸出 視窗中。不過,若要在已部署的應用程式中包含跟蹤功能,則必須在啟用trace編譯器指令的情況下編譯已插入檢測點的應用程式。這樣就可以將跟蹤 編譯成應用程式的發布版本。如果未啟用trace指令,將在編譯過程中忽...
條件編譯使用
include define c 1 int main 例子1 通過命令列定義巨集 include define c 1 int main 編譯命令 gcc dc 1 test.c 問題 間接包含同乙個標頭檔案是否會產生編譯錯誤?條件編譯可以解決標頭檔案重複包含的編譯錯誤 例子2 產品線區分及除錯 ...
條件編譯使用分析
專題三 編譯預處理。包括以下章節 3 1.c include 如果沒有定義巨集常量c,可以通過命令 gcc dc 1 e 3 1.c o 3 1.i 來定義巨集常量c,方便除錯。define c 1 int main else return 0 3 1.i 1 3 1.c 1 1 命令列 1 3 1...