github.com/razvanm/vanadium-go-1.3@v0.0.0-20160721203343-4a65068e5915/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 Directives are written as a whole-line comment of the form 36 37 //go:generate command argument... 38 39 (note: no space in "//go") where command is the generator to be 40 run, corresponding to an executable file that can be run locally. 41 It must either be in the shell path (gofmt), a fully qualified path 42 (/usr/you/bin/mytool), or a command alias, described below. 43 44 The arguments are space-separated tokens or double-quoted strings 45 passed to the generator as individual arguments when it is run. 46 47 Quoted strings use Go syntax and are evaluated before execution; a 48 quoted string appears a single argument to the generator. 49 50 Go generate sets several variables when it runs the generator: 51 52 $GOARCH 53 The execution architecture (arm, amd64, etc.) 54 $GOOS 55 The execution operating system (linux, windows, etc.) 56 $GOFILE 57 The base name of the file. 58 $GOPACKAGE 59 The name of the package of the file containing the directive. 60 61 Other than variable substition and quoted-string evaluation, no 62 special processing such as "globbing" is performed on the command 63 line. 64 65 As a last step before running the command, any invocations of any 66 environment variables with alphanumeric names, such as $GOFILE or 67 $HOME, are expanded throughout the command line. The syntax for 68 variable expansion is $NAME on all operating systems. Due to the 69 order of evaluation, variables are expanded even inside quoted 70 strings. If the variable NAME is not set, $NAME expands to the 71 empty string. 72 73 A directive of the form, 74 75 //go:generate -command xxx args... 76 77 specifies, for the remainder of this source file only, that the 78 string xxx represents the command identified by the arguments. This 79 can be used to create aliases or to handle multiword generators. 80 For example, 81 82 //go:generate -command yacc go tool yacc 83 84 specifies that the command "yacc" represents the generator 85 "go tool yacc". 86 87 Generate processes packages in the order given on the command line, 88 one at a time. If the command line lists .go files, they are treated 89 as a single package. Within a package, generate processes the 90 source files in a package in file name order, one at a time. Within 91 a source file, generate runs generators in the order they appear 92 in the file, one at a time. 93 94 If any generator returns an error exit status, "go generate" skips 95 all further processing for that package. 96 97 The generator is run in the package's source directory. 98 99 Go generate accepts one specific flag: 100 101 -run="" 102 if non-empty, specifies a regular expression to 103 select directives whose command matches the expression. 104 105 It also accepts the standard build flags -v, -n, and -x. 106 The -v flag prints the names of packages and files as they are 107 processed. 108 The -n flag prints commands that would be executed. 109 The -x flag prints commands as they are executed. 110 111 For more about specifying packages, see 'go help packages'. 112 `, 113 } 114 115 var generateRunFlag string // generate -run flag 116 117 func init() { 118 addBuildFlags(cmdGenerate) 119 cmdGenerate.Flag.StringVar(&generateRunFlag, "run", "", "") 120 } 121 122 func runGenerate(cmd *Command, args []string) { 123 // Even if the arguments are .go files, this loop suffices. 124 for _, pkg := range packages(args) { 125 for _, file := range pkg.gofiles { 126 if !generate(pkg.Name, file) { 127 break 128 } 129 } 130 } 131 } 132 133 // generate runs the generation directives for a single file. 134 func generate(pkg, absFile string) bool { 135 fd, err := os.Open(absFile) 136 if err != nil { 137 log.Fatalf("generate: %s", err) 138 } 139 defer fd.Close() 140 g := &Generator{ 141 r: fd, 142 path: absFile, 143 pkg: pkg, 144 commands: make(map[string][]string), 145 } 146 return g.run() 147 } 148 149 // A Generator represents the state of a single Go source file 150 // being scanned for generator commands. 151 type Generator struct { 152 r io.Reader 153 path string // full rooted path name. 154 dir string // full rooted directory of file. 155 file string // base name of file. 156 pkg string 157 commands map[string][]string 158 lineNum int 159 } 160 161 // run runs the generators in the current file. 162 func (g *Generator) run() (ok bool) { 163 // Processing below here calls g.errorf on failure, which does panic(stop). 164 // If we encounter an error, we abort the package. 165 defer func() { 166 e := recover() 167 if e != nil { 168 ok = false 169 if e != stop { 170 panic(e) 171 } 172 setExitStatus(1) 173 } 174 }() 175 g.dir, g.file = filepath.Split(g.path) 176 g.dir = filepath.Clean(g.dir) // No final separator please. 177 if buildV { 178 fmt.Fprintf(os.Stderr, "%s\n", shortPath(g.path)) 179 } 180 181 s := bufio.NewScanner(g.r) 182 for s.Scan() { 183 g.lineNum++ 184 if !bytes.HasPrefix(s.Bytes(), []byte("//go:generate ")) && !bytes.HasPrefix(s.Bytes(), []byte("//go:generate\t")) { 185 continue 186 } 187 words := g.split(s.Text()) 188 if len(words) == 0 { 189 g.errorf("no arguments to directive") 190 } 191 if words[0] == "-command" { 192 g.setShorthand(words) 193 continue 194 } 195 // Run the command line. 196 if buildN || buildX { 197 fmt.Fprintf(os.Stderr, "%s\n", strings.Join(words, " ")) 198 } 199 if buildN { 200 continue 201 } 202 g.exec(words) 203 } 204 if s.Err() != nil { 205 g.errorf("error reading %s: %s", shortPath(g.path), s.Err()) 206 } 207 return true 208 } 209 210 // split breaks the line into words, evaluating quoted 211 // strings and evaluating environment variables. 212 // The initial //go:generate element is dropped. 213 func (g *Generator) split(line string) []string { 214 // Parse line, obeying quoted strings. 215 var words []string 216 line = line[len("//go:generate "):] 217 // One (possibly quoted) word per iteration. 218 Words: 219 for { 220 line = strings.TrimLeft(line, " \t") 221 if len(line) == 0 { 222 break 223 } 224 if line[0] == '"' { 225 for i := 1; i < len(line); i++ { 226 c := line[i] // Only looking for ASCII so this is OK. 227 switch c { 228 case '\\': 229 if i+1 == len(line) { 230 g.errorf("bad backslash") 231 } 232 i++ // Absorb next byte (If it's a multibyte we'll get an error in Unquote). 233 case '"': 234 word, err := strconv.Unquote(line[0 : i+1]) 235 if err != nil { 236 g.errorf("bad quoted string") 237 } 238 words = append(words, word) 239 line = line[i+1:] 240 // Check the next character is space or end of line. 241 if len(line) > 0 && line[0] != ' ' && line[0] != '\t' { 242 g.errorf("expect space after quoted argument") 243 } 244 continue Words 245 } 246 } 247 g.errorf("mismatched quoted string") 248 } 249 i := strings.IndexAny(line, " \t") 250 if i < 0 { 251 i = len(line) 252 } 253 words = append(words, line[0:i]) 254 line = line[i:] 255 } 256 // Substitute command if required. 257 if len(words) > 0 && g.commands[words[0]] != nil { 258 // Replace 0th word by command substitution. 259 words = append(g.commands[words[0]], words[1:]...) 260 } 261 // Substitute environment variables. 262 for i, word := range words { 263 words[i] = g.expandEnv(word) 264 } 265 return words 266 } 267 268 var stop = fmt.Errorf("error in generation") 269 270 // errorf logs an error message prefixed with the file and line number. 271 // It then exits the program (with exit status 1) because generation stops 272 // at the first error. 273 func (g *Generator) errorf(format string, args ...interface{}) { 274 fmt.Fprintf(os.Stderr, "%s:%d: %s\n", shortPath(g.path), g.lineNum, 275 fmt.Sprintf(format, args...)) 276 panic(stop) 277 } 278 279 // expandEnv expands any $XXX invocations in word. 280 func (g *Generator) expandEnv(word string) string { 281 if !strings.ContainsRune(word, '$') { 282 return word 283 } 284 var buf bytes.Buffer 285 var w int 286 var r rune 287 for i := 0; i < len(word); i += w { 288 r, w = utf8.DecodeRuneInString(word[i:]) 289 if r != '$' { 290 buf.WriteRune(r) 291 continue 292 } 293 w += g.identLength(word[i+w:]) 294 envVar := word[i+1 : i+w] 295 var sub string 296 switch envVar { 297 case "GOARCH": 298 sub = runtime.GOARCH 299 case "GOOS": 300 sub = runtime.GOOS 301 case "GOFILE": 302 sub = g.file 303 case "GOPACKAGE": 304 sub = g.pkg 305 default: 306 sub = os.Getenv(envVar) 307 } 308 buf.WriteString(sub) 309 } 310 return buf.String() 311 } 312 313 // identLength returns the length of the identifier beginning the string. 314 func (g *Generator) identLength(word string) int { 315 for i, r := range word { 316 if r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) { 317 continue 318 } 319 return i 320 } 321 return len(word) 322 } 323 324 // setShorthand installs a new shorthand as defined by a -command directive. 325 func (g *Generator) setShorthand(words []string) { 326 // Create command shorthand. 327 if len(words) == 1 { 328 g.errorf("no command specified for -command") 329 } 330 command := words[1] 331 if g.commands[command] != nil { 332 g.errorf("command %q defined multiply defined", command) 333 } 334 g.commands[command] = words[2:len(words):len(words)] // force later append to make copy 335 } 336 337 // exec runs the command specified by the argument. The first word is 338 // the command name itself. 339 func (g *Generator) exec(words []string) { 340 cmd := exec.Command(words[0], words[1:]...) 341 // Standard in and out of generator should be the usual. 342 cmd.Stdout = os.Stdout 343 cmd.Stderr = os.Stderr 344 // Run the command in the package directory. 345 cmd.Dir = g.dir 346 env := []string{ 347 "GOARCH=" + runtime.GOARCH, 348 "GOOS=" + runtime.GOOS, 349 "GOFILE=" + g.file, 350 "GOPACKAGE=" + g.pkg, 351 } 352 cmd.Env = mergeEnvLists(env, os.Environ()) 353 err := cmd.Run() 354 if err != nil { 355 g.errorf("running %q: %s", words[0], err) 356 } 357 }