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