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