github.com/mdempsky/go@v0.0.0-20151201204031-5dd372bd1e70/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 "runtime" 18 "strconv" 19 "strings" 20 "unicode" 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 $GOLINE 66 The line number of the directive in the source file. 67 $GOPACKAGE 68 The name of the package of the file containing the directive. 69 $DOLLAR 70 A dollar sign. 71 72 Other than variable substitution and quoted-string evaluation, no 73 special processing such as "globbing" is performed on the command 74 line. 75 76 As a last step before running the command, any invocations of any 77 environment variables with alphanumeric names, such as $GOFILE or 78 $HOME, are expanded throughout the command line. The syntax for 79 variable expansion is $NAME on all operating systems. Due to the 80 order of evaluation, variables are expanded even inside quoted 81 strings. If the variable NAME is not set, $NAME expands to the 82 empty string. 83 84 A directive of the form, 85 86 //go:generate -command xxx args... 87 88 specifies, for the remainder of this source file only, that the 89 string xxx represents the command identified by the arguments. This 90 can be used to create aliases or to handle multiword generators. 91 For example, 92 93 //go:generate -command yacc go tool yacc 94 95 specifies that the command "yacc" represents the generator 96 "go tool yacc". 97 98 Generate processes packages in the order given on the command line, 99 one at a time. If the command line lists .go files, they are treated 100 as a single package. Within a package, generate processes the 101 source files in a package in file name order, one at a time. Within 102 a source file, generate runs generators in the order they appear 103 in the file, one at a time. 104 105 If any generator returns an error exit status, "go generate" skips 106 all further processing for that package. 107 108 The generator is run in the package's source directory. 109 110 Go generate accepts one specific flag: 111 112 -run="" 113 if non-empty, specifies a regular expression to select 114 directives whose full original source text (excluding 115 any trailing spaces and final newline) matches the 116 expression. 117 118 It also accepts the standard build flags including -v, -n, and -x. 119 The -v flag prints the names of packages and files as they are 120 processed. 121 The -n flag prints commands that would be executed. 122 The -x flag prints commands as they are executed. 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=" + runtime.GOARCH, 279 "GOOS=" + runtime.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 // identLength returns the length of the identifier beginning the string. 373 func (g *Generator) identLength(word string) int { 374 for i, r := range word { 375 if r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) { 376 continue 377 } 378 return i 379 } 380 return len(word) 381 } 382 383 // setShorthand installs a new shorthand as defined by a -command directive. 384 func (g *Generator) setShorthand(words []string) { 385 // Create command shorthand. 386 if len(words) == 1 { 387 g.errorf("no command specified for -command") 388 } 389 command := words[1] 390 if g.commands[command] != nil { 391 g.errorf("command %q defined multiply defined", command) 392 } 393 g.commands[command] = words[2:len(words):len(words)] // force later append to make copy 394 } 395 396 // exec runs the command specified by the argument. The first word is 397 // the command name itself. 398 func (g *Generator) exec(words []string) { 399 cmd := exec.Command(words[0], words[1:]...) 400 // Standard in and out of generator should be the usual. 401 cmd.Stdout = os.Stdout 402 cmd.Stderr = os.Stderr 403 // Run the command in the package directory. 404 cmd.Dir = g.dir 405 cmd.Env = mergeEnvLists(g.env, origEnv) 406 err := cmd.Run() 407 if err != nil { 408 g.errorf("running %q: %s", words[0], err) 409 } 410 }