github.com/yanyiwu/go@v0.0.0-20150106053140-03d6637dbb7f/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  		if non-empty, specifies a regular expression to
   110  		select directives whose command matches the expression.
   111  
   112  It also accepts the standard build flags -v, -n, and -x.
   113  The -v flag prints the names of packages and files as they are
   114  processed.
   115  The -n flag prints commands that would be executed.
   116  The -x flag prints commands as they are executed.
   117  
   118  For more about specifying packages, see 'go help packages'.
   119  	`,
   120  }
   121  
   122  var generateRunFlag string // generate -run flag
   123  
   124  func init() {
   125  	addBuildFlags(cmdGenerate)
   126  	cmdGenerate.Flag.StringVar(&generateRunFlag, "run", "", "")
   127  }
   128  
   129  func runGenerate(cmd *Command, args []string) {
   130  	// Even if the arguments are .go files, this loop suffices.
   131  	for _, pkg := range packages(args) {
   132  		for _, file := range pkg.gofiles {
   133  			if !generate(pkg.Name, file) {
   134  				break
   135  			}
   136  		}
   137  	}
   138  }
   139  
   140  // generate runs the generation directives for a single file.
   141  func generate(pkg, absFile string) bool {
   142  	fd, err := os.Open(absFile)
   143  	if err != nil {
   144  		log.Fatalf("generate: %s", err)
   145  	}
   146  	defer fd.Close()
   147  	g := &Generator{
   148  		r:        fd,
   149  		path:     absFile,
   150  		pkg:      pkg,
   151  		commands: make(map[string][]string),
   152  	}
   153  	return g.run()
   154  }
   155  
   156  // A Generator represents the state of a single Go source file
   157  // being scanned for generator commands.
   158  type Generator struct {
   159  	r        io.Reader
   160  	path     string // full rooted path name.
   161  	dir      string // full rooted directory of file.
   162  	file     string // base name of file.
   163  	pkg      string
   164  	commands map[string][]string
   165  	lineNum  int
   166  }
   167  
   168  // run runs the generators in the current file.
   169  func (g *Generator) run() (ok bool) {
   170  	// Processing below here calls g.errorf on failure, which does panic(stop).
   171  	// If we encounter an error, we abort the package.
   172  	defer func() {
   173  		e := recover()
   174  		if e != nil {
   175  			ok = false
   176  			if e != stop {
   177  				panic(e)
   178  			}
   179  			setExitStatus(1)
   180  		}
   181  	}()
   182  	g.dir, g.file = filepath.Split(g.path)
   183  	g.dir = filepath.Clean(g.dir) // No final separator please.
   184  	if buildV {
   185  		fmt.Fprintf(os.Stderr, "%s\n", shortPath(g.path))
   186  	}
   187  
   188  	// Scan for lines that start "//go:generate".
   189  	// Can't use bufio.Scanner because it can't handle long lines,
   190  	// which are likely to appear when using generate.
   191  	input := bufio.NewReader(g.r)
   192  	var err error
   193  	// One line per loop.
   194  	for {
   195  		g.lineNum++ // 1-indexed.
   196  		var buf []byte
   197  		buf, err = input.ReadSlice('\n')
   198  		if err == bufio.ErrBufferFull {
   199  			// Line too long - consume and ignore.
   200  			if isGoGenerate(buf) {
   201  				g.errorf("directive too long")
   202  			}
   203  			for err == bufio.ErrBufferFull {
   204  				_, err = input.ReadSlice('\n')
   205  			}
   206  			if err != nil {
   207  				break
   208  			}
   209  			continue
   210  		}
   211  
   212  		if err != nil {
   213  			// Check for marker at EOF without final \n.
   214  			if err == io.EOF && isGoGenerate(buf) {
   215  				err = io.ErrUnexpectedEOF
   216  			}
   217  			break
   218  		}
   219  
   220  		if !isGoGenerate(buf) {
   221  			continue
   222  		}
   223  
   224  		words := g.split(string(buf))
   225  		if len(words) == 0 {
   226  			g.errorf("no arguments to directive")
   227  		}
   228  		if words[0] == "-command" {
   229  			g.setShorthand(words)
   230  			continue
   231  		}
   232  		// Run the command line.
   233  		if buildN || buildX {
   234  			fmt.Fprintf(os.Stderr, "%s\n", strings.Join(words, " "))
   235  		}
   236  		if buildN {
   237  			continue
   238  		}
   239  		g.exec(words)
   240  	}
   241  	if err != nil && err != io.EOF {
   242  		g.errorf("error reading %s: %s", shortPath(g.path), err)
   243  	}
   244  	return true
   245  }
   246  
   247  func isGoGenerate(buf []byte) bool {
   248  	return bytes.HasPrefix(buf, []byte("//go:generate ")) || bytes.HasPrefix(buf, []byte("//go:generate\t"))
   249  }
   250  
   251  // split breaks the line into words, evaluating quoted
   252  // strings and evaluating environment variables.
   253  // The initial //go:generate element is present in line.
   254  func (g *Generator) split(line string) []string {
   255  	// Parse line, obeying quoted strings.
   256  	var words []string
   257  	line = line[len("//go:generate ") : len(line)-1] // Drop preamble and final newline.
   258  	// There may still be a carriage return.
   259  	if len(line) > 0 && line[len(line)-1] == '\r' {
   260  		line = line[:len(line)-1]
   261  	}
   262  	// One (possibly quoted) word per iteration.
   263  Words:
   264  	for {
   265  		line = strings.TrimLeft(line, " \t")
   266  		if len(line) == 0 {
   267  			break
   268  		}
   269  		if line[0] == '"' {
   270  			for i := 1; i < len(line); i++ {
   271  				c := line[i] // Only looking for ASCII so this is OK.
   272  				switch c {
   273  				case '\\':
   274  					if i+1 == len(line) {
   275  						g.errorf("bad backslash")
   276  					}
   277  					i++ // Absorb next byte (If it's a multibyte we'll get an error in Unquote).
   278  				case '"':
   279  					word, err := strconv.Unquote(line[0 : i+1])
   280  					if err != nil {
   281  						g.errorf("bad quoted string")
   282  					}
   283  					words = append(words, word)
   284  					line = line[i+1:]
   285  					// Check the next character is space or end of line.
   286  					if len(line) > 0 && line[0] != ' ' && line[0] != '\t' {
   287  						g.errorf("expect space after quoted argument")
   288  					}
   289  					continue Words
   290  				}
   291  			}
   292  			g.errorf("mismatched quoted string")
   293  		}
   294  		i := strings.IndexAny(line, " \t")
   295  		if i < 0 {
   296  			i = len(line)
   297  		}
   298  		words = append(words, line[0:i])
   299  		line = line[i:]
   300  	}
   301  	// Substitute command if required.
   302  	if len(words) > 0 && g.commands[words[0]] != nil {
   303  		// Replace 0th word by command substitution.
   304  		words = append(g.commands[words[0]], words[1:]...)
   305  	}
   306  	// Substitute environment variables.
   307  	for i, word := range words {
   308  		words[i] = g.expandEnv(word)
   309  	}
   310  	return words
   311  }
   312  
   313  var stop = fmt.Errorf("error in generation")
   314  
   315  // errorf logs an error message prefixed with the file and line number.
   316  // It then exits the program (with exit status 1) because generation stops
   317  // at the first error.
   318  func (g *Generator) errorf(format string, args ...interface{}) {
   319  	fmt.Fprintf(os.Stderr, "%s:%d: %s\n", shortPath(g.path), g.lineNum,
   320  		fmt.Sprintf(format, args...))
   321  	panic(stop)
   322  }
   323  
   324  // expandEnv expands any $XXX invocations in word.
   325  func (g *Generator) expandEnv(word string) string {
   326  	if !strings.ContainsRune(word, '$') {
   327  		return word
   328  	}
   329  	var buf bytes.Buffer
   330  	var w int
   331  	var r rune
   332  	for i := 0; i < len(word); i += w {
   333  		r, w = utf8.DecodeRuneInString(word[i:])
   334  		if r != '$' {
   335  			buf.WriteRune(r)
   336  			continue
   337  		}
   338  		w += g.identLength(word[i+w:])
   339  		envVar := word[i+1 : i+w]
   340  		var sub string
   341  		switch envVar {
   342  		case "GOARCH":
   343  			sub = runtime.GOARCH
   344  		case "GOOS":
   345  			sub = runtime.GOOS
   346  		case "GOFILE":
   347  			sub = g.file
   348  		case "GOPACKAGE":
   349  			sub = g.pkg
   350  		default:
   351  			sub = os.Getenv(envVar)
   352  		}
   353  		buf.WriteString(sub)
   354  	}
   355  	return buf.String()
   356  }
   357  
   358  // identLength returns the length of the identifier beginning the string.
   359  func (g *Generator) identLength(word string) int {
   360  	for i, r := range word {
   361  		if r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) {
   362  			continue
   363  		}
   364  		return i
   365  	}
   366  	return len(word)
   367  }
   368  
   369  // setShorthand installs a new shorthand as defined by a -command directive.
   370  func (g *Generator) setShorthand(words []string) {
   371  	// Create command shorthand.
   372  	if len(words) == 1 {
   373  		g.errorf("no command specified for -command")
   374  	}
   375  	command := words[1]
   376  	if g.commands[command] != nil {
   377  		g.errorf("command %q defined multiply defined", command)
   378  	}
   379  	g.commands[command] = words[2:len(words):len(words)] // force later append to make copy
   380  }
   381  
   382  // exec runs the command specified by the argument. The first word is
   383  // the command name itself.
   384  func (g *Generator) exec(words []string) {
   385  	cmd := exec.Command(words[0], words[1:]...)
   386  	// Standard in and out of generator should be the usual.
   387  	cmd.Stdout = os.Stdout
   388  	cmd.Stderr = os.Stderr
   389  	// Run the command in the package directory.
   390  	cmd.Dir = g.dir
   391  	env := []string{
   392  		"GOARCH=" + runtime.GOARCH,
   393  		"GOOS=" + runtime.GOOS,
   394  		"GOFILE=" + g.file,
   395  		"GOPACKAGE=" + g.pkg,
   396  	}
   397  	cmd.Env = mergeEnvLists(env, os.Environ())
   398  	err := cmd.Run()
   399  	if err != nil {
   400  		g.errorf("running %q: %s", words[0], err)
   401  	}
   402  }