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