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使得编写元编程工具更简单,更容易。