Merge "{veyron/veyron2}/services/mgmt/build: a simple Go build server"
diff --git a/services/mgmt/binary/binaryd/main.go b/services/mgmt/binary/binaryd/main.go
index 1ea4203..a676c94 100644
--- a/services/mgmt/binary/binaryd/main.go
+++ b/services/mgmt/binary/binaryd/main.go
@@ -80,7 +80,7 @@
 		return
 	}
 	if err := server.Serve(name, dispatcher); err != nil {
-		vlog.Errorf("Server(%v) failed: %v", name, err)
+		vlog.Errorf("Serve(%v) failed: %v", name, err)
 		return
 	}
 	vlog.Infof("Binary repository published at %v/%v", endpoint, name)
diff --git a/services/mgmt/build/buildd/main.go b/services/mgmt/build/buildd/main.go
new file mode 100644
index 0000000..1904b2b
--- /dev/null
+++ b/services/mgmt/build/buildd/main.go
@@ -0,0 +1,45 @@
+package main
+
+import (
+	"flag"
+
+	"veyron/lib/signals"
+	vflag "veyron/security/flag"
+	"veyron/services/mgmt/build/impl"
+
+	"veyron2/ipc"
+	"veyron2/rt"
+	"veyron2/services/mgmt/build"
+	"veyron2/vlog"
+)
+
+func main() {
+	var address, gobin, name, protocol string
+	// TODO(rthellend): Remove the address and protocol flags when the config manager is working.
+	flag.StringVar(&address, "address", "localhost:0", "network address to listen on")
+	flag.StringVar(&gobin, "gobin", "go", "path to the Go compiler")
+	flag.StringVar(&name, "name", "", "name to mount the build server as")
+	flag.StringVar(&protocol, "protocol", "tcp", "network type to listen on")
+	flag.Parse()
+	runtime := rt.Init()
+	defer runtime.Cleanup()
+	server, err := runtime.NewServer()
+	if err != nil {
+		vlog.Errorf("NewServer() failed: %v", err)
+		return
+	}
+	defer server.Stop()
+	endpoint, err := server.Listen(protocol, address)
+	if err != nil {
+		vlog.Errorf("Listen(%v, %v) failed: %v", protocol, address, err)
+		return
+	}
+	if err := server.Serve(name, ipc.SoloDispatcher(build.NewServerBuild(impl.NewInvoker(gobin)), vflag.NewAuthorizerOrDie())); err != nil {
+		vlog.Errorf("Serve(%v) failed: %v", name, err)
+		return
+	}
+	vlog.Infof("Build server endpoint=%q name=%q", endpoint, name)
+
+	// Wait until shutdown.
+	<-signals.ShutdownOnSignals()
+}
diff --git a/services/mgmt/build/impl/impl_test.go b/services/mgmt/build/impl/impl_test.go
new file mode 100644
index 0000000..31b2d71
--- /dev/null
+++ b/services/mgmt/build/impl/impl_test.go
@@ -0,0 +1,124 @@
+package impl
+
+import (
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	_ "veyron/lib/testutil"
+
+	"veyron2/ipc"
+	"veyron2/rt"
+	"veyron2/services/mgmt/build"
+)
+
+func init() {
+	rt.Init()
+}
+
+// startServer starts the build server.
+func startServer(t *testing.T) (build.Build, func()) {
+	root := os.Getenv("VEYRON_ROOT")
+	if root == "" {
+		t.Fatalf("VEYRON_ROOT is not set")
+	}
+	gobin := filepath.Join(root, "environment", "go", "bin", "go")
+	server, err := rt.R().NewServer()
+	if err != nil {
+		t.Fatalf("NewServer() failed: %v", err)
+	}
+	protocol, hostname := "tcp", "localhost:0"
+	endpoint, err := server.Listen(protocol, hostname)
+	if err != nil {
+		t.Fatalf("Listen(%v, %v) failed: %v", protocol, hostname, err)
+	}
+	unpublished := ""
+	if err := server.Serve(unpublished, ipc.SoloDispatcher(build.NewServerBuild(NewInvoker(gobin)), nil)); err != nil {
+		t.Fatalf("Serve(%q) failed: %v", unpublished, err)
+	}
+	name := "/" + endpoint.String()
+	client, err := build.BindBuild(name)
+	if err != nil {
+		t.Fatalf("BindBuild(%v) failed: %v", name, err)
+	}
+	return client, func() {
+		if err := server.Stop(); err != nil {
+			t.Fatalf("Stop() failed: %v", err)
+		}
+	}
+}
+
+func invokeBuild(t *testing.T, client build.Build, files []build.File) ([]byte, error) {
+	stream, err := client.Build(rt.R().NewContext())
+	if err != nil {
+		t.Errorf("Build() failed: %v", err)
+		return nil, err
+	}
+	for _, file := range files {
+		if err := stream.Send(file); err != nil {
+			t.Logf("Send() failed: %v", err)
+			stream.Cancel()
+			return nil, err
+		}
+	}
+	if err := stream.CloseSend(); err != nil {
+		t.Logf("CloseSend() failed: %v", err)
+		stream.Cancel()
+		return nil, err
+	}
+	output, err := stream.Finish()
+	if err != nil {
+		t.Logf("Finish() failed: %v", err)
+		stream.Cancel()
+		return nil, err
+	}
+	return output, nil
+}
+
+const mainSrc = `package main
+
+import "fmt"
+
+func main() {
+	fmt.Println("Hello World!")
+}
+`
+
+// TestSuccess checks that the build server successfully builds a
+// package that depends on the standard Go library.
+func TestSuccess(t *testing.T) {
+	client, cleanup := startServer(t)
+	defer cleanup()
+
+	files := []build.File{
+		build.File{
+			Name:     "test/main.go",
+			Contents: []byte(mainSrc),
+		},
+	}
+	output, err := invokeBuild(t, client, files)
+	if err != nil {
+		t.FailNow()
+	}
+	if got, expected := strings.TrimSpace(string(output)), "test"; got != expected {
+		t.Fatalf("Unexpected output: got %v, expected %v", got, expected)
+	}
+}
+
+// TestFailure checks that the build server fails to build a package
+// consisting of an empty file.
+func TestFailure(t *testing.T) {
+	client, cleanup := startServer(t)
+	defer cleanup()
+
+	files := []build.File{
+		build.File{
+			Name:     "test/main.go",
+			Contents: []byte(""),
+		},
+	}
+	if _, err := invokeBuild(t, client, files); err == nil {
+		t.FailNow()
+	}
+}
diff --git a/services/mgmt/build/impl/invoker.go b/services/mgmt/build/impl/invoker.go
new file mode 100644
index 0000000..b2b9c52
--- /dev/null
+++ b/services/mgmt/build/impl/invoker.go
@@ -0,0 +1,83 @@
+package impl
+
+import (
+	"errors"
+	"io"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+
+	"veyron2/ipc"
+	"veyron2/services/mgmt/binary"
+	"veyron2/services/mgmt/build"
+	"veyron2/vlog"
+)
+
+var (
+	errOperationFailed = errors.New("operation failed")
+)
+
+// invoker holds the state of a build server invocation.
+type invoker struct {
+	// gobin is the path to the Go compiler binary.
+	gobin string
+}
+
+// NewInvoker is the invoker factory.
+func NewInvoker(gobin string) *invoker {
+	return &invoker{
+		gobin: gobin,
+	}
+}
+
+// BUILD INTERFACE IMPLEMENTATION
+
+func (i *invoker) Build(_ ipc.ServerContext, stream build.BuildServiceBuildStream) ([]byte, error) {
+	dir, prefix := "", ""
+	dirPerm, filePerm := os.FileMode(0700), os.FileMode(0600)
+	root, err := ioutil.TempDir(dir, prefix)
+	if err != nil {
+		vlog.Errorf("TempDir(%v, %v) failed: %v", dir, prefix, err)
+		return nil, errOperationFailed
+	}
+	defer os.RemoveAll(root)
+	srcDir := filepath.Join(root, "go", "src")
+	if err := os.MkdirAll(srcDir, dirPerm); err != nil {
+		vlog.Errorf("MkdirAll(%v, %v) failed: %v", srcDir, dirPerm, err)
+		return nil, errOperationFailed
+	}
+	for {
+		srcFile, err := stream.Recv()
+		if err != nil && err != io.EOF {
+			vlog.Errorf("Recv() failed: %v", err)
+			return nil, errOperationFailed
+		}
+		if err == io.EOF {
+			break
+		}
+		filePath := filepath.Join(srcDir, filepath.FromSlash(srcFile.Name))
+		dir := filepath.Dir(filePath)
+		if err := os.MkdirAll(dir, dirPerm); err != nil {
+			vlog.Errorf("MkdirAll(%v, %v) failed: %v", dir, dirPerm, err)
+			return nil, errOperationFailed
+		}
+		if err := ioutil.WriteFile(filePath, srcFile.Contents, filePerm); err != nil {
+			vlog.Errorf("WriteFile(%v, %v) failed: %v", filePath, filePerm, err)
+			return nil, errOperationFailed
+		}
+	}
+	cmd := exec.Command(i.gobin, "build", "-v", "...")
+	cmd.Env = append(cmd.Env, "GOPATH="+filepath.Dir(srcDir))
+	bytes, err := cmd.CombinedOutput()
+	if err != nil {
+		vlog.Errorf("CombinedOutput() failed: %v", err)
+		return nil, errOperationFailed
+	}
+	return bytes, nil
+}
+
+func (i *invoker) Describe(_ ipc.ServerContext, name string) (binary.Description, error) {
+	// TODO(jsimsa): Implement.
+	return binary.Description{}, nil
+}
diff --git a/services/mgmt/node/impl/invoker.go b/services/mgmt/node/impl/invoker.go
index a0badb6..565d6d8 100644
--- a/services/mgmt/node/impl/invoker.go
+++ b/services/mgmt/node/impl/invoker.go
@@ -41,8 +41,8 @@
 	"time"
 
 	"veyron/lib/config"
-	ibuild "veyron/services/mgmt/build"
-	"veyron/services/mgmt/lib/binary"
+	"veyron/services/mgmt/build"
+	cbinary "veyron/services/mgmt/lib/binary"
 	vexec "veyron/services/mgmt/lib/exec"
 	"veyron/services/mgmt/profile"
 
@@ -51,7 +51,7 @@
 	"veyron2/naming"
 	"veyron2/rt"
 	"veyron2/services/mgmt/application"
-	"veyron2/services/mgmt/build"
+	"veyron2/services/mgmt/binary"
 	"veyron2/services/mgmt/node"
 	"veyron2/services/mgmt/repository"
 	"veyron2/verror"
@@ -125,24 +125,24 @@
 	// architecture is.
 	switch runtime.GOOS {
 	case "linux":
-		result.Format.Name = ibuild.ELF.String()
-		result.Format.Attributes["os"] = ibuild.LINUX.String()
+		result.Format.Name = build.ELF.String()
+		result.Format.Attributes["os"] = build.LINUX.String()
 	case "darwin":
-		result.Format.Name = ibuild.MACH.String()
-		result.Format.Attributes["os"] = ibuild.DARWIN.String()
+		result.Format.Name = build.MACH.String()
+		result.Format.Attributes["os"] = build.DARWIN.String()
 	case "windows":
-		result.Format.Name = ibuild.PE.String()
-		result.Format.Attributes["os"] = ibuild.WINDOWS.String()
+		result.Format.Name = build.PE.String()
+		result.Format.Attributes["os"] = build.WINDOWS.String()
 	default:
 		return nil, errors.New("Unsupported operating system: " + runtime.GOOS)
 	}
 	switch runtime.GOARCH {
 	case "amd64":
-		result.Format.Attributes["arch"] = ibuild.AMD64.String()
+		result.Format.Attributes["arch"] = build.AMD64.String()
 	case "arm":
-		result.Format.Attributes["arch"] = ibuild.AMD64.String()
+		result.Format.Attributes["arch"] = build.AMD64.String()
 	case "x86":
-		result.Format.Attributes["arch"] = ibuild.AMD64.String()
+		result.Format.Attributes["arch"] = build.AMD64.String()
 	default:
 		return nil, errors.New("Unsupported hardware architecture: " + runtime.GOARCH)
 	}
@@ -304,14 +304,14 @@
 	return result, nil
 }
 
-func (i *invoker) IsRunnable(call ipc.ServerContext, binary build.BinaryDescription) (bool, error) {
-	vlog.VI(0).Infof("%v.IsRunnable(%v)", i.suffix, binary)
+func (i *invoker) IsRunnable(call ipc.ServerContext, description binary.Description) (bool, error) {
+	vlog.VI(0).Infof("%v.IsRunnable(%v)", i.suffix, description)
 	nodeProfile, err := i.computeNodeProfile()
 	if err != nil {
 		return false, err
 	}
 	binaryProfiles := make([]profile.Specification, 0)
-	for name, _ := range binary.Profiles {
+	for name, _ := range description.Profiles {
 		profile, err := i.getProfile(name)
 		if err != nil {
 			return false, err
@@ -331,7 +331,7 @@
 // APPLICATION INTERFACE IMPLEMENTATION
 
 func downloadBinary(workspace, name string) error {
-	data, err := binary.Download(name)
+	data, err := cbinary.Download(name)
 	if err != nil {
 		vlog.Errorf("Download(%v) failed: %v", name, err)
 		return errOperationFailed
diff --git a/tools/vrpc/impl/impl.go b/tools/vrpc/impl/impl.go
index 27ce11e..aca51a3 100644
--- a/tools/vrpc/impl/impl.go
+++ b/tools/vrpc/impl/impl.go
@@ -16,7 +16,7 @@
 	"veyron2/vom"
 	"veyron2/wiretype"
 
-	idl_build "veyron2/services/mgmt/build"
+	idl_binary "veyron2/services/mgmt/binary"
 	idl_node "veyron2/services/mgmt/node"
 	idl_mounttable "veyron2/services/mounttable"
 )
@@ -126,7 +126,7 @@
 	vom.Register(x1)
 	var x2 idl_node.Description
 	vom.Register(x2)
-	var x3 idl_build.BinaryDescription
+	var x3 idl_binary.Description
 	vom.Register(x3)
 	var x4 idl_mounttable.MountedServer
 	vom.Register(x4)