blob: 2810b554b9cb433a8ca7a8d767223d63c4f3a499 [file] [log] [blame]
Jiri Simsad7616c92015-03-24 23:44:30 -07001// Copyright 2015 The Vanadium 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
Matt Rosencrantzbca49812015-03-01 21:32:54 -08005// The following enables go generate to generate the doc.go file.
Jiri Simsa32f76fb2015-04-07 15:39:23 -07006//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
Matt Rosencrantzbca49812015-03-01 21:32:54 -08007
8package main
9
10import (
11 "bytes"
12 "fmt"
13 "io/ioutil"
14 "log"
15 "os"
16 "path/filepath"
17 "strings"
18
19 "v.io/v23/vdlroot/vdltool"
Todd Wang9560b9c2015-05-11 13:27:58 -070020 "v.io/x/lib/cmdline"
Matt Rosencrantzbca49812015-03-01 21:32:54 -080021 "v.io/x/lib/textutil"
22 "v.io/x/ref/lib/vdl/build"
23 "v.io/x/ref/lib/vdl/codegen/golang"
24 "v.io/x/ref/lib/vdl/codegen/java"
25 "v.io/x/ref/lib/vdl/codegen/javascript"
26 "v.io/x/ref/lib/vdl/compile"
27 "v.io/x/ref/lib/vdl/vdlutil"
28)
29
30func init() {
31 log.SetFlags(log.Lshortfile | log.Ltime | log.Lmicroseconds)
32}
33
34func main() {
Todd Wang9560b9c2015-05-11 13:27:58 -070035 cmdline.Main(cmdVDL)
Matt Rosencrantzbca49812015-03-01 21:32:54 -080036}
37
38func checkErrors(errs *vdlutil.Errors) error {
39 if errs.IsEmpty() {
40 return nil
41 }
42 return fmt.Errorf(`
43%s (run with "vdl -v" for verbose logging or "vdl help" for help)`, errs)
44}
45
46// runHelper returns a function that generates a sorted list of transitive
47// targets, and calls the supplied run function.
Todd Wang9560b9c2015-05-11 13:27:58 -070048func runHelper(run func(targets []*build.Package, env *compile.Env)) cmdline.Runner {
49 return cmdline.RunnerFunc(func(_ *cmdline.Env, args []string) error {
Matt Rosencrantzbca49812015-03-01 21:32:54 -080050 if flagVerbose {
51 vdlutil.SetVerbose()
52 }
53 if len(args) == 0 {
54 // If the user doesn't specify any targets, the cwd is implied.
55 args = append(args, ".")
56 }
57 env := compile.NewEnv(flagMaxErrors)
58 env.DisallowPathQualifiers()
59 mode := build.UnknownPathIsError
60 if flagIgnoreUnknown {
61 mode = build.UnknownPathIsIgnored
62 }
63 var opts build.Opts
64 opts.Extensions = strings.Split(flagExts, ",")
65 opts.VDLConfigName = flagVDLConfig
66 targets := build.TransitivePackages(args, mode, opts, env.Errors)
67 if err := checkErrors(env.Errors); err != nil {
68 return err
69 }
70 run(targets, env)
71 return checkErrors(env.Errors)
Todd Wangf1550cf2015-05-11 10:58:41 -070072 })
Matt Rosencrantzbca49812015-03-01 21:32:54 -080073}
74
Todd Wang9560b9c2015-05-11 13:27:58 -070075var topicPackages = cmdline.Topic{
Matt Rosencrantzbca49812015-03-01 21:32:54 -080076 Name: "packages",
77 Short: "Description of package lists",
78 Long: `
79Most vdl commands apply to a list of packages:
80
81 vdl command <packages>
82
83<packages> are a list of packages to process, similar to the standard go tool.
84In its simplest form each package is an import path; e.g.
85 "v.io/x/ref/lib/vdl"
86
87A package that is an absolute path or that begins with a . or .. element is
88interpreted as a file system path, and denotes the package in that directory.
89
90A package is a pattern if it includes one or more "..." wildcards, each of which
91can match any string, including the empty string and strings containing
92slashes. Such a pattern expands to all packages found in VDLPATH with names
93matching the pattern. As a special-case, x/... matches x as well as x's
94subdirectories.
95
96The special-case "all" is a synonym for "...", and denotes all packages found
97in VDLPATH.
98
99Import path elements and file names are not allowed to begin with "." or "_";
100such paths are ignored in wildcard matches, and return errors if specified
101explicitly.
102
James Ringc36fad62015-06-09 13:31:00 -0700103Note that whereas GOPATH requires *.go source files and packages to appear
104under a "src" directory, VDLPATH requires *.vdl source files and packages to
105appear directly under the VDLPATH directories.
106
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800107 Run "vdl help vdlpath" to see docs on VDLPATH.
108 Run "go help packages" to see the standard go package docs.
109`,
110}
111
Todd Wang9560b9c2015-05-11 13:27:58 -0700112var topicVdlPath = cmdline.Topic{
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800113 Name: "vdlpath",
114 Short: "Description of VDLPATH environment variable",
115 Long: `
116The VDLPATH environment variable is used to resolve import statements.
117It must be set to compile and generate vdl packages.
118
James Ringc36fad62015-06-09 13:31:00 -0700119The format is a colon-separated list of directories containing vdl source code.
120These directories are searched recursively for VDL source files. The path
121below the directory determines the import path. If VDLPATH specifies multiple
122directories, imports are resolved by picking the first directory with a
123matching import name.
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800124
125An example:
126
127 VDPATH=/home/user/vdlA:/home/user/vdlB
128
James Ringc36fad62015-06-09 13:31:00 -0700129 /home/user/vdlA
130 foo/ (import "foo" refers here)
131 foo1.vdl
132 /home/user/vdlB
133 foo/ (this package is ignored)
134 foo2.vdl
135 bar/
136 baz/ (import "bar/baz" refers here)
137 baz.vdl
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800138`,
139}
140
Todd Wang9560b9c2015-05-11 13:27:58 -0700141var topicVdlRoot = cmdline.Topic{
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800142 Name: "vdlroot",
143 Short: "Description of VDLROOT environment variable",
144 Long: `
145The VDLROOT environment variable is similar to VDLPATH, but instead of pointing
146to multiple user source directories, it points at a single source directory
147containing the standard vdl packages.
148
149Setting VDLROOT is optional.
150
Jiri Simsa32f76fb2015-04-07 15:39:23 -0700151If VDLROOT is empty, we try to construct it out of the V23_ROOT environment
152variable. It is an error if both VDLROOT and V23_ROOT are empty.
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800153`,
154}
155
Todd Wang9560b9c2015-05-11 13:27:58 -0700156var topicVdlConfig = cmdline.Topic{
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800157 Name: "vdl.config",
158 Short: "Description of vdl.config files",
159 Long: `
160Each vdl source package P may contain an optional file "vdl.config" within the P
161directory. This file specifies additional configuration for the vdl tool.
162
163The format of this file is described by the vdltool.Config type in the "vdltool"
164standard package, located at VDLROOT/vdltool/config.vdl.
165
166If the file does not exist, we use the zero value of vdl.Config.
167`,
168}
169
170const pkgArgName = "<packages>"
171const pkgArgLong = `
172<packages> are a list of packages to process, similar to the standard go tool.
173For more information, run "vdl help packages".
174`
175
Todd Wang9560b9c2015-05-11 13:27:58 -0700176var cmdCompile = &cmdline.Command{
Todd Wangf1550cf2015-05-11 10:58:41 -0700177 Runner: runHelper(runCompile),
178 Name: "compile",
179 Short: "Compile packages and dependencies, but don't generate code",
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800180 Long: `
181Compile compiles packages and their transitive dependencies, but does not
182generate code. This is useful to sanity-check that your VDL files are valid.
183`,
184 ArgsName: pkgArgName,
185 ArgsLong: pkgArgLong,
186}
187
Todd Wang9560b9c2015-05-11 13:27:58 -0700188var cmdGenerate = &cmdline.Command{
Todd Wangf1550cf2015-05-11 10:58:41 -0700189 Runner: runHelper(runGenerate),
190 Name: "generate",
191 Short: "Compile packages and dependencies, and generate code",
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800192 Long: `
193Generate compiles packages and their transitive dependencies, and generates code
194in the specified languages.
195`,
196 ArgsName: pkgArgName,
197 ArgsLong: pkgArgLong,
198}
199
Todd Wang9560b9c2015-05-11 13:27:58 -0700200var cmdAudit = &cmdline.Command{
Todd Wangf1550cf2015-05-11 10:58:41 -0700201 Runner: runHelper(runAudit),
202 Name: "audit",
203 Short: "Check if any packages are stale and need generation",
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800204 Long: `
205Audit runs the same logic as generate, but doesn't write out generated files.
206Returns a 0 exit code if all packages are up-to-date, otherwise returns a
207non-0 exit code indicating some packages need generation.
208`,
209 ArgsName: pkgArgName,
210 ArgsLong: pkgArgLong,
211}
212
Todd Wang9560b9c2015-05-11 13:27:58 -0700213var cmdList = &cmdline.Command{
Todd Wangf1550cf2015-05-11 10:58:41 -0700214 Runner: runHelper(runList),
215 Name: "list",
216 Short: "List package and dependency info in transitive order",
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800217 Long: `
218List returns information about packages and their transitive dependencies, in
219transitive order. This is the same order the generate and compile commands use
220for processing. If "vdl list A" is run and A depends on B, which depends on C,
221the returned order will be C, B, A. If multiple packages are specified the
222ordering is over all combined dependencies.
223
224Reminder: cyclic dependencies between packages are not allowed. Cyclic
225dependencies between VDL files within the same package are also not allowed.
226This is more strict than regular Go; it makes it easier to generate code for
227other languages like C++.
228`,
229 ArgsName: pkgArgName,
230 ArgsLong: pkgArgLong,
231}
232
Bogdan Capritadc81f5f2015-03-18 10:08:03 -0700233var genLangAll = genLangs(vdltool.GenLanguageAll[:])
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800234
235type genLangs []vdltool.GenLanguage
236
237func (gls genLangs) String() string {
238 var ret string
239 for i, gl := range gls {
240 if i > 0 {
241 ret += ","
242 }
243 ret += gl.String()
244 }
245 return ret
246}
247
248func (gls *genLangs) Set(value string) error {
Todd Wang9560b9c2015-05-11 13:27:58 -0700249 // If the flag is repeated on the cmdline it is overridden. Duplicates within
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800250 // the comma separated list are ignored, and retain their original ordering.
251 *gls = genLangs{}
252 seen := make(map[vdltool.GenLanguage]bool)
253 for _, str := range strings.Split(value, ",") {
254 gl, err := vdltool.GenLanguageFromString(str)
255 if err != nil {
256 return err
257 }
258 if !seen[gl] {
259 seen[gl] = true
260 *gls = append(*gls, gl)
261 }
262 }
263 return nil
264}
265
266// genOutDir has three modes:
267// 1) If dir is non-empty, we use it as the out dir.
268// 2) If rules is non-empty, we translate using the xlate rules.
269// 3) If everything is empty, we generate in-place.
270type genOutDir struct {
271 dir string
272 rules xlateRules
273}
274
275// xlateSrcDst specifies a translation rule, where src must match the suffix of
276// the path just before the package path, and dst is the replacement for src.
277// If dst is the special string "SKIP" we'll skip generation of packages
278// matching the src.
279type xlateSrcDst struct {
280 src, dst string
281}
282
283// xlateRules specifies a collection of translation rules.
284type xlateRules []xlateSrcDst
285
286func (x *xlateRules) String() (ret string) {
287 for _, srcdst := range *x {
288 if len(ret) > 0 {
289 ret += ","
290 }
291 ret += srcdst.src + "->" + srcdst.dst
292 }
293 return
294}
295
296func (x *xlateRules) Set(value string) error {
297 for _, rule := range strings.Split(value, ",") {
298 srcdst := strings.Split(rule, "->")
299 if len(srcdst) != 2 {
300 return fmt.Errorf("invalid out dir xlate rule %q (not src->dst format)", rule)
301 }
302 *x = append(*x, xlateSrcDst{srcdst[0], srcdst[1]})
303 }
304 return nil
305}
306
307func (x *genOutDir) String() string {
308 if x.dir != "" {
309 return x.dir
310 }
311 return x.rules.String()
312}
313
314func (x *genOutDir) Set(value string) error {
315 if strings.Contains(value, "->") {
316 x.dir = ""
317 return x.rules.Set(value)
318 }
319 x.dir = value
320 return nil
321}
322
323var (
324 // Common flags for the tool itself, applicable to all commands.
325 flagVerbose bool
326 flagMaxErrors int
327 flagExts string
328 flagVDLConfig string
329 flagIgnoreUnknown bool
330
331 // Options for each command.
332 optCompileStatus bool
333 optGenStatus bool
334 optGenGoOutDir = genOutDir{}
335 optGenJavaOutDir = genOutDir{
336 rules: xlateRules{
Srdjan Petrovicdde7ba02015-06-18 17:41:30 -0700337 {"release/go/src", "release/java/lib/generated-src/vdl"},
338 {"roadmap/go/src", "release/java/lib/generated-src/vdl"},
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800339 },
340 }
341 optGenJavascriptOutDir = genOutDir{
342 rules: xlateRules{
343 {"release/go/src", "release/javascript/core/src"},
344 {"roadmap/go/src", "release/javascript/core/src"},
345 {"third_party/go/src", "SKIP"},
346 {"tools/go/src", "SKIP"},
347 // TODO(toddw): Skip vdlroot javascript generation for now.
348 {"release/go/src/v.io/v23/vdlroot", "SKIP"},
349 },
350 }
351 optGenJavaOutPkg = xlateRules{
352 {"v.io", "io/v"},
353 }
354 optPathToJSCore string
Todd Wang786cf4c2015-04-24 19:07:16 -0700355 // Default to just running the go lang; other langs need special setup.
356 optGenLangs = genLangs{vdltool.GenLanguageGo}
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800357)
358
359// Root returns the root command for the VDL tool.
Todd Wang9560b9c2015-05-11 13:27:58 -0700360var cmdVDL = &cmdline.Command{
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800361 Name: "vdl",
Todd Wang6ed3b6c2015-04-08 14:37:04 -0700362 Short: "manages Vanadium Definition Language source code",
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800363 Long: `
Todd Wang6ed3b6c2015-04-08 14:37:04 -0700364Command vdl manages Vanadium Definition Language source code. It's similar to
365the go tool used for managing Go source code.
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800366`,
Todd Wang9560b9c2015-05-11 13:27:58 -0700367 Children: []*cmdline.Command{cmdGenerate, cmdCompile, cmdAudit, cmdList},
368 Topics: []cmdline.Topic{topicPackages, topicVdlPath, topicVdlRoot, topicVdlConfig},
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800369}
370
371func init() {
372 // Common flags for the tool itself, applicable to all commands.
373 cmdVDL.Flags.BoolVar(&flagVerbose, "v", false, "Turn on verbose logging.")
Suharsh Sivakumar6901a2e2015-04-02 11:39:19 -0700374 cmdVDL.Flags.IntVar(&flagMaxErrors, "max-errors", -1, "Stop processing after this many errors, or -1 for unlimited.")
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800375 cmdVDL.Flags.StringVar(&flagExts, "exts", ".vdl", "Comma-separated list of valid VDL file name extensions.")
376 cmdVDL.Flags.StringVar(&flagVDLConfig, "vdl.config", "vdl.config", "Basename of the optional per-package config file.")
377 cmdVDL.Flags.BoolVar(&flagIgnoreUnknown, "ignore_unknown", false, "Ignore unknown packages provided on the command line.")
378
379 // Options for compile.
380 cmdCompile.Flags.BoolVar(&optCompileStatus, "status", true, "Show package names while we compile")
381
382 // Options for generate.
383 cmdGenerate.Flags.Var(&optGenLangs, "lang", "Comma-separated list of languages to generate, currently supporting "+genLangAll.String())
384 cmdGenerate.Flags.BoolVar(&optGenStatus, "status", true, "Show package names as they are updated")
385 // TODO(toddw): Move out_dir configuration into vdl.config, and provide a
386 // generic override mechanism for vdl.config.
Suharsh Sivakumar6901a2e2015-04-02 11:39:19 -0700387 cmdGenerate.Flags.Var(&optGenGoOutDir, "go-out-dir", `
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800388Go output directory. There are three modes:
389 "" : Generate output in-place in the source tree
390 "dir" : Generate output rooted at dir
391 "src->dst[,s2->d2...]" : Generate output using translation rules
392Assume your source tree is organized as follows:
393 VDLPATH=/home/vdl
James Ringc36fad62015-06-09 13:31:00 -0700394 /home/vdl/test_base/base1.vdl
395 /home/vdl/test_base/base2.vdl
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800396Here's example output under the different modes:
Suharsh Sivakumar6901a2e2015-04-02 11:39:19 -0700397 --go-out-dir=""
James Ringc36fad62015-06-09 13:31:00 -0700398 /home/vdl/test_base/base1.vdl.go
399 /home/vdl/test_base/base2.vdl.go
Suharsh Sivakumar6901a2e2015-04-02 11:39:19 -0700400 --go-out-dir="/tmp/foo"
Suharsh Sivakumar8646ba62015-03-18 15:22:28 -0700401 /tmp/foo/test_base/base1.vdl.go
402 /tmp/foo/test_base/base2.vdl.go
James Ringc36fad62015-06-09 13:31:00 -0700403 --go-out-dir="vdl->foo/bar"
404 /home/foo/bar/test_base/base1.vdl.go
405 /home/foo/bar/test_base/base2.vdl.go
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800406When the src->dst form is used, src must match the suffix of the path just
407before the package path, and dst is the replacement for src. Use commas to
408separate multiple rules; the first rule matching src is used. The special dst
409SKIP indicates matching packages are skipped.`)
Suharsh Sivakumar6901a2e2015-04-02 11:39:19 -0700410 cmdGenerate.Flags.Var(&optGenJavaOutDir, "java-out-dir",
411 "Same semantics as --go-out-dir but applies to java code generation.")
412 cmdGenerate.Flags.Var(&optGenJavaOutPkg, "java-out-pkg", `
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800413Java output package translation rules. Must be of the form:
414 "src->dst[,s2->d2...]"
415If a VDL package has a prefix src, the prefix will be replaced with dst. Use
416commas to separate multiple rules; the first rule matching src is used, and if
417there are no matching rules, the package remains unchanged. The special dst
418SKIP indicates matching packages are skipped.`)
Suharsh Sivakumar6901a2e2015-04-02 11:39:19 -0700419 cmdGenerate.Flags.Var(&optGenJavascriptOutDir, "js-out-dir",
420 "Same semantics as --go-out-dir but applies to js code generation.")
421 cmdGenerate.Flags.StringVar(&optPathToJSCore, "js-relative-path-to-core", "",
422 "If set, this is the relative path from js-out-dir to the root of the JS core")
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800423
424 // Options for audit are identical to generate.
425 cmdAudit.Flags = cmdGenerate.Flags
426}
427
428func runCompile(targets []*build.Package, env *compile.Env) {
429 for _, target := range targets {
430 pkg := build.BuildPackage(target, env)
431 if pkg != nil && optCompileStatus {
432 fmt.Println(pkg.Path)
433 }
434 }
435}
436
437func runGenerate(targets []*build.Package, env *compile.Env) {
438 gen(false, targets, env)
439}
440
441func runAudit(targets []*build.Package, env *compile.Env) {
442 if gen(true, targets, env) && env.Errors.IsEmpty() {
443 // Some packages are stale, and there were no errors; return an arbitrary
444 // non-0 exit code. Errors are handled in runHelper, as usual.
445 os.Exit(10)
446 }
447}
448
449func shouldGenerate(config vdltool.Config, lang vdltool.GenLanguage) bool {
450 // If config.GenLanguages is empty, all languages are allowed to be generated.
451 _, ok := config.GenLanguages[lang]
452 return len(config.GenLanguages) == 0 || ok
453}
454
455// gen generates the given targets with env. If audit is true, only checks
456// whether any packages are stale; otherwise files will actually be written out.
457// Returns true if any packages are stale.
458func gen(audit bool, targets []*build.Package, env *compile.Env) bool {
459 anychanged := false
460 for _, target := range targets {
461 pkg := build.BuildPackage(target, env)
462 if pkg == nil {
463 // Stop at the first package that fails to compile.
464 if env.Errors.IsEmpty() {
465 env.Errors.Errorf("%s: internal error (compiled into nil package)", target.Path)
466 }
467 return true
468 }
469 // TODO(toddw): Skip code generation if the semantic contents of the
470 // generated file haven't changed.
471 pkgchanged := false
472 for _, gl := range optGenLangs {
473 switch gl {
474 case vdltool.GenLanguageGo:
475 if !shouldGenerate(pkg.Config, vdltool.GenLanguageGo) {
476 continue
477 }
478 dir, err := xlateOutDir(target.Dir, target.GenPath, optGenGoOutDir, pkg.GenPath)
Suharsh Sivakumar6901a2e2015-04-02 11:39:19 -0700479 if handleErrorOrSkip("--go-out-dir", err, env) {
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800480 continue
481 }
482 for _, file := range pkg.Files {
483 data := golang.Generate(file, env)
484 if writeFile(audit, data, dir, file.BaseName+".go", env) {
485 pkgchanged = true
486 }
487 }
488 case vdltool.GenLanguageJava:
489 if !shouldGenerate(pkg.Config, vdltool.GenLanguageJava) {
490 continue
491 }
492 pkgPath, err := xlatePkgPath(pkg.GenPath, optGenJavaOutPkg)
Suharsh Sivakumar6901a2e2015-04-02 11:39:19 -0700493 if handleErrorOrSkip("--java-out-pkg", err, env) {
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800494 continue
495 }
496 dir, err := xlateOutDir(target.Dir, target.GenPath, optGenJavaOutDir, pkgPath)
Suharsh Sivakumar6901a2e2015-04-02 11:39:19 -0700497 if handleErrorOrSkip("--java-out-dir", err, env) {
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800498 continue
499 }
500 java.SetPkgPathXlator(func(pkgPath string) string {
501 result, _ := xlatePkgPath(pkgPath, optGenJavaOutPkg)
502 return result
503 })
504 for _, file := range java.Generate(pkg, env) {
505 fileDir := filepath.Join(dir, file.Dir)
506 if writeFile(audit, file.Data, fileDir, file.Name, env) {
507 pkgchanged = true
508 }
509 }
510 case vdltool.GenLanguageJavascript:
511 if !shouldGenerate(pkg.Config, vdltool.GenLanguageJavascript) {
512 continue
513 }
514 dir, err := xlateOutDir(target.Dir, target.GenPath, optGenJavascriptOutDir, pkg.GenPath)
Suharsh Sivakumar6901a2e2015-04-02 11:39:19 -0700515 if handleErrorOrSkip("--js-out-dir", err, env) {
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800516 continue
517 }
518 path := func(importPath string) string {
519 prefix := filepath.Clean(target.Dir[0 : len(target.Dir)-len(target.GenPath)])
520 pkgDir := filepath.Join(prefix, filepath.FromSlash(importPath))
521 fullDir, err := xlateOutDir(pkgDir, importPath, optGenJavascriptOutDir, importPath)
522 if err != nil {
523 panic(err)
524 }
525 cleanPath, err := filepath.Rel(dir, fullDir)
526 if err != nil {
527 panic(err)
528 }
529 return cleanPath
530 }
531 data := javascript.Generate(pkg, env, path, optPathToJSCore)
532 if writeFile(audit, data, dir, "index.js", env) {
533 pkgchanged = true
534 }
535 default:
536 env.Errors.Errorf("Generating code for language %v isn't supported", gl)
537 }
538 }
539 if pkgchanged {
540 anychanged = true
541 if optGenStatus {
542 fmt.Println(pkg.Path)
543 }
544 }
545 }
546 return anychanged
547}
548
549// writeFile writes data into the standard location for file, using the given
550// suffix. Errors are reported via env. Returns true iff the file doesn't
551// already exist with the given data.
552func writeFile(audit bool, data []byte, dirName, baseName string, env *compile.Env) bool {
553 dstName := filepath.Join(dirName, baseName)
554 // Don't change anything if old and new are the same.
555 if oldData, err := ioutil.ReadFile(dstName); err == nil && bytes.Equal(oldData, data) {
556 return false
557 }
558 if !audit {
559 // Create containing directory, if it doesn't already exist.
560 if err := os.MkdirAll(dirName, os.FileMode(0777)); err != nil {
561 env.Errors.Errorf("Couldn't create directory %s: %v", dirName, err)
562 return true
563 }
564 if err := ioutil.WriteFile(dstName, data, os.FileMode(0666)); err != nil {
565 env.Errors.Errorf("Couldn't write file %s: %v", dstName, err)
566 return true
567 }
568 }
569 return true
570}
571
572func handleErrorOrSkip(prefix string, err error, env *compile.Env) bool {
573 if err != nil {
574 if err != errSkip {
575 env.Errors.Errorf("%s error: %v", prefix, err)
576 }
577 return true
578 }
579 return false
580}
581
582var errSkip = fmt.Errorf("SKIP")
583
584func xlateOutDir(dir, path string, outdir genOutDir, outPkgPath string) (string, error) {
585 path = filepath.FromSlash(path)
586 outPkgPath = filepath.FromSlash(outPkgPath)
587 // Strip package path from the directory.
588 if !strings.HasSuffix(dir, path) {
589 return "", fmt.Errorf("package dir %q doesn't end with package path %q", dir, path)
590 }
591 dir = filepath.Clean(dir[:len(dir)-len(path)])
592
593 switch {
594 case outdir.dir != "":
595 return filepath.Join(outdir.dir, outPkgPath), nil
596 case len(outdir.rules) == 0:
597 return filepath.Join(dir, outPkgPath), nil
598 }
599 // Try translation rules in order.
600 for _, xlate := range outdir.rules {
601 d := dir
602 if !strings.HasSuffix(d, xlate.src) {
603 continue
604 }
605 if xlate.dst == "SKIP" {
606 return "", errSkip
607 }
608 d = filepath.Clean(d[:len(d)-len(xlate.src)])
609 return filepath.Join(d, xlate.dst, outPkgPath), nil
610 }
611 return "", fmt.Errorf("package prefix %q doesn't match translation rules %q", dir, outdir)
612}
613
614func xlatePkgPath(pkgPath string, rules xlateRules) (string, error) {
615 for _, xlate := range rules {
616 if !strings.HasPrefix(pkgPath, xlate.src) {
617 continue
618 }
619 if xlate.dst == "SKIP" {
620 return pkgPath, errSkip
621 }
622 return xlate.dst + pkgPath[len(xlate.src):], nil
623 }
624 return pkgPath, nil
625}
626
627func runList(targets []*build.Package, env *compile.Env) {
628 for tx, target := range targets {
629 num := fmt.Sprintf("%d", tx)
630 fmt.Printf("%s %s\n", num, strings.Repeat("=", termWidth()-len(num)-1))
631 fmt.Printf("Name: %v\n", target.Name)
632 fmt.Printf("Config: %+v\n", target.Config)
633 fmt.Printf("Path: %v\n", target.Path)
634 fmt.Printf("GenPath: %v\n", target.GenPath)
635 fmt.Printf("Dir: %v\n", target.Dir)
636 if len(target.BaseFileNames) > 0 {
637 fmt.Print("Files:\n")
638 for _, file := range target.BaseFileNames {
639 fmt.Printf(" %v\n", file)
640 }
641 }
642 }
643}
644
645func termWidth() int {
646 if _, width, err := textutil.TerminalSize(); err == nil && width > 0 {
647 return width
648 }
649 return 80 // have a reasonable default
650}