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