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