github.com/gophergala2016/cmd-go-js@v0.0.0-20160421080227-24a7748a7d62/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 -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 } 183 184 // run runs the generators in the current file. 185 func (g *Generator) run() (ok bool) { 186 // Processing below here calls g.errorf on failure, which does panic(stop). 187 // If we encounter an error, we abort the package. 188 defer func() { 189 e := recover() 190 if e != nil { 191 ok = false 192 if e != stop { 193 panic(e) 194 } 195 setExitStatus(1) 196 } 197 }() 198 g.dir, g.file = filepath.Split(g.path) 199 g.dir = filepath.Clean(g.dir) // No final separator please. 200 if buildV { 201 fmt.Fprintf(os.Stderr, "%s\n", shortPath(g.path)) 202 } 203 204 // Scan for lines that start "//go:generate". 205 // Can't use bufio.Scanner because it can't handle long lines, 206 // which are likely to appear when using generate. 207 input := bufio.NewReader(g.r) 208 var err error 209 // One line per loop. 210 for { 211 g.lineNum++ // 1-indexed. 212 var buf []byte 213 buf, err = input.ReadSlice('\n') 214 if err == bufio.ErrBufferFull { 215 // Line too long - consume and ignore. 216 if isGoGenerate(buf) { 217 g.errorf("directive too long") 218 } 219 for err == bufio.ErrBufferFull { 220 _, err = input.ReadSlice('\n') 221 } 222 if err != nil { 223 break 224 } 225 continue 226 } 227 228 if err != nil { 229 // Check for marker at EOF without final \n. 230 if err == io.EOF && isGoGenerate(buf) { 231 err = io.ErrUnexpectedEOF 232 } 233 break 234 } 235 236 if !isGoGenerate(buf) { 237 continue 238 } 239 if generateRunFlag != "" { 240 if !generateRunRE.Match(bytes.TrimSpace(buf)) { 241 continue 242 } 243 } 244 245 words := g.split(string(buf)) 246 if len(words) == 0 { 247 g.errorf("no arguments to directive") 248 } 249 if words[0] == "-command" { 250 g.setShorthand(words) 251 continue 252 } 253 // Run the command line. 254 if buildN || buildX { 255 fmt.Fprintf(os.Stderr, "%s\n", strings.Join(words, " ")) 256 } 257 if buildN { 258 continue 259 } 260 g.exec(words) 261 } 262 if err != nil && err != io.EOF { 263 g.errorf("error reading %s: %s", shortPath(g.path), err) 264 } 265 return true 266 } 267 268 func isGoGenerate(buf []byte) bool { 269 return bytes.HasPrefix(buf, []byte("//go:generate ")) || bytes.HasPrefix(buf, []byte("//go:generate\t")) 270 } 271 272 // split breaks the line into words, evaluating quoted 273 // strings and evaluating environment variables. 274 // The initial //go:generate element is present in line. 275 func (g *Generator) split(line string) []string { 276 // Parse line, obeying quoted strings. 277 var words []string 278 line = line[len("//go:generate ") : len(line)-1] // Drop preamble and final newline. 279 // There may still be a carriage return. 280 if len(line) > 0 && line[len(line)-1] == '\r' { 281 line = line[:len(line)-1] 282 } 283 // One (possibly quoted) word per iteration. 284 Words: 285 for { 286 line = strings.TrimLeft(line, " \t") 287 if len(line) == 0 { 288 break 289 } 290 if line[0] == '"' { 291 for i := 1; i < len(line); i++ { 292 c := line[i] // Only looking for ASCII so this is OK. 293 switch c { 294 case '\\': 295 if i+1 == len(line) { 296 g.errorf("bad backslash") 297 } 298 i++ // Absorb next byte (If it's a multibyte we'll get an error in Unquote). 299 case '"': 300 word, err := strconv.Unquote(line[0 : i+1]) 301 if err != nil { 302 g.errorf("bad quoted string") 303 } 304 words = append(words, word) 305 line = line[i+1:] 306 // Check the next character is space or end of line. 307 if len(line) > 0 && line[0] != ' ' && line[0] != '\t' { 308 g.errorf("expect space after quoted argument") 309 } 310 continue Words 311 } 312 } 313 g.errorf("mismatched quoted string") 314 } 315 i := strings.IndexAny(line, " \t") 316 if i < 0 { 317 i = len(line) 318 } 319 words = append(words, line[0:i]) 320 line = line[i:] 321 } 322 // Substitute command if required. 323 if len(words) > 0 && g.commands[words[0]] != nil { 324 // Replace 0th word by command substitution. 325 words = append(g.commands[words[0]], words[1:]...) 326 } 327 // Substitute environment variables. 328 for i, word := range words { 329 words[i] = os.Expand(word, g.expandVar) 330 } 331 return words 332 } 333 334 var stop = fmt.Errorf("error in generation") 335 336 // errorf logs an error message prefixed with the file and line number. 337 // It then exits the program (with exit status 1) because generation stops 338 // at the first error. 339 func (g *Generator) errorf(format string, args ...interface{}) { 340 fmt.Fprintf(os.Stderr, "%s:%d: %s\n", shortPath(g.path), g.lineNum, 341 fmt.Sprintf(format, args...)) 342 panic(stop) 343 } 344 345 // expandVar expands the $XXX invocation in word. It is called 346 // by os.Expand. 347 func (g *Generator) expandVar(word string) string { 348 switch word { 349 case "GOARCH": 350 return buildContext.GOARCH 351 case "GOOS": 352 return buildContext.GOOS 353 case "GOFILE": 354 return g.file 355 case "GOLINE": 356 return fmt.Sprint(g.lineNum) 357 case "GOPACKAGE": 358 return g.pkg 359 case "DOLLAR": 360 return "$" 361 default: 362 return os.Getenv(word) 363 } 364 } 365 366 // identLength returns the length of the identifier beginning the string. 367 func (g *Generator) identLength(word string) int { 368 for i, r := range word { 369 if r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) { 370 continue 371 } 372 return i 373 } 374 return len(word) 375 } 376 377 // setShorthand installs a new shorthand as defined by a -command directive. 378 func (g *Generator) setShorthand(words []string) { 379 // Create command shorthand. 380 if len(words) == 1 { 381 g.errorf("no command specified for -command") 382 } 383 command := words[1] 384 if g.commands[command] != nil { 385 g.errorf("command %q defined multiply defined", command) 386 } 387 g.commands[command] = words[2:len(words):len(words)] // force later append to make copy 388 } 389 390 // exec runs the command specified by the argument. The first word is 391 // the command name itself. 392 func (g *Generator) exec(words []string) { 393 cmd := exec.Command(words[0], words[1:]...) 394 // Standard in and out of generator should be the usual. 395 cmd.Stdout = os.Stdout 396 cmd.Stderr = os.Stderr 397 // Run the command in the package directory. 398 cmd.Dir = g.dir 399 env := []string{ 400 "GOARCH=" + runtime.GOARCH, 401 "GOOS=" + runtime.GOOS, 402 "GOFILE=" + g.file, 403 "GOPACKAGE=" + g.pkg, 404 } 405 cmd.Env = mergeEnvLists(env, origEnv) 406 err := cmd.Run() 407 if err != nil { 408 g.errorf("running %q: %s", words[0], err) 409 } 410 }