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