golang交叉编译和条件编译的实际应用

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

注意编译标签注释 如果不是写在源码文件的第一行的话,需要上下空一行,与正常的注释和代码隔开,不然的话,编译器会忽略,无法识别。

编译标签注释之间也会有逻辑运算,对应关系如下

  • 空格 OR
  • 逗号 AND
  • 感叹号 NOT
  • 换行 AND

按照官网的例子来说明下

// +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 分享一个生物专业程序员心路历程。

编辑于 2019-11-19 11:20