veyron/tools: Merge the impl and main packages
This change gets rid of the impl packages and moves the impl.go files to
the main package for each tool under veyron/tools/.
Change-Id: Ieae4bc1dd02296f58f8cb6327b903d025cf833f5
diff --git a/tools/build/impl.go b/tools/build/impl.go
new file mode 100644
index 0000000..d047478
--- /dev/null
+++ b/tools/build/impl.go
@@ -0,0 +1,241 @@
+package main
+
+import (
+ "fmt"
+ "go/build"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "time"
+
+ "veyron.io/veyron/veyron/lib/cmdline"
+
+ "veyron.io/veyron/veyron2/context"
+ "veyron.io/veyron/veyron2/rt"
+ vbuild "veyron.io/veyron/veyron2/services/mgmt/build"
+)
+
+var (
+ flagArch string
+ flagOS string
+)
+
+func init() {
+ cmdBuild.Flags.StringVar(&flagArch, "arch", runtime.GOARCH, "Target architecture.")
+ cmdBuild.Flags.StringVar(&flagOS, "os", runtime.GOOS, "Target operating system.")
+}
+
+var cmdRoot = &cmdline.Command{
+ Name: "build",
+ Short: "Tool for interacting with the veyron build server",
+ Long: `
+The build tool tool facilitates interaction with the veyron build server.
+`,
+ Children: []*cmdline.Command{cmdBuild},
+}
+
+// root returns a command that represents the root of the veyron tool.
+func root() *cmdline.Command {
+ return cmdRoot
+}
+
+var cmdBuild = &cmdline.Command{
+ Run: runBuild,
+ Name: "build",
+ Short: "Build veyron Go packages",
+ Long: `
+Build veyron Go packages using a remote build server. The command
+collects all source code files that are not part of the Go standard
+library that the target packages depend on, sends them to a build
+server, and receives the built binaries.
+`,
+ ArgsName: "<name> <packages>",
+ ArgsLong: `
+<name> is a veyron object name of a build server
+<packages> is a list of packages to build, specified as arguments for
+each command. The format is similar to the go tool. In its simplest
+form each package is an import path; e.g. "veyron/tools/build". A
+package that ends with "..." does a wildcard match against all
+packages with that prefix.
+`,
+}
+
+// TODO(jsimsa): Add support for importing (and remotely building)
+// packages from multiple package source root GOPATH directories with
+// identical names.
+func importPackages(paths []string, pkgMap map[string]*build.Package) error {
+ for _, path := range paths {
+ recurse := false
+ if strings.HasSuffix(path, "...") {
+ recurse = true
+ path = strings.TrimSuffix(path, "...")
+ }
+ if _, exists := pkgMap[path]; !exists {
+ srcDir, mode := "", build.ImportMode(0)
+ pkg, err := build.Import(path, srcDir, mode)
+ if err != nil {
+ // "C" is a pseudo-package for cgo: http://golang.org/cmd/cgo/
+ // Do not attempt recursive imports.
+ if pkg.ImportPath == "C" {
+ continue
+ }
+ return fmt.Errorf("Import(%q,%q,%v) failed: %v", path, srcDir, mode, err)
+ }
+ if pkg.Goroot {
+ continue
+ }
+ pkgMap[path] = pkg
+ if err := importPackages(pkg.Imports, pkgMap); err != nil {
+ return err
+ }
+ }
+ if recurse {
+ pkg := pkgMap[path]
+ fis, err := ioutil.ReadDir(pkg.Dir)
+ if err != nil {
+ return fmt.Errorf("ReadDir(%v) failed: %v", pkg.Dir)
+ }
+ for _, fi := range fis {
+ if fi.IsDir() {
+ subPath := filepath.Join(path, fi.Name(), "...")
+ if err := importPackages([]string{subPath}, pkgMap); err != nil {
+ return err
+ }
+ }
+ }
+ }
+ }
+ return nil
+}
+
+func getSources(pkgMap map[string]*build.Package, cancel <-chan struct{}, errchan chan<- error) <-chan vbuild.File {
+ sources := make(chan vbuild.File)
+ go func() {
+ defer close(sources)
+ for _, pkg := range pkgMap {
+ for _, files := range [][]string{pkg.CFiles, pkg.CgoFiles, pkg.GoFiles, pkg.SFiles} {
+ for _, file := range files {
+ path := filepath.Join(pkg.Dir, file)
+ bytes, err := ioutil.ReadFile(path)
+ if err != nil {
+ errchan <- fmt.Errorf("ReadFile(%v) failed: %v", path, err)
+ return
+ }
+ select {
+ case sources <- vbuild.File{Contents: bytes, Name: filepath.Join(pkg.ImportPath, file)}:
+ case <-cancel:
+ errchan <- nil
+ return
+ }
+ }
+ }
+ }
+ errchan <- nil
+ }()
+ return sources
+}
+
+func invokeBuild(ctx context.T, name string, sources <-chan vbuild.File, cancel <-chan struct{}, errchan chan<- error) <-chan vbuild.File {
+ binaries := make(chan vbuild.File)
+ go func() {
+ defer close(binaries)
+ rt.Init()
+ client, err := vbuild.BindBuilder(name)
+ if err != nil {
+ errchan <- fmt.Errorf("BindBuilder(%v) failed: %v", name, err)
+ return
+ }
+ stream, err := client.Build(ctx, vbuild.Architecture(flagArch), vbuild.OperatingSystem(flagOS))
+ if err != nil {
+ errchan <- fmt.Errorf("Build() failed: %v", err)
+ return
+ }
+ sender := stream.SendStream()
+ for source := range sources {
+ if err := sender.Send(source); err != nil {
+ stream.Cancel()
+ errchan <- fmt.Errorf("Send() failed: %v", err)
+ return
+ }
+ }
+ if err := sender.Close(); err != nil {
+ errchan <- fmt.Errorf("Close() failed: %v", err)
+ return
+ }
+ iterator := stream.RecvStream()
+ for iterator.Advance() {
+ // TODO(mattr): This custom cancellation can probably be folded into the
+ // cancellation mechanism provided by the context.
+ select {
+ case binaries <- iterator.Value():
+ case <-cancel:
+ errchan <- nil
+ return
+ }
+ }
+ if err := iterator.Err(); err != nil {
+ errchan <- fmt.Errorf("Advance() failed: %v", err)
+ return
+ }
+ if out, err := stream.Finish(); err != nil {
+ errchan <- fmt.Errorf("Finish() failed: (%v, %v)", string(out), err)
+ return
+ }
+ errchan <- nil
+ }()
+ return binaries
+}
+
+func saveBinaries(prefix string, binaries <-chan vbuild.File, cancel chan<- struct{}, errchan chan<- error) {
+ go func() {
+ for binary := range binaries {
+ path, perm := filepath.Join(prefix, filepath.Base(binary.Name)), os.FileMode(0755)
+ if err := ioutil.WriteFile(path, binary.Contents, perm); err != nil {
+ errchan <- fmt.Errorf("WriteFile(%v, %v) failed: %v", path, perm, err)
+ return
+ }
+ fmt.Printf("Generated binary %v\n", path)
+ }
+ errchan <- nil
+ }()
+}
+
+// runBuild identifies the source files needed to build the packages
+// specified on command-line and then creates a pipeline that
+// concurrently 1) reads the source files, 2) sends them to the build
+// server and receives binaries from the build server, and 3) writes
+// the binaries out to the disk.
+func runBuild(command *cmdline.Command, args []string) error {
+ name, paths := args[0], args[1:]
+ pkgMap := map[string]*build.Package{}
+ if err := importPackages(paths, pkgMap); err != nil {
+ return err
+ }
+ cancel, errchan := make(chan struct{}), make(chan error)
+ defer close(errchan)
+
+ ctx, ctxCancel := rt.R().NewContext().WithTimeout(time.Minute)
+ defer ctxCancel()
+
+ // Start all stages of the pipeline.
+ sources := getSources(pkgMap, cancel, errchan)
+ binaries := invokeBuild(ctx, name, sources, cancel, errchan)
+ saveBinaries(os.TempDir(), binaries, cancel, errchan)
+ // Wait for all stages of the pipeline to terminate.
+ cancelled, errors, numStages := false, []error{}, 3
+ for i := 0; i < numStages; i++ {
+ if err := <-errchan; err != nil {
+ errors = append(errors, err)
+ if !cancelled {
+ close(cancel)
+ cancelled = true
+ }
+ }
+ }
+ if len(errors) != 0 {
+ return fmt.Errorf("build failed(%v)", errors)
+ }
+ return nil
+}