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