add builtin VDLROOT support to vdl

Currently, the vdl binary requires the v23 source distribution to run.
This CL removes that requirement by compiling the necessary parts of the
v23 source distribution into the vdl tool itself.

There are two components: the builtin root generator, and the extractor.

The generator runs at v23 go generate time and walks the
v.io/v23/vdlroot package looking for VDL files. Any VDL file found is
added to a tar file. That tar file is then gzip'd, base64 encoded and
placed inside a const at the top of builtin_vdlroot.go.

The extractor runs in vdl main() if --use_builtin_vdlroot is set and no
VDLROOT could be determined (using existing build.VdlRootDir logic). The
extractor creates a new temporary directory and untars the vdl sources
to that directory. It then sets the VDLROOT environment. vdl tool
execution then proceeds as usual.

MultiPart: 1/2
Change-Id: I25fcbdb0ff9ff5029e6aeb8082e8116b1406f6b5
diff --git a/cmd/vdl/builtin_vdlroot.go b/cmd/vdl/builtin_vdlroot.go
new file mode 100644
index 0000000..584141e
--- /dev/null
+++ b/cmd/vdl/builtin_vdlroot.go
@@ -0,0 +1,78 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file is automatically generated, your changes will be lost.
+// See make_builtin_vdlroot.go.
+
+package main
+
+//go:generate v23 go run make_builtin_vdlroot.go
+
+const (
+	// builtinVdlrootData contains a base64-encoded gzip'd tar file. This file
+	// contains all of the VDL files required to run the VDL tool.
+	builtinVdlrootData = `H4sIAAAJbogA/+xaa3PcNnd+v0q/AqN+iJ2udyXfWjsfOq7tuM44tidS0pl6PBosid2FRRIMQWq16e
+S/9znnAOBF8iWJk47ziuPLkgQOzuU5N4Derivddo1ZpF/z87z4x+e8DnHdv3uX/8c1/f/24b2j+Fue
+H925f+fuP9RnZeJ9V+db3WD5P0pnKtwXci0W6rGrd41db1p1+/DonjrZGPWTrnRuu1I96tqNa/xcPS
+oKxYO8aow3zbnJ5/uY/KM3yq1Uu7Feedc1mVGZy43C7dqdm6YyuVrulFb/efzklm93haFZhc1MhZnt
+Rrcq05VaGrVyXZUrW+GhUS+eP3768vipWtnCzPdpymudnem1UQmmKjcrWxmv2l1tiKuaGKtaW61BpT
+XNSoMZDZqlgRB5P9PP9+spNV7jeZqWG581dknUN8M1ISq4TeTn+7T4YJ5vmy5r1f/u773UpVG48AQc
+7e+9Plu/1u0m3T9xmRq+f1ouTe6VevOWfykFfl465WuTWV0o1+SGBs73975ncTxGyi8a+Ypei6qDtB
+XWn+//ynIJxQ/LZGhMbvJLwsnk3y2YcBA4/US1BhkCA2HuZQ7UpTX7J8+rR83a05M3b/FLsXXrrlW6
+WXclYOL39151bRiUxuDRZNDz6rhtjC6V+g8a0xPy8viGq1vrKl3cZIJhcBobCF4x+EQLf7S6rnaq11
+KLN0FvROZDSsNttS5MYjiojKZN9DVSVbw5ocE0wy3fGQzGivyIPXpI9df9/+9AdX39KVdrS7Ogfz57
+1u+vD+f/o6PD+/cm+f/o3p071/n/r7i+uPxPWE2pH7arct3kffbXFF89x8ald0XXSgnQmAJvzmW6J8
+6JJok6nRmjLdg2hdsiBtKYnztL6bV1qm7cuYWAWzwgGpkra0xd2sK2O8xpt8ZUKrerFRJy1dL4daPL
+ksoSU53bxlWcWeZKPTOVaXQLuqyylWuIYD91OJyitM10UewSB7prXYmlM0yvoGrP/COJOiR/EpaoTc
+Sb0SpIGmVdmEbZ3AYKnYdu+7KItMRa/2FYVeXg1bM6WYtkY83yw5wsRuvAIwQ7dmXKIdb7zviHROzo
+Jmvc6KbdfAUcYQ6NapwwR/Qq14LldVfohuGFa9O29cPFwlTzrT2ztcmtnrtmvaC7xQuj61NvoID8U4
+Y/dqijbEXMnv5YWVKaLk5PSNpPmM1lXqUlg58+gvZtlibfZuEaw4DpbRgR6lnxjFtSJSxp6Ln/JCkP
+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`
+)
diff --git a/cmd/vdl/builtin_vdlroot_support.go b/cmd/vdl/builtin_vdlroot_support.go
new file mode 100644
index 0000000..57e0cfa
--- /dev/null
+++ b/cmd/vdl/builtin_vdlroot_support.go
@@ -0,0 +1,100 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"archive/tar"
+	"compress/gzip"
+	"encoding/base64"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"v.io/x/ref/lib/vdl/build"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+func extractTarFile(basePath string, reader *tar.Reader) error {
+	for {
+		header, err := reader.Next()
+		if err == io.EOF {
+			return nil
+		} else if err != nil {
+			return err
+		}
+		// If it's a directory, ignore it. We use MkdirAll on all files anyway.
+		if header.FileInfo().IsDir() {
+			continue
+		}
+		fullPath := filepath.Join(basePath, header.Name)
+		if err := os.MkdirAll(filepath.Dir(fullPath), os.FileMode(0755)); err != nil {
+			return err
+		}
+		f, err := os.OpenFile(fullPath, os.O_CREATE|os.O_WRONLY, os.FileMode(header.Mode))
+		if err != nil {
+			return err
+		}
+		n, err := io.Copy(f, reader)
+		if err != nil {
+			return err
+		}
+		if n != header.Size {
+			return fmt.Errorf("while reading %s from archive, wrote %d bytes but was expecting to write %d", header.Name, header.Size, n)
+		}
+	}
+}
+
+func extractBuiltinVdlroot(destDir string) error {
+	decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(builtinVdlrootData))
+	gzipReader, err := gzip.NewReader(decoder)
+	if err != nil {
+		return err
+	}
+	tarReader := tar.NewReader(gzipReader)
+	return extractTarFile(destDir, tarReader)
+}
+
+// maybeExtractBuiltinVdlroot checks to see if VDLROOT is set or can be
+// determined from V23_ROOT. If not, the builtin root VDL definitions are
+// extracted to a new temporary directory and the VDLROOT environment variable
+// is set. cleanupFunc should be called unconditionally (even if there is
+// a non-empty set of errors returned).
+func maybeExtractBuiltinVdlroot() (func() error, *vdlutil.Errors) {
+	noopCleanup := func() error { return nil }
+	if !flagBuiltinVdlroot {
+		return noopCleanup, vdlutil.NewErrors(-1)
+	}
+	errs := vdlutil.NewErrors(-1)
+	build.VdlRootDir(errs)
+	if errs.IsEmpty() {
+		// No errors? We have a vdlroot already and we don't need to do
+		// anything.
+		return noopCleanup, errs
+	}
+	errs.Reset()
+	// Otherwise, we don't have a VDL rootdir.
+	dir, err := ioutil.TempDir("", "vdlroot-")
+	if err != nil {
+		errs.Errorf("TempDir failed, couldn't create temporary VDLROOT: %v", err)
+		return noopCleanup, errs
+	}
+	removeAllCleanup := func() error {
+		return os.RemoveAll(dir)
+	}
+	// Extract the files to the new VDLROOT
+	if err := extractBuiltinVdlroot(dir); err != nil {
+		errs.Errorf("Could not extract builtin VDL types: %v", err)
+		return removeAllCleanup, errs
+	}
+	if err := os.Setenv("VDLROOT", dir); err != nil {
+		errs.Errorf("Setenv(VDLROOT, %q) failed: %v", dir, err)
+		return removeAllCleanup, errs
+	}
+	vdlutil.Vlog.Printf("set VDLROOT to newly-extracted root at %s pid = %d", dir, os.Getpid())
+	return removeAllCleanup, errs
+}
diff --git a/cmd/vdl/doc.go b/cmd/vdl/doc.go
index 5cc386a..7daef03 100644
--- a/cmd/vdl/doc.go
+++ b/cmd/vdl/doc.go
@@ -26,6 +26,9 @@
    vdl.config  Description of vdl.config files
 
 The vdl flags are:
+ -builtin_vdlroot=false
+   If V23_ROOT and VDLROOT are not set, use built-in VDL definitions for core
+   types
  -exts=.vdl
    Comma-separated list of valid VDL file name extensions.
  -ignore_unknown=false
diff --git a/cmd/vdl/main.go b/cmd/vdl/main.go
index 2810b55..0749c07 100644
--- a/cmd/vdl/main.go
+++ b/cmd/vdl/main.go
@@ -16,6 +16,7 @@
 	"path/filepath"
 	"strings"
 
+	"v.io/jiri/collect"
 	"v.io/v23/vdlroot/vdltool"
 	"v.io/x/lib/cmdline"
 	"v.io/x/lib/textutil"
@@ -32,7 +33,26 @@
 }
 
 func main() {
-	cmdline.Main(cmdVDL)
+  env := cmdline.EnvFromOS()
+  err := runMain(env)
+  os.Exit(cmdline.ExitCode(err, env.Stderr))
+}
+
+func runMain(env *cmdline.Env) (e error) {
+	args := os.Args[1:]
+	runner, args, err := cmdline.Parse(cmdVDL, env, args)
+	if err != nil {
+		return err
+	}
+	cleanup, errs := maybeExtractBuiltinVdlroot()
+	defer collect.Error(cleanup, &err)
+	if err := checkErrors(errs); err != nil {
+		return err
+	}
+	if err := runner.Run(env, args); err != nil {
+		return err
+	}
+	return nil
 }
 
 func checkErrors(errs *vdlutil.Errors) error {
@@ -322,11 +342,12 @@
 
 var (
 	// Common flags for the tool itself, applicable to all commands.
-	flagVerbose       bool
-	flagMaxErrors     int
-	flagExts          string
-	flagVDLConfig     string
-	flagIgnoreUnknown bool
+	flagVerbose        bool
+	flagMaxErrors      int
+	flagExts           string
+	flagVDLConfig      string
+	flagIgnoreUnknown  bool
+	flagBuiltinVdlroot bool
 
 	// Options for each command.
 	optCompileStatus bool
@@ -375,6 +396,7 @@
 	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.")
+	cmdVDL.Flags.BoolVar(&flagBuiltinVdlroot, "builtin_vdlroot", false, "If V23_ROOT and VDLROOT are not set, use built-in VDL definitions for core types")
 
 	// Options for compile.
 	cmdCompile.Flags.BoolVar(&optCompileStatus, "status", true, "Show package names while we compile")
diff --git a/cmd/vdl/make_builtin_vdlroot.go b/cmd/vdl/make_builtin_vdlroot.go
new file mode 100644
index 0000000..1377fd2
--- /dev/null
+++ b/cmd/vdl/make_builtin_vdlroot.go
@@ -0,0 +1,171 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Command make_builtin_vdlroot runs at v23 go generate time. It emits a Go
+// source file called builtin_vdlroot.go containing a gzip'd version of all
+// the core VDL types required by the VDL tool. This allows the VDL tool to
+// run 'standalone' (i.e. without access to the Vanadium source code).
+//
+// +build ignored
+
+package main
+
+import (
+	"archive/tar"
+	"compress/gzip"
+	"encoding/base64"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"log"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+const (
+	outputPreamble = `// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file is automatically generated, your changes will be lost.
+// See make_builtin_vdlroot.go.
+
+package main
+
+//go:generate v23 go run make_builtin_vdlroot.go
+
+const (
+	// builtinVdlrootData contains a base64-encoded gzip'd tar file. This file
+	// contains all of the VDL files required to run the VDL tool.
+	builtinVdlrootData = ` + "`"
+	outputFooter = "`" + `
+)
+`
+)
+
+// writeBytes writes the given bytes to the given writer, returning an error if
+// the underlying Write fails or if the number of bytes written is not equal to
+// len(content).
+func writeBytes(out io.Writer, content []byte) error {
+	n, err := out.Write(content)
+	if err == nil && n != len(content) {
+		err = fmt.Errorf("wrote an unexpected number of bytes, wanted %d, wrote %d", len(content), n)
+	}
+	return err
+}
+
+// writeString writes the given string to the given writer, returning an error
+// if the underlying Write fails or if the number of bytes written is not equal
+// to len(content).
+func writeString(out io.Writer, content string) error {
+	return writeBytes(out, []byte(content))
+}
+
+type wrapWriter struct {
+	writer       io.Writer
+	totalWritten int
+}
+
+// Writes b to the underlying writer but inserts a \n every 78 characters. The
+// returned bytes-written count includes the \n characters.
+func (w *wrapWriter) Write(b []byte) (int, error) {
+  const max = 78
+  n := 0
+  for len(b) > 0 {
+    newline := true
+    chunk := max - w.totalWritten%max
+    if len(b) < chunk {
+      chunk = len(b)
+      newline = false
+    }
+    if err := writeBytes(w.writer, b[:chunk]); err != nil {
+      return n, err
+    }
+    n += chunk
+    w.totalWritten += chunk
+    b = b[chunk:]
+    if newline {
+      if err := writeString(w.writer, "\n"); err != nil {
+        return n, err
+      }
+      n++
+    }
+  }
+  return n, nil
+}
+
+// writeVdlrootData creates a gzip'd tar file containing all of the VDL files
+// in vdlroot. The data is encoded as base64. Does not close out.
+func writeVdlrootData(out io.Writer) error {
+	v23root := os.Getenv("V23_ROOT")
+	if v23root == "" {
+		return fmt.Errorf("V23_ROOT is not set")
+	}
+	vdlroot := filepath.Join(v23root, "release", "go", "src", "v.io", "v23", "vdlroot")
+	wrapWriter := &wrapWriter{
+		writer:       out,
+		totalWritten: 0,
+	}
+	base64writer := base64.NewEncoder(base64.StdEncoding, wrapWriter)
+	gzipWriter := gzip.NewWriter(base64writer)
+	tarWriter := tar.NewWriter(gzipWriter)
+	walkFn := func(path string, info os.FileInfo, err error) error {
+		if strings.HasSuffix(path, ".vdl") {
+			content, err := ioutil.ReadFile(path)
+			if err != nil {
+				return err
+			}
+			relPath, err := filepath.Rel(vdlroot, path)
+			if err != nil {
+				return err
+			}
+			header := tar.Header{
+				Mode:    int64(0644),
+				Name:    relPath,
+				Size:    int64(len(content)),
+			}
+			if err := tarWriter.WriteHeader(&header); err != nil {
+				return err
+			}
+			return writeBytes(tarWriter, content)
+		}
+		return nil
+	}
+	if err := filepath.Walk(vdlroot, walkFn); err != nil {
+		log.Printf("Walk() failed: %v", err)
+	}
+	if err := tarWriter.Close(); err != nil {
+		log.Printf("Close() of tar file failed: %v", err)
+		return err
+	}
+	if err := gzipWriter.Close(); err != nil {
+		log.Printf("Close() of gzip file failed: %v", err)
+		return err
+	}
+	if err := base64writer.Close(); err != nil {
+		log.Printf("Close() of base64 file failed: %v", err)
+		return err
+	}
+	return nil
+}
+
+func main() {
+	filename := "builtin_vdlroot.go"
+	f, err := os.Create(filename)
+	if err != nil {
+		log.Printf("Create(%q) failed: %v", filename, err)
+		os.Exit(1)
+	}
+	defer f.Close()
+	if err := writeString(f, outputPreamble); err != nil {
+		os.Exit(1)
+	}
+	if err := writeVdlrootData(f); err != nil {
+		os.Exit(1)
+	}
+	if err := writeString(f, outputFooter); err != nil {
+		os.Exit(1)
+	}
+}
diff --git a/cmd/vdl/vdl_test.go b/cmd/vdl/vdl_test.go
index 7a127cd..99f5c5a 100644
--- a/cmd/vdl/vdl_test.go
+++ b/cmd/vdl/vdl_test.go
@@ -8,10 +8,12 @@
 	"bytes"
 	"fmt"
 	"io/ioutil"
+	"os"
 	"path/filepath"
 	"strings"
 	"testing"
 
+	"v.io/x/lib/envvar"
 	"v.io/x/ref/test/v23tests"
 )
 
@@ -22,18 +24,7 @@
 
 //go:generate v23 test generate
 
-// Compares generated VDL files against the copy in the repo.
-func TestVDLGenerator(t *testing.T) {
-	testEnv := v23tests.New(t)
-	defer testEnv.Cleanup()
-	vdlBin := testEnv.BuildGoPkg("v.io/x/ref/cmd/vdl")
-
-	// Use vdl to generate Go code from input, into a temporary directory.
-	outDir := testEnv.NewTempDir("")
-	// TODO(toddw): test the generated java and javascript files too.
-	outOpt := fmt.Sprintf("--go-out-dir=%s", outDir)
-	vdlBin.Run("generate", "--lang=go", outOpt, testDir)
-	// Check that each *.vdl.go file in the testDir matches the generated output.
+func verifyOutput(t *testing.T, outDir string) {
 	entries, err := ioutil.ReadDir(testDir)
 	if err != nil {
 		t.Fatalf("ReadDir(%v) failed: %v", testDir, err)
@@ -62,3 +53,34 @@
 		t.Fatalf("testDir %s has no golden files *.vdl.go", testDir)
 	}
 }
+
+// Compares generated VDL files against the copy in the repo.
+func TestVDLGenerator(t *testing.T) {
+	testEnv := v23tests.New(t)
+	defer testEnv.Cleanup()
+	vdlBin := testEnv.BuildGoPkg("v.io/x/ref/cmd/vdl")
+
+	// Use vdl to generate Go code from input, into a temporary directory.
+	outDir := testEnv.NewTempDir("")
+	// TODO(toddw): test the generated java and javascript files too.
+	outOpt := fmt.Sprintf("--go-out-dir=%s", outDir)
+	vdlBin.Run("generate", "--lang=go", outOpt, testDir)
+	// Check that each *.vdl.go file in the testDir matches the generated output.
+	verifyOutput(t, outDir)
+}
+
+// Asserts that the VDL command can run to completion without VDLROOT or
+// V23_ROOT being set.
+func TestVDLGeneratorWithNoVDLRoot(t *testing.T) {
+	testEnv := v23tests.New(t)
+	defer testEnv.Cleanup()
+	vdlBin := testEnv.BuildGoPkg("v.io/x/ref/cmd/vdl")
+
+	outDir := testEnv.NewTempDir("")
+	outOpt := fmt.Sprintf("--go-out-dir=%s", outDir)
+	env := envvar.SliceToMap(os.Environ())
+	env["V23_ROOT"] = ""
+	env["VDLROOT"] = ""
+	vdlBin.WithEnv(envvar.MapToSlice(env)...).Run("-v", "--builtin_vdlroot", "generate", "--lang=go", outOpt, testDir)
+	verifyOutput(t, outDir)
+}
diff --git a/lib/vdl/build/build.go b/lib/vdl/build/build.go
index 8ce8ee7..0d6bea5 100644
--- a/lib/vdl/build/build.go
+++ b/lib/vdl/build/build.go
@@ -270,13 +270,20 @@
 // neither VDLROOT nor V23_ROOT is specified.
 func SrcDirs(errs *vdlutil.Errors) []string {
 	var srcDirs []string
-	if root := vdlRootDir(errs); root != "" {
+	if root := VdlRootDir(errs); root != "" {
 		srcDirs = append(srcDirs, root)
 	}
 	return append(srcDirs, vdlPathSrcDirs(errs)...)
 }
 
-func vdlRootDir(errs *vdlutil.Errors) string {
+// VdlRootDir returns the VDL root directory, based on the VDLPATH, VDLROOT and
+// V23_ROOT environment variables.
+//
+// VDLROOT is a single directory specifying the location of the standard vdl
+// packages.  It has the same requirements as VDLPATH components.  If VDLROOT is
+// empty, we use V23_ROOT to construct the VDLROOT.  An error is reported if
+// neither VDLROOT nor V23_ROOT is specified.
+func VdlRootDir(errs *vdlutil.Errors) string {
 	vdlroot := os.Getenv("VDLROOT")
 	if vdlroot == "" {
 		// Try to construct VDLROOT out of V23_ROOT.