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