blob: 252598632b958c1fb3b56e24e98cf4a9c166bcd6 [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.
Nicolas Lacasse1b1b9382015-09-24 10:00:35 -07006//go:generate go run $JIRI_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
James Ring58ba6212015-09-16 09:52:23 -070019 "v.io/jiri/collect"
Matt Rosencrantzbca49812015-03-01 21:32:54 -080020 "v.io/v23/vdlroot/vdltool"
Todd Wang9560b9c2015-05-11 13:27:58 -070021 "v.io/x/lib/cmdline"
Matt Rosencrantzbca49812015-03-01 21:32:54 -080022 "v.io/x/lib/textutil"
23 "v.io/x/ref/lib/vdl/build"
24 "v.io/x/ref/lib/vdl/codegen/golang"
25 "v.io/x/ref/lib/vdl/codegen/java"
26 "v.io/x/ref/lib/vdl/codegen/javascript"
27 "v.io/x/ref/lib/vdl/compile"
28 "v.io/x/ref/lib/vdl/vdlutil"
29)
30
31func init() {
32 log.SetFlags(log.Lshortfile | log.Ltime | log.Lmicroseconds)
33}
34
35func main() {
James Ring58ba6212015-09-16 09:52:23 -070036 env := cmdline.EnvFromOS()
37 err := runMain(env)
38 os.Exit(cmdline.ExitCode(err, env.Stderr))
39}
40
41func runMain(env *cmdline.Env) (e error) {
42 args := os.Args[1:]
43 runner, args, err := cmdline.Parse(cmdVDL, env, args)
44 if err != nil {
45 return err
46 }
47 cleanup, errs := maybeExtractBuiltinVdlroot()
48 defer collect.Error(cleanup, &err)
49 if err := checkErrors(errs); err != nil {
50 return err
51 }
52 if err := runner.Run(env, args); err != nil {
53 return err
54 }
55 return nil
Matt Rosencrantzbca49812015-03-01 21:32:54 -080056}
57
58func checkErrors(errs *vdlutil.Errors) error {
59 if errs.IsEmpty() {
60 return nil
61 }
62 return fmt.Errorf(`
63%s (run with "vdl -v" for verbose logging or "vdl help" for help)`, errs)
64}
65
66// runHelper returns a function that generates a sorted list of transitive
67// targets, and calls the supplied run function.
Todd Wang9560b9c2015-05-11 13:27:58 -070068func runHelper(run func(targets []*build.Package, env *compile.Env)) cmdline.Runner {
69 return cmdline.RunnerFunc(func(_ *cmdline.Env, args []string) error {
Matt Rosencrantzbca49812015-03-01 21:32:54 -080070 if flagVerbose {
71 vdlutil.SetVerbose()
72 }
73 if len(args) == 0 {
74 // If the user doesn't specify any targets, the cwd is implied.
75 args = append(args, ".")
76 }
77 env := compile.NewEnv(flagMaxErrors)
78 env.DisallowPathQualifiers()
79 mode := build.UnknownPathIsError
80 if flagIgnoreUnknown {
81 mode = build.UnknownPathIsIgnored
82 }
83 var opts build.Opts
84 opts.Extensions = strings.Split(flagExts, ",")
85 opts.VDLConfigName = flagVDLConfig
86 targets := build.TransitivePackages(args, mode, opts, env.Errors)
87 if err := checkErrors(env.Errors); err != nil {
88 return err
89 }
90 run(targets, env)
91 return checkErrors(env.Errors)
Todd Wangf1550cf2015-05-11 10:58:41 -070092 })
Matt Rosencrantzbca49812015-03-01 21:32:54 -080093}
94
Todd Wang9560b9c2015-05-11 13:27:58 -070095var topicPackages = cmdline.Topic{
Matt Rosencrantzbca49812015-03-01 21:32:54 -080096 Name: "packages",
97 Short: "Description of package lists",
98 Long: `
99Most vdl commands apply to a list of packages:
100
101 vdl command <packages>
102
103<packages> are a list of packages to process, similar to the standard go tool.
104In its simplest form each package is an import path; e.g.
105 "v.io/x/ref/lib/vdl"
106
107A package that is an absolute path or that begins with a . or .. element is
108interpreted as a file system path, and denotes the package in that directory.
109
110A package is a pattern if it includes one or more "..." wildcards, each of which
111can match any string, including the empty string and strings containing
112slashes. Such a pattern expands to all packages found in VDLPATH with names
113matching the pattern. As a special-case, x/... matches x as well as x's
114subdirectories.
115
116The special-case "all" is a synonym for "...", and denotes all packages found
117in VDLPATH.
118
119Import path elements and file names are not allowed to begin with "." or "_";
120such paths are ignored in wildcard matches, and return errors if specified
121explicitly.
122
James Ringc36fad62015-06-09 13:31:00 -0700123Note that whereas GOPATH requires *.go source files and packages to appear
124under a "src" directory, VDLPATH requires *.vdl source files and packages to
125appear directly under the VDLPATH directories.
126
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800127 Run "vdl help vdlpath" to see docs on VDLPATH.
128 Run "go help packages" to see the standard go package docs.
129`,
130}
131
Todd Wang9560b9c2015-05-11 13:27:58 -0700132var topicVdlPath = cmdline.Topic{
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800133 Name: "vdlpath",
134 Short: "Description of VDLPATH environment variable",
135 Long: `
136The VDLPATH environment variable is used to resolve import statements.
137It must be set to compile and generate vdl packages.
138
James Ringc36fad62015-06-09 13:31:00 -0700139The format is a colon-separated list of directories containing vdl source code.
140These directories are searched recursively for VDL source files. The path
141below the directory determines the import path. If VDLPATH specifies multiple
142directories, imports are resolved by picking the first directory with a
143matching import name.
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800144
145An example:
146
147 VDPATH=/home/user/vdlA:/home/user/vdlB
148
James Ringc36fad62015-06-09 13:31:00 -0700149 /home/user/vdlA
150 foo/ (import "foo" refers here)
151 foo1.vdl
152 /home/user/vdlB
153 foo/ (this package is ignored)
154 foo2.vdl
155 bar/
156 baz/ (import "bar/baz" refers here)
157 baz.vdl
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800158`,
159}
160
Todd Wang9560b9c2015-05-11 13:27:58 -0700161var topicVdlRoot = cmdline.Topic{
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800162 Name: "vdlroot",
163 Short: "Description of VDLROOT environment variable",
164 Long: `
165The VDLROOT environment variable is similar to VDLPATH, but instead of pointing
166to multiple user source directories, it points at a single source directory
167containing the standard vdl packages.
168
169Setting VDLROOT is optional.
170
Nicolas Lacasse1b1b9382015-09-24 10:00:35 -0700171If VDLROOT is empty, we try to construct it out of the JIRI_ROOT environment
172variable. It is an error if both VDLROOT and JIRI_ROOT are empty.
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800173`,
174}
175
Todd Wang9560b9c2015-05-11 13:27:58 -0700176var topicVdlConfig = cmdline.Topic{
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800177 Name: "vdl.config",
178 Short: "Description of vdl.config files",
179 Long: `
180Each vdl source package P may contain an optional file "vdl.config" within the P
181directory. This file specifies additional configuration for the vdl tool.
182
183The format of this file is described by the vdltool.Config type in the "vdltool"
184standard package, located at VDLROOT/vdltool/config.vdl.
185
186If the file does not exist, we use the zero value of vdl.Config.
187`,
188}
189
190const pkgArgName = "<packages>"
191const pkgArgLong = `
192<packages> are a list of packages to process, similar to the standard go tool.
193For more information, run "vdl help packages".
194`
195
Todd Wang9560b9c2015-05-11 13:27:58 -0700196var cmdCompile = &cmdline.Command{
Todd Wangf1550cf2015-05-11 10:58:41 -0700197 Runner: runHelper(runCompile),
198 Name: "compile",
199 Short: "Compile packages and dependencies, but don't generate code",
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800200 Long: `
201Compile compiles packages and their transitive dependencies, but does not
202generate code. This is useful to sanity-check that your VDL files are valid.
203`,
204 ArgsName: pkgArgName,
205 ArgsLong: pkgArgLong,
206}
207
Todd Wang9560b9c2015-05-11 13:27:58 -0700208var cmdGenerate = &cmdline.Command{
Todd Wangf1550cf2015-05-11 10:58:41 -0700209 Runner: runHelper(runGenerate),
210 Name: "generate",
211 Short: "Compile packages and dependencies, and generate code",
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800212 Long: `
213Generate compiles packages and their transitive dependencies, and generates code
214in the specified languages.
215`,
216 ArgsName: pkgArgName,
217 ArgsLong: pkgArgLong,
218}
219
Todd Wang9560b9c2015-05-11 13:27:58 -0700220var cmdAudit = &cmdline.Command{
Todd Wangf1550cf2015-05-11 10:58:41 -0700221 Runner: runHelper(runAudit),
222 Name: "audit",
223 Short: "Check if any packages are stale and need generation",
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800224 Long: `
225Audit runs the same logic as generate, but doesn't write out generated files.
226Returns a 0 exit code if all packages are up-to-date, otherwise returns a
227non-0 exit code indicating some packages need generation.
228`,
229 ArgsName: pkgArgName,
230 ArgsLong: pkgArgLong,
231}
232
Todd Wang9560b9c2015-05-11 13:27:58 -0700233var cmdList = &cmdline.Command{
Todd Wangf1550cf2015-05-11 10:58:41 -0700234 Runner: runHelper(runList),
235 Name: "list",
236 Short: "List package and dependency info in transitive order",
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800237 Long: `
238List returns information about packages and their transitive dependencies, in
239transitive order. This is the same order the generate and compile commands use
240for processing. If "vdl list A" is run and A depends on B, which depends on C,
241the returned order will be C, B, A. If multiple packages are specified the
242ordering is over all combined dependencies.
243
244Reminder: cyclic dependencies between packages are not allowed. Cyclic
245dependencies between VDL files within the same package are also not allowed.
246This is more strict than regular Go; it makes it easier to generate code for
247other languages like C++.
248`,
249 ArgsName: pkgArgName,
250 ArgsLong: pkgArgLong,
251}
252
Bogdan Capritadc81f5f2015-03-18 10:08:03 -0700253var genLangAll = genLangs(vdltool.GenLanguageAll[:])
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800254
255type genLangs []vdltool.GenLanguage
256
257func (gls genLangs) String() string {
258 var ret string
259 for i, gl := range gls {
260 if i > 0 {
261 ret += ","
262 }
263 ret += gl.String()
264 }
265 return ret
266}
267
268func (gls *genLangs) Set(value string) error {
Todd Wang9560b9c2015-05-11 13:27:58 -0700269 // If the flag is repeated on the cmdline it is overridden. Duplicates within
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800270 // the comma separated list are ignored, and retain their original ordering.
271 *gls = genLangs{}
272 seen := make(map[vdltool.GenLanguage]bool)
273 for _, str := range strings.Split(value, ",") {
274 gl, err := vdltool.GenLanguageFromString(str)
275 if err != nil {
276 return err
277 }
278 if !seen[gl] {
279 seen[gl] = true
280 *gls = append(*gls, gl)
281 }
282 }
283 return nil
284}
285
286// genOutDir has three modes:
287// 1) If dir is non-empty, we use it as the out dir.
288// 2) If rules is non-empty, we translate using the xlate rules.
289// 3) If everything is empty, we generate in-place.
290type genOutDir struct {
291 dir string
292 rules xlateRules
293}
294
295// xlateSrcDst specifies a translation rule, where src must match the suffix of
296// the path just before the package path, and dst is the replacement for src.
297// If dst is the special string "SKIP" we'll skip generation of packages
298// matching the src.
299type xlateSrcDst struct {
300 src, dst string
301}
302
303// xlateRules specifies a collection of translation rules.
304type xlateRules []xlateSrcDst
305
306func (x *xlateRules) String() (ret string) {
307 for _, srcdst := range *x {
308 if len(ret) > 0 {
309 ret += ","
310 }
311 ret += srcdst.src + "->" + srcdst.dst
312 }
313 return
314}
315
316func (x *xlateRules) Set(value string) error {
317 for _, rule := range strings.Split(value, ",") {
318 srcdst := strings.Split(rule, "->")
319 if len(srcdst) != 2 {
320 return fmt.Errorf("invalid out dir xlate rule %q (not src->dst format)", rule)
321 }
322 *x = append(*x, xlateSrcDst{srcdst[0], srcdst[1]})
323 }
324 return nil
325}
326
327func (x *genOutDir) String() string {
328 if x.dir != "" {
329 return x.dir
330 }
331 return x.rules.String()
332}
333
334func (x *genOutDir) Set(value string) error {
335 if strings.Contains(value, "->") {
336 x.dir = ""
337 return x.rules.Set(value)
338 }
339 x.dir = value
340 return nil
341}
342
343var (
344 // Common flags for the tool itself, applicable to all commands.
James Ring58ba6212015-09-16 09:52:23 -0700345 flagVerbose bool
346 flagMaxErrors int
347 flagExts string
348 flagVDLConfig string
349 flagIgnoreUnknown bool
350 flagBuiltinVdlroot bool
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800351
352 // Options for each command.
353 optCompileStatus bool
354 optGenStatus bool
355 optGenGoOutDir = genOutDir{}
356 optGenJavaOutDir = genOutDir{
357 rules: xlateRules{
Srdjan Petrovicdde7ba02015-06-18 17:41:30 -0700358 {"release/go/src", "release/java/lib/generated-src/vdl"},
359 {"roadmap/go/src", "release/java/lib/generated-src/vdl"},
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800360 },
361 }
362 optGenJavascriptOutDir = genOutDir{
363 rules: xlateRules{
364 {"release/go/src", "release/javascript/core/src"},
365 {"roadmap/go/src", "release/javascript/core/src"},
366 {"third_party/go/src", "SKIP"},
367 {"tools/go/src", "SKIP"},
368 // TODO(toddw): Skip vdlroot javascript generation for now.
369 {"release/go/src/v.io/v23/vdlroot", "SKIP"},
370 },
371 }
372 optGenJavaOutPkg = xlateRules{
373 {"v.io", "io/v"},
374 }
375 optPathToJSCore string
Todd Wang786cf4c2015-04-24 19:07:16 -0700376 // Default to just running the go lang; other langs need special setup.
377 optGenLangs = genLangs{vdltool.GenLanguageGo}
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800378)
379
380// Root returns the root command for the VDL tool.
Todd Wang9560b9c2015-05-11 13:27:58 -0700381var cmdVDL = &cmdline.Command{
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800382 Name: "vdl",
Todd Wang6ed3b6c2015-04-08 14:37:04 -0700383 Short: "manages Vanadium Definition Language source code",
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800384 Long: `
Todd Wang6ed3b6c2015-04-08 14:37:04 -0700385Command vdl manages Vanadium Definition Language source code. It's similar to
386the go tool used for managing Go source code.
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800387`,
Todd Wang9560b9c2015-05-11 13:27:58 -0700388 Children: []*cmdline.Command{cmdGenerate, cmdCompile, cmdAudit, cmdList},
389 Topics: []cmdline.Topic{topicPackages, topicVdlPath, topicVdlRoot, topicVdlConfig},
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800390}
391
392func init() {
393 // Common flags for the tool itself, applicable to all commands.
394 cmdVDL.Flags.BoolVar(&flagVerbose, "v", false, "Turn on verbose logging.")
Suharsh Sivakumar6901a2e2015-04-02 11:39:19 -0700395 cmdVDL.Flags.IntVar(&flagMaxErrors, "max-errors", -1, "Stop processing after this many errors, or -1 for unlimited.")
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800396 cmdVDL.Flags.StringVar(&flagExts, "exts", ".vdl", "Comma-separated list of valid VDL file name extensions.")
397 cmdVDL.Flags.StringVar(&flagVDLConfig, "vdl.config", "vdl.config", "Basename of the optional per-package config file.")
398 cmdVDL.Flags.BoolVar(&flagIgnoreUnknown, "ignore_unknown", false, "Ignore unknown packages provided on the command line.")
Nicolas Lacasse1b1b9382015-09-24 10:00:35 -0700399 cmdVDL.Flags.BoolVar(&flagBuiltinVdlroot, "builtin_vdlroot", false, "If JIRI_ROOT and VDLROOT are not set, use built-in VDL definitions for core types")
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800400
401 // Options for compile.
402 cmdCompile.Flags.BoolVar(&optCompileStatus, "status", true, "Show package names while we compile")
403
404 // Options for generate.
405 cmdGenerate.Flags.Var(&optGenLangs, "lang", "Comma-separated list of languages to generate, currently supporting "+genLangAll.String())
406 cmdGenerate.Flags.BoolVar(&optGenStatus, "status", true, "Show package names as they are updated")
407 // TODO(toddw): Move out_dir configuration into vdl.config, and provide a
408 // generic override mechanism for vdl.config.
Suharsh Sivakumar6901a2e2015-04-02 11:39:19 -0700409 cmdGenerate.Flags.Var(&optGenGoOutDir, "go-out-dir", `
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800410Go output directory. There are three modes:
411 "" : Generate output in-place in the source tree
412 "dir" : Generate output rooted at dir
413 "src->dst[,s2->d2...]" : Generate output using translation rules
414Assume your source tree is organized as follows:
415 VDLPATH=/home/vdl
James Ringc36fad62015-06-09 13:31:00 -0700416 /home/vdl/test_base/base1.vdl
417 /home/vdl/test_base/base2.vdl
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800418Here's example output under the different modes:
Suharsh Sivakumar6901a2e2015-04-02 11:39:19 -0700419 --go-out-dir=""
James Ringc36fad62015-06-09 13:31:00 -0700420 /home/vdl/test_base/base1.vdl.go
421 /home/vdl/test_base/base2.vdl.go
Suharsh Sivakumar6901a2e2015-04-02 11:39:19 -0700422 --go-out-dir="/tmp/foo"
Suharsh Sivakumar8646ba62015-03-18 15:22:28 -0700423 /tmp/foo/test_base/base1.vdl.go
424 /tmp/foo/test_base/base2.vdl.go
James Ringc36fad62015-06-09 13:31:00 -0700425 --go-out-dir="vdl->foo/bar"
426 /home/foo/bar/test_base/base1.vdl.go
427 /home/foo/bar/test_base/base2.vdl.go
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800428When the src->dst form is used, src must match the suffix of the path just
429before the package path, and dst is the replacement for src. Use commas to
430separate multiple rules; the first rule matching src is used. The special dst
431SKIP indicates matching packages are skipped.`)
Suharsh Sivakumar6901a2e2015-04-02 11:39:19 -0700432 cmdGenerate.Flags.Var(&optGenJavaOutDir, "java-out-dir",
433 "Same semantics as --go-out-dir but applies to java code generation.")
434 cmdGenerate.Flags.Var(&optGenJavaOutPkg, "java-out-pkg", `
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800435Java output package translation rules. Must be of the form:
436 "src->dst[,s2->d2...]"
437If a VDL package has a prefix src, the prefix will be replaced with dst. Use
438commas to separate multiple rules; the first rule matching src is used, and if
439there are no matching rules, the package remains unchanged. The special dst
440SKIP indicates matching packages are skipped.`)
Suharsh Sivakumar6901a2e2015-04-02 11:39:19 -0700441 cmdGenerate.Flags.Var(&optGenJavascriptOutDir, "js-out-dir",
442 "Same semantics as --go-out-dir but applies to js code generation.")
443 cmdGenerate.Flags.StringVar(&optPathToJSCore, "js-relative-path-to-core", "",
444 "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 -0800445
446 // Options for audit are identical to generate.
447 cmdAudit.Flags = cmdGenerate.Flags
448}
449
450func runCompile(targets []*build.Package, env *compile.Env) {
451 for _, target := range targets {
452 pkg := build.BuildPackage(target, env)
453 if pkg != nil && optCompileStatus {
454 fmt.Println(pkg.Path)
455 }
456 }
457}
458
459func runGenerate(targets []*build.Package, env *compile.Env) {
460 gen(false, targets, env)
461}
462
463func runAudit(targets []*build.Package, env *compile.Env) {
464 if gen(true, targets, env) && env.Errors.IsEmpty() {
465 // Some packages are stale, and there were no errors; return an arbitrary
466 // non-0 exit code. Errors are handled in runHelper, as usual.
467 os.Exit(10)
468 }
469}
470
471func shouldGenerate(config vdltool.Config, lang vdltool.GenLanguage) bool {
472 // If config.GenLanguages is empty, all languages are allowed to be generated.
473 _, ok := config.GenLanguages[lang]
474 return len(config.GenLanguages) == 0 || ok
475}
476
477// gen generates the given targets with env. If audit is true, only checks
478// whether any packages are stale; otherwise files will actually be written out.
479// Returns true if any packages are stale.
480func gen(audit bool, targets []*build.Package, env *compile.Env) bool {
481 anychanged := false
482 for _, target := range targets {
483 pkg := build.BuildPackage(target, env)
484 if pkg == nil {
485 // Stop at the first package that fails to compile.
486 if env.Errors.IsEmpty() {
487 env.Errors.Errorf("%s: internal error (compiled into nil package)", target.Path)
488 }
489 return true
490 }
491 // TODO(toddw): Skip code generation if the semantic contents of the
492 // generated file haven't changed.
493 pkgchanged := false
494 for _, gl := range optGenLangs {
495 switch gl {
496 case vdltool.GenLanguageGo:
497 if !shouldGenerate(pkg.Config, vdltool.GenLanguageGo) {
498 continue
499 }
500 dir, err := xlateOutDir(target.Dir, target.GenPath, optGenGoOutDir, pkg.GenPath)
Suharsh Sivakumar6901a2e2015-04-02 11:39:19 -0700501 if handleErrorOrSkip("--go-out-dir", err, env) {
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800502 continue
503 }
504 for _, file := range pkg.Files {
505 data := golang.Generate(file, env)
506 if writeFile(audit, data, dir, file.BaseName+".go", env) {
507 pkgchanged = true
508 }
509 }
510 case vdltool.GenLanguageJava:
511 if !shouldGenerate(pkg.Config, vdltool.GenLanguageJava) {
512 continue
513 }
514 pkgPath, err := xlatePkgPath(pkg.GenPath, optGenJavaOutPkg)
Suharsh Sivakumar6901a2e2015-04-02 11:39:19 -0700515 if handleErrorOrSkip("--java-out-pkg", err, env) {
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800516 continue
517 }
518 dir, err := xlateOutDir(target.Dir, target.GenPath, optGenJavaOutDir, pkgPath)
Suharsh Sivakumar6901a2e2015-04-02 11:39:19 -0700519 if handleErrorOrSkip("--java-out-dir", err, env) {
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800520 continue
521 }
522 java.SetPkgPathXlator(func(pkgPath string) string {
523 result, _ := xlatePkgPath(pkgPath, optGenJavaOutPkg)
524 return result
525 })
526 for _, file := range java.Generate(pkg, env) {
527 fileDir := filepath.Join(dir, file.Dir)
528 if writeFile(audit, file.Data, fileDir, file.Name, env) {
529 pkgchanged = true
530 }
531 }
532 case vdltool.GenLanguageJavascript:
533 if !shouldGenerate(pkg.Config, vdltool.GenLanguageJavascript) {
534 continue
535 }
536 dir, err := xlateOutDir(target.Dir, target.GenPath, optGenJavascriptOutDir, pkg.GenPath)
Suharsh Sivakumar6901a2e2015-04-02 11:39:19 -0700537 if handleErrorOrSkip("--js-out-dir", err, env) {
Matt Rosencrantzbca49812015-03-01 21:32:54 -0800538 continue
539 }
540 path := func(importPath string) string {
541 prefix := filepath.Clean(target.Dir[0 : len(target.Dir)-len(target.GenPath)])
542 pkgDir := filepath.Join(prefix, filepath.FromSlash(importPath))
543 fullDir, err := xlateOutDir(pkgDir, importPath, optGenJavascriptOutDir, importPath)
544 if err != nil {
545 panic(err)
546 }
547 cleanPath, err := filepath.Rel(dir, fullDir)
548 if err != nil {
549 panic(err)
550 }
551 return cleanPath
552 }
553 data := javascript.Generate(pkg, env, path, optPathToJSCore)
554 if writeFile(audit, data, dir, "index.js", env) {
555 pkgchanged = true
556 }
557 default:
558 env.Errors.Errorf("Generating code for language %v isn't supported", gl)
559 }
560 }
561 if pkgchanged {
562 anychanged = true
563 if optGenStatus {
564 fmt.Println(pkg.Path)
565 }
566 }
567 }
568 return anychanged
569}
570
571// writeFile writes data into the standard location for file, using the given
572// suffix. Errors are reported via env. Returns true iff the file doesn't
573// already exist with the given data.
574func writeFile(audit bool, data []byte, dirName, baseName string, env *compile.Env) bool {
575 dstName := filepath.Join(dirName, baseName)
576 // Don't change anything if old and new are the same.
577 if oldData, err := ioutil.ReadFile(dstName); err == nil && bytes.Equal(oldData, data) {
578 return false
579 }
580 if !audit {
581 // Create containing directory, if it doesn't already exist.
582 if err := os.MkdirAll(dirName, os.FileMode(0777)); err != nil {
583 env.Errors.Errorf("Couldn't create directory %s: %v", dirName, err)
584 return true
585 }
586 if err := ioutil.WriteFile(dstName, data, os.FileMode(0666)); err != nil {
587 env.Errors.Errorf("Couldn't write file %s: %v", dstName, err)
588 return true
589 }
590 }
591 return true
592}
593
594func handleErrorOrSkip(prefix string, err error, env *compile.Env) bool {
595 if err != nil {
596 if err != errSkip {
597 env.Errors.Errorf("%s error: %v", prefix, err)
598 }
599 return true
600 }
601 return false
602}
603
604var errSkip = fmt.Errorf("SKIP")
605
606func xlateOutDir(dir, path string, outdir genOutDir, outPkgPath string) (string, error) {
607 path = filepath.FromSlash(path)
608 outPkgPath = filepath.FromSlash(outPkgPath)
609 // Strip package path from the directory.
610 if !strings.HasSuffix(dir, path) {
611 return "", fmt.Errorf("package dir %q doesn't end with package path %q", dir, path)
612 }
613 dir = filepath.Clean(dir[:len(dir)-len(path)])
614
615 switch {
616 case outdir.dir != "":
617 return filepath.Join(outdir.dir, outPkgPath), nil
618 case len(outdir.rules) == 0:
619 return filepath.Join(dir, outPkgPath), nil
620 }
621 // Try translation rules in order.
622 for _, xlate := range outdir.rules {
623 d := dir
624 if !strings.HasSuffix(d, xlate.src) {
625 continue
626 }
627 if xlate.dst == "SKIP" {
628 return "", errSkip
629 }
630 d = filepath.Clean(d[:len(d)-len(xlate.src)])
631 return filepath.Join(d, xlate.dst, outPkgPath), nil
632 }
633 return "", fmt.Errorf("package prefix %q doesn't match translation rules %q", dir, outdir)
634}
635
636func xlatePkgPath(pkgPath string, rules xlateRules) (string, error) {
637 for _, xlate := range rules {
638 if !strings.HasPrefix(pkgPath, xlate.src) {
639 continue
640 }
641 if xlate.dst == "SKIP" {
642 return pkgPath, errSkip
643 }
644 return xlate.dst + pkgPath[len(xlate.src):], nil
645 }
646 return pkgPath, nil
647}
648
649func runList(targets []*build.Package, env *compile.Env) {
650 for tx, target := range targets {
651 num := fmt.Sprintf("%d", tx)
652 fmt.Printf("%s %s\n", num, strings.Repeat("=", termWidth()-len(num)-1))
653 fmt.Printf("Name: %v\n", target.Name)
654 fmt.Printf("Config: %+v\n", target.Config)
655 fmt.Printf("Path: %v\n", target.Path)
656 fmt.Printf("GenPath: %v\n", target.GenPath)
657 fmt.Printf("Dir: %v\n", target.Dir)
658 if len(target.BaseFileNames) > 0 {
659 fmt.Print("Files:\n")
660 for _, file := range target.BaseFileNames {
661 fmt.Printf(" %v\n", file)
662 }
663 }
664 }
665}
666
667func termWidth() int {
668 if _, width, err := textutil.TerminalSize(); err == nil && width > 0 {
669 return width
670 }
671 return 80 // have a reasonable default
672}