blob: b8e0b90b59d7d16765a1570085507d876a405aef [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 Simsa764efb72014-12-25 20:57:03 -080013 "v.io/core/veyron2/context"
14 vbuild "v.io/core/veyron2/services/mgmt/build"
Todd Wang478fcf92014-12-26 12:37:37 -080015 "v.io/lib/cmdline"
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
Matt Rosencrantz4f8ac602014-12-29 14:42:48 -0800111func getSources(ctx *context.T, pkgMap map[string]*build.Package, errchan chan<- error) <-chan vbuild.File {
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700112 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)}:
Matt Rosencrantz9346b412014-12-18 15:59:19 -0800126 case <-ctx.Done():
127 errchan <- fmt.Errorf("Get sources failed: %v", ctx.Err())
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700128 return
129 }
130 }
131 }
132 }
133 errchan <- nil
134 }()
135 return sources
136}
137
Matt Rosencrantz4f8ac602014-12-29 14:42:48 -0800138func invokeBuild(ctx *context.T, name string, sources <-chan vbuild.File, 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)
Matt Rosencrantz89445a42015-01-05 13:32:37 -0800142 ctx, cancel := context.WithCancel(ctx)
Matt Rosencrantz9346b412014-12-18 15:59:19 -0800143 defer cancel()
144
Todd Wang702385a2014-11-07 01:54:08 -0800145 client := vbuild.BuilderClient(name)
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700146 stream, err := client.Build(ctx, vbuild.Architecture(flagArch), vbuild.OperatingSystem(flagOS))
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700147 if err != nil {
148 errchan <- fmt.Errorf("Build() failed: %v", err)
149 return
150 }
151 sender := stream.SendStream()
152 for source := range sources {
153 if err := sender.Send(source); err != nil {
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700154 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() {
164 select {
165 case binaries <- iterator.Value():
Matt Rosencrantz9346b412014-12-18 15:59:19 -0800166 case <-ctx.Done():
167 errchan <- fmt.Errorf("Invoke build failed: %v", ctx.Err())
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700168 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
Matt Rosencrantz4f8ac602014-12-29 14:42:48 -0800184func saveBinaries(ctx *context.T, prefix string, binaries <-chan vbuild.File, errchan chan<- error) {
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700185 go func() {
186 for binary := range binaries {
Matt Rosencrantz9346b412014-12-18 15:59:19 -0800187 select {
188 case <-ctx.Done():
189 errchan <- fmt.Errorf("Save binaries failed: %v", ctx.Err())
190 return
191 default:
192 }
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700193 path, perm := filepath.Join(prefix, filepath.Base(binary.Name)), os.FileMode(0755)
194 if err := ioutil.WriteFile(path, binary.Contents, perm); err != nil {
195 errchan <- fmt.Errorf("WriteFile(%v, %v) failed: %v", path, perm, err)
196 return
197 }
198 fmt.Printf("Generated binary %v\n", path)
199 }
200 errchan <- nil
201 }()
202}
203
204// runBuild identifies the source files needed to build the packages
205// specified on command-line and then creates a pipeline that
206// concurrently 1) reads the source files, 2) sends them to the build
207// server and receives binaries from the build server, and 3) writes
208// the binaries out to the disk.
209func runBuild(command *cmdline.Command, args []string) error {
210 name, paths := args[0], args[1:]
211 pkgMap := map[string]*build.Package{}
212 if err := importPackages(paths, pkgMap); err != nil {
213 return err
214 }
Matt Rosencrantz9346b412014-12-18 15:59:19 -0800215 errchan := make(chan error)
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700216 defer close(errchan)
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700217
Matt Rosencrantza5ad2722015-01-22 11:17:47 -0800218 ctx, ctxCancel := context.WithTimeout(gctx, time.Minute)
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700219 defer ctxCancel()
220
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700221 // Start all stages of the pipeline.
Matt Rosencrantz9346b412014-12-18 15:59:19 -0800222 sources := getSources(ctx, pkgMap, errchan)
223 binaries := invokeBuild(ctx, name, sources, errchan)
224 saveBinaries(ctx, os.TempDir(), binaries, errchan)
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700225 // Wait for all stages of the pipeline to terminate.
Matt Rosencrantz9346b412014-12-18 15:59:19 -0800226 errors, numStages := []error{}, 3
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700227 for i := 0; i < numStages; i++ {
228 if err := <-errchan; err != nil {
229 errors = append(errors, err)
Matt Rosencrantz9346b412014-12-18 15:59:19 -0800230 ctxCancel()
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700231 }
232 }
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700233 if len(errors) != 0 {
Asim Shankarff5b6352014-08-07 14:52:03 -0700234 return fmt.Errorf("build failed(%v)", errors)
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700235 }
236 return nil
237}