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