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