blob: d0c0e5d9007fe942f938aae914a7f1c81e77f32b [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"
Jiri Simsadf4e2322015-02-03 17:08:40 -08009 "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
Todd Wang478fcf92014-12-26 12:37:37 -080013 "v.io/lib/cmdline"
Jiri Simsa6ac95222015-02-23 16:11:49 -080014 "v.io/v23/context"
15 vbuild "v.io/v23/services/mgmt/build"
Jiri Simsa9d22b7d2014-08-05 14:10:22 -070016)
17
Jiri Simsadf4e2322015-02-03 17:08:40 -080018const (
19 defaultArch = "$GOARCH"
20 defaultOS = "$GOOS"
21)
22
Jiri Simsa9d22b7d2014-08-05 14:10:22 -070023var (
24 flagArch string
25 flagOS string
26)
27
28func init() {
Jiri Simsadf4e2322015-02-03 17:08:40 -080029 cmdBuild.Flags.StringVar(&flagArch, "arch", defaultArch, "Target architecture.")
30 cmdBuild.Flags.StringVar(&flagOS, "os", defaultOS, "Target operating system.")
31}
32
33// substituteVarsInFlags substitutes environment variables in default
34// values of relevant flags.
35func substituteVarsInFlags() {
36 if flagArch == defaultArch {
37 flagArch = runtime.GOARCH
38 }
39 if flagOS == defaultOS {
40 flagOS = runtime.GOOS
41 }
Jiri Simsa9d22b7d2014-08-05 14:10:22 -070042}
43
44var cmdRoot = &cmdline.Command{
Todd Wangfcb72a52014-10-01 09:53:56 -070045 Name: "build",
46 Short: "Tool for interacting with the veyron build server",
47 Long: `
48The build tool tool facilitates interaction with the veyron build server.
49`,
Jiri Simsa9d22b7d2014-08-05 14:10:22 -070050 Children: []*cmdline.Command{cmdBuild},
51}
52
Robin Thellend18205cf2014-10-21 13:53:59 -070053// root returns a command that represents the root of the veyron tool.
54func root() *cmdline.Command {
Jiri Simsa9d22b7d2014-08-05 14:10:22 -070055 return cmdRoot
56}
57
58var cmdBuild = &cmdline.Command{
59 Run: runBuild,
60 Name: "build",
61 Short: "Build veyron Go packages",
62 Long: `
63Build veyron Go packages using a remote build server. The command
64collects all source code files that are not part of the Go standard
65library that the target packages depend on, sends them to a build
66server, and receives the built binaries.
67`,
68 ArgsName: "<name> <packages>",
69 ArgsLong: `
70<name> is a veyron object name of a build server
71<packages> is a list of packages to build, specified as arguments for
72each command. The format is similar to the go tool. In its simplest
73form each package is an import path; e.g. "veyron/tools/build". A
74package that ends with "..." does a wildcard match against all
75packages with that prefix.
76`,
77}
78
79// TODO(jsimsa): Add support for importing (and remotely building)
80// packages from multiple package source root GOPATH directories with
81// identical names.
82func importPackages(paths []string, pkgMap map[string]*build.Package) error {
83 for _, path := range paths {
84 recurse := false
85 if strings.HasSuffix(path, "...") {
86 recurse = true
87 path = strings.TrimSuffix(path, "...")
88 }
89 if _, exists := pkgMap[path]; !exists {
90 srcDir, mode := "", build.ImportMode(0)
91 pkg, err := build.Import(path, srcDir, mode)
92 if err != nil {
Asim Shankar553bfb82014-08-16 10:09:03 -070093 // "C" is a pseudo-package for cgo: http://golang.org/cmd/cgo/
94 // Do not attempt recursive imports.
95 if pkg.ImportPath == "C" {
96 continue
97 }
Jiri Simsa9d22b7d2014-08-05 14:10:22 -070098 return fmt.Errorf("Import(%q,%q,%v) failed: %v", path, srcDir, mode, err)
99 }
100 if pkg.Goroot {
101 continue
102 }
103 pkgMap[path] = pkg
104 if err := importPackages(pkg.Imports, pkgMap); err != nil {
105 return err
106 }
107 }
108 if recurse {
109 pkg := pkgMap[path]
110 fis, err := ioutil.ReadDir(pkg.Dir)
111 if err != nil {
112 return fmt.Errorf("ReadDir(%v) failed: %v", pkg.Dir)
113 }
114 for _, fi := range fis {
115 if fi.IsDir() {
116 subPath := filepath.Join(path, fi.Name(), "...")
117 if err := importPackages([]string{subPath}, pkgMap); err != nil {
118 return err
119 }
120 }
121 }
122 }
123 }
124 return nil
125}
126
Matt Rosencrantz4f8ac602014-12-29 14:42:48 -0800127func getSources(ctx *context.T, pkgMap map[string]*build.Package, errchan chan<- error) <-chan vbuild.File {
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700128 sources := make(chan vbuild.File)
129 go func() {
130 defer close(sources)
131 for _, pkg := range pkgMap {
132 for _, files := range [][]string{pkg.CFiles, pkg.CgoFiles, pkg.GoFiles, pkg.SFiles} {
133 for _, file := range files {
134 path := filepath.Join(pkg.Dir, file)
135 bytes, err := ioutil.ReadFile(path)
136 if err != nil {
137 errchan <- fmt.Errorf("ReadFile(%v) failed: %v", path, err)
138 return
139 }
140 select {
141 case sources <- vbuild.File{Contents: bytes, Name: filepath.Join(pkg.ImportPath, file)}:
Matt Rosencrantz9346b412014-12-18 15:59:19 -0800142 case <-ctx.Done():
143 errchan <- fmt.Errorf("Get sources failed: %v", ctx.Err())
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700144 return
145 }
146 }
147 }
148 }
149 errchan <- nil
150 }()
151 return sources
152}
153
Matt Rosencrantz4f8ac602014-12-29 14:42:48 -0800154func invokeBuild(ctx *context.T, name string, sources <-chan vbuild.File, errchan chan<- error) <-chan vbuild.File {
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700155 binaries := make(chan vbuild.File)
156 go func() {
157 defer close(binaries)
Matt Rosencrantz89445a42015-01-05 13:32:37 -0800158 ctx, cancel := context.WithCancel(ctx)
Matt Rosencrantz9346b412014-12-18 15:59:19 -0800159 defer cancel()
160
Todd Wang702385a2014-11-07 01:54:08 -0800161 client := vbuild.BuilderClient(name)
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700162 stream, err := client.Build(ctx, vbuild.Architecture(flagArch), vbuild.OperatingSystem(flagOS))
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700163 if err != nil {
164 errchan <- fmt.Errorf("Build() failed: %v", err)
165 return
166 }
167 sender := stream.SendStream()
168 for source := range sources {
169 if err := sender.Send(source); err != nil {
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700170 errchan <- fmt.Errorf("Send() failed: %v", err)
171 return
172 }
173 }
174 if err := sender.Close(); err != nil {
175 errchan <- fmt.Errorf("Close() failed: %v", err)
176 return
177 }
178 iterator := stream.RecvStream()
179 for iterator.Advance() {
180 select {
181 case binaries <- iterator.Value():
Matt Rosencrantz9346b412014-12-18 15:59:19 -0800182 case <-ctx.Done():
183 errchan <- fmt.Errorf("Invoke build failed: %v", ctx.Err())
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700184 return
185 }
186 }
187 if err := iterator.Err(); err != nil {
188 errchan <- fmt.Errorf("Advance() failed: %v", err)
189 return
190 }
Jiri Simsa1023a342014-08-20 18:01:22 -0700191 if out, err := stream.Finish(); err != nil {
Jiri Simsa701cdc62014-08-21 16:52:10 -0700192 errchan <- fmt.Errorf("Finish() failed: (%v, %v)", string(out), err)
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700193 return
194 }
195 errchan <- nil
196 }()
197 return binaries
198}
199
Matt Rosencrantz4f8ac602014-12-29 14:42:48 -0800200func saveBinaries(ctx *context.T, prefix string, binaries <-chan vbuild.File, errchan chan<- error) {
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700201 go func() {
202 for binary := range binaries {
Matt Rosencrantz9346b412014-12-18 15:59:19 -0800203 select {
204 case <-ctx.Done():
205 errchan <- fmt.Errorf("Save binaries failed: %v", ctx.Err())
206 return
207 default:
208 }
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700209 path, perm := filepath.Join(prefix, filepath.Base(binary.Name)), os.FileMode(0755)
210 if err := ioutil.WriteFile(path, binary.Contents, perm); err != nil {
211 errchan <- fmt.Errorf("WriteFile(%v, %v) failed: %v", path, perm, err)
212 return
213 }
214 fmt.Printf("Generated binary %v\n", path)
215 }
216 errchan <- nil
217 }()
218}
219
220// runBuild identifies the source files needed to build the packages
221// specified on command-line and then creates a pipeline that
222// concurrently 1) reads the source files, 2) sends them to the build
223// server and receives binaries from the build server, and 3) writes
224// the binaries out to the disk.
225func runBuild(command *cmdline.Command, args []string) error {
226 name, paths := args[0], args[1:]
227 pkgMap := map[string]*build.Package{}
228 if err := importPackages(paths, pkgMap); err != nil {
229 return err
230 }
Matt Rosencrantz9346b412014-12-18 15:59:19 -0800231 errchan := make(chan error)
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700232 defer close(errchan)
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700233
Matt Rosencrantza5ad2722015-01-22 11:17:47 -0800234 ctx, ctxCancel := context.WithTimeout(gctx, time.Minute)
Matt Rosencrantz137b8d22014-08-18 09:56:15 -0700235 defer ctxCancel()
236
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700237 // Start all stages of the pipeline.
Matt Rosencrantz9346b412014-12-18 15:59:19 -0800238 sources := getSources(ctx, pkgMap, errchan)
239 binaries := invokeBuild(ctx, name, sources, errchan)
240 saveBinaries(ctx, os.TempDir(), binaries, errchan)
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700241 // Wait for all stages of the pipeline to terminate.
Matt Rosencrantz9346b412014-12-18 15:59:19 -0800242 errors, numStages := []error{}, 3
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700243 for i := 0; i < numStages; i++ {
244 if err := <-errchan; err != nil {
245 errors = append(errors, err)
Matt Rosencrantz9346b412014-12-18 15:59:19 -0800246 ctxCancel()
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700247 }
248 }
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700249 if len(errors) != 0 {
Asim Shankarff5b6352014-08-07 14:52:03 -0700250 return fmt.Errorf("build failed(%v)", errors)
Jiri Simsa9d22b7d2014-08-05 14:10:22 -0700251 }
252 return nil
253}