diff options
| author | Ian Lance Taylor <iant@golang.org> | 2022-07-15 10:45:48 -0700 |
|---|---|---|
| committer | Ian Lance Taylor <iant@golang.org> | 2022-07-15 22:34:39 +0000 |
| commit | 6a2ebdf614176dec8332c735399b8117170ff435 (patch) | |
| tree | 660b36fb42e7a12f4f4c8643a04e0d180c762637 | |
| parent | 5aaf86f411be33d4996dec7d000284ffe60dbe5c (diff) | |
| download | go-x-proposal-6a2ebdf614176dec8332c735399b8117170ff435.tar.xz | |
design/go-generate: copy from original go.dev/s/go1.4-generate
A step toward gathering design docs in one place.
When this is submitted I will update the go.dev/s link.
Change-Id: Ib34d955fdcfad9ecded5ed323922e8eef7b49d90
Reviewed-on: https://go-review.googlesource.com/c/proposal/+/417642
Reviewed-by: Rob Pike <r@golang.org>
| -rw-r--r-- | design/go-generate.md | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/design/go-generate.md b/design/go-generate.md new file mode 100644 index 0000000..9129017 --- /dev/null +++ b/design/go-generate.md @@ -0,0 +1,342 @@ +# Go generate: A Proposal + +Author: Rob Pike + +Accepted in the Go 1.4 release. + +## Introduction + +The go build command automates the construction of Go programs but +sometimes preliminary processing is required, processing that go build +does not support. +Motivating examples include: + +- yacc: generating .go files from yacc grammar (.y) files +- protobufs: generating .pb.go files from protocol buffer definition (.proto) files +- Unicode: generating tables from UnicodeData.txt +- HTML: embedding .html files into Go source code +- bindata: translating binary files such as JPEGs into byte arrays in Go source + +There are other processing steps one can imagine: + +- string methods: generating String() string methods for types used as enumerated constants +- macros: generating customized implementations given generalized packages, such as sort.Ints from ints + +This proposal offers a design for smooth automation of such processing. + +## Non-goal + +It is not a goal of this proposal to build a generalized build system +like the Unix make(1) utility. +We deliberately avoid doing any dependency analysis. +The tool does what is asked of it, nothing more. + +It is hoped, however, that it may replace many existing uses of +make(1) in the Go repo at least. + +## Design + +There are two basic elements, a new subcommand for the go command, +called go generate, and directives inside Go source files that control +generation. + +When go generate runs, it scans Go source files looking for those +directives, and for each one executes a generator that typically +creates a new Go source file. +The go generate tool also sets the build tag "generate" so that files +may be examined by go generate but ignored during build. + +The usage is: + +``` +go generate [-run regexp] [file.go...|packagePath...] +``` + +(Plus the usual `-x`, `-n`, `-v` and `-tags` options.) +If packages are named, each Go source file in each package is scanned +for generator directives, and for each directive, the specified +generator is run; if files are named, they must be Go source files and +generation happens only for directives in those files. +Given no arguments, generator processing is applied to the Go source +files in the current directory. + +The `-run` flag takes a regular expression, analogous to that of the +go test subcommand, that restricts generation to those directives +whose command (see below) matches the regular expression. + +Generator directives may appear anywhere in the Go source file and are +processed sequentially (no parallelism) in source order as presented +to the tool. +Each directive is a // comment beginning a line, with syntax + +``` +//go:generate command arg... +``` + +where command is the generator (such as `yacc`) to be run, +corresponding to an executable file that can be run locally; it must +either be in the shell path (`gofmt`) or fully qualified +(`/usr/you/bin/mytool`) and is run in the package directory. + +The arguments are space-separated tokens (or double-quoted strings) +passed to the generator as individual arguments when it is run. +Shell-like variable expansion is available for any environment +variables such as `$HOME`. +Also, the special variable `$GOFILE` refers to the name of the file +containing the directive. +(We may need other special variables such as `$GOPACKAGE`. +When the generator is run, these are also provided in the shell +environment.) +No other special processing, such as globbing, is provided. + +No further generators are run if any generator returns an error exit +status. + +As an example, say we have a package `my/own/gopher` that includes a +yacc grammar in file `gopher.y`. +Inside `main.go` (not `gopher.y`) we place the directive + +``` +//go:generate yacc -o gopher.go gopher.y +``` + +(More about what `yacc` means in the next section.) +Whenever we need to update the generated file, we give the shell +command, + +``` +% go generate my/own/gopher +``` + +or, if we are already in the source directory, + +``` +% go generate +``` + +If we want to make sure that only the yacc generator is run, we +execute + +``` +% go generate -run yacc +``` + +If we have fixed a bug in yacc and want to update all yacc-generated +files in our tree, we can run + +``` +% go generate -run yacc all +``` + +The typical cycle for a package author developing software that uses +`go generate` is + +``` +% edit … +% go generate +% go test +``` + +and once things are settled, the author commits the generated files to +the source repository, so that they are available to clients that use +go get: + +``` +% git add *.go +% git commit +``` + +## Commands + +The yacc program is of course not the standard version, but is +accessed from the command line by + +``` +go tool yacc args... +``` + +To make it easy to use tools like yacc that are not installed in +$PATH, have complex access methods, or benefit from extra flags or +other wrapping, there is a special directive that defines a shorthand +for a command. +It is a `go:generate` directive followed by the keyword/flag +`-command` and which generator it defines; the rest of the line is +substituted for the command name when the generator is run. +Thus to define `yacc` as a generator command we access normally by +running `go tool yacc`, we first write the directive + +``` +//go:generate -command yacc go tool yacc +``` + +and then all other generator directives using `yacc` that follow in +that file (only) can be written as above: + +``` +//go:generate yacc -o gopher.go gopher.y +``` + +which will be translated to + +``` +go tool yacc -o gopher.go gopher.y +``` + +when run. + +## Discussion + +This design is unusual but is driven by several motivating principles. + +First, `go generate` is intended[^1] to be run by the author of a +package, not the client of it. +The author of the package generates the required Go files and includes +them in the package; the client does a regular `go get` or `go +build`. +Generation through `go generate` is not part of the build, just a tool +for package authors. +This avoids complicating the dependency analysis done by Go build. + +[^1]: One can imagine scenarios where the author wishes the client to +run the generator, but in such cases the author must guarantee that +the client has the generator available. +Regardless, `go get` will not automate the running of the processor, +so further installation instructions will need to be provided by the +author. + +Second, `go build` should never cause generation to happen +automatically by the client of the package. Generators should run only +when explicitly requested. + +Third, the author of the package should have great freedom in what +generator to use (that is a key goal of the proposal), but the client +might not have that processor available. +As a simple example, if it is a shell script, it will not run on +Windows. +It is important that automated generation not break clients but be +invisible to them, which is another reason it should be run only by +the author of the package. + +Finally, it must fit well with the existing go command, which means it +applies only to Go source files and packages. +This is why the directives are in Go files but not, for example, in +the .y file holding a yacc grammar. + +## Examples + +Here are some hypothetical worked examples. +There are countless more possibilities. + +### String methods + +We wish to generate a String method for a named constant type. +We write a tool, say `strmeth`, that reads a definition for a single +constant type and values and prints a complete Go source file +containing a method definition for that type. + +In our Go source file, `main.go`, we decorate each constant +declaration like this (with some blank lines interposed so the +generator directive does not appear in the doc comment): + +```Go +//go:generate strmeth Day -o day_string.go $GOFILE + +// Day represents the day of the week +type Day int +const ( + Sunday Day = iota + Monday + ... +) +``` + +The `strmeth` generator parses the Go source to find the definition of +the `Day` type and its constants, and writes out a `String() string` +method for that type. +For the user, generation of the string method is trivial: just run `go +generate`. + +### Yacc + +As outlined above, we define a custom command + +``` +//go:generate -command yacc go tool yacc +``` + +and then anywhere in main.go (say) we write + +``` +//go:generate yacc -o foo.go foo.y +``` + +### Protocol buffers + +The process is the same as with yacc. +Inside `main.go`, we write, for each protocol buffer file we have, a +line like + +``` +//go:generate protoc -go_out=. file.proto +``` + +Because of the way protoc works, we could generate multiple proto +definitions into a single `.pb.go` file like this: + +``` +//go:generate protoc -go_out=. file1.proto file2.proto +``` + +Since no globbing is provided, one cannot say `*.proto`, but this is +intentional, for simplicity and clarity of dependency. + +Caveat: The protoc program must be run at the root of the source tree; +we would need to provide a `-cd` option to it or wrap it somehow. + +### Binary data + +A tool that converts binary files into byte arrays that can be +compiled into Go binaries would work similarly. +Again, in the Go source we write something like + +``` +//go:generate bindata -o jpegs.go pic1.jpg pic2.jpg pic3.jpg +``` + +This is also demonstrates another reason the annotations are in Go +source: there is no easy way to inject them into binary files. + +### Sort + +One could imagine a variant sort implementation that allows one to +specify concrete types that have custom sorters, just by automatic +rewriting of macro-like sort definition. +To do this, we write a `sort.go` file that contains a complete +implementation of sort on an explicit but undefined type spelled, say, +`TYPE`. +In that file we provide a build tag so it is never compiled (`TYPE` is +not defined, so it won't compile) but is processed by `go generate`: + +``` +// +build generate +``` + +Then we write an generator directive for each type for which we want a +custom sort: + +``` +//go:generate rename TYPE=int +//go:generate rename TYPE=strings +``` + +or perhaps + +``` +//go:generate rename TYPE=int TYPE=strings +``` + +The rename processor would be a simple wrapping of `gofmt -r`, perhaps +written as a shell script. + +There are many more possibilities, and it is a goal of this proposal +to encourage experimentation with pre-build-time code generation. |
