blob: e4a05fc08a17edd5efde7785371bfd9be5a98fe5 [file] [log] [blame]
Dan Willemsenc2af0be2017-01-20 14:10:01 -08001// Copyright 2017 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package main
16
17import (
18 "bytes"
19 "context"
20 "flag"
21 "fmt"
Dan Willemsenf624fb92017-05-19 16:39:04 -070022 "io/ioutil"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080023 "os"
24 "path/filepath"
25 "runtime"
26 "strings"
27 "sync"
Steven Moreland552432e2017-03-29 19:26:09 -070028 "time"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080029
30 "android/soong/ui/build"
31 "android/soong/ui/logger"
Dan Willemsend9f6fa22016-08-21 15:17:17 -070032 "android/soong/ui/tracer"
Dan Willemsene3480762017-11-07 11:23:27 -080033 "android/soong/zip"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080034)
35
36// We default to number of cpus / 4, which seems to be the sweet spot for my
37// system. I suspect this is mostly due to memory or disk bandwidth though, and
38// may depend on the size ofthe source tree, so this probably isn't a great
39// default.
40func detectNumJobs() int {
41 if runtime.NumCPU() < 4 {
42 return 1
43 }
44 return runtime.NumCPU() / 4
45}
46
47var numJobs = flag.Int("j", detectNumJobs(), "number of parallel kati jobs")
48
Dan Willemsene3480762017-11-07 11:23:27 -080049var keepArtifacts = flag.Bool("keep", false, "keep archives of artifacts")
Dan Willemsenc2af0be2017-01-20 14:10:01 -080050
51var outDir = flag.String("out", "", "path to store output directories (defaults to tmpdir under $OUT when empty)")
Dan Willemsenf624fb92017-05-19 16:39:04 -070052var alternateResultDir = flag.Bool("dist", false, "write select results to $DIST_DIR (or <out>/dist when empty)")
Dan Willemsenc2af0be2017-01-20 14:10:01 -080053
54var onlyConfig = flag.Bool("only-config", false, "Only run product config (not Soong or Kati)")
55var onlySoong = flag.Bool("only-soong", false, "Only run product config and Soong (not Kati)")
56
Dan Willemsen5ed900b2017-05-07 11:40:30 -070057var buildVariant = flag.String("variant", "eng", "build variant to use")
58
Dan Willemsen9957b9c2017-10-06 15:05:05 -070059var skipProducts = flag.String("skip-products", "", "comma-separated list of products to skip (known failures, etc)")
60
Dan Willemsenf624fb92017-05-19 16:39:04 -070061const errorLeadingLines = 20
62const errorTrailingLines = 20
63
Dan Willemsenc2af0be2017-01-20 14:10:01 -080064type Product struct {
Dan Willemsenf624fb92017-05-19 16:39:04 -070065 ctx build.Context
66 config build.Config
67 logFile string
Dan Willemsenc2af0be2017-01-20 14:10:01 -080068}
69
Dan Willemsena4e43a72017-05-06 16:58:26 -070070type Status struct {
71 cur int
72 total int
73 failed int
74
75 ctx build.Context
76 haveBlankLine bool
77 smartTerminal bool
78
79 lock sync.Mutex
80}
81
82func NewStatus(ctx build.Context) *Status {
83 return &Status{
84 ctx: ctx,
85 haveBlankLine: true,
86 smartTerminal: ctx.IsTerminal(),
87 }
88}
89
90func (s *Status) SetTotal(total int) {
91 s.total = total
92}
93
Dan Willemsenf624fb92017-05-19 16:39:04 -070094func (s *Status) Fail(product string, err error, logFile string) {
Dan Willemsena4e43a72017-05-06 16:58:26 -070095 s.Finish(product)
96
97 s.lock.Lock()
98 defer s.lock.Unlock()
99
100 if s.smartTerminal && !s.haveBlankLine {
101 fmt.Fprintln(s.ctx.Stdout())
102 s.haveBlankLine = true
103 }
104
105 s.failed++
106 fmt.Fprintln(s.ctx.Stderr(), "FAILED:", product)
107 s.ctx.Verboseln("FAILED:", product)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700108
109 if logFile != "" {
110 data, err := ioutil.ReadFile(logFile)
111 if err == nil {
112 lines := strings.Split(strings.TrimSpace(string(data)), "\n")
113 if len(lines) > errorLeadingLines+errorTrailingLines+1 {
114 lines[errorLeadingLines] = fmt.Sprintf("... skipping %d lines ...",
115 len(lines)-errorLeadingLines-errorTrailingLines)
116
117 lines = append(lines[:errorLeadingLines+1],
118 lines[len(lines)-errorTrailingLines:]...)
119 }
120 for _, line := range lines {
121 fmt.Fprintln(s.ctx.Stderr(), "> ", line)
122 s.ctx.Verboseln(line)
123 }
124 }
125 }
126
127 s.ctx.Print(err)
Dan Willemsena4e43a72017-05-06 16:58:26 -0700128}
129
130func (s *Status) Finish(product string) {
131 s.lock.Lock()
132 defer s.lock.Unlock()
133
134 s.cur++
135 line := fmt.Sprintf("[%d/%d] %s", s.cur, s.total, product)
136
137 if s.smartTerminal {
138 if max, ok := s.ctx.TermWidth(); ok {
139 if len(line) > max {
140 line = line[:max]
141 }
142 }
143
144 fmt.Fprint(s.ctx.Stdout(), "\r", line, "\x1b[K")
145 s.haveBlankLine = false
146 } else {
147 s.ctx.Println(line)
148 }
149}
150
151func (s *Status) Finished() int {
152 s.lock.Lock()
153 defer s.lock.Unlock()
154
155 if !s.haveBlankLine {
156 fmt.Fprintln(s.ctx.Stdout())
157 s.haveBlankLine = true
158 }
159 return s.failed
160}
161
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800162func main() {
163 log := logger.New(os.Stderr)
164 defer log.Cleanup()
165
166 flag.Parse()
167
168 ctx, cancel := context.WithCancel(context.Background())
169 defer cancel()
170
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700171 trace := tracer.New(log)
172 defer trace.Close()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800173
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700174 build.SetupSignals(log, cancel, func() {
175 trace.Close()
176 log.Cleanup()
177 })
178
179 buildCtx := build.Context{&build.ContextImpl{
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800180 Context: ctx,
181 Logger: log,
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700182 Tracer: trace,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800183 StdioInterface: build.StdioImpl{},
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700184 }}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800185
Dan Willemsena4e43a72017-05-06 16:58:26 -0700186 status := NewStatus(buildCtx)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800187
188 config := build.NewConfig(buildCtx)
189 if *outDir == "" {
Steven Moreland552432e2017-03-29 19:26:09 -0700190 name := "multiproduct-" + time.Now().Format("20060102150405")
191
192 *outDir = filepath.Join(config.OutDir(), name)
193
Dan Willemsenf624fb92017-05-19 16:39:04 -0700194 // Ensure the empty files exist in the output directory
195 // containing our output directory too. This is mostly for
196 // safety, but also triggers the ninja_build file so that our
197 // build servers know that they can parse the output as if it
198 // was ninja output.
199 build.SetupOutDir(buildCtx, config)
200
Steven Moreland552432e2017-03-29 19:26:09 -0700201 if err := os.MkdirAll(*outDir, 0777); err != nil {
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800202 log.Fatalf("Failed to create tempdir: %v", err)
203 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800204 }
205 config.Environment().Set("OUT_DIR", *outDir)
206 log.Println("Output directory:", *outDir)
207
Dan Willemsene3480762017-11-07 11:23:27 -0800208 logsDir := filepath.Join(config.OutDir(), "logs")
209 os.MkdirAll(logsDir, 0777)
210
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800211 build.SetupOutDir(buildCtx, config)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700212 if *alternateResultDir {
Dan Willemsene3480762017-11-07 11:23:27 -0800213 distLogsDir := filepath.Join(config.DistDir(), "logs")
214 os.MkdirAll(distLogsDir, 0777)
215 log.SetOutput(filepath.Join(distLogsDir, "soong.log"))
216 trace.SetOutput(filepath.Join(distLogsDir, "build.trace"))
Dan Willemsenf624fb92017-05-19 16:39:04 -0700217 } else {
218 log.SetOutput(filepath.Join(config.OutDir(), "soong.log"))
219 trace.SetOutput(filepath.Join(config.OutDir(), "build.trace"))
220 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800221
Dan Willemsenb2e6c2e2017-07-13 17:24:44 -0700222 vars, err := build.DumpMakeVars(buildCtx, config, nil, []string{"all_named_products"})
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800223 if err != nil {
224 log.Fatal(err)
225 }
Dan Willemsen9957b9c2017-10-06 15:05:05 -0700226 productsList := strings.Fields(vars["all_named_products"])
227
228 products := make([]string, 0, len(productsList))
229 skipList := strings.Split(*skipProducts, ",")
230 skipProduct := func(p string) bool {
231 for _, s := range skipList {
232 if p == s {
233 return true
234 }
235 }
236 return false
237 }
238 for _, product := range productsList {
239 if !skipProduct(product) {
240 products = append(products, product)
241 } else {
242 log.Verbose("Skipping: ", product)
243 }
244 }
245
246 log.Verbose("Got product list: ", products)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800247
Dan Willemsena4e43a72017-05-06 16:58:26 -0700248 status.SetTotal(len(products))
249
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800250 var wg sync.WaitGroup
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800251 productConfigs := make(chan Product, len(products))
252
Jeff Gastonb64fc1c2017-08-04 12:30:12 -0700253 finder := build.NewSourceFinder(buildCtx, config)
254 defer finder.Shutdown()
255
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800256 // Run the product config for every product in parallel
257 for _, product := range products {
258 wg.Add(1)
259 go func(product string) {
Dan Willemsenf624fb92017-05-19 16:39:04 -0700260 var stdLog string
261
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800262 defer wg.Done()
263 defer logger.Recover(func(err error) {
Dan Willemsenf624fb92017-05-19 16:39:04 -0700264 status.Fail(product, err, stdLog)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800265 })
266
267 productOutDir := filepath.Join(config.OutDir(), product)
Dan Willemsene3480762017-11-07 11:23:27 -0800268 productLogDir := filepath.Join(logsDir, product)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800269
270 if err := os.MkdirAll(productOutDir, 0777); err != nil {
271 log.Fatalf("Error creating out directory: %v", err)
272 }
Dan Willemsene3480762017-11-07 11:23:27 -0800273 if err := os.MkdirAll(productLogDir, 0777); err != nil {
274 log.Fatalf("Error creating log directory: %v", err)
275 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800276
Dan Willemsenf624fb92017-05-19 16:39:04 -0700277 stdLog = filepath.Join(productLogDir, "std.log")
278 f, err := os.Create(stdLog)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800279 if err != nil {
280 log.Fatalf("Error creating std.log: %v", err)
281 }
282
283 productLog := logger.New(&bytes.Buffer{})
Dan Willemsenf624fb92017-05-19 16:39:04 -0700284 productLog.SetOutput(filepath.Join(productLogDir, "soong.log"))
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800285
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700286 productCtx := build.Context{&build.ContextImpl{
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800287 Context: ctx,
288 Logger: productLog,
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700289 Tracer: trace,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800290 StdioInterface: build.NewCustomStdio(nil, f, f),
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700291 Thread: trace.NewThread(product),
292 }}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800293
294 productConfig := build.NewConfig(productCtx)
295 productConfig.Environment().Set("OUT_DIR", productOutDir)
Jeff Gaston743e29e2017-08-17 14:09:23 -0700296 build.FindSources(productCtx, productConfig, finder)
Dan Willemsen5ed900b2017-05-07 11:40:30 -0700297 productConfig.Lunch(productCtx, product, *buildVariant)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800298
299 build.Build(productCtx, productConfig, build.BuildProductConfig)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700300 productConfigs <- Product{productCtx, productConfig, stdLog}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800301 }(product)
302 }
303 go func() {
304 defer close(productConfigs)
305 wg.Wait()
306 }()
307
308 var wg2 sync.WaitGroup
309 // Then run up to numJobs worth of Soong and Kati
310 for i := 0; i < *numJobs; i++ {
311 wg2.Add(1)
312 go func() {
313 defer wg2.Done()
314 for product := range productConfigs {
315 func() {
316 defer logger.Recover(func(err error) {
Dan Willemsenf624fb92017-05-19 16:39:04 -0700317 status.Fail(product.config.TargetProduct(), err, product.logFile)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800318 })
319
Dan Willemsene3480762017-11-07 11:23:27 -0800320 defer func() {
321 if *keepArtifacts {
322 args := zip.ZipArgs{
323 FileArgs: []zip.FileArg{
324 {
325 GlobDir: product.config.OutDir(),
326 SourcePrefixToStrip: product.config.OutDir(),
327 },
328 },
329 OutputFilePath: filepath.Join(config.OutDir(), product.config.TargetProduct()+".zip"),
330 NumParallelJobs: runtime.NumCPU(),
331 CompressionLevel: 5,
332 }
333 if err := zip.Run(args); err != nil {
334 log.Fatalf("Error zipping artifacts: %v", err)
335 }
336 }
337 os.RemoveAll(product.config.OutDir())
338 }()
339
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800340 buildWhat := 0
341 if !*onlyConfig {
342 buildWhat |= build.BuildSoong
343 if !*onlySoong {
344 buildWhat |= build.BuildKati
345 }
346 }
347 build.Build(product.ctx, product.config, buildWhat)
Dan Willemsena4e43a72017-05-06 16:58:26 -0700348 status.Finish(product.config.TargetProduct())
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800349 }()
350 }
351 }()
352 }
Dan Willemsena4e43a72017-05-06 16:58:26 -0700353 wg2.Wait()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800354
Dan Willemsene3480762017-11-07 11:23:27 -0800355 if *alternateResultDir {
356 args := zip.ZipArgs{
357 FileArgs: []zip.FileArg{
358 {GlobDir: logsDir, SourcePrefixToStrip: logsDir},
359 },
360 OutputFilePath: filepath.Join(config.DistDir(), "logs.zip"),
361 NumParallelJobs: runtime.NumCPU(),
362 CompressionLevel: 5,
363 }
364 if err := zip.Run(args); err != nil {
365 log.Fatalf("Error zipping logs: %v", err)
366 }
367 }
368
Dan Willemsena4e43a72017-05-06 16:58:26 -0700369 if count := status.Finished(); count > 0 {
370 log.Fatalln(count, "products failed")
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800371 }
372}