Source file src/pkg/cmd/go/internal/generate/generate.go
1
2
3
4
5
6 package generate
7
8 import (
9 "bufio"
10 "bytes"
11 "fmt"
12 "io"
13 "log"
14 "os"
15 "os/exec"
16 "path/filepath"
17 "regexp"
18 "strconv"
19 "strings"
20
21 "cmd/go/internal/base"
22 "cmd/go/internal/cfg"
23 "cmd/go/internal/load"
24 "cmd/go/internal/modload"
25 "cmd/go/internal/work"
26 )
27
28 var CmdGenerate = &base.Command{
29 Run: runGenerate,
30 UsageLine: "go generate [-run regexp] [-n] [-v] [-x] [build flags] [file.go... | packages]",
31 Short: "generate Go files by processing source",
32 Long: `
33 Generate runs commands described by directives within existing
34 files. Those commands can run any process but the intent is to
35 create or update Go source files.
36
37 Go generate is never run automatically by go build, go get, go test,
38 and so on. It must be run explicitly.
39
40 Go generate scans the file for directives, which are lines of
41 the form,
42
43 //go:generate command argument...
44
45 (note: no leading spaces and no space in "//go") where command
46 is the generator to be run, corresponding to an executable file
47 that can be run locally. It must either be in the shell path
48 (gofmt), a fully qualified path (/usr/you/bin/mytool), or a
49 command alias, described below.
50
51 To convey to humans and machine tools that code is generated,
52 generated source should have a line that matches the following
53 regular expression (in Go syntax):
54
55 ^// Code generated .* DO NOT EDIT\.$
56
57 The line may appear anywhere in the file, but is typically
58 placed near the beginning so it is easy to find.
59
60 Note that go generate does not parse the file, so lines that look
61 like directives in comments or multiline strings will be treated
62 as directives.
63
64 The arguments to the directive are space-separated tokens or
65 double-quoted strings passed to the generator as individual
66 arguments when it is run.
67
68 Quoted strings use Go syntax and are evaluated before execution; a
69 quoted string appears as a single argument to the generator.
70
71 Go generate sets several variables when it runs the generator:
72
73 $GOARCH
74 The execution architecture (arm, amd64, etc.)
75 $GOOS
76 The execution operating system (linux, windows, etc.)
77 $GOFILE
78 The base name of the file.
79 $GOLINE
80 The line number of the directive in the source file.
81 $GOPACKAGE
82 The name of the package of the file containing the directive.
83 $DOLLAR
84 A dollar sign.
85
86 Other than variable substitution and quoted-string evaluation, no
87 special processing such as "globbing" is performed on the command
88 line.
89
90 As a last step before running the command, any invocations of any
91 environment variables with alphanumeric names, such as $GOFILE or
92 $HOME, are expanded throughout the command line. The syntax for
93 variable expansion is $NAME on all operating systems. Due to the
94 order of evaluation, variables are expanded even inside quoted
95 strings. If the variable NAME is not set, $NAME expands to the
96 empty string.
97
98 A directive of the form,
99
100 //go:generate -command xxx args...
101
102 specifies, for the remainder of this source file only, that the
103 string xxx represents the command identified by the arguments. This
104 can be used to create aliases or to handle multiword generators.
105 For example,
106
107 //go:generate -command foo go tool foo
108
109 specifies that the command "foo" represents the generator
110 "go tool foo".
111
112 Generate processes packages in the order given on the command line,
113 one at a time. If the command line lists .go files from a single directory,
114 they are treated as a single package. Within a package, generate processes the
115 source files in a package in file name order, one at a time. Within
116 a source file, generate runs generators in the order they appear
117 in the file, one at a time. The go generate tool also sets the build
118 tag "generate" so that files may be examined by go generate but ignored
119 during build.
120
121 If any generator returns an error exit status, "go generate" skips
122 all further processing for that package.
123
124 The generator is run in the package's source directory.
125
126 Go generate accepts one specific flag:
127
128 -run=""
129 if non-empty, specifies a regular expression to select
130 directives whose full original source text (excluding
131 any trailing spaces and final newline) matches the
132 expression.
133
134 It also accepts the standard build flags including -v, -n, and -x.
135 The -v flag prints the names of packages and files as they are
136 processed.
137 The -n flag prints commands that would be executed.
138 The -x flag prints commands as they are executed.
139
140 For more about build flags, see 'go help build'.
141
142 For more about specifying packages, see 'go help packages'.
143 `,
144 }
145
146 var (
147 generateRunFlag string
148 generateRunRE *regexp.Regexp
149 )
150
151 func init() {
152 work.AddBuildFlags(CmdGenerate)
153 CmdGenerate.Flag.StringVar(&generateRunFlag, "run", "", "")
154 }
155
156 func runGenerate(cmd *base.Command, args []string) {
157 load.IgnoreImports = true
158
159 if generateRunFlag != "" {
160 var err error
161 generateRunRE, err = regexp.Compile(generateRunFlag)
162 if err != nil {
163 log.Fatalf("generate: %s", err)
164 }
165 }
166
167 cfg.BuildContext.BuildTags = append(cfg.BuildContext.BuildTags, "generate")
168
169
170 printed := false
171 for _, pkg := range load.Packages(args) {
172 if modload.Enabled() && pkg.Module != nil && !pkg.Module.Main {
173 if !printed {
174 fmt.Fprintf(os.Stderr, "go: not generating in packages in dependency modules\n")
175 printed = true
176 }
177 continue
178 }
179
180 pkgName := pkg.Name
181
182 for _, file := range pkg.InternalGoFiles() {
183 if !generate(pkgName, file) {
184 break
185 }
186 }
187
188 pkgName += "_test"
189
190 for _, file := range pkg.InternalXGoFiles() {
191 if !generate(pkgName, file) {
192 break
193 }
194 }
195 }
196 }
197
198
199 func generate(pkg, absFile string) bool {
200 fd, err := os.Open(absFile)
201 if err != nil {
202 log.Fatalf("generate: %s", err)
203 }
204 defer fd.Close()
205 g := &Generator{
206 r: fd,
207 path: absFile,
208 pkg: pkg,
209 commands: make(map[string][]string),
210 }
211 return g.run()
212 }
213
214
215
216 type Generator struct {
217 r io.Reader
218 path string
219 dir string
220 file string
221 pkg string
222 commands map[string][]string
223 lineNum int
224 env []string
225 }
226
227
228 func (g *Generator) run() (ok bool) {
229
230
231 defer func() {
232 e := recover()
233 if e != nil {
234 ok = false
235 if e != stop {
236 panic(e)
237 }
238 base.SetExitStatus(1)
239 }
240 }()
241 g.dir, g.file = filepath.Split(g.path)
242 g.dir = filepath.Clean(g.dir)
243 if cfg.BuildV {
244 fmt.Fprintf(os.Stderr, "%s\n", base.ShortPath(g.path))
245 }
246
247
248
249
250 input := bufio.NewReader(g.r)
251 var err error
252
253 for {
254 g.lineNum++
255 var buf []byte
256 buf, err = input.ReadSlice('\n')
257 if err == bufio.ErrBufferFull {
258
259 if isGoGenerate(buf) {
260 g.errorf("directive too long")
261 }
262 for err == bufio.ErrBufferFull {
263 _, err = input.ReadSlice('\n')
264 }
265 if err != nil {
266 break
267 }
268 continue
269 }
270
271 if err != nil {
272
273 if err == io.EOF && isGoGenerate(buf) {
274 err = io.ErrUnexpectedEOF
275 }
276 break
277 }
278
279 if !isGoGenerate(buf) {
280 continue
281 }
282 if generateRunFlag != "" {
283 if !generateRunRE.Match(bytes.TrimSpace(buf)) {
284 continue
285 }
286 }
287
288 g.setEnv()
289 words := g.split(string(buf))
290 if len(words) == 0 {
291 g.errorf("no arguments to directive")
292 }
293 if words[0] == "-command" {
294 g.setShorthand(words)
295 continue
296 }
297
298 if cfg.BuildN || cfg.BuildX {
299 fmt.Fprintf(os.Stderr, "%s\n", strings.Join(words, " "))
300 }
301 if cfg.BuildN {
302 continue
303 }
304 g.exec(words)
305 }
306 if err != nil && err != io.EOF {
307 g.errorf("error reading %s: %s", base.ShortPath(g.path), err)
308 }
309 return true
310 }
311
312 func isGoGenerate(buf []byte) bool {
313 return bytes.HasPrefix(buf, []byte("//go:generate ")) || bytes.HasPrefix(buf, []byte("//go:generate\t"))
314 }
315
316
317
318 func (g *Generator) setEnv() {
319 g.env = []string{
320 "GOARCH=" + cfg.BuildContext.GOARCH,
321 "GOOS=" + cfg.BuildContext.GOOS,
322 "GOFILE=" + g.file,
323 "GOLINE=" + strconv.Itoa(g.lineNum),
324 "GOPACKAGE=" + g.pkg,
325 "DOLLAR=" + "$",
326 }
327 }
328
329
330
331
332 func (g *Generator) split(line string) []string {
333
334 var words []string
335 line = line[len("//go:generate ") : len(line)-1]
336
337 if len(line) > 0 && line[len(line)-1] == '\r' {
338 line = line[:len(line)-1]
339 }
340
341 Words:
342 for {
343 line = strings.TrimLeft(line, " \t")
344 if len(line) == 0 {
345 break
346 }
347 if line[0] == '"' {
348 for i := 1; i < len(line); i++ {
349 c := line[i]
350 switch c {
351 case '\\':
352 if i+1 == len(line) {
353 g.errorf("bad backslash")
354 }
355 i++
356 case '"':
357 word, err := strconv.Unquote(line[0 : i+1])
358 if err != nil {
359 g.errorf("bad quoted string")
360 }
361 words = append(words, word)
362 line = line[i+1:]
363
364 if len(line) > 0 && line[0] != ' ' && line[0] != '\t' {
365 g.errorf("expect space after quoted argument")
366 }
367 continue Words
368 }
369 }
370 g.errorf("mismatched quoted string")
371 }
372 i := strings.IndexAny(line, " \t")
373 if i < 0 {
374 i = len(line)
375 }
376 words = append(words, line[0:i])
377 line = line[i:]
378 }
379
380 if len(words) > 0 && g.commands[words[0]] != nil {
381
382
383
384
385
386 tmpCmdWords := append([]string(nil), (g.commands[words[0]])...)
387 words = append(tmpCmdWords, words[1:]...)
388 }
389
390 for i, word := range words {
391 words[i] = os.Expand(word, g.expandVar)
392 }
393 return words
394 }
395
396 var stop = fmt.Errorf("error in generation")
397
398
399
400
401 func (g *Generator) errorf(format string, args ...interface{}) {
402 fmt.Fprintf(os.Stderr, "%s:%d: %s\n", base.ShortPath(g.path), g.lineNum,
403 fmt.Sprintf(format, args...))
404 panic(stop)
405 }
406
407
408
409 func (g *Generator) expandVar(word string) string {
410 w := word + "="
411 for _, e := range g.env {
412 if strings.HasPrefix(e, w) {
413 return e[len(w):]
414 }
415 }
416 return os.Getenv(word)
417 }
418
419
420 func (g *Generator) setShorthand(words []string) {
421
422 if len(words) == 1 {
423 g.errorf("no command specified for -command")
424 }
425 command := words[1]
426 if g.commands[command] != nil {
427 g.errorf("command %q multiply defined", command)
428 }
429 g.commands[command] = words[2:len(words):len(words)]
430 }
431
432
433
434 func (g *Generator) exec(words []string) {
435 cmd := exec.Command(words[0], words[1:]...)
436
437 cmd.Stdout = os.Stdout
438 cmd.Stderr = os.Stderr
439
440 cmd.Dir = g.dir
441 cmd.Env = append(cfg.OrigEnv, g.env...)
442 err := cmd.Run()
443 if err != nil {
444 g.errorf("running %q: %s", words[0], err)
445 }
446 }
447
View as plain text