...

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

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

View as plain text