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