1// Copyright 2011 The Go Authors.  All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package main
6
7import (
8	"bufio"
9	"bytes"
10	"fmt"
11	"io"
12	"log"
13	"os"
14	"os/exec"
15	"path/filepath"
16	"regexp"
17	"runtime"
18	"strconv"
19	"strings"
20	"unicode"
21)
22
23var cmdGenerate = &Command{
24	Run:       runGenerate,
25	UsageLine: "generate [-run regexp] [file.go... | packages]",
26	Short:     "generate Go files by processing source",
27	Long: `
28Generate runs commands described by directives within existing
29files. Those commands can run any process but the intent is to
30create or update Go source files, for instance by running yacc.
31
32Go generate is never run automatically by go build, go get, go test,
33and so on. It must be run explicitly.
34
35Go generate scans the file for directives, which are lines of
36the form,
37
38	//go:generate command argument...
39
40(note: no leading spaces and no space in "//go") where command
41is the generator to be run, corresponding to an executable file
42that can be run locally. It must either be in the shell path
43(gofmt), a fully qualified path (/usr/you/bin/mytool), or a
44command alias, described below.
45
46Note that go generate does not parse the file, so lines that look
47like directives in comments or multiline strings will be treated
48as directives.
49
50The arguments to the directive are space-separated tokens or
51double-quoted strings passed to the generator as individual
52arguments when it is run.
53
54Quoted strings use Go syntax and are evaluated before execution; a
55quoted string appears as a single argument to the generator.
56
57Go generate sets several variables when it runs the generator:
58
59	$GOARCH
60		The execution architecture (arm, amd64, etc.)
61	$GOOS
62		The execution operating system (linux, windows, etc.)
63	$GOFILE
64		The base name of the file.
65	$GOLINE
66		The line number of the directive in the source file.
67	$GOPACKAGE
68		The name of the package of the file containing the directive.
69	$DOLLAR
70		A dollar sign.
71
72Other than variable substitution and quoted-string evaluation, no
73special processing such as "globbing" is performed on the command
74line.
75
76As a last step before running the command, any invocations of any
77environment variables with alphanumeric names, such as $GOFILE or
78$HOME, are expanded throughout the command line. The syntax for
79variable expansion is $NAME on all operating systems.  Due to the
80order of evaluation, variables are expanded even inside quoted
81strings. If the variable NAME is not set, $NAME expands to the
82empty string.
83
84A directive of the form,
85
86	//go:generate -command xxx args...
87
88specifies, for the remainder of this source file only, that the
89string xxx represents the command identified by the arguments. This
90can be used to create aliases or to handle multiword generators.
91For example,
92
93	//go:generate -command yacc go tool yacc
94
95specifies that the command "yacc" represents the generator
96"go tool yacc".
97
98Generate processes packages in the order given on the command line,
99one at a time. If the command line lists .go files, they are treated
100as a single package. Within a package, generate processes the
101source files in a package in file name order, one at a time. Within
102a source file, generate runs generators in the order they appear
103in the file, one at a time.
104
105If any generator returns an error exit status, "go generate" skips
106all further processing for that package.
107
108The generator is run in the package's source directory.
109
110Go generate accepts one specific flag:
111
112	-run=""
113		if non-empty, specifies a regular expression to select
114		directives whose full original source text (excluding
115		any trailing spaces and final newline) matches the
116		expression.
117
118It also accepts the standard build flags -v, -n, and -x.
119The -v flag prints the names of packages and files as they are
120processed.
121The -n flag prints commands that would be executed.
122The -x flag prints commands as they are executed.
123
124For more about specifying packages, see 'go help packages'.
125	`,
126}
127
128var (
129	generateRunFlag string         // generate -run flag
130	generateRunRE   *regexp.Regexp // compiled expression for -run
131)
132
133func init() {
134	addBuildFlags(cmdGenerate)
135	cmdGenerate.Flag.StringVar(&generateRunFlag, "run", "", "")
136}
137
138func runGenerate(cmd *Command, args []string) {
139	if generateRunFlag != "" {
140		var err error
141		generateRunRE, err = regexp.Compile(generateRunFlag)
142		if err != nil {
143			log.Fatalf("generate: %s", err)
144		}
145	}
146	// Even if the arguments are .go files, this loop suffices.
147	for _, pkg := range packages(args) {
148		for _, file := range pkg.gofiles {
149			if !generate(pkg.Name, file) {
150				break
151			}
152		}
153	}
154}
155
156// generate runs the generation directives for a single file.
157func generate(pkg, absFile string) bool {
158	fd, err := os.Open(absFile)
159	if err != nil {
160		log.Fatalf("generate: %s", err)
161	}
162	defer fd.Close()
163	g := &Generator{
164		r:        fd,
165		path:     absFile,
166		pkg:      pkg,
167		commands: make(map[string][]string),
168	}
169	return g.run()
170}
171
172// A Generator represents the state of a single Go source file
173// being scanned for generator commands.
174type Generator struct {
175	r        io.Reader
176	path     string // full rooted path name.
177	dir      string // full rooted directory of file.
178	file     string // base name of file.
179	pkg      string
180	commands map[string][]string
181	lineNum  int // current line number.
182}
183
184// run runs the generators in the current file.
185func (g *Generator) run() (ok bool) {
186	// Processing below here calls g.errorf on failure, which does panic(stop).
187	// If we encounter an error, we abort the package.
188	defer func() {
189		e := recover()
190		if e != nil {
191			ok = false
192			if e != stop {
193				panic(e)
194			}
195			setExitStatus(1)
196		}
197	}()
198	g.dir, g.file = filepath.Split(g.path)
199	g.dir = filepath.Clean(g.dir) // No final separator please.
200	if buildV {
201		fmt.Fprintf(os.Stderr, "%s\n", shortPath(g.path))
202	}
203
204	// Scan for lines that start "//go:generate".
205	// Can't use bufio.Scanner because it can't handle long lines,
206	// which are likely to appear when using generate.
207	input := bufio.NewReader(g.r)
208	var err error
209	// One line per loop.
210	for {
211		g.lineNum++ // 1-indexed.
212		var buf []byte
213		buf, err = input.ReadSlice('\n')
214		if err == bufio.ErrBufferFull {
215			// Line too long - consume and ignore.
216			if isGoGenerate(buf) {
217				g.errorf("directive too long")
218			}
219			for err == bufio.ErrBufferFull {
220				_, err = input.ReadSlice('\n')
221			}
222			if err != nil {
223				break
224			}
225			continue
226		}
227
228		if err != nil {
229			// Check for marker at EOF without final \n.
230			if err == io.EOF && isGoGenerate(buf) {
231				err = io.ErrUnexpectedEOF
232			}
233			break
234		}
235
236		if !isGoGenerate(buf) {
237			continue
238		}
239		if generateRunFlag != "" {
240			if !generateRunRE.Match(bytes.TrimSpace(buf)) {
241				continue
242			}
243		}
244
245		words := g.split(string(buf))
246		if len(words) == 0 {
247			g.errorf("no arguments to directive")
248		}
249		if words[0] == "-command" {
250			g.setShorthand(words)
251			continue
252		}
253		// Run the command line.
254		if buildN || buildX {
255			fmt.Fprintf(os.Stderr, "%s\n", strings.Join(words, " "))
256		}
257		if buildN {
258			continue
259		}
260		g.exec(words)
261	}
262	if err != nil && err != io.EOF {
263		g.errorf("error reading %s: %s", shortPath(g.path), err)
264	}
265	return true
266}
267
268func isGoGenerate(buf []byte) bool {
269	return bytes.HasPrefix(buf, []byte("//go:generate ")) || bytes.HasPrefix(buf, []byte("//go:generate\t"))
270}
271
272// split breaks the line into words, evaluating quoted
273// strings and evaluating environment variables.
274// The initial //go:generate element is present in line.
275func (g *Generator) split(line string) []string {
276	// Parse line, obeying quoted strings.
277	var words []string
278	line = line[len("//go:generate ") : len(line)-1] // Drop preamble and final newline.
279	// There may still be a carriage return.
280	if len(line) > 0 && line[len(line)-1] == '\r' {
281		line = line[:len(line)-1]
282	}
283	// One (possibly quoted) word per iteration.
284Words:
285	for {
286		line = strings.TrimLeft(line, " \t")
287		if len(line) == 0 {
288			break
289		}
290		if line[0] == '"' {
291			for i := 1; i < len(line); i++ {
292				c := line[i] // Only looking for ASCII so this is OK.
293				switch c {
294				case '\\':
295					if i+1 == len(line) {
296						g.errorf("bad backslash")
297					}
298					i++ // Absorb next byte (If it's a multibyte we'll get an error in Unquote).
299				case '"':
300					word, err := strconv.Unquote(line[0 : i+1])
301					if err != nil {
302						g.errorf("bad quoted string")
303					}
304					words = append(words, word)
305					line = line[i+1:]
306					// Check the next character is space or end of line.
307					if len(line) > 0 && line[0] != ' ' && line[0] != '\t' {
308						g.errorf("expect space after quoted argument")
309					}
310					continue Words
311				}
312			}
313			g.errorf("mismatched quoted string")
314		}
315		i := strings.IndexAny(line, " \t")
316		if i < 0 {
317			i = len(line)
318		}
319		words = append(words, line[0:i])
320		line = line[i:]
321	}
322	// Substitute command if required.
323	if len(words) > 0 && g.commands[words[0]] != nil {
324		// Replace 0th word by command substitution.
325		words = append(g.commands[words[0]], words[1:]...)
326	}
327	// Substitute environment variables.
328	for i, word := range words {
329		words[i] = os.Expand(word, g.expandVar)
330	}
331	return words
332}
333
334var stop = fmt.Errorf("error in generation")
335
336// errorf logs an error message prefixed with the file and line number.
337// It then exits the program (with exit status 1) because generation stops
338// at the first error.
339func (g *Generator) errorf(format string, args ...interface{}) {
340	fmt.Fprintf(os.Stderr, "%s:%d: %s\n", shortPath(g.path), g.lineNum,
341		fmt.Sprintf(format, args...))
342	panic(stop)
343}
344
345// expandVar expands the $XXX invocation in word. It is called
346// by os.Expand.
347func (g *Generator) expandVar(word string) string {
348	switch word {
349	case "GOARCH":
350		return buildContext.GOARCH
351	case "GOOS":
352		return buildContext.GOOS
353	case "GOFILE":
354		return g.file
355	case "GOLINE":
356		return fmt.Sprint(g.lineNum)
357	case "GOPACKAGE":
358		return g.pkg
359	case "DOLLAR":
360		return "$"
361	default:
362		return os.Getenv(word)
363	}
364}
365
366// identLength returns the length of the identifier beginning the string.
367func (g *Generator) identLength(word string) int {
368	for i, r := range word {
369		if r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) {
370			continue
371		}
372		return i
373	}
374	return len(word)
375}
376
377// setShorthand installs a new shorthand as defined by a -command directive.
378func (g *Generator) setShorthand(words []string) {
379	// Create command shorthand.
380	if len(words) == 1 {
381		g.errorf("no command specified for -command")
382	}
383	command := words[1]
384	if g.commands[command] != nil {
385		g.errorf("command %q defined multiply defined", command)
386	}
387	g.commands[command] = words[2:len(words):len(words)] // force later append to make copy
388}
389
390// exec runs the command specified by the argument. The first word is
391// the command name itself.
392func (g *Generator) exec(words []string) {
393	cmd := exec.Command(words[0], words[1:]...)
394	// Standard in and out of generator should be the usual.
395	cmd.Stdout = os.Stdout
396	cmd.Stderr = os.Stderr
397	// Run the command in the package directory.
398	cmd.Dir = g.dir
399	env := []string{
400		"GOARCH=" + runtime.GOARCH,
401		"GOOS=" + runtime.GOOS,
402		"GOFILE=" + g.file,
403		"GOPACKAGE=" + g.pkg,
404	}
405	cmd.Env = mergeEnvLists(env, origEnv)
406	err := cmd.Run()
407	if err != nil {
408		g.errorf("running %q: %s", words[0], err)
409	}
410}
411