github.com/letsencrypt/go@v0.0.0-20160714163537-4054769a31f6/src/cmd/go/generate.go (about)

     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, for instance by running yacc.
    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 yacc go tool yacc
    92  
    93  specifies that the command "yacc" represents the generator
    94  "go tool yacc".
    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  	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.
   157  func 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.
   174  type 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  	env      []string
   183  }
   184  
   185  // run runs the generators in the current file.
   186  func (g *Generator) run() (ok bool) {
   187  	// Processing below here calls g.errorf on failure, which does panic(stop).
   188  	// If we encounter an error, we abort the package.
   189  	defer func() {
   190  		e := recover()
   191  		if e != nil {
   192  			ok = false
   193  			if e != stop {
   194  				panic(e)
   195  			}
   196  			setExitStatus(1)
   197  		}
   198  	}()
   199  	g.dir, g.file = filepath.Split(g.path)
   200  	g.dir = filepath.Clean(g.dir) // No final separator please.
   201  	if buildV {
   202  		fmt.Fprintf(os.Stderr, "%s\n", shortPath(g.path))
   203  	}
   204  
   205  	// Scan for lines that start "//go:generate".
   206  	// Can't use bufio.Scanner because it can't handle long lines,
   207  	// which are likely to appear when using generate.
   208  	input := bufio.NewReader(g.r)
   209  	var err error
   210  	// One line per loop.
   211  	for {
   212  		g.lineNum++ // 1-indexed.
   213  		var buf []byte
   214  		buf, err = input.ReadSlice('\n')
   215  		if err == bufio.ErrBufferFull {
   216  			// Line too long - consume and ignore.
   217  			if isGoGenerate(buf) {
   218  				g.errorf("directive too long")
   219  			}
   220  			for err == bufio.ErrBufferFull {
   221  				_, err = input.ReadSlice('\n')
   222  			}
   223  			if err != nil {
   224  				break
   225  			}
   226  			continue
   227  		}
   228  
   229  		if err != nil {
   230  			// Check for marker at EOF without final \n.
   231  			if err == io.EOF && isGoGenerate(buf) {
   232  				err = io.ErrUnexpectedEOF
   233  			}
   234  			break
   235  		}
   236  
   237  		if !isGoGenerate(buf) {
   238  			continue
   239  		}
   240  		if generateRunFlag != "" {
   241  			if !generateRunRE.Match(bytes.TrimSpace(buf)) {
   242  				continue
   243  			}
   244  		}
   245  
   246  		g.setEnv()
   247  		words := g.split(string(buf))
   248  		if len(words) == 0 {
   249  			g.errorf("no arguments to directive")
   250  		}
   251  		if words[0] == "-command" {
   252  			g.setShorthand(words)
   253  			continue
   254  		}
   255  		// Run the command line.
   256  		if buildN || buildX {
   257  			fmt.Fprintf(os.Stderr, "%s\n", strings.Join(words, " "))
   258  		}
   259  		if buildN {
   260  			continue
   261  		}
   262  		g.exec(words)
   263  	}
   264  	if err != nil && err != io.EOF {
   265  		g.errorf("error reading %s: %s", shortPath(g.path), err)
   266  	}
   267  	return true
   268  }
   269  
   270  func isGoGenerate(buf []byte) bool {
   271  	return bytes.HasPrefix(buf, []byte("//go:generate ")) || bytes.HasPrefix(buf, []byte("//go:generate\t"))
   272  }
   273  
   274  // setEnv sets the extra environment variables used when executing a
   275  // single go:generate command.
   276  func (g *Generator) setEnv() {
   277  	g.env = []string{
   278  		"GOARCH=" + buildContext.GOARCH,
   279  		"GOOS=" + buildContext.GOOS,
   280  		"GOFILE=" + g.file,
   281  		"GOLINE=" + strconv.Itoa(g.lineNum),
   282  		"GOPACKAGE=" + g.pkg,
   283  		"DOLLAR=" + "$",
   284  	}
   285  }
   286  
   287  // split breaks the line into words, evaluating quoted
   288  // strings and evaluating environment variables.
   289  // The initial //go:generate element is present in line.
   290  func (g *Generator) split(line string) []string {
   291  	// Parse line, obeying quoted strings.
   292  	var words []string
   293  	line = line[len("//go:generate ") : len(line)-1] // Drop preamble and final newline.
   294  	// There may still be a carriage return.
   295  	if len(line) > 0 && line[len(line)-1] == '\r' {
   296  		line = line[:len(line)-1]
   297  	}
   298  	// One (possibly quoted) word per iteration.
   299  Words:
   300  	for {
   301  		line = strings.TrimLeft(line, " \t")
   302  		if len(line) == 0 {
   303  			break
   304  		}
   305  		if line[0] == '"' {
   306  			for i := 1; i < len(line); i++ {
   307  				c := line[i] // Only looking for ASCII so this is OK.
   308  				switch c {
   309  				case '\\':
   310  					if i+1 == len(line) {
   311  						g.errorf("bad backslash")
   312  					}
   313  					i++ // Absorb next byte (If it's a multibyte we'll get an error in Unquote).
   314  				case '"':
   315  					word, err := strconv.Unquote(line[0 : i+1])
   316  					if err != nil {
   317  						g.errorf("bad quoted string")
   318  					}
   319  					words = append(words, word)
   320  					line = line[i+1:]
   321  					// Check the next character is space or end of line.
   322  					if len(line) > 0 && line[0] != ' ' && line[0] != '\t' {
   323  						g.errorf("expect space after quoted argument")
   324  					}
   325  					continue Words
   326  				}
   327  			}
   328  			g.errorf("mismatched quoted string")
   329  		}
   330  		i := strings.IndexAny(line, " \t")
   331  		if i < 0 {
   332  			i = len(line)
   333  		}
   334  		words = append(words, line[0:i])
   335  		line = line[i:]
   336  	}
   337  	// Substitute command if required.
   338  	if len(words) > 0 && g.commands[words[0]] != nil {
   339  		// Replace 0th word by command substitution.
   340  		words = append(g.commands[words[0]], words[1:]...)
   341  	}
   342  	// Substitute environment variables.
   343  	for i, word := range words {
   344  		words[i] = os.Expand(word, g.expandVar)
   345  	}
   346  	return words
   347  }
   348  
   349  var stop = fmt.Errorf("error in generation")
   350  
   351  // errorf logs an error message prefixed with the file and line number.
   352  // It then exits the program (with exit status 1) because generation stops
   353  // at the first error.
   354  func (g *Generator) errorf(format string, args ...interface{}) {
   355  	fmt.Fprintf(os.Stderr, "%s:%d: %s\n", shortPath(g.path), g.lineNum,
   356  		fmt.Sprintf(format, args...))
   357  	panic(stop)
   358  }
   359  
   360  // expandVar expands the $XXX invocation in word. It is called
   361  // by os.Expand.
   362  func (g *Generator) expandVar(word string) string {
   363  	w := word + "="
   364  	for _, e := range g.env {
   365  		if strings.HasPrefix(e, w) {
   366  			return e[len(w):]
   367  		}
   368  	}
   369  	return os.Getenv(word)
   370  }
   371  
   372  // setShorthand installs a new shorthand as defined by a -command directive.
   373  func (g *Generator) setShorthand(words []string) {
   374  	// Create command shorthand.
   375  	if len(words) == 1 {
   376  		g.errorf("no command specified for -command")
   377  	}
   378  	command := words[1]
   379  	if g.commands[command] != nil {
   380  		g.errorf("command %q defined multiply defined", command)
   381  	}
   382  	g.commands[command] = words[2:len(words):len(words)] // force later append to make copy
   383  }
   384  
   385  // exec runs the command specified by the argument. The first word is
   386  // the command name itself.
   387  func (g *Generator) exec(words []string) {
   388  	cmd := exec.Command(words[0], words[1:]...)
   389  	// Standard in and out of generator should be the usual.
   390  	cmd.Stdout = os.Stdout
   391  	cmd.Stderr = os.Stderr
   392  	// Run the command in the package directory.
   393  	cmd.Dir = g.dir
   394  	cmd.Env = mergeEnvLists(g.env, origEnv)
   395  	err := cmd.Run()
   396  	if err != nil {
   397  		g.errorf("running %q: %s", words[0], err)
   398  	}
   399  }