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