当前位置: 代码迷 >> 综合 >> go generate 的用法
  详细解决方案

go generate 的用法

热度:27   发布时间:2023-12-09 10:34:53.0

概述

golang tools 是官方提供的工具集,是 Gophers 的工具宝库,值得好好探索一番,参见 GitHub,文档地址。里面有丰富的开发辅助工具,所有的 Go 开发插件都离不开这些工具的支持。例如goimports工具自动导入使用的包,去掉未使用的包;gorename用来重命名标识符。

今天要使用的stringer工具也在tools工具包中。

命令描述如下:

go help generateusage: go generate [-run regexp] [-n] [-v] [-x] [build flags] [file.go... | packages]Generate runs commands described by directives within existing
files. Those commands can run any process but the intent is to
create or update Go source files.Go generate is never run automatically by go build, go get, go test,
and so on. It must be run explicitly.Go generate scans the file for directives, which are lines of
the form,//go:generate command argument...(note: no leading spaces and no space in "//go") where command
is the generator to be run, corresponding to an executable file
that can be run locally. It must either be in the shell path
(gofmt), a fully qualified path (/usr/you/bin/mytool), or a
command alias, described below....

总的说来 go generate 允许你在 go 代码中来运行当前系统中已安装的程序,原则上你可以运行任何程序,但是此命令设计的初衷是用来创建或者更新go源码文件。
如果要执行的程序不在环境变量中,则需要写全路径。
必须手动执行 go generate,才会去解析执行 generate 指令,并且 go generate 命令不会执行go源代码。
单个运行只需要在对应目录(包)下执行:go generate
对应有多个目录,且又有多个generate需要执行的,在最上层目录下运行:go generate ./…

一个简单的例子:

package mainimport "fmt"//go:generate echo hellofunc main() {
    fmt.Println("Hello world!")
}

在linux系统上运行 go generate
输出 hello
就相当于你直接运行 echo hello

go generate命令是go 1.4版本里面新添加的一个命令,当运行go generate时,它将扫描与当前包相关的源代码文件,找出所有包含"//go:generate"的特殊注释,提取并执行该特殊注释后面的命令,命令为可执行程序,形同shell下面执行。

使用场景

  1. 在build之前生成一些特定文件(下文介绍)。
  2. yacc:从 .y 文件生成 .go 文件。
  3. protobufs:从 protocol buffer 定义文件(.proto)生成 .pb.go 文件。
  4. Unicode:从 UnicodeData.txt 生成 Unicode 表。

注意事项

  1. 必须在.go源码文件中。
  2. 每个源码文件可以包含多个generate特殊注释。
  3. 显示运行go generate命令时,才会执行特殊注释后面的命令。
  4. 如果前面的注释执行出错,则终止执行。
  5. //与go:generate之间不能有空格。

可以使用的环境变量

$GOARCH:体系架构 (arm、amd64等)
$GOOS:OS环境(linux、windows等)
$GOFILE:当前处理中的文件名
$GOLINE:当前命令在文件中的行号
$GOPACKAGE:当前处理文件的包名
$DOLLAR:美元符号

在开发API Server的时候我们需要去定义一些错误码及其描述,每次定义错误码的时候,同时需要添加描述信息。而且描述信息经常会忘。比如:
errcode.go

package maintype ErrCode int//go:generate stringer -type ErrCode
const (ERR_CODE_OK             ErrCode = 0 // OKERR_CODE_INVALID_PARAMS ErrCode = 1 // 无效参数ERR_CODE_TIMEOUT        ErrCode = 2 // 超时
)func main() {
    fmt.Println(ERR_CODE_INVALID_PARAMS)
}

然后我想使用 go generate 来替我生成描述文件。

安装 stringer 程序:go get golang.org/x/tools/cmd/stringer

stringer命令可以为给定类型生成String方法。

stringer有两种模式,默认是根据变量/常量名来生成字符串描述。我们在常量定义上增加注释:

//go:generate stringer -type ErrCode

选项-type指定stringer命令作用的类型名。

然后在同一个目录下执行:
go generate

会在同一个目录下生成一个文件 errcode_string.go 文件名格式是类型名小写 _string.go。也可以通过 -output 选项指定输出文件名,例如下面就是指定输出文件名为 code_string.go

//go:generate stringer -type ErrCode -output code_string.go

我们来看看这个文件的内容:

// Code generated by "stringer -type ErrCode"; DO NOT EDIT.package mainimport "strconv"func _() {
    // An "invalid array index" compiler error signifies that the constant values have changed.// Re-run the stringer command to generate them again.var x [1]struct{
    }_ = x[ERR_CODE_OK-0]_ = x[ERR_CODE_INVALID_PARAMS-1]_ = x[ERR_CODE_TIMEOUT-2]
}const _ErrCode_name = "ERR_CODE_OKERR_CODE_INVALID_PARAMSERR_CODE_TIMEOUT"var _ErrCode_index = [...]uint8{
    0, 11, 34, 50}func (i ErrCode) String() string {
    if i < 0 || i >= ErrCode(len(_ErrCode_index)-1) {
    return "ErrCode(" + strconv.FormatInt(int64(i), 10) + ")"}return _ErrCode_name[_ErrCode_index[i]:_ErrCode_index[i+1]]
}

因为 fmt 包中的很多方法会自动优先调用 String() 方法,所以 fmt.Println(ERR_CODE_INVALID_PARAMS) 将会输出 1,但是我们想要的是后面的注释文本,修改指令
//go:generate stringer -type ErrCode -linecomment

调用 go generate 重新生成

// Code generated by "stringer -type ErrCode -linecomment"; DO NOT EDIT.package mainimport "strconv"func _() {
    // An "invalid array index" compiler error signifies that the constant values have changed.// Re-run the stringer command to generate them again.var x [1]struct{
    }_ = x[ERR_CODE_OK-0]_ = x[ERR_CODE_INVALID_PARAMS-1]_ = x[ERR_CODE_TIMEOUT-2]
}const _ErrCode_name = "OK无效参数超时"var _ErrCode_index = [...]uint8{
    0, 2, 14, 20}func (i ErrCode) String() string {
    if i < 0 || i >= ErrCode(len(_ErrCode_index)-1) {
    return "ErrCode(" + strconv.FormatInt(int64(i), 10) + ")"}return _ErrCode_name[_ErrCode_index[i]:_ErrCode_index[i+1]]
}

此时 fmt.Println(ERR_CODE_INVALID_PARAMS) 将会输出 无效参数,符合预期。

生成的代码做了一些优化,减少了字符串对象的数量。

现在你就不用担心添加了错误码而忘记添加错误信息了。

每次编译都需要先运行 go generate 貌似有点麻烦,可以写到 shell 文件。

  相关解决方案