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)
+	}
+}