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