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