Robin Thellend | 18205cf | 2014-10-21 13:53:59 -0700 | [diff] [blame] | 1 | package main |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "go/build" |
| 6 | "io/ioutil" |
| 7 | "os" |
| 8 | "path/filepath" |
Jiri Simsa | df4e232 | 2015-02-03 17:08:40 -0800 | [diff] [blame] | 9 | "runtime" |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 10 | "strings" |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 11 | "time" |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 12 | |
Todd Wang | 478fcf9 | 2014-12-26 12:37:37 -0800 | [diff] [blame] | 13 | "v.io/lib/cmdline" |
Jiri Simsa | 6ac9522 | 2015-02-23 16:11:49 -0800 | [diff] [blame] | 14 | "v.io/v23/context" |
| 15 | vbuild "v.io/v23/services/mgmt/build" |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 16 | ) |
| 17 | |
Jiri Simsa | df4e232 | 2015-02-03 17:08:40 -0800 | [diff] [blame] | 18 | const ( |
| 19 | defaultArch = "$GOARCH" |
| 20 | defaultOS = "$GOOS" |
| 21 | ) |
| 22 | |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 23 | var ( |
| 24 | flagArch string |
| 25 | flagOS string |
| 26 | ) |
| 27 | |
| 28 | func init() { |
Jiri Simsa | df4e232 | 2015-02-03 17:08:40 -0800 | [diff] [blame] | 29 | 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. |
| 35 | func substituteVarsInFlags() { |
| 36 | if flagArch == defaultArch { |
| 37 | flagArch = runtime.GOARCH |
| 38 | } |
| 39 | if flagOS == defaultOS { |
| 40 | flagOS = runtime.GOOS |
| 41 | } |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 42 | } |
| 43 | |
| 44 | var cmdRoot = &cmdline.Command{ |
Todd Wang | fcb72a5 | 2014-10-01 09:53:56 -0700 | [diff] [blame] | 45 | Name: "build", |
| 46 | Short: "Tool for interacting with the veyron build server", |
| 47 | Long: ` |
| 48 | The build tool tool facilitates interaction with the veyron build server. |
| 49 | `, |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 50 | Children: []*cmdline.Command{cmdBuild}, |
| 51 | } |
| 52 | |
Robin Thellend | 18205cf | 2014-10-21 13:53:59 -0700 | [diff] [blame] | 53 | // root returns a command that represents the root of the veyron tool. |
| 54 | func root() *cmdline.Command { |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 55 | return cmdRoot |
| 56 | } |
| 57 | |
| 58 | var cmdBuild = &cmdline.Command{ |
| 59 | Run: runBuild, |
| 60 | Name: "build", |
| 61 | Short: "Build veyron Go packages", |
| 62 | Long: ` |
| 63 | Build veyron Go packages using a remote build server. The command |
| 64 | collects all source code files that are not part of the Go standard |
| 65 | library that the target packages depend on, sends them to a build |
| 66 | server, 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 |
| 72 | each command. The format is similar to the go tool. In its simplest |
| 73 | form each package is an import path; e.g. "veyron/tools/build". A |
| 74 | package that ends with "..." does a wildcard match against all |
| 75 | packages 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. |
| 82 | func 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 Shankar | 553bfb8 | 2014-08-16 10:09:03 -0700 | [diff] [blame] | 93 | // "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 Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 98 | 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 Rosencrantz | 4f8ac60 | 2014-12-29 14:42:48 -0800 | [diff] [blame] | 127 | func getSources(ctx *context.T, pkgMap map[string]*build.Package, errchan chan<- error) <-chan vbuild.File { |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 128 | 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 Rosencrantz | 9346b41 | 2014-12-18 15:59:19 -0800 | [diff] [blame] | 142 | case <-ctx.Done(): |
| 143 | errchan <- fmt.Errorf("Get sources failed: %v", ctx.Err()) |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 144 | return |
| 145 | } |
| 146 | } |
| 147 | } |
| 148 | } |
| 149 | errchan <- nil |
| 150 | }() |
| 151 | return sources |
| 152 | } |
| 153 | |
Matt Rosencrantz | 4f8ac60 | 2014-12-29 14:42:48 -0800 | [diff] [blame] | 154 | func invokeBuild(ctx *context.T, name string, sources <-chan vbuild.File, errchan chan<- error) <-chan vbuild.File { |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 155 | binaries := make(chan vbuild.File) |
| 156 | go func() { |
| 157 | defer close(binaries) |
Matt Rosencrantz | 89445a4 | 2015-01-05 13:32:37 -0800 | [diff] [blame] | 158 | ctx, cancel := context.WithCancel(ctx) |
Matt Rosencrantz | 9346b41 | 2014-12-18 15:59:19 -0800 | [diff] [blame] | 159 | defer cancel() |
| 160 | |
Todd Wang | 702385a | 2014-11-07 01:54:08 -0800 | [diff] [blame] | 161 | client := vbuild.BuilderClient(name) |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 162 | stream, err := client.Build(ctx, vbuild.Architecture(flagArch), vbuild.OperatingSystem(flagOS)) |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 163 | 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 Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 170 | 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 Rosencrantz | 9346b41 | 2014-12-18 15:59:19 -0800 | [diff] [blame] | 182 | case <-ctx.Done(): |
| 183 | errchan <- fmt.Errorf("Invoke build failed: %v", ctx.Err()) |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 184 | return |
| 185 | } |
| 186 | } |
| 187 | if err := iterator.Err(); err != nil { |
| 188 | errchan <- fmt.Errorf("Advance() failed: %v", err) |
| 189 | return |
| 190 | } |
Jiri Simsa | 1023a34 | 2014-08-20 18:01:22 -0700 | [diff] [blame] | 191 | if out, err := stream.Finish(); err != nil { |
Jiri Simsa | 701cdc6 | 2014-08-21 16:52:10 -0700 | [diff] [blame] | 192 | errchan <- fmt.Errorf("Finish() failed: (%v, %v)", string(out), err) |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 193 | return |
| 194 | } |
| 195 | errchan <- nil |
| 196 | }() |
| 197 | return binaries |
| 198 | } |
| 199 | |
Matt Rosencrantz | 4f8ac60 | 2014-12-29 14:42:48 -0800 | [diff] [blame] | 200 | func saveBinaries(ctx *context.T, prefix string, binaries <-chan vbuild.File, errchan chan<- error) { |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 201 | go func() { |
| 202 | for binary := range binaries { |
Matt Rosencrantz | 9346b41 | 2014-12-18 15:59:19 -0800 | [diff] [blame] | 203 | select { |
| 204 | case <-ctx.Done(): |
| 205 | errchan <- fmt.Errorf("Save binaries failed: %v", ctx.Err()) |
| 206 | return |
| 207 | default: |
| 208 | } |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 209 | 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. |
| 225 | func 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 Rosencrantz | 9346b41 | 2014-12-18 15:59:19 -0800 | [diff] [blame] | 231 | errchan := make(chan error) |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 232 | defer close(errchan) |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 233 | |
Matt Rosencrantz | a5ad272 | 2015-01-22 11:17:47 -0800 | [diff] [blame] | 234 | ctx, ctxCancel := context.WithTimeout(gctx, time.Minute) |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 235 | defer ctxCancel() |
| 236 | |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 237 | // Start all stages of the pipeline. |
Matt Rosencrantz | 9346b41 | 2014-12-18 15:59:19 -0800 | [diff] [blame] | 238 | sources := getSources(ctx, pkgMap, errchan) |
| 239 | binaries := invokeBuild(ctx, name, sources, errchan) |
| 240 | saveBinaries(ctx, os.TempDir(), binaries, errchan) |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 241 | // Wait for all stages of the pipeline to terminate. |
Matt Rosencrantz | 9346b41 | 2014-12-18 15:59:19 -0800 | [diff] [blame] | 242 | errors, numStages := []error{}, 3 |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 243 | for i := 0; i < numStages; i++ { |
| 244 | if err := <-errchan; err != nil { |
| 245 | errors = append(errors, err) |
Matt Rosencrantz | 9346b41 | 2014-12-18 15:59:19 -0800 | [diff] [blame] | 246 | ctxCancel() |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 247 | } |
| 248 | } |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 249 | if len(errors) != 0 { |
Asim Shankar | ff5b635 | 2014-08-07 14:52:03 -0700 | [diff] [blame] | 250 | return fmt.Errorf("build failed(%v)", errors) |
Jiri Simsa | 9d22b7d | 2014-08-05 14:10:22 -0700 | [diff] [blame] | 251 | } |
| 252 | return nil |
| 253 | } |