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