In Go, there is an unwritten habit that many people like to use generated code, for example, the directory structure of the project, the stub code of grpc are generated by tools, and small ones such as static files embedded in the code, automatically generated enum type String form, etc. Anyway, the pattern of generated code can always be seen.
In fact, this may be related to the Go official is also to promote this way, for example, Go from version 1.4 onwards go generate function, although I started to write the draft of this article more than 2 years ago, but so long actually not how seriously to understand this function. Recently, I’ve been trying to use this feature, so I’ve really gotten to know go genreate, and in this article I’ll try to summarize what go generate is all about.
How it works
The way go generate works is when you type go generate ./...
, go looks in your current directory for places that contain the //go:generate
comment, which is usually followed by a command, such as this comment.
|
|
It’s actually the same as if you run it in this directory: mygentool arg1 arg2 -on
, but the difference is that you don’t get any additional metadata if you run it locally, but if you run it through go generate
, go will add some additional properties to you by default, which can be verified with this program (note that the generate tool needs to be in your PATH directory, if it’s in the current directory, don’t forget to add the current directory to your PATH environment variable).
|
|
As you can see, Go passes in a lot of environment variables for us by default, such as what file this comment is in, what line it’s on, what the package name is, and then what directory you’re executing the go generate
command from. With these parameters, we can do a lot of interesting things.
stringer
Now take a look at an example mentioned in the official Go blog: Go Generate, an example of a stringer, which is actually a method that adds a string to the enum type.
Then execute the command.
You can see that the current directory will have one more file: pill_string.go , then try to test it.
You can find that the enum type has a String
method, and the return value of this method is the String value of the Enum.
By looking at the stringer code, we can see that stringer is an executable program that supports the following parameters.
|
|
Its implementation is to parse your file by ast (ast.Inspect(file.file, file.genDecl)
), then find the specified name, iterate over its values, then merge those values into an array, and finally construct the structure of stringer.
yacc
Finally, an advanced use is to call yacc to automatically generate code via go generate. This is actually a metaprogramming idea, where we specify a metadata and then create code from that metadata (e.g. create a struct, which then automatically generates a lot of built-in methods, somewhat similar to proto -> go code, but more advanced and feature-rich).
I’m not familiar with yacc, I just know it’s a compilation tool, and I don’t usually know about it or use it. So here’s an introduction based on the official documentation, starting with installing the Go version of yacc.
|
|
Then edit your yacc file, for example I copied one from repo, and create the go file containing the go generate command.
Then run go generate . /...
command, and you find a local file calc.go
. I’m not familiar with yacc, but from the code it looks like a syntax rule defined by clac, which should be used to parse the syntax of a specific rule.
I won’t expand on this because I’m not good at it, but the function is still used in this way, without departing from the basic method of operation.
Sample code
All the code for this article can be found in this repo: https://github.com/liuliqiang/blog_codes/tree/master/golang/tools/generator
Reference
https://github.com/golang/tools/blob/master/cmd/stringer/stringer.go
https://eli.thegreenplace.net/2021/a-comprehensive-guide-to-go-generate/
https://liqiang.io/post/using-go-generate-with-go-a10df252