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