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