github.com/FenixAra/go@v0.0.0-20170127160404-96ea0918e670/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 ignoreImports = true 140 141 if generateRunFlag != "" { 142 var err error 143 generateRunRE, err = regexp.Compile(generateRunFlag) 144 if err != nil { 145 log.Fatalf("generate: %s", err) 146 } 147 } 148 // Even if the arguments are .go files, this loop suffices. 149 for _, pkg := range packages(args) { 150 for _, file := range pkg.gofiles { 151 if !generate(pkg.Name, file) { 152 break 153 } 154 } 155 } 156 } 157 158 // generate runs the generation directives for a single file. 159 func generate(pkg, absFile string) bool { 160 fd, err := os.Open(absFile) 161 if err != nil { 162 log.Fatalf("generate: %s", err) 163 } 164 defer fd.Close() 165 g := &Generator{ 166 r: fd, 167 path: absFile, 168 pkg: pkg, 169 commands: make(map[string][]string), 170 } 171 return g.run() 172 } 173 174 // A Generator represents the state of a single Go source file 175 // being scanned for generator commands. 176 type Generator struct { 177 r io.Reader 178 path string // full rooted path name. 179 dir string // full rooted directory of file. 180 file string // base name of file. 181 pkg string 182 commands map[string][]string 183 lineNum int // current line number. 184 env []string 185 } 186 187 // run runs the generators in the current file. 188 func (g *Generator) run() (ok bool) { 189 // Processing below here calls g.errorf on failure, which does panic(stop). 190 // If we encounter an error, we abort the package. 191 defer func() { 192 e := recover() 193 if e != nil { 194 ok = false 195 if e != stop { 196 panic(e) 197 } 198 setExitStatus(1) 199 } 200 }() 201 g.dir, g.file = filepath.Split(g.path) 202 g.dir = filepath.Clean(g.dir) // No final separator please. 203 if buildV { 204 fmt.Fprintf(os.Stderr, "%s\n", shortPath(g.path)) 205 } 206 207 // Scan for lines that start "//go:generate". 208 // Can't use bufio.Scanner because it can't handle long lines, 209 // which are likely to appear when using generate. 210 input := bufio.NewReader(g.r) 211 var err error 212 // One line per loop. 213 for { 214 g.lineNum++ // 1-indexed. 215 var buf []byte 216 buf, err = input.ReadSlice('\n') 217 if err == bufio.ErrBufferFull { 218 // Line too long - consume and ignore. 219 if isGoGenerate(buf) { 220 g.errorf("directive too long") 221 } 222 for err == bufio.ErrBufferFull { 223 _, err = input.ReadSlice('\n') 224 } 225 if err != nil { 226 break 227 } 228 continue 229 } 230 231 if err != nil { 232 // Check for marker at EOF without final \n. 233 if err == io.EOF && isGoGenerate(buf) { 234 err = io.ErrUnexpectedEOF 235 } 236 break 237 } 238 239 if !isGoGenerate(buf) { 240 continue 241 } 242 if generateRunFlag != "" { 243 if !generateRunRE.Match(bytes.TrimSpace(buf)) { 244 continue 245 } 246 } 247 248 g.setEnv() 249 words := g.split(string(buf)) 250 if len(words) == 0 { 251 g.errorf("no arguments to directive") 252 } 253 if words[0] == "-command" { 254 g.setShorthand(words) 255 continue 256 } 257 // Run the command line. 258 if buildN || buildX { 259 fmt.Fprintf(os.Stderr, "%s\n", strings.Join(words, " ")) 260 } 261 if buildN { 262 continue 263 } 264 g.exec(words) 265 } 266 if err != nil && err != io.EOF { 267 g.errorf("error reading %s: %s", shortPath(g.path), err) 268 } 269 return true 270 } 271 272 func isGoGenerate(buf []byte) bool { 273 return bytes.HasPrefix(buf, []byte("//go:generate ")) || bytes.HasPrefix(buf, []byte("//go:generate\t")) 274 } 275 276 // setEnv sets the extra environment variables used when executing a 277 // single go:generate command. 278 func (g *Generator) setEnv() { 279 g.env = []string{ 280 "GOARCH=" + buildContext.GOARCH, 281 "GOOS=" + buildContext.GOOS, 282 "GOFILE=" + g.file, 283 "GOLINE=" + strconv.Itoa(g.lineNum), 284 "GOPACKAGE=" + g.pkg, 285 "DOLLAR=" + "$", 286 } 287 } 288 289 // split breaks the line into words, evaluating quoted 290 // strings and evaluating environment variables. 291 // The initial //go:generate element is present in line. 292 func (g *Generator) split(line string) []string { 293 // Parse line, obeying quoted strings. 294 var words []string 295 line = line[len("//go:generate ") : len(line)-1] // Drop preamble and final newline. 296 // There may still be a carriage return. 297 if len(line) > 0 && line[len(line)-1] == '\r' { 298 line = line[:len(line)-1] 299 } 300 // One (possibly quoted) word per iteration. 301 Words: 302 for { 303 line = strings.TrimLeft(line, " \t") 304 if len(line) == 0 { 305 break 306 } 307 if line[0] == '"' { 308 for i := 1; i < len(line); i++ { 309 c := line[i] // Only looking for ASCII so this is OK. 310 switch c { 311 case '\\': 312 if i+1 == len(line) { 313 g.errorf("bad backslash") 314 } 315 i++ // Absorb next byte (If it's a multibyte we'll get an error in Unquote). 316 case '"': 317 word, err := strconv.Unquote(line[0 : i+1]) 318 if err != nil { 319 g.errorf("bad quoted string") 320 } 321 words = append(words, word) 322 line = line[i+1:] 323 // Check the next character is space or end of line. 324 if len(line) > 0 && line[0] != ' ' && line[0] != '\t' { 325 g.errorf("expect space after quoted argument") 326 } 327 continue Words 328 } 329 } 330 g.errorf("mismatched quoted string") 331 } 332 i := strings.IndexAny(line, " \t") 333 if i < 0 { 334 i = len(line) 335 } 336 words = append(words, line[0:i]) 337 line = line[i:] 338 } 339 // Substitute command if required. 340 if len(words) > 0 && g.commands[words[0]] != nil { 341 // Replace 0th word by command substitution. 342 words = append(g.commands[words[0]], words[1:]...) 343 } 344 // Substitute environment variables. 345 for i, word := range words { 346 words[i] = os.Expand(word, g.expandVar) 347 } 348 return words 349 } 350 351 var stop = fmt.Errorf("error in generation") 352 353 // errorf logs an error message prefixed with the file and line number. 354 // It then exits the program (with exit status 1) because generation stops 355 // at the first error. 356 func (g *Generator) errorf(format string, args ...interface{}) { 357 fmt.Fprintf(os.Stderr, "%s:%d: %s\n", shortPath(g.path), g.lineNum, 358 fmt.Sprintf(format, args...)) 359 panic(stop) 360 } 361 362 // expandVar expands the $XXX invocation in word. It is called 363 // by os.Expand. 364 func (g *Generator) expandVar(word string) string { 365 w := word + "=" 366 for _, e := range g.env { 367 if strings.HasPrefix(e, w) { 368 return e[len(w):] 369 } 370 } 371 return os.Getenv(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 cmd.Env = mergeEnvLists(g.env, origEnv) 397 err := cmd.Run() 398 if err != nil { 399 g.errorf("running %q: %s", words[0], err) 400 } 401 }