blob: 6126bfcc27693330edf20b58b62a7dc252206b0c [file] [log] [blame]
Jiri Simsa9d22b7d2014-08-05 14:10:22 -07001package impl
2
3import (
4 "fmt"
5 "go/build"
6 "io/ioutil"
7 "os"
8 "path/filepath"
9 "runtime"
10 "strings"
Matt Rosencrantz137b8d22014-08-18 09:56:15 -070011 "time"
Jiri Simsa9d22b7d2014-08-05 14:10:22 -070012
Jiri Simsa519c5072014-09-17 21:37:57 -070013 "veyron.io/veyron/veyron/lib/cmdline"
Jiri Simsa9d22b7d2014-08-05 14:10:22 -070014
Jiri Simsa519c5072014-09-17 21:37:57 -070015 "veyron.io/veyron/veyron2/context"
16 "veyron.io/veyron/veyron2/rt"
17 vbuild "veyron.io/veyron/veyron2/services/mgmt/build"
Jiri Simsa9d22b7d2014-08-05 14:10:22 -070018)
19
20var (
21 flagArch string
22 flagOS string
23)
24
25func init() {
26 cmdBuild.Flags.StringVar(&flagArch, "arch", runtime.GOARCH, "Target architecture.")
27 cmdBuild.Flags.StringVar(&flagOS, "os", runtime.GOOS, "Target operating system.")
28}
29
30var cmdRoot = &cmdline.Command{
Todd Wangfcb72a52014-10-01 09:53:56 -070031 Name: "build",
32 Short: "Tool for interacting with the veyron build server",
33 Long: `
34The build tool tool facilitates interaction with the veyron build server.
35`,
Jiri Simsa9d22b7d2014-08-05 14:10:22 -070036 Children: []*cmdline.Command{cmdBuild},
37}
38
39// Root returns a command that represents the root of the veyron tool.
40func Root() *cmdline.Command {
41 return cmdRoot
42}
43
44var cmdBuild = &cmdline.Command{
45 Run: runBuild,
46 Name: "build",
47 Short: "Build veyron Go packages",
48 Long: `
49Build veyron Go packages using a remote build server. The command
50collects all source code files that are not part of the Go standard
51library that the target packages depend on, sends them to a build
52server, and receives the built binaries.
53`,
54 ArgsName: "<name> <packages>",
55 ArgsLong: `
56<name> is a veyron object name of a build server
57<packages> is a list of packages to build, specified as arguments for
58each command. The format is similar to the go tool. In its simplest
59form each package is an import path; e.g. "veyron/tools/build". A
60package that ends with "..." does a wildcard match against all
61packages with that prefix.
62`,
63}
64
65// TODO(jsimsa): Add support for importing (and remotely building)
66// packages from multiple package source root GOPATH directories with
67// identical names.
68func importPackages(paths []string, pkgMap map[string]*build.Package) error {
69 for _, path := range paths {
70 recurse := false
71 if strings.HasSuffix(path, "...") {
72 recurse = true
73 path = strings.TrimSuffix(path, "...")
74 }
75 if _, exists := pkgMap[path]; !exists {
76 srcDir, mode := "", build.ImportMode(0)
77 pkg, err := build.Import(path, srcDir, mode)
78 if err != nil {
Asim Shankar553bfb82014-08-16 10:09:03 -070079 // "C" is a pseudo-package for cgo: http://golang.org/cmd/cgo/
80 // Do not attempt recursive imports.
81 if pkg.ImportPath == "C" {
82 continue
83 }
Jiri Simsa9d22b7d2014-08-05 14:10:22 -070084 return fmt.Errorf("Import(%q,%q,%v) failed: %v", path, srcDir, mode, err)
85 }
86 if pkg.Goroot {
87 continue
88 }
89 pkgMap[path] = pkg
90 if err := importPackages(pkg.Imports, pkgMap); err != nil {
91 return err
92 }
93 }
94 if recurse {
95 pkg := pkgMap[path]
96 fis, err := ioutil.ReadDir(pkg.Dir)
97 if err != nil {
98 return fmt.Errorf("ReadDir(%v) failed: %v", pkg.Dir)
99 }
100 for _, fi := range fis {
101 if fi.IsDir() {
102 subPath := filepath.Join(path, fi.Name(), "...")
103 if err := importPackages([]string{subPath}, pkgMap); err != nil {
104 return err
105 }
106 }
107 }
108 }
109 }
110 return nil
111}
112
113func getSources(pkgMap map[string]*build.Package, cancel <-chan struct{}, errchan chan<- error) <-chan vbuild.File {
114 sources := make(chan vbuild.File)
115 go func() {
116 defer close(sources)
117 for _, pkg := range pkgMap {
118 for _, files := range [][]string{pkg.CFiles, pkg.CgoFiles, pkg.GoFiles, pkg.SFiles} {
119 for _, file := range files {
120 path := filepath.Join(pkg.Dir, file)
121 bytes, err := ioutil.ReadFile(path)
122 if err != nil {
123 errchan <- fmt.Errorf("ReadFile(%v) failed: %v", path, err)
124 return
125 }
126 select {
127 case sources <- vbuild.File{Contents: bytes, Name: filepath.Join(pkg.ImportPath, file)}:
128 case <-cancel:
129 errchan <- nil
130 return
131 }
132 }
133 }
134 }
135 errchan <- nil
136 }()
137 return sources
138}
139
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700140func invokeBuild(ctx context.T, name string, sources <-chan vbuild.File, cancel <-chan struct{}, errchan chan<- error) <-chan vbuild.File {
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700141 binaries := make(chan vbuild.File)
142 go func() {
143 defer close(binaries)
144 rt.Init()
145 client, err := vbuild.BindBuilder(name)
146 if err != nil {
147 errchan <- fmt.Errorf("BindBuilder(%v) failed: %v", name, err)
148 return
149 }
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700150 stream, err := client.Build(ctx, vbuild.Architecture(flagArch), vbuild.OperatingSystem(flagOS))
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700151 if err != nil {
152 errchan <- fmt.Errorf("Build() failed: %v", err)
153 return
154 }
155 sender := stream.SendStream()
156 for source := range sources {
157 if err := sender.Send(source); err != nil {
158 stream.Cancel()
159 errchan <- fmt.Errorf("Send() failed: %v", err)
160 return
161 }
162 }
163 if err := sender.Close(); err != nil {
164 errchan <- fmt.Errorf("Close() failed: %v", err)
165 return
166 }
167 iterator := stream.RecvStream()
168 for iterator.Advance() {
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700169 // TODO(mattr): This custom cancellation can probably be folded into the
170 // cancellation mechanism provided by the context.
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700171 select {
172 case binaries <- iterator.Value():
173 case <-cancel:
174 errchan <- nil
175 return
176 }
177 }
178 if err := iterator.Err(); err != nil {
179 errchan <- fmt.Errorf("Advance() failed: %v", err)
180 return
181 }
Jiri Simsa1023a342014-08-20 18:01:22 -0700182 if out, err := stream.Finish(); err != nil {
Jiri Simsa701cdc62014-08-21 16:52:10 -0700183 errchan <- fmt.Errorf("Finish() failed: (%v, %v)", string(out), err)
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700184 return
185 }
186 errchan <- nil
187 }()
188 return binaries
189}
190
191func saveBinaries(prefix string, binaries <-chan vbuild.File, cancel chan<- struct{}, errchan chan<- error) {
192 go func() {
193 for binary := range binaries {
194 path, perm := filepath.Join(prefix, filepath.Base(binary.Name)), os.FileMode(0755)
195 if err := ioutil.WriteFile(path, binary.Contents, perm); err != nil {
196 errchan <- fmt.Errorf("WriteFile(%v, %v) failed: %v", path, perm, err)
197 return
198 }
199 fmt.Printf("Generated binary %v\n", path)
200 }
201 errchan <- nil
202 }()
203}
204
205// runBuild identifies the source files needed to build the packages
206// specified on command-line and then creates a pipeline that
207// concurrently 1) reads the source files, 2) sends them to the build
208// server and receives binaries from the build server, and 3) writes
209// the binaries out to the disk.
210func runBuild(command *cmdline.Command, args []string) error {
211 name, paths := args[0], args[1:]
212 pkgMap := map[string]*build.Package{}
213 if err := importPackages(paths, pkgMap); err != nil {
214 return err
215 }
216 cancel, errchan := make(chan struct{}), make(chan error)
217 defer close(errchan)
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700218
219 ctx, ctxCancel := rt.R().NewContext().WithTimeout(time.Minute)
220 defer ctxCancel()
221
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700222 // Start all stages of the pipeline.
223 sources := getSources(pkgMap, cancel, errchan)
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700224 binaries := invokeBuild(ctx, name, sources, cancel, errchan)
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700225 saveBinaries(os.TempDir(), binaries, cancel, errchan)
226 // Wait for all stages of the pipeline to terminate.
227 cancelled, errors, numStages := false, []error{}, 3
228 for i := 0; i < numStages; i++ {
229 if err := <-errchan; err != nil {
230 errors = append(errors, err)
231 if !cancelled {
232 close(cancel)
233 cancelled = true
234 }
235 }
236 }
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700237 if len(errors) != 0 {
Asim Shankarff5b6352014-08-07 14:52:03 -0700238 return fmt.Errorf("build failed(%v)", errors)
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700239 }
240 return nil
241}