...

Source file src/cmd/go/internal/generate/generate.go

Documentation: cmd/go/internal/generate

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

View as plain text