blob: 431b0bb69829a682a0970da8b2e230e881bef7f9 [file] [log] [blame]
// 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 (
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"v.io/v23/context"
"v.io/v23/rpc"
"v.io/v23/services/binary"
"v.io/v23/services/build"
"v.io/v23/verror"
"v.io/x/lib/host"
)
const pkgPath = "v.io/x/ref/services/build/buildd"
// Errors
var (
errBuildFailed = verror.Register(pkgPath+".errBuildFailed", verror.NoRetry, "{1:}{2:} build failed{:_}")
)
// builderService implements the Builder server interface.
type builderService struct {
// Path to the binary and the value of the GOROOT environment variable.
gobin, goroot string
}
// NewBuilderService returns a new Build service implementation.
func NewBuilderService(gobin, goroot string) build.BuilderServerMethods {
return &builderService{
gobin: gobin,
goroot: goroot,
}
}
// TODO(jsimsa): Add support for building for a specific profile
// specified as a suffix the Build().
//
// TODO(jsimsa): Analyze the binary files for shared library
// dependencies and ship these back.
func (i *builderService) Build(ctx *context.T, call build.BuilderBuildServerCall, arch build.Architecture, opsys build.OperatingSystem) ([]byte, error) {
ctx.VI(1).Infof("Build(%v, %v) called.", arch, opsys)
dir, prefix := "", ""
dirPerm, filePerm := os.FileMode(0700), os.FileMode(0600)
root, err := ioutil.TempDir(dir, prefix)
if err != nil {
ctx.Errorf("TempDir(%v, %v) failed: %v", dir, prefix, err)
return nil, verror.New(verror.ErrInternal, ctx)
}
defer os.RemoveAll(root)
srcDir := filepath.Join(root, "go", "src")
if err := os.MkdirAll(srcDir, dirPerm); err != nil {
ctx.Errorf("MkdirAll(%v, %v) failed: %v", srcDir, dirPerm, err)
return nil, verror.New(verror.ErrInternal, ctx)
}
iterator := call.RecvStream()
for iterator.Advance() {
srcFile := iterator.Value()
filePath := filepath.Join(srcDir, filepath.FromSlash(srcFile.Name))
dir := filepath.Dir(filePath)
if err := os.MkdirAll(dir, dirPerm); err != nil {
ctx.Errorf("MkdirAll(%v, %v) failed: %v", dir, dirPerm, err)
return nil, verror.New(verror.ErrInternal, ctx)
}
if err := ioutil.WriteFile(filePath, srcFile.Contents, filePerm); err != nil {
ctx.Errorf("WriteFile(%v, %v) failed: %v", filePath, filePerm, err)
return nil, verror.New(verror.ErrInternal, ctx)
}
}
if err := iterator.Err(); err != nil {
ctx.Errorf("Advance() failed: %v", err)
return nil, verror.New(verror.ErrInternal, ctx)
}
// NOTE: we actually want run "go install -v {srcDir}/..." here, but
// the go tool seems to have a bug where it doesn't interpret rooted
// (absolute) paths with wildcards correctly. So we run "go install
// -v all" instead, which has the downside that it might cause some
// standard packages to be built spuriously.
cmd := exec.Command(i.gobin, "install", "-v", "all")
cmd.Env = append(cmd.Env, "GOARCH="+arch.ToGoArch())
cmd.Env = append(cmd.Env, "GOOS="+opsys.ToGoOS())
cmd.Env = append(cmd.Env, "GOPATH="+filepath.Dir(srcDir))
if i.goroot != "" {
cmd.Env = append(cmd.Env, "GOROOT="+i.goroot)
}
if tmpdir, ok := os.LookupEnv("TMPDIR"); ok {
cmd.Env = append(cmd.Env, "TMPDIR="+tmpdir)
}
var output bytes.Buffer
cmd.Stdout = &output
cmd.Stderr = &output
if err := cmd.Run(); err != nil {
ctx.Errorf("Run(%q) failed: %v", strings.Join(cmd.Args, " "), err)
if output.Len() != 0 {
ctx.Errorf("%v", output.String())
}
return output.Bytes(), verror.New(errBuildFailed, ctx)
}
binDir := filepath.Join(root, "go", "bin")
machineArch, err := host.Arch()
if err != nil {
ctx.Errorf("Arch() failed: %v", err)
return nil, verror.New(verror.ErrInternal, ctx)
}
if machineArch != arch.ToGoArch() || runtime.GOOS != opsys.ToGoOS() {
binDir = filepath.Join(binDir, fmt.Sprintf("%v_%v", opsys.ToGoOS(), arch.ToGoArch()))
}
files, err := ioutil.ReadDir(binDir)
if err != nil && !os.IsNotExist(err) {
ctx.Errorf("ReadDir(%v) failed: %v", binDir, err)
return nil, verror.New(verror.ErrInternal, ctx)
}
for _, file := range files {
binPath := filepath.Join(binDir, file.Name())
bytes, err := ioutil.ReadFile(binPath)
if err != nil {
ctx.Errorf("ReadFile(%v) failed: %v", binPath, err)
return nil, verror.New(verror.ErrInternal, ctx)
}
result := build.File{
Name: "bin/" + file.Name(),
Contents: bytes,
}
if err := call.SendStream().Send(result); err != nil {
ctx.Errorf("Send() failed: %v", err)
return nil, verror.New(verror.ErrInternal, ctx)
}
}
return output.Bytes(), nil
}
func (i *builderService) Describe(_ *context.T, _ rpc.ServerCall, name string) (binary.Description, error) {
// TODO(jsimsa): Implement.
return binary.Description{}, nil
}