golang之条件编译

Go语言可以通过go/build包里定义的tags和命名约定来让Go的包可以运行不同的代码。

标签编译

在源代码里添加标注,通常称之为编译标签(build tag)。编译标签采用靠近源代码文件顶部用注释的方式添加。go build在构建一个包的时候会读取这个包里的每个源文件并且分析编译便签,这些标签决定了这个源文件是否参与本次编译。

  • 编译标签由空格分隔的编译选项(options)以”或”的逻辑关系组成(a build tag is evaluated as the OR of space-separated options)。
  • 每个编译选项由逗号分隔的条件项以逻辑”与”的关系组成( each option evaluates as the AND of its comma-separated terms)
  • 每个条件项的名字用字母+数字表示,在前面加!表示否定的意思(each term is an alphanumeric word or, preceded by !, its negation)

注:一个源文件可以有多个编译标签,多个编译标签之间是逻辑“与”的关系,一个编译标签可以包括由空格分割的多个标签,这些标签是逻辑“或”的关系。例子:

    // +build linux darwin  
    // +build 386  
    这个将限制此源文件只能在 linux/386或者darwin/386平台下编译

下边以json包的适配为例,介绍标签编译:

背景

Golang提供的标准json解析库——encoding/json,在开发高性能、高并发的网络服务时会产生性能问题。替代的方案是使用高性能的json解析库,比如json-iterator和easyjson。在正式引用高性能的json解析库(以json-iterator为例)通常的做法是小范围的进行测试,此时就会出现两个库并存的时候,解决方案是使用标签编译选择运行的解析库

统一的JSON库

现在我们需要两个库并存,所以我们先得统一这两个库的用法(参考适配器模式),这里我们使用一个自定义的json包来适配encoding/jsonjson-iterator

json/json.go

// +build !jsoniter
//必须留一行空格,否则会报重定义的错误
package json

import (
    "encoding/json"
    "fmt"
)

func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
    fmt.Println("Use [encoding/json] package")
    return json.MarshalIndent(v,prefix,indent)
}

json/jsoniter.go

// +build jsoniter

package json

import (
    "fmt"
    "github.com/json-iterator/go"
)

var (
    json = jsoniter.ConfigCompatibleWithStandardLibrary
)

func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
    fmt.Println("Use [jsoniter] package")
    return json.MarshalIndent(v,prefix,indent)
}

目录结构如下:

json
├── json.go
└── jsoniter.go

: 上述例子中如果编译标签包的声明 之间没有空行隔开,编译标签会被当做包声明的注释而不是编译标签,切记空行必须要有。

main函数

此处引用的json包下的两个go文件中都有MarshalIndent函数定义,并且签名一致,但它们又是使用不同的json解析库实现,此处相当于做了统一的适配包装,所以调用可以统一。

package main                                                                                                                        
import (    
    "fmt"   
    "pracitice/json_test/json"
)           

type user struct {
    Name   string
    Age int 
}           

func main() {
    u := user{"xiyan", 7}
    b, err := json.MarshalIndent(u, "", "")
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(string(b))
    }       

} 

直接运行go run main.go结果:

Use [encoding/json] package
{
"Name": "xiyan",
"Age": 7
}

使用标签编译运行go run -tags=jsoniter main.go

Use [jsoniter] package
{"Name":"xiyan","Age":7}

总结

标签编译的关键在于 -tags=jsoniter , -tags 这个标志,是Go语言为我们提供的条件编译方式之一。如果我们不是运行,而是编译构建的话,改为go build -tags=jsoniter 即可生成调用了对应包的可执行文件。

// +build !jsoniter
表示,tags不是jsoniter的时候编译这个Go文件。

// +build jsoniter
表示,tags是jsoniter的时候编译这个Go文件。

这两行是Go语言条件编译的关键。+build可以理解标签编译tags的声明关键字,后面跟着tags的条件。

文件后缀

使用这种方案比编译标签要简单,go/build可以在不读取源文件的情况下就可以决定哪些文件不需要参加编译。文件命名约定可以在go/build包里找到详细的说明,简单来说如果你的源文件包含后缀:_$GOOS.go,那么这个源文件只会在这个平台下编译, _$GOARCH.go也是如此。 这两个后缀可以结合在一起使用,顺序只能为:_$GOOS_$GOARCH.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将会忽略所有以下划线或者点开头的源文件

参考资料

  • Go语言中自动选择json解析库
  • 使用go build进行条件编译(译文)
  • How to use conditional compilation with the go build tool

你可能感兴趣的:(go语言)