github.com/tcnksm/go@v0.0.0-20141208075154-439b32936367/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 68 Other than variable substitution and quoted-string evaluation, no 69 special processing such as "globbing" is performed on the command 70 line. 71 72 As a last step before running the command, any invocations of any 73 environment variables with alphanumeric names, such as $GOFILE or 74 $HOME, are expanded throughout the command line. The syntax for 75 variable expansion is $NAME on all operating systems. Due to the 76 order of evaluation, variables are expanded even inside quoted 77 strings. If the variable NAME is not set, $NAME expands to the 78 empty string. 79 80 A directive of the form, 81 82 //go:generate -command xxx args... 83 84 specifies, for the remainder of this source file only, that the 85 string xxx represents the command identified by the arguments. This 86 can be used to create aliases or to handle multiword generators. 87 For example, 88 89 //go:generate -command yacc go tool yacc 90 91 specifies that the command "yacc" represents the generator 92 "go tool yacc". 93 94 Generate processes packages in the order given on the command line, 95 one at a time. If the command line lists .go files, they are treated 96 as a single package. Within a package, generate processes the 97 source files in a package in file name order, one at a time. Within 98 a source file, generate runs generators in the order they appear 99 in the file, one at a time. 100 101 If any generator returns an error exit status, "go generate" skips 102 all further processing for that package. 103 104 The generator is run in the package's source directory. 105 106 Go generate accepts one specific flag: 107 108 -run="" 109 if non-empty, specifies a regular expression to 110 select directives whose command matches the expression. 111 112 It also accepts the standard build flags -v, -n, and -x. 113 The -v flag prints the names of packages and files as they are 114 processed. 115 The -n flag prints commands that would be executed. 116 The -x flag prints commands as they are executed. 117 118 For more about specifying packages, see 'go help packages'. 119 `, 120 } 121 122 var generateRunFlag string // generate -run flag 123 124 func init() { 125 addBuildFlags(cmdGenerate) 126 cmdGenerate.Flag.StringVar(&generateRunFlag, "run", "", "") 127 } 128 129 func runGenerate(cmd *Command, args []string) { 130 // Even if the arguments are .go files, this loop suffices. 131 for _, pkg := range packages(args) { 132 for _, file := range pkg.gofiles { 133 if !generate(pkg.Name, file) { 134 break 135 } 136 } 137 } 138 } 139 140 // generate runs the generation directives for a single file. 141 func generate(pkg, absFile string) bool { 142 fd, err := os.Open(absFile) 143 if err != nil { 144 log.Fatalf("generate: %s", err) 145 } 146 defer fd.Close() 147 g := &Generator{ 148 r: fd, 149 path: absFile, 150 pkg: pkg, 151 commands: make(map[string][]string), 152 } 153 return g.run() 154 } 155 156 // A Generator represents the state of a single Go source file 157 // being scanned for generator commands. 158 type Generator struct { 159 r io.Reader 160 path string // full rooted path name. 161 dir string // full rooted directory of file. 162 file string // base name of file. 163 pkg string 164 commands map[string][]string 165 lineNum int 166 } 167 168 // run runs the generators in the current file. 169 func (g *Generator) run() (ok bool) { 170 // Processing below here calls g.errorf on failure, which does panic(stop). 171 // If we encounter an error, we abort the package. 172 defer func() { 173 e := recover() 174 if e != nil { 175 ok = false 176 if e != stop { 177 panic(e) 178 } 179 setExitStatus(1) 180 } 181 }() 182 g.dir, g.file = filepath.Split(g.path) 183 g.dir = filepath.Clean(g.dir) // No final separator please. 184 if buildV { 185 fmt.Fprintf(os.Stderr, "%s\n", shortPath(g.path)) 186 } 187 188 // Scan for lines that start "//go:generate". 189 // Can't use bufio.Scanner because it can't handle long lines, 190 // which are likely to appear when using generate. 191 input := bufio.NewReader(g.r) 192 var err error 193 // One line per loop. 194 for { 195 g.lineNum++ // 1-indexed. 196 var buf []byte 197 buf, err = input.ReadSlice('\n') 198 if err == bufio.ErrBufferFull { 199 // Line too long - consume and ignore. 200 if isGoGenerate(buf) { 201 g.errorf("directive too long") 202 } 203 for err == bufio.ErrBufferFull { 204 _, err = input.ReadSlice('\n') 205 } 206 if err != nil { 207 break 208 } 209 continue 210 } 211 212 if err != nil { 213 // Check for marker at EOF without final \n. 214 if err == io.EOF && isGoGenerate(buf) { 215 err = io.ErrUnexpectedEOF 216 } 217 break 218 } 219 220 if !isGoGenerate(buf) { 221 continue 222 } 223 224 words := g.split(string(buf)) 225 if len(words) == 0 { 226 g.errorf("no arguments to directive") 227 } 228 if words[0] == "-command" { 229 g.setShorthand(words) 230 continue 231 } 232 // Run the command line. 233 if buildN || buildX { 234 fmt.Fprintf(os.Stderr, "%s\n", strings.Join(words, " ")) 235 } 236 if buildN { 237 continue 238 } 239 g.exec(words) 240 } 241 if err != nil && err != io.EOF { 242 g.errorf("error reading %s: %s", shortPath(g.path), err) 243 } 244 return true 245 } 246 247 func isGoGenerate(buf []byte) bool { 248 return bytes.HasPrefix(buf, []byte("//go:generate ")) || bytes.HasPrefix(buf, []byte("//go:generate\t")) 249 } 250 251 // split breaks the line into words, evaluating quoted 252 // strings and evaluating environment variables. 253 // The initial //go:generate element is present in line. 254 func (g *Generator) split(line string) []string { 255 // Parse line, obeying quoted strings. 256 var words []string 257 line = line[len("//go:generate ") : len(line)-1] // Drop preamble and final newline. 258 // One (possibly quoted) word per iteration. 259 Words: 260 for { 261 line = strings.TrimLeft(line, " \t") 262 if len(line) == 0 { 263 break 264 } 265 if line[0] == '"' { 266 for i := 1; i < len(line); i++ { 267 c := line[i] // Only looking for ASCII so this is OK. 268 switch c { 269 case '\\': 270 if i+1 == len(line) { 271 g.errorf("bad backslash") 272 } 273 i++ // Absorb next byte (If it's a multibyte we'll get an error in Unquote). 274 case '"': 275 word, err := strconv.Unquote(line[0 : i+1]) 276 if err != nil { 277 g.errorf("bad quoted string") 278 } 279 words = append(words, word) 280 line = line[i+1:] 281 // Check the next character is space or end of line. 282 if len(line) > 0 && line[0] != ' ' && line[0] != '\t' { 283 g.errorf("expect space after quoted argument") 284 } 285 continue Words 286 } 287 } 288 g.errorf("mismatched quoted string") 289 } 290 i := strings.IndexAny(line, " \t") 291 if i < 0 { 292 i = len(line) 293 } 294 words = append(words, line[0:i]) 295 line = line[i:] 296 } 297 // Substitute command if required. 298 if len(words) > 0 && g.commands[words[0]] != nil { 299 // Replace 0th word by command substitution. 300 words = append(g.commands[words[0]], words[1:]...) 301 } 302 // Substitute environment variables. 303 for i, word := range words { 304 words[i] = g.expandEnv(word) 305 } 306 return words 307 } 308 309 var stop = fmt.Errorf("error in generation") 310 311 // errorf logs an error message prefixed with the file and line number. 312 // It then exits the program (with exit status 1) because generation stops 313 // at the first error. 314 func (g *Generator) errorf(format string, args ...interface{}) { 315 fmt.Fprintf(os.Stderr, "%s:%d: %s\n", shortPath(g.path), g.lineNum, 316 fmt.Sprintf(format, args...)) 317 panic(stop) 318 } 319 320 // expandEnv expands any $XXX invocations in word. 321 func (g *Generator) expandEnv(word string) string { 322 if !strings.ContainsRune(word, '$') { 323 return word 324 } 325 var buf bytes.Buffer 326 var w int 327 var r rune 328 for i := 0; i < len(word); i += w { 329 r, w = utf8.DecodeRuneInString(word[i:]) 330 if r != '$' { 331 buf.WriteRune(r) 332 continue 333 } 334 w += g.identLength(word[i+w:]) 335 envVar := word[i+1 : i+w] 336 var sub string 337 switch envVar { 338 case "GOARCH": 339 sub = runtime.GOARCH 340 case "GOOS": 341 sub = runtime.GOOS 342 case "GOFILE": 343 sub = g.file 344 case "GOPACKAGE": 345 sub = g.pkg 346 default: 347 sub = os.Getenv(envVar) 348 } 349 buf.WriteString(sub) 350 } 351 return buf.String() 352 } 353 354 // identLength returns the length of the identifier beginning the string. 355 func (g *Generator) identLength(word string) int { 356 for i, r := range word { 357 if r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) { 358 continue 359 } 360 return i 361 } 362 return len(word) 363 } 364 365 // setShorthand installs a new shorthand as defined by a -command directive. 366 func (g *Generator) setShorthand(words []string) { 367 // Create command shorthand. 368 if len(words) == 1 { 369 g.errorf("no command specified for -command") 370 } 371 command := words[1] 372 if g.commands[command] != nil { 373 g.errorf("command %q defined multiply defined", command) 374 } 375 g.commands[command] = words[2:len(words):len(words)] // force later append to make copy 376 } 377 378 // exec runs the command specified by the argument. The first word is 379 // the command name itself. 380 func (g *Generator) exec(words []string) { 381 cmd := exec.Command(words[0], words[1:]...) 382 // Standard in and out of generator should be the usual. 383 cmd.Stdout = os.Stdout 384 cmd.Stderr = os.Stderr 385 // Run the command in the package directory. 386 cmd.Dir = g.dir 387 env := []string{ 388 "GOARCH=" + runtime.GOARCH, 389 "GOOS=" + runtime.GOOS, 390 "GOFILE=" + g.file, 391 "GOPACKAGE=" + g.pkg, 392 } 393 cmd.Env = mergeEnvLists(env, os.Environ()) 394 err := cmd.Run() 395 if err != nil { 396 g.errorf("running %q: %s", words[0], err) 397 } 398 }