golang 交叉編譯和條件編譯的實際應用

什麼是交叉編譯

先給出_維基百科_和_百度百科_解釋(tips: 維基百科只有交叉編譯器的解釋)

維基百科

交叉編譯器(英語:Cross compiler)是指一個在某個系統平臺下可以產生另一個系統平臺的可執行文件的編譯器。交叉編譯器在目標系統平臺(開發出來的應用程序序所運行的平臺)難以或不容易編譯時非常有用。

百度百科

交叉編譯是在一個平臺上生成另一個平臺上的可執行代碼。同一個體系結構可以運行不同的操作系統;同樣,同一個操作系統也可以在不同的體系結構上運行。

雖然維基百科上只有交叉編譯器的解釋,但是結合百度百科來看,其實意思是一致的,就是可以在 A 平臺編譯出 B 或者 C 平臺的可執行程序

所以交叉編譯是在當前基礎平臺(開發者使用的環境)編譯出在分發平臺(運行環境)能運行的程序的解決方案。

有時候我們在開發一個項目的時候,目標平臺資源並沒有準備好,比如在 windows 開發,但是運行平臺是 linux,而 linux 服務器還沒購買,或者 linux 是並不允許安裝編譯器等等,這時候我們需要在開發機編譯出目標機的運行程序,那麼交叉編譯將變得非常有用。

什麼是條件編譯

維基百科

百度百科

條件編譯允許只編譯源文件中滿足條件的程序 段,使生成的目標程序較短,從而減少了內存的開銷,並提高程序的效率,可以按不同的 條件去編譯不同的程序部分,因而產生不同的目標代碼文件。這對於程序的移植和調試是很有用的。 [2] 另外,條件編譯是爲了讓程序在各種不同的軟硬件環境下都以運行。即,提高了程序的可移植性和靈活性。

所謂的條件編譯,就是在指定的條件下編譯滿足條件的源碼文件或者代碼段。以達到適配指定的運行環境。實際使用中可以用於調試或者發佈(區分開發調試環境和線上部署環境)。

golang 中使用交叉編譯

我們知道 golang 一份代碼可以編譯出在不同系統和 cpu 架構運行的二進制文件。go 也提供了很多環境變量,我們可以設置環境變量的值,來編譯不同目標平臺。

GOOS 目標平臺, GOARCH 目標架構

# 編譯目標平臺linux 64位
GOOS=linux GOARCH=amd64 go build main.go

# 編譯目標平臺windows 64位
GOOS=windows GOARCH=amd64 go build main.go

常用的 GOOS 和 GOARCH

golang 使用條件編譯

golang 中有兩種使用條件編譯的方式,一種是通過文件名的 命名規則,另一種則是註釋,一種特別的 標籤註釋,通過這種註釋,golang 編譯器可以在編譯時識別要編譯的文件或者代碼段。

1、通過**_命名規則_**

* _GOOS
* _GOARCH
* _GOOS_GOARCH
(示例:source_windows_amd64.go),其中GOOS和GOARCH分別代表任何已知的操作系統和體系結構值(也就是環境變量GOOS和GOARCH的值),符合命名規則的文件會按照隱式約束構建。

注意下命名的順序。__GOOS_GOARCH 是可以的,但是__GOARCH_GOOS 不行,也就是說 GOOS 必須在 GOARCH 之前。

比如我們自定義 config_linux_amd64.go 那麼就會在 linux 平臺,64 位架構的 cpu 下編譯。

2、標籤註釋 條件編譯,標籤註釋格式以 // +build 開頭,比如官網例子:

// +build linux,cgo darwin,cgo

注意編譯標籤註釋 如果不是寫在源碼文件的第一行的話,需要上下空一行,與正常的註釋和代碼隔開,不然的話,編譯器會忽略,無法識別。

編譯標籤註釋之間也會有邏輯運算,對應關係如下

按照官網的例子來說明下

// +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 獲取,比如 linux
CPU 架構, 值可以通過 runtime.GOARCH 獲取 , 比如 amd64 編譯器,如 gc, gccgo
是否開啓 Cgo, cgo
語言版本, Go 版本如 go1.1,...,go1.12
自定義標籤, 任意標籤,可以是發佈版本號,開發版本等等, 比如生產環境 prod

實際應用

在實際開發中,一般正常的商業項目都會區分_開發環境_、_測試環境_、_灰度驗證環境_、_正式環境_。那麼這麼多環境,**數據源,redis,日誌級別**等的配置一般也不一樣。如何保證在不同的環境使用不同的配置呢。

1、 啓動時 指定配置文件

編譯後的可執行文件在目標環境執行時,可以通過指定參數的方式來確定執行環境,讀取的配置文件。

# 假設編譯後的執行文件名爲 server, $exec_path爲可執行文件所在路徑,比如/usr/local
# 開發調試環境
$exec_path/server debug
# 線上正式環境
$exec_path/server prod

如果使用這種方式就需要在代碼裏判斷傳遞的參數,然後使用對應的配置。編寫文件 server.go 如下

package main

import (
    "fmt"
    "os"
)

func main()  {
    serverMode := os.Args[1]
    switch serverMode {
    case "debug":
        // 加載調試模式配置
        fmt.Printf("傳遞模式爲%s,加載調試模式配置\r\n", serverMode)
    case "prod":
        // 加載正式環境配置
        fmt.Printf("傳遞模式爲%s,加載正式環境配置\r\n", serverMode)
    default:
        panic("啓動模式錯誤")
    }
}

編譯啓動

# 編譯
go build server.go 
# 執行
./server debug
#輸出如下
傳遞模式爲debug,加載調試模式配置

2、 通過 環境變量

# 設置環境變量(linux環境)
export SERVER_MODE='debug'

# 在代碼裏讀取變量,然後可以按照讀取的值加載不同的配置。代碼處理與1中傳遞啓動參數類似。
os.Getenv("SERVER_MODE")

3、 編譯時 使用 ldflags

編寫 config.go 內容如下

package main

import "fmt"

var mode string

func main()  {
    fmt.Println("mode value is:", mode)
}

編譯運行

# 編譯
go build -ldflags '-X main.mode=prod' config.go

# 運行
./config

# 輸出如下
mode value is: prod

既然編譯時可以確定 mode 的值,那麼想要根據 mode 加載不同的配置,那麼就輕而易舉的解決了。

4、 使用條件編譯 分別編寫 config_prod.go 和 config_dev.go 分別代表生產環境和開發環境的配置。 項目佈局如下

.
├── config
│   ├── config_dev.go
│   └── config_prod.go
└── main.go

config_prod.go

//+build prod

package config

var String = "this is prod mode"
var String2 = "prod test"

func Config() string {
    return String2
}

config_dev.go

//+build dev

package config

var String1 = "this is debug mode"
var String2 = "debug test"

func Config() string {
    return String2
}

main.go

package main

import (
    "config"
    "fmt"
)

func main()  {
    fmt.Println("this build is: ", config.String1, config.String2, config.Config())
}

編譯運行

# 編譯(-o 指定編譯後生成的可執行文件名)
go build -tags prod -o main

# 運行
 ./main 

# 輸出結果
this build is:  this is prod mode prod test prod test

可見使用這種方式也達到了不同環境使用不同配置的目的。實際項目開發中,根據項目規劃需要來選擇。

Tips: 在 goland 中添加自定義的編譯標籤時,會提示如下錯誤:

並且在 main.go 裏面識別不到 config 裏面的內容:

這是因爲自定義編譯標籤 goland 是無法識別的。只需要點擊右上角的 Edit Go Project Settings 填入自定義標籤即可。

填入當前使用的標籤

總結

上面簡單介紹了交叉編譯和條件編譯,以及在實際項目中如何來區分環境,可以通過 4 種不同的方式來達到我們的目的,有更好方式歡迎交流和補充。

參考文檔

go build 文檔

歡迎關注公衆號:xmgtony 分享一個生物專業程序員心路歷程。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://zhuanlan.zhihu.com/p/92235251