blob: ab829638ca055707540e0146568e9c85325a97a1 [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 (
Dan Willemsenc2af0be2017-01-20 14:10:01 -080018 "context"
19 "flag"
20 "fmt"
Dan Willemsenf624fb92017-05-19 16:39:04 -070021 "io/ioutil"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080022 "os"
23 "path/filepath"
24 "runtime"
25 "strings"
26 "sync"
Dan Willemsen22de2162017-12-11 14:35:23 -080027 "syscall"
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)")
Jeff Gastonb61e3f72017-10-25 15:02:45 -070060var includeProducts = flag.String("products", "", "comma-separated list of products to build")
Dan Willemsen9957b9c2017-10-06 15:05:05 -070061
Dan Willemsenf624fb92017-05-19 16:39:04 -070062const errorLeadingLines = 20
63const errorTrailingLines = 20
64
Dan Willemsenc2af0be2017-01-20 14:10:01 -080065type Product struct {
Dan Willemsenf624fb92017-05-19 16:39:04 -070066 ctx build.Context
67 config build.Config
68 logFile string
Dan Willemsenc2af0be2017-01-20 14:10:01 -080069}
70
Dan Willemsena4e43a72017-05-06 16:58:26 -070071type Status struct {
72 cur int
73 total int
74 failed int
75
76 ctx build.Context
77 haveBlankLine bool
78 smartTerminal bool
79
80 lock sync.Mutex
81}
82
83func NewStatus(ctx build.Context) *Status {
84 return &Status{
85 ctx: ctx,
86 haveBlankLine: true,
87 smartTerminal: ctx.IsTerminal(),
88 }
89}
90
91func (s *Status) SetTotal(total int) {
92 s.total = total
93}
94
Dan Willemsenf624fb92017-05-19 16:39:04 -070095func (s *Status) Fail(product string, err error, logFile string) {
Dan Willemsena4e43a72017-05-06 16:58:26 -070096 s.Finish(product)
97
98 s.lock.Lock()
99 defer s.lock.Unlock()
100
101 if s.smartTerminal && !s.haveBlankLine {
102 fmt.Fprintln(s.ctx.Stdout())
103 s.haveBlankLine = true
104 }
105
106 s.failed++
107 fmt.Fprintln(s.ctx.Stderr(), "FAILED:", product)
108 s.ctx.Verboseln("FAILED:", product)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700109
110 if logFile != "" {
111 data, err := ioutil.ReadFile(logFile)
112 if err == nil {
113 lines := strings.Split(strings.TrimSpace(string(data)), "\n")
114 if len(lines) > errorLeadingLines+errorTrailingLines+1 {
115 lines[errorLeadingLines] = fmt.Sprintf("... skipping %d lines ...",
116 len(lines)-errorLeadingLines-errorTrailingLines)
117
118 lines = append(lines[:errorLeadingLines+1],
119 lines[len(lines)-errorTrailingLines:]...)
120 }
121 for _, line := range lines {
122 fmt.Fprintln(s.ctx.Stderr(), "> ", line)
123 s.ctx.Verboseln(line)
124 }
125 }
126 }
127
128 s.ctx.Print(err)
Dan Willemsena4e43a72017-05-06 16:58:26 -0700129}
130
131func (s *Status) Finish(product string) {
132 s.lock.Lock()
133 defer s.lock.Unlock()
134
135 s.cur++
136 line := fmt.Sprintf("[%d/%d] %s", s.cur, s.total, product)
137
138 if s.smartTerminal {
139 if max, ok := s.ctx.TermWidth(); ok {
140 if len(line) > max {
141 line = line[:max]
142 }
143 }
144
145 fmt.Fprint(s.ctx.Stdout(), "\r", line, "\x1b[K")
146 s.haveBlankLine = false
147 } else {
148 s.ctx.Println(line)
149 }
150}
151
152func (s *Status) Finished() int {
153 s.lock.Lock()
154 defer s.lock.Unlock()
155
156 if !s.haveBlankLine {
157 fmt.Fprintln(s.ctx.Stdout())
158 s.haveBlankLine = true
159 }
160 return s.failed
161}
162
Dan Willemsen22de2162017-12-11 14:35:23 -0800163// TODO(b/70370883): This tool uses a lot of open files -- over the default
164// soft limit of 1024 on some systems. So bump up to the hard limit until I fix
165// the algorithm.
166func setMaxFiles(log logger.Logger) {
167 var limits syscall.Rlimit
168
169 err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limits)
170 if err != nil {
171 log.Println("Failed to get file limit:", err)
172 return
173 }
174
175 log.Verbosef("Current file limits: %d soft, %d hard", limits.Cur, limits.Max)
176 if limits.Cur == limits.Max {
177 return
178 }
179
180 limits.Cur = limits.Max
181 err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limits)
182 if err != nil {
183 log.Println("Failed to increase file limit:", err)
184 }
185}
186
Jeff Gastonb61e3f72017-10-25 15:02:45 -0700187func inList(str string, list []string) bool {
188 for _, other := range list {
189 if str == other {
190 return true
191 }
192 }
193 return false
194}
195
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800196func main() {
197 log := logger.New(os.Stderr)
198 defer log.Cleanup()
199
200 flag.Parse()
201
202 ctx, cancel := context.WithCancel(context.Background())
203 defer cancel()
204
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700205 trace := tracer.New(log)
206 defer trace.Close()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800207
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700208 build.SetupSignals(log, cancel, func() {
209 trace.Close()
210 log.Cleanup()
211 })
212
213 buildCtx := build.Context{&build.ContextImpl{
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800214 Context: ctx,
215 Logger: log,
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700216 Tracer: trace,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800217 StdioInterface: build.StdioImpl{},
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700218 }}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800219
Dan Willemsena4e43a72017-05-06 16:58:26 -0700220 status := NewStatus(buildCtx)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800221
222 config := build.NewConfig(buildCtx)
223 if *outDir == "" {
Steven Moreland552432e2017-03-29 19:26:09 -0700224 name := "multiproduct-" + time.Now().Format("20060102150405")
225
226 *outDir = filepath.Join(config.OutDir(), name)
227
Dan Willemsenf624fb92017-05-19 16:39:04 -0700228 // Ensure the empty files exist in the output directory
229 // containing our output directory too. This is mostly for
230 // safety, but also triggers the ninja_build file so that our
231 // build servers know that they can parse the output as if it
232 // was ninja output.
233 build.SetupOutDir(buildCtx, config)
234
Steven Moreland552432e2017-03-29 19:26:09 -0700235 if err := os.MkdirAll(*outDir, 0777); err != nil {
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800236 log.Fatalf("Failed to create tempdir: %v", err)
237 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800238 }
239 config.Environment().Set("OUT_DIR", *outDir)
240 log.Println("Output directory:", *outDir)
241
Dan Willemsene3480762017-11-07 11:23:27 -0800242 logsDir := filepath.Join(config.OutDir(), "logs")
243 os.MkdirAll(logsDir, 0777)
244
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800245 build.SetupOutDir(buildCtx, config)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700246 if *alternateResultDir {
Dan Willemsene3480762017-11-07 11:23:27 -0800247 distLogsDir := filepath.Join(config.DistDir(), "logs")
248 os.MkdirAll(distLogsDir, 0777)
249 log.SetOutput(filepath.Join(distLogsDir, "soong.log"))
250 trace.SetOutput(filepath.Join(distLogsDir, "build.trace"))
Dan Willemsenf624fb92017-05-19 16:39:04 -0700251 } else {
252 log.SetOutput(filepath.Join(config.OutDir(), "soong.log"))
253 trace.SetOutput(filepath.Join(config.OutDir(), "build.trace"))
254 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800255
Dan Willemsen22de2162017-12-11 14:35:23 -0800256 setMaxFiles(log)
257
Dan Willemsen04d76ef2018-05-15 00:52:29 -0700258 finder := build.NewSourceFinder(buildCtx, config)
259 defer finder.Shutdown()
260
261 build.FindSources(buildCtx, config, finder)
262
Dan Willemsenb2e6c2e2017-07-13 17:24:44 -0700263 vars, err := build.DumpMakeVars(buildCtx, config, nil, []string{"all_named_products"})
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800264 if err != nil {
265 log.Fatal(err)
266 }
Jeff Gastonb61e3f72017-10-25 15:02:45 -0700267 var productsList []string
268 allProducts := strings.Fields(vars["all_named_products"])
269
270 if *includeProducts != "" {
271 missingProducts := []string{}
272 for _, product := range strings.Split(*includeProducts, ",") {
273 if inList(product, allProducts) {
274 productsList = append(productsList, product)
275 } else {
276 missingProducts = append(missingProducts, product)
277 }
278 }
279 if len(missingProducts) > 0 {
280 log.Fatalf("Products don't exist: %s\n", missingProducts)
281 }
282 } else {
283 productsList = allProducts
284 }
Dan Willemsen9957b9c2017-10-06 15:05:05 -0700285
286 products := make([]string, 0, len(productsList))
287 skipList := strings.Split(*skipProducts, ",")
288 skipProduct := func(p string) bool {
289 for _, s := range skipList {
290 if p == s {
291 return true
292 }
293 }
294 return false
295 }
296 for _, product := range productsList {
297 if !skipProduct(product) {
298 products = append(products, product)
299 } else {
300 log.Verbose("Skipping: ", product)
301 }
302 }
303
304 log.Verbose("Got product list: ", products)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800305
Dan Willemsena4e43a72017-05-06 16:58:26 -0700306 status.SetTotal(len(products))
307
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800308 var wg sync.WaitGroup
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800309 productConfigs := make(chan Product, len(products))
310
311 // Run the product config for every product in parallel
312 for _, product := range products {
313 wg.Add(1)
314 go func(product string) {
Dan Willemsenf624fb92017-05-19 16:39:04 -0700315 var stdLog string
316
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800317 defer wg.Done()
318 defer logger.Recover(func(err error) {
Dan Willemsenf624fb92017-05-19 16:39:04 -0700319 status.Fail(product, err, stdLog)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800320 })
321
322 productOutDir := filepath.Join(config.OutDir(), product)
Dan Willemsene3480762017-11-07 11:23:27 -0800323 productLogDir := filepath.Join(logsDir, product)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800324
325 if err := os.MkdirAll(productOutDir, 0777); err != nil {
326 log.Fatalf("Error creating out directory: %v", err)
327 }
Dan Willemsene3480762017-11-07 11:23:27 -0800328 if err := os.MkdirAll(productLogDir, 0777); err != nil {
329 log.Fatalf("Error creating log directory: %v", err)
330 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800331
Dan Willemsenf624fb92017-05-19 16:39:04 -0700332 stdLog = filepath.Join(productLogDir, "std.log")
333 f, err := os.Create(stdLog)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800334 if err != nil {
335 log.Fatalf("Error creating std.log: %v", err)
336 }
337
Dan Willemsenf1963962017-11-11 15:44:51 -0800338 productLog := logger.New(f)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700339 productLog.SetOutput(filepath.Join(productLogDir, "soong.log"))
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800340
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700341 productCtx := build.Context{&build.ContextImpl{
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800342 Context: ctx,
343 Logger: productLog,
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700344 Tracer: trace,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800345 StdioInterface: build.NewCustomStdio(nil, f, f),
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700346 Thread: trace.NewThread(product),
347 }}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800348
349 productConfig := build.NewConfig(productCtx)
350 productConfig.Environment().Set("OUT_DIR", productOutDir)
Jeff Gaston743e29e2017-08-17 14:09:23 -0700351 build.FindSources(productCtx, productConfig, finder)
Dan Willemsen5ed900b2017-05-07 11:40:30 -0700352 productConfig.Lunch(productCtx, product, *buildVariant)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800353
354 build.Build(productCtx, productConfig, build.BuildProductConfig)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700355 productConfigs <- Product{productCtx, productConfig, stdLog}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800356 }(product)
357 }
358 go func() {
359 defer close(productConfigs)
360 wg.Wait()
361 }()
362
363 var wg2 sync.WaitGroup
364 // Then run up to numJobs worth of Soong and Kati
365 for i := 0; i < *numJobs; i++ {
366 wg2.Add(1)
367 go func() {
368 defer wg2.Done()
369 for product := range productConfigs {
370 func() {
371 defer logger.Recover(func(err error) {
Dan Willemsenf624fb92017-05-19 16:39:04 -0700372 status.Fail(product.config.TargetProduct(), err, product.logFile)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800373 })
374
Dan Willemsene3480762017-11-07 11:23:27 -0800375 defer func() {
376 if *keepArtifacts {
377 args := zip.ZipArgs{
378 FileArgs: []zip.FileArg{
379 {
380 GlobDir: product.config.OutDir(),
381 SourcePrefixToStrip: product.config.OutDir(),
382 },
383 },
384 OutputFilePath: filepath.Join(config.OutDir(), product.config.TargetProduct()+".zip"),
385 NumParallelJobs: runtime.NumCPU(),
386 CompressionLevel: 5,
387 }
388 if err := zip.Run(args); err != nil {
389 log.Fatalf("Error zipping artifacts: %v", err)
390 }
391 }
392 os.RemoveAll(product.config.OutDir())
393 }()
394
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800395 buildWhat := 0
396 if !*onlyConfig {
397 buildWhat |= build.BuildSoong
398 if !*onlySoong {
399 buildWhat |= build.BuildKati
400 }
401 }
402 build.Build(product.ctx, product.config, buildWhat)
Dan Willemsena4e43a72017-05-06 16:58:26 -0700403 status.Finish(product.config.TargetProduct())
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800404 }()
405 }
406 }()
407 }
Dan Willemsena4e43a72017-05-06 16:58:26 -0700408 wg2.Wait()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800409
Dan Willemsene3480762017-11-07 11:23:27 -0800410 if *alternateResultDir {
411 args := zip.ZipArgs{
412 FileArgs: []zip.FileArg{
413 {GlobDir: logsDir, SourcePrefixToStrip: logsDir},
414 },
415 OutputFilePath: filepath.Join(config.DistDir(), "logs.zip"),
416 NumParallelJobs: runtime.NumCPU(),
417 CompressionLevel: 5,
418 }
419 if err := zip.Run(args); err != nil {
420 log.Fatalf("Error zipping logs: %v", err)
421 }
422 }
423
Dan Willemsena4e43a72017-05-06 16:58:26 -0700424 if count := status.Finished(); count > 0 {
425 log.Fatalln(count, "products failed")
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800426 }
427}