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