go generate
命令是从Go1.4开始才设计的,用于在编译前自动化生成某类代码。go generate
和go build
是完全不一样的命令,通过分析源码中特殊的注释,然后执行相应的命令。
这些命令都是很明确的,没有任何的依赖在里面。这个go generate
是给源码开发人员用的,不是给使用这个包的人用的,是方便你来生成一些代码的。
有几点需要注意:
.go
源码文件中。generate
特殊注释时,显式运行go generate
命令时,才会执行特殊注释后面的命令。例如,使用yacc来生成代码,可能如下命令:
go tool yacc -o gopher.go -p parser gopher.y
-o
指定了输出的文件名,-p
指定了package
的名称,这是一个单独的命令,如果我们想用go generate
来触发这个命令,那么就可以在当前目录的任意一个xxx.go
文件里面的任意位置增加一行如下的注释:
//go:generate go tool yacc -o gopher.go -p parser gopher.y
//go:generate 是没有任何空格的,这其实就是一个固定的格式,在扫描源码文件的时候就是根据这个来判断的。
这时可以通过如下命令来生成、编译、测试。如果gopher.y
文件有修改,那么就重新执行go generate
重新生成文件就好。
go generate
go build
go test
在下面这些场景下,我们会使用go generate
命令(不完整列表):
goyacc
Go的Yacc。从.y
文件生成.go
文件stringer
实现fmt.Stringer枚举的接口。gostringer
fmt.GoStringer为枚举实现接口。jsonenums
枚举的实现json.Marshaler
和json.Unmarshaler
接口。go-syncmap
使用软件包作为的通用模板生成Go代码sync.Map
。go-syncpool
使用软件包作为的通用模板生成Go代码sync.Pool
。go-atomicvalue
使用软件包作为的通用模板生成Go代码atomic.Value
。go-nulljson
使用包作为实现database/sql.Scanner
和的通用模板生成Go代码database/sql/driver.Valuer
。go-enum
使用包作为实现接口的通用模板生成Go代码fmt.Stringer|binary|json|text|sql|yaml
枚举。go-import
执行非go文件的自动导入。gojson
从示例json文档生成go结构定义。vfsgen
生成静态实现给定虚拟文件系统的vfsdata.go
文件。goreuse
使用包作为通用模板通过替换定义来生成Go代码。embedfiles
将文件嵌入Go代码。ragel
状态机编译器peachpy
嵌入在Python中的x86-64汇编器,生成Go汇编bundle
Bundle创建适用于包含在特定目标软件包中的源软件包的单一源文件版本。msgp
MessagePack的Go代码生成器protobuf
从protocol buffer
定义文件.proto
生成 .pb.go
文件thriftrw
thriftgogen-avro
avroswagger-gen-types
从swagger定义中去生成代码avo
使用Go生成汇编代码Wire
Go的编译时依赖注入sumgen
从sum-type声明生成接口方法实现interface-extractor
生成所需类型的接口,仅在包内使用方法。deep-copy
为给定类型创建深度复制方法。Unicode
从UnicodeData.txt
生成Unicode
表HTML
将HTML
文件嵌入到Go源码binddata
将形如JPEG
这样的文件转成Go代码中的字节数组。ints
的sort.Ints
在hubble中存在大量的probuf,yacc,stringer自动生成,同时makefile文件中也有相关的构建脚本。
go generate
命令格式如下所示:
go generate [-run regexp] [-n] [-v] [-x] [command] [build flags] [file.go... | packages]
参数说明如下:
-run
正则表达式匹配命令行,仅执行匹配的命令;-v
输出被处理的包名和源文件名;-n
显示不执行命令;-x
显示并执行命令;command
可以是在环境变量 PATH 中的任何命令。执行go generate
命令时,也可以使用一些环境变量,如下所示:
$GOARCH
体系架构(arm、amd64 等);$GOOS
当前的 OS 环境(linux、windows 等);$GOFILE
当前处理中的文件名;$GOLINE
当前命令在文件中的行号;$GOPACKAGE
当前处理文件的包名;实现类似于shell脚本的功能。
示例
假设我们有一个main.go
文件,内容如下:
package main
import "fmt"
//go:generate go run main.go
//go:generate go version
func main() {
fmt.Println("Hello World!")
}
执行go generate -x
命令,输出结果如下:
go run main.go
Hello World!
go version
go version go1.13.4 linux/amd64
通过运行结果可以看出,相当于写了一个顺序执行go run main.go
、go version
的脚本。这里-x
参数是为了将执行的具体命令打印出来。
参照官方博客,例子在代码。
在定义枚举时,如果增加一个新的枚举类型,可能需要修改大量的方法,而使用了go:generate stringer
可以简化这个过程,再增加新枚举类型时可能只需要修改枚举定义即可。
这里介绍另一个例子,例如有一个错误定义:
const (
ERR_CODE_OK = 0 // OK
ERR_CODE_INVALID_PARAMS = 1 // invalid parameter
ERR_CODE_TIMEOUT = 2 // time out
// ...
)
如果需要将错误码和描述信息对应上,可能需要做:
var mapErrDesc = map[int]string {
ERR_CODE_OK: "OK",
ERR_CODE_INVALID_PARAMS: "invalid parameter",
ERR_CODE_TIMEOUT: "time out",
// ...
}
func GetDescription(errCode int) string {
if desc, exist := mapErrDesc[errCode]; exist {
return desc
}
return fmt.Sprintf("error code: %d", errCode)
}
这时每次添加新的错误码时除了要修改错误码时也需要修改map。
使用go generate方式,如果本地没有,首先安装stringer包。
git clone https://github.com/golang/tools/ $GOPATH/src/golang.org/x/tools
go install golang.org/x/tools/cmd/stringer
代码修改为:
type ErrCode int
//go:generate stringer -type ErrCode -linecomment
const (
ERR_CODE_OK ErrCode = 0 // OK
ERR_CODE_INVALID_PARAMS ErrCode = 1 // invalid parameter
ERR_CODE_TIMEOUT ErrCode = 2 // time out
// ...
)
执行go generate命令,这时会自动生成文件,包含一个string()方法,这时使用它就可以得到对应的错误描述了。再修改枚举时只需要修改枚举类型本身即可。
复杂例子可以参考hubble或者jsonenums。jsonenums是一个用于为枚举类型自动生成JSON编组样板代码的类库。在Go标准类库里面已经有大量可以用于解析AST的接口,而AST使得编写元编程工具更简单,更容易。