TBR: ref: namespace_browser: Move v.io/x/ref/tools to v.io/x/ref/cmd.
This is part of the v.io reorganization. cmd is the standard
go directory for commandline binaries. We actually have two:
cmd for regular command line tools, and services for servers.
Note, I am temporarily duplicating the vdl tool for ease of transition.
MultiPart: 4/8
Change-Id: I1880ea23f643b60d315b14e2695c63932fe99625
diff --git a/cmd/vdl/arith_test.go b/cmd/vdl/arith_test.go
new file mode 100644
index 0000000..ddc636e
--- /dev/null
+++ b/cmd/vdl/arith_test.go
@@ -0,0 +1,481 @@
+package main_test
+
+// This test assumes the vdl packages under veyron2/vdl/testdata have been
+// compiled using the vdl binary, and runs end-to-end ipc tests against the
+// generated output. It's meant as a final sanity check of the vdl compiler; by
+// using the compiled results we're behaving as an end-user would behave.
+
+import (
+ "errors"
+ "math"
+ "reflect"
+ "testing"
+
+ "v.io/v23"
+ "v.io/v23/context"
+ "v.io/v23/ipc"
+ "v.io/v23/vdl"
+ "v.io/x/ref/lib/vdl/testdata/arith"
+ "v.io/x/ref/lib/vdl/testdata/base"
+
+ "v.io/x/ref/lib/testutil"
+ _ "v.io/x/ref/profiles"
+)
+
+var generatedError = errors.New("generated error")
+
+func newServer(ctx *context.T) ipc.Server {
+ s, err := v23.NewServer(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return s
+}
+
+// serverArith implements the arith.Arith interface.
+type serverArith struct{}
+
+func (*serverArith) Add(_ ipc.ServerCall, A, B int32) (int32, error) {
+ return A + B, nil
+}
+
+func (*serverArith) DivMod(_ ipc.ServerCall, A, B int32) (int32, int32, error) {
+ return A / B, A % B, nil
+}
+
+func (*serverArith) Sub(_ ipc.ServerCall, args base.Args) (int32, error) {
+ return args.A - args.B, nil
+}
+
+func (*serverArith) Mul(_ ipc.ServerCall, nestedArgs base.NestedArgs) (int32, error) {
+ return nestedArgs.Args.A * nestedArgs.Args.B, nil
+}
+
+func (*serverArith) Count(ctx arith.ArithCountContext, start int32) error {
+ const kNum = 1000
+ for i := int32(0); i < kNum; i++ {
+ if err := ctx.SendStream().Send(start + i); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (*serverArith) StreamingAdd(ctx arith.ArithStreamingAddContext) (int32, error) {
+ var total int32
+ for ctx.RecvStream().Advance() {
+ value := ctx.RecvStream().Value()
+ total += value
+ ctx.SendStream().Send(total)
+ }
+ return total, ctx.RecvStream().Err()
+}
+
+func (*serverArith) GenError(_ ipc.ServerCall) error {
+ return generatedError
+}
+
+func (*serverArith) QuoteAny(_ ipc.ServerCall, any *vdl.Value) (*vdl.Value, error) {
+ return vdl.StringValue(any.String()), nil
+}
+
+type serverCalculator struct {
+ serverArith
+}
+
+func (*serverCalculator) Sine(_ ipc.ServerCall, angle float64) (float64, error) {
+ return math.Sin(angle), nil
+}
+
+func (*serverCalculator) Cosine(_ ipc.ServerCall, angle float64) (float64, error) {
+ return math.Cos(angle), nil
+}
+
+func (*serverCalculator) Exp(_ ipc.ServerCall, x float64) (float64, error) {
+ return math.Exp(x), nil
+}
+
+func (*serverCalculator) On(_ ipc.ServerCall) error {
+ return nil
+}
+
+func (*serverCalculator) Off(_ ipc.ServerCall) error {
+ return nil
+}
+
+func TestCalculator(t *testing.T) {
+ ctx, shutdown := testutil.InitForTest()
+ defer shutdown()
+
+ server := newServer(ctx)
+ eps, err := server.Listen(v23.GetListenSpec(ctx))
+ if err := server.Serve("", arith.CalculatorServer(&serverCalculator{}), nil); err != nil {
+ t.Fatal(err)
+ }
+ root := eps[0].Name()
+ // Synchronous calls
+ calculator := arith.CalculatorClient(root)
+ sine, err := calculator.Sine(ctx, 0)
+ if err != nil {
+ t.Errorf("Sine: got %q but expected no error", err)
+ }
+ if sine != 0 {
+ t.Errorf("Sine: expected 0 got %f", sine)
+ }
+ cosine, err := calculator.Cosine(ctx, 0)
+ if err != nil {
+ t.Errorf("Cosine: got %q but expected no error", err)
+ }
+ if cosine != 1 {
+ t.Errorf("Cosine: expected 1 got %f", cosine)
+ }
+
+ ar := arith.ArithClient(root)
+ sum, err := ar.Add(ctx, 7, 8)
+ if err != nil {
+ t.Errorf("Add: got %q but expected no error", err)
+ }
+ if sum != 15 {
+ t.Errorf("Add: expected 15 got %d", sum)
+ }
+ ar = calculator
+ sum, err = ar.Add(ctx, 7, 8)
+ if err != nil {
+ t.Errorf("Add: got %q but expected no error", err)
+ }
+ if sum != 15 {
+ t.Errorf("Add: expected 15 got %d", sum)
+ }
+
+ trig := arith.TrigonometryClient(root)
+ cosine, err = trig.Cosine(ctx, 0)
+ if err != nil {
+ t.Errorf("Cosine: got %q but expected no error", err)
+ }
+ if cosine != 1 {
+ t.Errorf("Cosine: expected 1 got %f", cosine)
+ }
+
+ // Test auto-generated methods.
+ serverStub := arith.CalculatorServer(&serverCalculator{})
+ expectDesc(t, serverStub.Describe__(), []ipc.InterfaceDesc{
+ {
+ Name: "Calculator",
+ PkgPath: "v.io/x/ref/lib/vdl/testdata/arith",
+ Embeds: []ipc.EmbedDesc{
+ {
+ Name: "Arith",
+ PkgPath: "v.io/x/ref/lib/vdl/testdata/arith",
+ },
+ {
+ Name: "AdvancedMath",
+ PkgPath: "v.io/x/ref/lib/vdl/testdata/arith",
+ },
+ },
+ Methods: []ipc.MethodDesc{
+ {Name: "On"},
+ {Name: "Off", Tags: []*vdl.Value{vdl.StringValue("offtag")}},
+ },
+ },
+ {
+ Name: "Arith",
+ PkgPath: "v.io/x/ref/lib/vdl/testdata/arith",
+ Methods: []ipc.MethodDesc{
+ {
+ Name: "Add",
+ InArgs: []ipc.ArgDesc{{Name: "a"}, {Name: "b"}},
+ OutArgs: []ipc.ArgDesc{{}},
+ },
+ {
+ Name: "DivMod",
+ InArgs: []ipc.ArgDesc{{Name: "a"}, {Name: "b"}},
+ OutArgs: []ipc.ArgDesc{{Name: "quot"}, {Name: "rem"}},
+ },
+ {
+ Name: "Sub",
+ InArgs: []ipc.ArgDesc{{Name: "args"}},
+ OutArgs: []ipc.ArgDesc{{}},
+ },
+ {
+ Name: "Mul",
+ InArgs: []ipc.ArgDesc{{Name: "nested"}},
+ OutArgs: []ipc.ArgDesc{{}},
+ },
+ {
+ Name: "GenError",
+ Tags: []*vdl.Value{vdl.StringValue("foo"), vdl.StringValue("barz"), vdl.StringValue("hello"), vdl.Int32Value(129), vdl.Uint64Value(0x24)},
+ },
+ {
+ Name: "Count",
+ InArgs: []ipc.ArgDesc{{Name: "start"}},
+ },
+ {
+ Name: "StreamingAdd",
+ OutArgs: []ipc.ArgDesc{{Name: "total"}},
+ },
+ {
+ Name: "QuoteAny",
+ InArgs: []ipc.ArgDesc{{Name: "a"}},
+ OutArgs: []ipc.ArgDesc{{}},
+ },
+ },
+ },
+ {
+ Name: "AdvancedMath",
+ PkgPath: "v.io/x/ref/lib/vdl/testdata/arith",
+ Embeds: []ipc.EmbedDesc{
+ {
+ Name: "Trigonometry",
+ PkgPath: "v.io/x/ref/lib/vdl/testdata/arith",
+ },
+ {
+ Name: "Exp",
+ PkgPath: "v.io/x/ref/lib/vdl/testdata/arith/exp",
+ }},
+ },
+ {
+ Name: "Trigonometry",
+ PkgPath: "v.io/x/ref/lib/vdl/testdata/arith",
+ Doc: "// Trigonometry is an interface that specifies a couple trigonometric functions.",
+ Methods: []ipc.MethodDesc{
+ {
+ Name: "Sine",
+ InArgs: []ipc.ArgDesc{
+ {"angle", ``}, // float64
+ },
+ OutArgs: []ipc.ArgDesc{
+ {"", ``}, // float64
+ },
+ },
+ {
+ Name: "Cosine",
+ InArgs: []ipc.ArgDesc{
+ {"angle", ``}, // float64
+ },
+ OutArgs: []ipc.ArgDesc{
+ {"", ``}, // float64
+ },
+ },
+ },
+ },
+ {
+ Name: "Exp",
+ PkgPath: "v.io/x/ref/lib/vdl/testdata/arith/exp",
+ Methods: []ipc.MethodDesc{
+ {
+ Name: "Exp",
+ InArgs: []ipc.ArgDesc{
+ {"x", ``}, // float64
+ },
+ OutArgs: []ipc.ArgDesc{
+ {"", ``}, // float64
+ },
+ },
+ },
+ },
+ })
+}
+
+func TestArith(t *testing.T) {
+ ctx, shutdown := testutil.InitForTest()
+ defer shutdown()
+
+ // TODO(bprosnitz) Split this test up -- it is quite long and hard to debug.
+
+ // We try a few types of dispatchers on the server side, to verify that
+ // anything dispatching to Arith or an interface embedding Arith (like
+ // Calculator) works for a client looking to talk to an Arith service.
+ objects := []interface{}{
+ arith.ArithServer(&serverArith{}),
+ arith.ArithServer(&serverCalculator{}),
+ arith.CalculatorServer(&serverCalculator{}),
+ }
+
+ for i, obj := range objects {
+ server := newServer(ctx)
+ defer server.Stop()
+ eps, err := server.Listen(v23.GetListenSpec(ctx))
+ if err != nil {
+ t.Fatal(err)
+ }
+ root := eps[0].Name()
+ if err := server.Serve("", obj, nil); err != nil {
+ t.Fatalf("%d: %v", i, err)
+ }
+ // Synchronous calls
+ ar := arith.ArithClient(root)
+ sum, err := ar.Add(ctx, 7, 8)
+ if err != nil {
+ t.Errorf("Add: got %q but expected no error", err)
+ }
+ if sum != 15 {
+ t.Errorf("Add: expected 15 got %d", sum)
+ }
+ q, r, err := ar.DivMod(ctx, 7, 3)
+ if err != nil {
+ t.Errorf("DivMod: got %q but expected no error", err)
+ }
+ if q != 2 || r != 1 {
+ t.Errorf("DivMod: expected (2,1) got (%d,%d)", q, r)
+ }
+ diff, err := ar.Sub(ctx, base.Args{7, 8})
+ if err != nil {
+ t.Errorf("Sub: got %q but expected no error", err)
+ }
+ if diff != -1 {
+ t.Errorf("Sub: got %d, expected -1", diff)
+ }
+ prod, err := ar.Mul(ctx, base.NestedArgs{base.Args{7, 8}})
+ if err != nil {
+ t.Errorf("Mul: got %q, but expected no error", err)
+ }
+ if prod != 56 {
+ t.Errorf("Sub: got %d, expected 56", prod)
+ }
+ stream, err := ar.Count(ctx, 35)
+ if err != nil {
+ t.Fatalf("error while executing Count %v", err)
+ }
+
+ countIterator := stream.RecvStream()
+ for i := int32(0); i < 1000; i++ {
+ if !countIterator.Advance() {
+ t.Errorf("Error getting value %v", countIterator.Err())
+ }
+ val := countIterator.Value()
+ if val != 35+i {
+ t.Errorf("Expected value %d, got %d", 35+i, val)
+ }
+ }
+ if countIterator.Advance() || countIterator.Err() != nil {
+ t.Errorf("Reply stream should have been closed %v", countIterator.Err())
+ }
+
+ if err := stream.Finish(); err != nil {
+ t.Errorf("Count failed with %v", err)
+ }
+
+ addStream, err := ar.StreamingAdd(ctx)
+
+ go func() {
+ sender := addStream.SendStream()
+ for i := int32(0); i < 100; i++ {
+ if err := sender.Send(i); err != nil {
+ t.Errorf("Send error %v", err)
+ }
+ }
+ if err := sender.Close(); err != nil {
+ t.Errorf("Close error %v", err)
+ }
+ }()
+
+ var expectedSum int32
+ rStream := addStream.RecvStream()
+ for i := int32(0); i < 100; i++ {
+ expectedSum += i
+ if !rStream.Advance() {
+ t.Errorf("Error getting value %v", rStream.Err())
+ }
+ value := rStream.Value()
+ if value != expectedSum {
+ t.Errorf("Got %d but expected %d", value, expectedSum)
+ }
+ }
+
+ if rStream.Advance() || rStream.Err() != nil {
+ t.Errorf("Reply stream should have been closed %v", rStream.Err())
+ }
+
+ total, err := addStream.Finish()
+
+ if err != nil {
+ t.Errorf("Count failed with %v", err)
+ }
+
+ if total != expectedSum {
+ t.Errorf("Got %d but expexted %d", total, expectedSum)
+ }
+
+ if err := ar.GenError(ctx); err == nil {
+ t.Errorf("GenError: got %v but expected %v", err, generatedError)
+ }
+
+ // Server-side stubs
+
+ serverStub := arith.ArithServer(&serverArith{})
+ expectDesc(t, serverStub.Describe__(), []ipc.InterfaceDesc{
+ {
+ Name: "Arith",
+ PkgPath: "v.io/x/ref/lib/vdl/testdata/arith",
+ Methods: []ipc.MethodDesc{
+ {
+ Name: "Add",
+ InArgs: []ipc.ArgDesc{{Name: "a"}, {Name: "b"}},
+ OutArgs: []ipc.ArgDesc{{}},
+ },
+ {
+ Name: "DivMod",
+ InArgs: []ipc.ArgDesc{{Name: "a"}, {Name: "b"}},
+ OutArgs: []ipc.ArgDesc{{Name: "quot"}, {Name: "rem"}},
+ },
+ {
+ Name: "Sub",
+ InArgs: []ipc.ArgDesc{{Name: "args"}},
+ OutArgs: []ipc.ArgDesc{{}},
+ },
+ {
+ Name: "Mul",
+ InArgs: []ipc.ArgDesc{{Name: "nested"}},
+ OutArgs: []ipc.ArgDesc{{}},
+ },
+ {
+ Name: "GenError",
+ Tags: []*vdl.Value{vdl.StringValue("foo"), vdl.StringValue("barz"), vdl.StringValue("hello"), vdl.Int32Value(129), vdl.Uint64Value(0x24)},
+ },
+ {
+ Name: "Count",
+ InArgs: []ipc.ArgDesc{{Name: "start"}},
+ },
+ {
+ Name: "StreamingAdd",
+ OutArgs: []ipc.ArgDesc{{Name: "total"}},
+ },
+ {
+ Name: "QuoteAny",
+ InArgs: []ipc.ArgDesc{{Name: "a"}},
+ OutArgs: []ipc.ArgDesc{{}},
+ },
+ },
+ },
+ })
+ }
+}
+
+func expectDesc(t *testing.T, got, want []ipc.InterfaceDesc) {
+ stripDesc(got)
+ stripDesc(want)
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("Describe__ got %#v, want %#v", got, want)
+ }
+}
+
+func stripDesc(desc []ipc.InterfaceDesc) {
+ // Don't bother testing the documentation, to avoid spurious changes.
+ for i := range desc {
+ desc[i].Doc = ""
+ for j := range desc[i].Embeds {
+ desc[i].Embeds[j].Doc = ""
+ }
+ for j := range desc[i].Methods {
+ desc[i].Methods[j].Doc = ""
+ for k := range desc[i].Methods[j].InArgs {
+ desc[i].Methods[j].InArgs[k].Doc = ""
+ }
+ for k := range desc[i].Methods[j].OutArgs {
+ desc[i].Methods[j].OutArgs[k].Doc = ""
+ }
+ desc[i].Methods[j].InStream.Doc = ""
+ desc[i].Methods[j].OutStream.Doc = ""
+ }
+ }
+}
diff --git a/cmd/vdl/doc.go b/cmd/vdl/doc.go
new file mode 100644
index 0000000..e4831a0
--- /dev/null
+++ b/cmd/vdl/doc.go
@@ -0,0 +1,279 @@
+// This file was auto-generated via go generate.
+// DO NOT UPDATE MANUALLY
+
+/*
+The vdl tool manages veyron VDL source code. It's similar to the go tool used
+for managing Go source code.
+
+Usage:
+ vdl [flags] <command>
+
+The vdl commands are:
+ generate Compile packages and dependencies, and generate code
+ compile Compile packages and dependencies, but don't generate code
+ audit Check if any packages are stale and need generation
+ list List package and dependency info in transitive order
+ help Display help for commands or topics
+Run "vdl help [command]" for command usage.
+
+The vdl additional help topics are:
+ packages Description of package lists
+ vdlpath Description of VDLPATH environment variable
+ vdlroot Description of VDLROOT environment variable
+ vdl.config Description of vdl.config files
+Run "vdl help [topic]" for topic details.
+
+The vdl flags are:
+ -exts=.vdl
+ Comma-separated list of valid VDL file name extensions.
+ -ignore_unknown=false
+ Ignore unknown packages provided on the command line.
+ -max_errors=-1
+ Stop processing after this many errors, or -1 for unlimited.
+ -v=false
+ Turn on verbose logging.
+ -vdl.config=vdl.config
+ Basename of the optional per-package config file.
+
+Vdl Generate
+
+Generate compiles packages and their transitive dependencies, and generates code
+in the specified languages.
+
+Usage:
+ vdl generate [flags] <packages>
+
+<packages> are a list of packages to process, similar to the standard go tool.
+For more information, run "vdl help packages".
+
+The vdl generate flags are:
+ -go_out_dir=
+ Go output directory. There are three modes:
+ "" : Generate output in-place in the source tree
+ "dir" : Generate output rooted at dir
+ "src->dst[,s2->d2...]" : Generate output using translation rules
+ Assume your source tree is organized as follows:
+ VDLPATH=/home/vdl
+ /home/vdl/src/veyron/test_base/base1.vdl
+ /home/vdl/src/veyron/test_base/base2.vdl
+ Here's example output under the different modes:
+ --go_out_dir=""
+ /home/vdl/src/veyron/test_base/base1.vdl.go
+ /home/vdl/src/veyron/test_base/base2.vdl.go
+ --go_out_dir="/tmp/foo"
+ /tmp/foo/veyron/test_base/base1.vdl.go
+ /tmp/foo/veyron/test_base/base2.vdl.go
+ --go_out_dir="vdl/src->foo/bar/src"
+ /home/foo/bar/src/veyron/test_base/base1.vdl.go
+ /home/foo/bar/src/veyron/test_base/base2.vdl.go
+ When the src->dst form is used, src must match the suffix of the path just
+ before the package path, and dst is the replacement for src. Use commas to
+ separate multiple rules; the first rule matching src is used. The special
+ dst SKIP indicates matching packages are skipped.
+ -java_out_dir=go/src->java/src/vdl/java
+ Same semantics as --go_out_dir but applies to java code generation.
+ -java_out_pkg=v.io->io/v
+ Java output package translation rules. Must be of the form:
+ "src->dst[,s2->d2...]"
+ If a VDL package has a prefix src, the prefix will be replaced with dst. Use
+ commas to separate multiple rules; the first rule matching src is used, and
+ if there are no matching rules, the package remains unchanged. The special
+ dst SKIP indicates matching packages are skipped.
+ -js_out_dir=release/go/src->release/javascript/core/src,roadmap/go/src->release/javascript/core/src,third_party/go/src->SKIP,tools/go/src->SKIP,release/go/src/v.io/v23/vdlroot->SKIP
+ Same semantics as --go_out_dir but applies to js code generation.
+ -js_relative_path_to_core=
+ If set, this is the relative path from js_out_dir to the root of the JS core
+ -lang=Go,Java
+ Comma-separated list of languages to generate, currently supporting
+ Go,Java,Javascript
+ -status=true
+ Show package names as they are updated
+
+Vdl Compile
+
+Compile compiles packages and their transitive dependencies, but does not
+generate code. This is useful to sanity-check that your VDL files are valid.
+
+Usage:
+ vdl compile [flags] <packages>
+
+<packages> are a list of packages to process, similar to the standard go tool.
+For more information, run "vdl help packages".
+
+The vdl compile flags are:
+ -status=true
+ Show package names while we compile
+
+Vdl Audit
+
+Audit runs the same logic as generate, but doesn't write out generated files.
+Returns a 0 exit code if all packages are up-to-date, otherwise returns a non-0
+exit code indicating some packages need generation.
+
+Usage:
+ vdl audit [flags] <packages>
+
+<packages> are a list of packages to process, similar to the standard go tool.
+For more information, run "vdl help packages".
+
+The vdl audit flags are:
+ -go_out_dir=
+ Go output directory. There are three modes:
+ "" : Generate output in-place in the source tree
+ "dir" : Generate output rooted at dir
+ "src->dst[,s2->d2...]" : Generate output using translation rules
+ Assume your source tree is organized as follows:
+ VDLPATH=/home/vdl
+ /home/vdl/src/veyron/test_base/base1.vdl
+ /home/vdl/src/veyron/test_base/base2.vdl
+ Here's example output under the different modes:
+ --go_out_dir=""
+ /home/vdl/src/veyron/test_base/base1.vdl.go
+ /home/vdl/src/veyron/test_base/base2.vdl.go
+ --go_out_dir="/tmp/foo"
+ /tmp/foo/veyron/test_base/base1.vdl.go
+ /tmp/foo/veyron/test_base/base2.vdl.go
+ --go_out_dir="vdl/src->foo/bar/src"
+ /home/foo/bar/src/veyron/test_base/base1.vdl.go
+ /home/foo/bar/src/veyron/test_base/base2.vdl.go
+ When the src->dst form is used, src must match the suffix of the path just
+ before the package path, and dst is the replacement for src. Use commas to
+ separate multiple rules; the first rule matching src is used. The special
+ dst SKIP indicates matching packages are skipped.
+ -java_out_dir=go/src->java/src/vdl/java
+ Same semantics as --go_out_dir but applies to java code generation.
+ -java_out_pkg=v.io->io/v
+ Java output package translation rules. Must be of the form:
+ "src->dst[,s2->d2...]"
+ If a VDL package has a prefix src, the prefix will be replaced with dst. Use
+ commas to separate multiple rules; the first rule matching src is used, and
+ if there are no matching rules, the package remains unchanged. The special
+ dst SKIP indicates matching packages are skipped.
+ -js_out_dir=release/go/src->release/javascript/core/src,roadmap/go/src->release/javascript/core/src,third_party/go/src->SKIP,tools/go/src->SKIP,release/go/src/v.io/v23/vdlroot->SKIP
+ Same semantics as --go_out_dir but applies to js code generation.
+ -js_relative_path_to_core=
+ If set, this is the relative path from js_out_dir to the root of the JS core
+ -lang=Go,Java
+ Comma-separated list of languages to generate, currently supporting
+ Go,Java,Javascript
+ -status=true
+ Show package names as they are updated
+
+Vdl List
+
+List returns information about packages and their transitive dependencies, in
+transitive order. This is the same order the generate and compile commands use
+for processing. If "vdl list A" is run and A depends on B, which depends on C,
+the returned order will be C, B, A. If multiple packages are specified the
+ordering is over all combined dependencies.
+
+Reminder: cyclic dependencies between packages are not allowed. Cyclic
+dependencies between VDL files within the same package are also not allowed.
+This is more strict than regular Go; it makes it easier to generate code for
+other languages like C++.
+
+Usage:
+ vdl list <packages>
+
+<packages> are a list of packages to process, similar to the standard go tool.
+For more information, run "vdl help packages".
+
+Vdl Help
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+The output is formatted to a target width in runes. The target width is
+determined by checking the environment variable CMDLINE_WIDTH, falling back on
+the terminal width from the OS, falling back on 80 chars. By setting
+CMDLINE_WIDTH=x, if x > 0 the width is x, if x < 0 the width is unlimited, and
+if x == 0 or is unset one of the fallbacks is used.
+
+Usage:
+ vdl help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The vdl help flags are:
+ -style=text
+ The formatting style for help output, either "text" or "godoc".
+
+Vdl Packages - help topic
+
+Most vdl commands apply to a list of packages:
+
+ vdl command <packages>
+
+<packages> are a list of packages to process, similar to the standard go tool.
+In its simplest form each package is an import path; e.g.
+ "v.io/x/ref/lib/vdl"
+
+A package that is an absolute path or that begins with a . or .. element is
+interpreted as a file system path, and denotes the package in that directory.
+
+A package is a pattern if it includes one or more "..." wildcards, each of which
+can match any string, including the empty string and strings containing slashes.
+Such a pattern expands to all packages found in VDLPATH with names matching the
+pattern. As a special-case, x/... matches x as well as x's subdirectories.
+
+The special-case "all" is a synonym for "...", and denotes all packages found in
+VDLPATH.
+
+Import path elements and file names are not allowed to begin with "." or "_";
+such paths are ignored in wildcard matches, and return errors if specified
+explicitly.
+
+ Run "vdl help vdlpath" to see docs on VDLPATH.
+ Run "go help packages" to see the standard go package docs.
+
+Vdl Vdlpath - help topic
+
+The VDLPATH environment variable is used to resolve import statements. It must
+be set to compile and generate vdl packages.
+
+The format is a colon-separated list of directories, where each directory must
+have a "src/" directory that holds vdl source code. The path below 'src'
+determines the import path. If VDLPATH specifies multiple directories, imports
+are resolved by picking the first directory with a matching import name.
+
+An example:
+
+ VDPATH=/home/user/vdlA:/home/user/vdlB
+
+ /home/user/vdlA/
+ src/
+ foo/ (import "foo" refers here)
+ foo1.vdl
+ /home/user/vdlB/
+ src/
+ foo/ (this package is ignored)
+ foo2.vdl
+ bar/
+ baz/ (import "bar/baz" refers here)
+ baz.vdl
+
+Vdl Vdlroot - help topic
+
+The VDLROOT environment variable is similar to VDLPATH, but instead of pointing
+to multiple user source directories, it points at a single source directory
+containing the standard vdl packages.
+
+Setting VDLROOT is optional.
+
+If VDLROOT is empty, we try to construct it out of the VANADIUM_ROOT environment
+variable. It is an error if both VDLROOT and VANADIUM_ROOT are empty.
+
+Vdl Vdl.Config - help topic
+
+Each vdl source package P may contain an optional file "vdl.config" within the P
+directory. This file specifies additional configuration for the vdl tool.
+
+The format of this file is described by the vdltool.Config type in the "vdltool"
+standard package, located at VDLROOT/vdltool/config.vdl.
+
+If the file does not exist, we use the zero value of vdl.Config.
+*/
+package main
diff --git a/cmd/vdl/main.go b/cmd/vdl/main.go
new file mode 100644
index 0000000..bcd20ee
--- /dev/null
+++ b/cmd/vdl/main.go
@@ -0,0 +1,642 @@
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $VANADIUM_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "v.io/v23/vdlroot/vdltool"
+ "v.io/x/lib/cmdline"
+ "v.io/x/lib/textutil"
+ "v.io/x/ref/lib/vdl/build"
+ "v.io/x/ref/lib/vdl/codegen/golang"
+ "v.io/x/ref/lib/vdl/codegen/java"
+ "v.io/x/ref/lib/vdl/codegen/javascript"
+ "v.io/x/ref/lib/vdl/compile"
+ "v.io/x/ref/lib/vdl/vdlutil"
+)
+
+func init() {
+ log.SetFlags(log.Lshortfile | log.Ltime | log.Lmicroseconds)
+}
+
+func main() {
+ os.Exit(cmdVDL.Main())
+}
+
+func checkErrors(errs *vdlutil.Errors) error {
+ if errs.IsEmpty() {
+ return nil
+ }
+ return fmt.Errorf(`
+%s (run with "vdl -v" for verbose logging or "vdl help" for help)`, errs)
+}
+
+// runHelper returns a function that generates a sorted list of transitive
+// targets, and calls the supplied run function.
+func runHelper(run func(targets []*build.Package, env *compile.Env)) func(cmd *cmdline.Command, args []string) error {
+ return func(cmd *cmdline.Command, args []string) error {
+ if flagVerbose {
+ vdlutil.SetVerbose()
+ }
+ if len(args) == 0 {
+ // If the user doesn't specify any targets, the cwd is implied.
+ args = append(args, ".")
+ }
+ env := compile.NewEnv(flagMaxErrors)
+ env.DisallowPathQualifiers()
+ mode := build.UnknownPathIsError
+ if flagIgnoreUnknown {
+ mode = build.UnknownPathIsIgnored
+ }
+ var opts build.Opts
+ opts.Extensions = strings.Split(flagExts, ",")
+ opts.VDLConfigName = flagVDLConfig
+ targets := build.TransitivePackages(args, mode, opts, env.Errors)
+ if err := checkErrors(env.Errors); err != nil {
+ return err
+ }
+ run(targets, env)
+ return checkErrors(env.Errors)
+ }
+}
+
+var topicPackages = cmdline.Topic{
+ Name: "packages",
+ Short: "Description of package lists",
+ Long: `
+Most vdl commands apply to a list of packages:
+
+ vdl command <packages>
+
+<packages> are a list of packages to process, similar to the standard go tool.
+In its simplest form each package is an import path; e.g.
+ "v.io/x/ref/lib/vdl"
+
+A package that is an absolute path or that begins with a . or .. element is
+interpreted as a file system path, and denotes the package in that directory.
+
+A package is a pattern if it includes one or more "..." wildcards, each of which
+can match any string, including the empty string and strings containing
+slashes. Such a pattern expands to all packages found in VDLPATH with names
+matching the pattern. As a special-case, x/... matches x as well as x's
+subdirectories.
+
+The special-case "all" is a synonym for "...", and denotes all packages found
+in VDLPATH.
+
+Import path elements and file names are not allowed to begin with "." or "_";
+such paths are ignored in wildcard matches, and return errors if specified
+explicitly.
+
+ Run "vdl help vdlpath" to see docs on VDLPATH.
+ Run "go help packages" to see the standard go package docs.
+`,
+}
+
+var topicVdlPath = cmdline.Topic{
+ Name: "vdlpath",
+ Short: "Description of VDLPATH environment variable",
+ Long: `
+The VDLPATH environment variable is used to resolve import statements.
+It must be set to compile and generate vdl packages.
+
+The format is a colon-separated list of directories, where each directory must
+have a "src/" directory that holds vdl source code. The path below 'src'
+determines the import path. If VDLPATH specifies multiple directories, imports
+are resolved by picking the first directory with a matching import name.
+
+An example:
+
+ VDPATH=/home/user/vdlA:/home/user/vdlB
+
+ /home/user/vdlA/
+ src/
+ foo/ (import "foo" refers here)
+ foo1.vdl
+ /home/user/vdlB/
+ src/
+ foo/ (this package is ignored)
+ foo2.vdl
+ bar/
+ baz/ (import "bar/baz" refers here)
+ baz.vdl
+`,
+}
+
+var topicVdlRoot = cmdline.Topic{
+ Name: "vdlroot",
+ Short: "Description of VDLROOT environment variable",
+ Long: `
+The VDLROOT environment variable is similar to VDLPATH, but instead of pointing
+to multiple user source directories, it points at a single source directory
+containing the standard vdl packages.
+
+Setting VDLROOT is optional.
+
+If VDLROOT is empty, we try to construct it out of the VANADIUM_ROOT environment
+variable. It is an error if both VDLROOT and VANADIUM_ROOT are empty.
+`,
+}
+
+var topicVdlConfig = cmdline.Topic{
+ Name: "vdl.config",
+ Short: "Description of vdl.config files",
+ Long: `
+Each vdl source package P may contain an optional file "vdl.config" within the P
+directory. This file specifies additional configuration for the vdl tool.
+
+The format of this file is described by the vdltool.Config type in the "vdltool"
+standard package, located at VDLROOT/vdltool/config.vdl.
+
+If the file does not exist, we use the zero value of vdl.Config.
+`,
+}
+
+const pkgArgName = "<packages>"
+const pkgArgLong = `
+<packages> are a list of packages to process, similar to the standard go tool.
+For more information, run "vdl help packages".
+`
+
+var cmdCompile = &cmdline.Command{
+ Run: runHelper(runCompile),
+ Name: "compile",
+ Short: "Compile packages and dependencies, but don't generate code",
+ Long: `
+Compile compiles packages and their transitive dependencies, but does not
+generate code. This is useful to sanity-check that your VDL files are valid.
+`,
+ ArgsName: pkgArgName,
+ ArgsLong: pkgArgLong,
+}
+
+var cmdGenerate = &cmdline.Command{
+ Run: runHelper(runGenerate),
+ Name: "generate",
+ Short: "Compile packages and dependencies, and generate code",
+ Long: `
+Generate compiles packages and their transitive dependencies, and generates code
+in the specified languages.
+`,
+ ArgsName: pkgArgName,
+ ArgsLong: pkgArgLong,
+}
+
+var cmdAudit = &cmdline.Command{
+ Run: runHelper(runAudit),
+ Name: "audit",
+ Short: "Check if any packages are stale and need generation",
+ Long: `
+Audit runs the same logic as generate, but doesn't write out generated files.
+Returns a 0 exit code if all packages are up-to-date, otherwise returns a
+non-0 exit code indicating some packages need generation.
+`,
+ ArgsName: pkgArgName,
+ ArgsLong: pkgArgLong,
+}
+
+var cmdList = &cmdline.Command{
+ Run: runHelper(runList),
+ Name: "list",
+ Short: "List package and dependency info in transitive order",
+ Long: `
+List returns information about packages and their transitive dependencies, in
+transitive order. This is the same order the generate and compile commands use
+for processing. If "vdl list A" is run and A depends on B, which depends on C,
+the returned order will be C, B, A. If multiple packages are specified the
+ordering is over all combined dependencies.
+
+Reminder: cyclic dependencies between packages are not allowed. Cyclic
+dependencies between VDL files within the same package are also not allowed.
+This is more strict than regular Go; it makes it easier to generate code for
+other languages like C++.
+`,
+ ArgsName: pkgArgName,
+ ArgsLong: pkgArgLong,
+}
+
+var genLangAll = genLangs(vdltool.GenLanguageAll)
+
+type genLangs []vdltool.GenLanguage
+
+func (gls genLangs) String() string {
+ var ret string
+ for i, gl := range gls {
+ if i > 0 {
+ ret += ","
+ }
+ ret += gl.String()
+ }
+ return ret
+}
+
+func (gls *genLangs) Set(value string) error {
+ // If the flag is repeated on the cmdline it is overridden. Duplicates within
+ // the comma separated list are ignored, and retain their original ordering.
+ *gls = genLangs{}
+ seen := make(map[vdltool.GenLanguage]bool)
+ for _, str := range strings.Split(value, ",") {
+ gl, err := vdltool.GenLanguageFromString(str)
+ if err != nil {
+ return err
+ }
+ if !seen[gl] {
+ seen[gl] = true
+ *gls = append(*gls, gl)
+ }
+ }
+ return nil
+}
+
+// genOutDir has three modes:
+// 1) If dir is non-empty, we use it as the out dir.
+// 2) If rules is non-empty, we translate using the xlate rules.
+// 3) If everything is empty, we generate in-place.
+type genOutDir struct {
+ dir string
+ rules xlateRules
+}
+
+// xlateSrcDst specifies a translation rule, where src must match the suffix of
+// the path just before the package path, and dst is the replacement for src.
+// If dst is the special string "SKIP" we'll skip generation of packages
+// matching the src.
+type xlateSrcDst struct {
+ src, dst string
+}
+
+// xlateRules specifies a collection of translation rules.
+type xlateRules []xlateSrcDst
+
+func (x *xlateRules) String() (ret string) {
+ for _, srcdst := range *x {
+ if len(ret) > 0 {
+ ret += ","
+ }
+ ret += srcdst.src + "->" + srcdst.dst
+ }
+ return
+}
+
+func (x *xlateRules) Set(value string) error {
+ for _, rule := range strings.Split(value, ",") {
+ srcdst := strings.Split(rule, "->")
+ if len(srcdst) != 2 {
+ return fmt.Errorf("invalid out dir xlate rule %q (not src->dst format)", rule)
+ }
+ *x = append(*x, xlateSrcDst{srcdst[0], srcdst[1]})
+ }
+ return nil
+}
+
+func (x *genOutDir) String() string {
+ if x.dir != "" {
+ return x.dir
+ }
+ return x.rules.String()
+}
+
+func (x *genOutDir) Set(value string) error {
+ if strings.Contains(value, "->") {
+ x.dir = ""
+ return x.rules.Set(value)
+ }
+ x.dir = value
+ return nil
+}
+
+var (
+ // Common flags for the tool itself, applicable to all commands.
+ flagVerbose bool
+ flagMaxErrors int
+ flagExts string
+ flagVDLConfig string
+ flagIgnoreUnknown bool
+
+ // Options for each command.
+ optCompileStatus bool
+ optGenStatus bool
+ optGenGoOutDir = genOutDir{}
+ optGenJavaOutDir = genOutDir{
+ rules: xlateRules{
+ {"go/src", "java/src/vdl/java"},
+ },
+ }
+ optGenJavascriptOutDir = genOutDir{
+ rules: xlateRules{
+ {"release/go/src", "release/javascript/core/src"},
+ {"roadmap/go/src", "release/javascript/core/src"},
+ {"third_party/go/src", "SKIP"},
+ {"tools/go/src", "SKIP"},
+ // TODO(toddw): Skip vdlroot javascript generation for now.
+ {"release/go/src/v.io/v23/vdlroot", "SKIP"},
+ },
+ }
+ optGenJavaOutPkg = xlateRules{
+ {"v.io", "io/v"},
+ }
+ optPathToJSCore string
+ // TODO(bjornick): Add javascript to the default gen langs.
+ optGenLangs = genLangs{vdltool.GenLanguageGo, vdltool.GenLanguageJava}
+)
+
+// Root returns the root command for the VDL tool.
+var cmdVDL = &cmdline.Command{
+ Name: "vdl",
+ Short: "Manage veyron VDL source code",
+ Long: `
+The vdl tool manages veyron VDL source code. It's similar to the go tool used
+for managing Go source code.
+`,
+ Children: []*cmdline.Command{cmdGenerate, cmdCompile, cmdAudit, cmdList},
+ Topics: []cmdline.Topic{topicPackages, topicVdlPath, topicVdlRoot, topicVdlConfig},
+}
+
+func init() {
+ // Common flags for the tool itself, applicable to all commands.
+ cmdVDL.Flags.BoolVar(&flagVerbose, "v", false, "Turn on verbose logging.")
+ cmdVDL.Flags.IntVar(&flagMaxErrors, "max_errors", -1, "Stop processing after this many errors, or -1 for unlimited.")
+ cmdVDL.Flags.StringVar(&flagExts, "exts", ".vdl", "Comma-separated list of valid VDL file name extensions.")
+ cmdVDL.Flags.StringVar(&flagVDLConfig, "vdl.config", "vdl.config", "Basename of the optional per-package config file.")
+ cmdVDL.Flags.BoolVar(&flagIgnoreUnknown, "ignore_unknown", false, "Ignore unknown packages provided on the command line.")
+
+ // Options for compile.
+ cmdCompile.Flags.BoolVar(&optCompileStatus, "status", true, "Show package names while we compile")
+
+ // Options for generate.
+ cmdGenerate.Flags.Var(&optGenLangs, "lang", "Comma-separated list of languages to generate, currently supporting "+genLangAll.String())
+ cmdGenerate.Flags.BoolVar(&optGenStatus, "status", true, "Show package names as they are updated")
+ // TODO(toddw): Move out_dir configuration into vdl.config, and provide a
+ // generic override mechanism for vdl.config.
+ cmdGenerate.Flags.Var(&optGenGoOutDir, "go_out_dir", `
+Go output directory. There are three modes:
+ "" : Generate output in-place in the source tree
+ "dir" : Generate output rooted at dir
+ "src->dst[,s2->d2...]" : Generate output using translation rules
+Assume your source tree is organized as follows:
+ VDLPATH=/home/vdl
+ /home/vdl/src/veyron/test_base/base1.vdl
+ /home/vdl/src/veyron/test_base/base2.vdl
+Here's example output under the different modes:
+ --go_out_dir=""
+ /home/vdl/src/veyron/test_base/base1.vdl.go
+ /home/vdl/src/veyron/test_base/base2.vdl.go
+ --go_out_dir="/tmp/foo"
+ /tmp/foo/veyron/test_base/base1.vdl.go
+ /tmp/foo/veyron/test_base/base2.vdl.go
+ --go_out_dir="vdl/src->foo/bar/src"
+ /home/foo/bar/src/veyron/test_base/base1.vdl.go
+ /home/foo/bar/src/veyron/test_base/base2.vdl.go
+When the src->dst form is used, src must match the suffix of the path just
+before the package path, and dst is the replacement for src. Use commas to
+separate multiple rules; the first rule matching src is used. The special dst
+SKIP indicates matching packages are skipped.`)
+ cmdGenerate.Flags.Var(&optGenJavaOutDir, "java_out_dir",
+ "Same semantics as --go_out_dir but applies to java code generation.")
+ cmdGenerate.Flags.Var(&optGenJavaOutPkg, "java_out_pkg", `
+Java output package translation rules. Must be of the form:
+ "src->dst[,s2->d2...]"
+If a VDL package has a prefix src, the prefix will be replaced with dst. Use
+commas to separate multiple rules; the first rule matching src is used, and if
+there are no matching rules, the package remains unchanged. The special dst
+SKIP indicates matching packages are skipped.`)
+ cmdGenerate.Flags.Var(&optGenJavascriptOutDir, "js_out_dir",
+ "Same semantics as --go_out_dir but applies to js code generation.")
+ cmdGenerate.Flags.StringVar(&optPathToJSCore, "js_relative_path_to_core", "",
+ "If set, this is the relative path from js_out_dir to the root of the JS core")
+
+ // Options for audit are identical to generate.
+ cmdAudit.Flags = cmdGenerate.Flags
+}
+
+func runCompile(targets []*build.Package, env *compile.Env) {
+ for _, target := range targets {
+ pkg := build.BuildPackage(target, env)
+ if pkg != nil && optCompileStatus {
+ fmt.Println(pkg.Path)
+ }
+ }
+}
+
+func runGenerate(targets []*build.Package, env *compile.Env) {
+ gen(false, targets, env)
+}
+
+func runAudit(targets []*build.Package, env *compile.Env) {
+ if gen(true, targets, env) && env.Errors.IsEmpty() {
+ // Some packages are stale, and there were no errors; return an arbitrary
+ // non-0 exit code. Errors are handled in runHelper, as usual.
+ os.Exit(10)
+ }
+}
+
+func shouldGenerate(config vdltool.Config, lang vdltool.GenLanguage) bool {
+ // If config.GenLanguages is empty, all languages are allowed to be generated.
+ _, ok := config.GenLanguages[lang]
+ return len(config.GenLanguages) == 0 || ok
+}
+
+// gen generates the given targets with env. If audit is true, only checks
+// whether any packages are stale; otherwise files will actually be written out.
+// Returns true if any packages are stale.
+func gen(audit bool, targets []*build.Package, env *compile.Env) bool {
+ anychanged := false
+ for _, target := range targets {
+ pkg := build.BuildPackage(target, env)
+ if pkg == nil {
+ // Stop at the first package that fails to compile.
+ if env.Errors.IsEmpty() {
+ env.Errors.Errorf("%s: internal error (compiled into nil package)", target.Path)
+ }
+ return true
+ }
+ // TODO(toddw): Skip code generation if the semantic contents of the
+ // generated file haven't changed.
+ pkgchanged := false
+ for _, gl := range optGenLangs {
+ switch gl {
+ case vdltool.GenLanguageGo:
+ if !shouldGenerate(pkg.Config, vdltool.GenLanguageGo) {
+ continue
+ }
+ dir, err := xlateOutDir(target.Dir, target.GenPath, optGenGoOutDir, pkg.GenPath)
+ if handleErrorOrSkip("--go_out_dir", err, env) {
+ continue
+ }
+ for _, file := range pkg.Files {
+ data := golang.Generate(file, env)
+ if writeFile(audit, data, dir, file.BaseName+".go", env) {
+ pkgchanged = true
+ }
+ }
+ case vdltool.GenLanguageJava:
+ if !shouldGenerate(pkg.Config, vdltool.GenLanguageJava) {
+ continue
+ }
+ pkgPath, err := xlatePkgPath(pkg.GenPath, optGenJavaOutPkg)
+ if handleErrorOrSkip("--java_out_pkg", err, env) {
+ continue
+ }
+ dir, err := xlateOutDir(target.Dir, target.GenPath, optGenJavaOutDir, pkgPath)
+ if handleErrorOrSkip("--java_out_dir", err, env) {
+ continue
+ }
+ java.SetPkgPathXlator(func(pkgPath string) string {
+ result, _ := xlatePkgPath(pkgPath, optGenJavaOutPkg)
+ return result
+ })
+ for _, file := range java.Generate(pkg, env) {
+ fileDir := filepath.Join(dir, file.Dir)
+ if writeFile(audit, file.Data, fileDir, file.Name, env) {
+ pkgchanged = true
+ }
+ }
+ case vdltool.GenLanguageJavascript:
+ if !shouldGenerate(pkg.Config, vdltool.GenLanguageJavascript) {
+ continue
+ }
+ dir, err := xlateOutDir(target.Dir, target.GenPath, optGenJavascriptOutDir, pkg.GenPath)
+ if handleErrorOrSkip("--js_out_dir", err, env) {
+ continue
+ }
+ path := func(importPath string) string {
+ prefix := filepath.Clean(target.Dir[0 : len(target.Dir)-len(target.GenPath)])
+ pkgDir := filepath.Join(prefix, filepath.FromSlash(importPath))
+ fullDir, err := xlateOutDir(pkgDir, importPath, optGenJavascriptOutDir, importPath)
+ if err != nil {
+ panic(err)
+ }
+ cleanPath, err := filepath.Rel(dir, fullDir)
+ if err != nil {
+ panic(err)
+ }
+ return cleanPath
+ }
+ data := javascript.Generate(pkg, env, path, optPathToJSCore)
+ if writeFile(audit, data, dir, "index.js", env) {
+ pkgchanged = true
+ }
+ default:
+ env.Errors.Errorf("Generating code for language %v isn't supported", gl)
+ }
+ }
+ if pkgchanged {
+ anychanged = true
+ if optGenStatus {
+ fmt.Println(pkg.Path)
+ }
+ }
+ }
+ return anychanged
+}
+
+// writeFile writes data into the standard location for file, using the given
+// suffix. Errors are reported via env. Returns true iff the file doesn't
+// already exist with the given data.
+func writeFile(audit bool, data []byte, dirName, baseName string, env *compile.Env) bool {
+ dstName := filepath.Join(dirName, baseName)
+ // Don't change anything if old and new are the same.
+ if oldData, err := ioutil.ReadFile(dstName); err == nil && bytes.Equal(oldData, data) {
+ return false
+ }
+ if !audit {
+ // Create containing directory, if it doesn't already exist.
+ if err := os.MkdirAll(dirName, os.FileMode(0777)); err != nil {
+ env.Errors.Errorf("Couldn't create directory %s: %v", dirName, err)
+ return true
+ }
+ if err := ioutil.WriteFile(dstName, data, os.FileMode(0666)); err != nil {
+ env.Errors.Errorf("Couldn't write file %s: %v", dstName, err)
+ return true
+ }
+ }
+ return true
+}
+
+func handleErrorOrSkip(prefix string, err error, env *compile.Env) bool {
+ if err != nil {
+ if err != errSkip {
+ env.Errors.Errorf("%s error: %v", prefix, err)
+ }
+ return true
+ }
+ return false
+}
+
+var errSkip = fmt.Errorf("SKIP")
+
+func xlateOutDir(dir, path string, outdir genOutDir, outPkgPath string) (string, error) {
+ path = filepath.FromSlash(path)
+ outPkgPath = filepath.FromSlash(outPkgPath)
+ // Strip package path from the directory.
+ if !strings.HasSuffix(dir, path) {
+ return "", fmt.Errorf("package dir %q doesn't end with package path %q", dir, path)
+ }
+ dir = filepath.Clean(dir[:len(dir)-len(path)])
+
+ switch {
+ case outdir.dir != "":
+ return filepath.Join(outdir.dir, outPkgPath), nil
+ case len(outdir.rules) == 0:
+ return filepath.Join(dir, outPkgPath), nil
+ }
+ // Try translation rules in order.
+ for _, xlate := range outdir.rules {
+ d := dir
+ if !strings.HasSuffix(d, xlate.src) {
+ continue
+ }
+ if xlate.dst == "SKIP" {
+ return "", errSkip
+ }
+ d = filepath.Clean(d[:len(d)-len(xlate.src)])
+ return filepath.Join(d, xlate.dst, outPkgPath), nil
+ }
+ return "", fmt.Errorf("package prefix %q doesn't match translation rules %q", dir, outdir)
+}
+
+func xlatePkgPath(pkgPath string, rules xlateRules) (string, error) {
+ for _, xlate := range rules {
+ if !strings.HasPrefix(pkgPath, xlate.src) {
+ continue
+ }
+ if xlate.dst == "SKIP" {
+ return pkgPath, errSkip
+ }
+ return xlate.dst + pkgPath[len(xlate.src):], nil
+ }
+ return pkgPath, nil
+}
+
+func runList(targets []*build.Package, env *compile.Env) {
+ for tx, target := range targets {
+ num := fmt.Sprintf("%d", tx)
+ fmt.Printf("%s %s\n", num, strings.Repeat("=", termWidth()-len(num)-1))
+ fmt.Printf("Name: %v\n", target.Name)
+ fmt.Printf("Config: %+v\n", target.Config)
+ fmt.Printf("Path: %v\n", target.Path)
+ fmt.Printf("GenPath: %v\n", target.GenPath)
+ fmt.Printf("Dir: %v\n", target.Dir)
+ if len(target.BaseFileNames) > 0 {
+ fmt.Print("Files:\n")
+ for _, file := range target.BaseFileNames {
+ fmt.Printf(" %v\n", file)
+ }
+ }
+ }
+}
+
+func termWidth() int {
+ if _, width, err := textutil.TerminalSize(); err == nil && width > 0 {
+ return width
+ }
+ return 80 // have a reasonable default
+}
diff --git a/cmd/vdl/vdl_test.go b/cmd/vdl/vdl_test.go
new file mode 100644
index 0000000..93b2f9d
--- /dev/null
+++ b/cmd/vdl/vdl_test.go
@@ -0,0 +1,61 @@
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+const (
+ testDir = "../../lib/vdl/testdata/base"
+ outPkgPath = "v.io/x/ref/lib/vdl/testdata/base"
+)
+
+// Compares generated VDL files against the copy in the repo.
+func TestVDLGenerator(t *testing.T) {
+ // Setup the vdl command-line.
+ cmdVDL.Init(nil, os.Stdout, os.Stderr)
+ // Use vdl to generate Go code from input, into a temporary directory.
+ outDir, err := ioutil.TempDir("", "vdltest")
+ if err != nil {
+ t.Fatalf("TempDir() failed: %v", err)
+ }
+ defer os.RemoveAll(outDir)
+ // TODO(toddw): test the generated java and javascript files too.
+ outOpt := fmt.Sprintf("--go_out_dir=%s", outDir)
+ if err := cmdVDL.Execute([]string{"generate", "--lang=go", outOpt, testDir}); err != nil {
+ t.Fatalf("Execute() failed: %v", err)
+ }
+ // Check that each *.vdl.go file in the testDir matches the generated output.
+ entries, err := ioutil.ReadDir(testDir)
+ if err != nil {
+ t.Fatalf("ReadDir(%v) failed: %v", testDir, err)
+ }
+ numEqual := 0
+ for _, entry := range entries {
+ if !strings.HasSuffix(entry.Name(), ".vdl.go") {
+ continue
+ }
+ testFile := filepath.Join(testDir, entry.Name())
+ testBytes, err := ioutil.ReadFile(testFile)
+ if err != nil {
+ t.Fatalf("ReadFile(%v) failed: %v", testFile, err)
+ }
+ outFile := filepath.Join(outDir, outPkgPath, entry.Name())
+ outBytes, err := ioutil.ReadFile(outFile)
+ if err != nil {
+ t.Fatalf("ReadFile(%v) failed: %v", outFile, err)
+ }
+ if !bytes.Equal(outBytes, testBytes) {
+ t.Fatalf("GOT:\n%v\n\nWANT:\n%v\n", string(outBytes), string(testBytes))
+ }
+ numEqual++
+ }
+ if numEqual == 0 {
+ t.Fatalf("testDir %s has no golden files *.vdl.go", testDir)
+ }
+}