blob: 1171a6521a24a440aeca904d09d7e0279c3cc107 [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 Willemsen41538382018-08-31 19:51:35 -070021 "io"
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"
Dan Willemsen22de2162017-12-11 14:35:23 -080028 "syscall"
Steven Moreland552432e2017-03-29 19:26:09 -070029 "time"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080030
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -070031 "android/soong/finder"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080032 "android/soong/ui/build"
33 "android/soong/ui/logger"
Dan Willemsenb82471a2018-05-17 16:37:09 -070034 "android/soong/ui/status"
35 "android/soong/ui/terminal"
Dan Willemsend9f6fa22016-08-21 15:17:17 -070036 "android/soong/ui/tracer"
Dan Willemsene3480762017-11-07 11:23:27 -080037 "android/soong/zip"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080038)
39
40// We default to number of cpus / 4, which seems to be the sweet spot for my
41// system. I suspect this is mostly due to memory or disk bandwidth though, and
42// may depend on the size ofthe source tree, so this probably isn't a great
43// default.
44func detectNumJobs() int {
45 if runtime.NumCPU() < 4 {
46 return 1
47 }
48 return runtime.NumCPU() / 4
49}
50
51var numJobs = flag.Int("j", detectNumJobs(), "number of parallel kati jobs")
52
Dan Willemsene3480762017-11-07 11:23:27 -080053var keepArtifacts = flag.Bool("keep", false, "keep archives of artifacts")
Dan Willemsen41538382018-08-31 19:51:35 -070054var incremental = flag.Bool("incremental", false, "run in incremental mode (saving intermediates)")
Dan Willemsenc2af0be2017-01-20 14:10:01 -080055
56var outDir = flag.String("out", "", "path to store output directories (defaults to tmpdir under $OUT when empty)")
Dan Willemsenf624fb92017-05-19 16:39:04 -070057var alternateResultDir = flag.Bool("dist", false, "write select results to $DIST_DIR (or <out>/dist when empty)")
Dan Willemsenc2af0be2017-01-20 14:10:01 -080058
59var onlyConfig = flag.Bool("only-config", false, "Only run product config (not Soong or Kati)")
60var onlySoong = flag.Bool("only-soong", false, "Only run product config and Soong (not Kati)")
61
Dan Willemsen5ed900b2017-05-07 11:40:30 -070062var buildVariant = flag.String("variant", "eng", "build variant to use")
63
Dan Willemsen9957b9c2017-10-06 15:05:05 -070064var skipProducts = flag.String("skip-products", "", "comma-separated list of products to skip (known failures, etc)")
Jeff Gastonb61e3f72017-10-25 15:02:45 -070065var includeProducts = flag.String("products", "", "comma-separated list of products to build")
Dan Willemsen9957b9c2017-10-06 15:05:05 -070066
Dan Willemsenf624fb92017-05-19 16:39:04 -070067const errorLeadingLines = 20
68const errorTrailingLines = 20
69
Dan Willemsenb82471a2018-05-17 16:37:09 -070070func errMsgFromLog(filename string) string {
71 if filename == "" {
72 return ""
Dan Willemsena4e43a72017-05-06 16:58:26 -070073 }
74
Dan Willemsenb82471a2018-05-17 16:37:09 -070075 data, err := ioutil.ReadFile(filename)
76 if err != nil {
77 return ""
Dan Willemsenf624fb92017-05-19 16:39:04 -070078 }
79
Dan Willemsenb82471a2018-05-17 16:37:09 -070080 lines := strings.Split(strings.TrimSpace(string(data)), "\n")
81 if len(lines) > errorLeadingLines+errorTrailingLines+1 {
82 lines[errorLeadingLines] = fmt.Sprintf("... skipping %d lines ...",
83 len(lines)-errorLeadingLines-errorTrailingLines)
Dan Willemsena4e43a72017-05-06 16:58:26 -070084
Dan Willemsenb82471a2018-05-17 16:37:09 -070085 lines = append(lines[:errorLeadingLines+1],
86 lines[len(lines)-errorTrailingLines:]...)
Dan Willemsena4e43a72017-05-06 16:58:26 -070087 }
Dan Willemsenb82471a2018-05-17 16:37:09 -070088 var buf strings.Builder
89 for _, line := range lines {
90 buf.WriteString("> ")
91 buf.WriteString(line)
92 buf.WriteString("\n")
Dan Willemsena4e43a72017-05-06 16:58:26 -070093 }
Dan Willemsenb82471a2018-05-17 16:37:09 -070094 return buf.String()
Dan Willemsena4e43a72017-05-06 16:58:26 -070095}
96
Dan Willemsen22de2162017-12-11 14:35:23 -080097// TODO(b/70370883): This tool uses a lot of open files -- over the default
98// soft limit of 1024 on some systems. So bump up to the hard limit until I fix
99// the algorithm.
100func setMaxFiles(log logger.Logger) {
101 var limits syscall.Rlimit
102
103 err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limits)
104 if err != nil {
105 log.Println("Failed to get file limit:", err)
106 return
107 }
108
109 log.Verbosef("Current file limits: %d soft, %d hard", limits.Cur, limits.Max)
110 if limits.Cur == limits.Max {
111 return
112 }
113
114 limits.Cur = limits.Max
115 err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limits)
116 if err != nil {
117 log.Println("Failed to increase file limit:", err)
118 }
119}
120
Jeff Gastonb61e3f72017-10-25 15:02:45 -0700121func inList(str string, list []string) bool {
122 for _, other := range list {
123 if str == other {
124 return true
125 }
126 }
127 return false
128}
129
Dan Willemsen41538382018-08-31 19:51:35 -0700130func copyFile(from, to string) error {
131 fromFile, err := os.Open(from)
132 if err != nil {
133 return err
134 }
135 defer fromFile.Close()
136
137 toFile, err := os.Create(to)
138 if err != nil {
139 return err
140 }
141 defer toFile.Close()
142
143 _, err = io.Copy(toFile, fromFile)
144 return err
145}
146
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700147type mpContext struct {
148 Context context.Context
149 Logger logger.Logger
150 Status status.ToolStatus
151 Tracer tracer.Tracer
152 Finder *finder.Finder
153 Config build.Config
154
155 LogsDir string
156}
157
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800158func main() {
Colin Cross097ed2a2019-06-08 21:48:58 -0700159 stdio := terminal.StdioImpl{}
Dan Willemsenb82471a2018-05-17 16:37:09 -0700160
Colin Crosse0df1a32019-06-09 19:40:08 -0700161 output := terminal.NewStatusOutput(stdio.Stdout(), "",
162 build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"))
163
164 log := logger.New(output)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800165 defer log.Cleanup()
166
167 flag.Parse()
168
169 ctx, cancel := context.WithCancel(context.Background())
170 defer cancel()
171
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700172 trace := tracer.New(log)
173 defer trace.Close()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800174
Dan Willemsenb82471a2018-05-17 16:37:09 -0700175 stat := &status.Status{}
176 defer stat.Finish()
Colin Crosse0df1a32019-06-09 19:40:08 -0700177 stat.AddOutput(output)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700178
Dan Willemsen34218ec2018-07-19 22:57:18 -0700179 var failures failureCount
180 stat.AddOutput(&failures)
181
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700182 build.SetupSignals(log, cancel, func() {
183 trace.Close()
184 log.Cleanup()
Dan Willemsenb82471a2018-05-17 16:37:09 -0700185 stat.Finish()
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700186 })
187
Dan Willemsen59339a22018-07-22 21:18:45 -0700188 buildCtx := build.Context{ContextImpl: &build.ContextImpl{
Dan Willemsenb82471a2018-05-17 16:37:09 -0700189 Context: ctx,
190 Logger: log,
191 Tracer: trace,
Colin Crosse0df1a32019-06-09 19:40:08 -0700192 Writer: output,
Dan Willemsenb82471a2018-05-17 16:37:09 -0700193 Status: stat,
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700194 }}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800195
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800196 config := build.NewConfig(buildCtx)
197 if *outDir == "" {
Dan Willemsen41538382018-08-31 19:51:35 -0700198 name := "multiproduct"
199 if !*incremental {
200 name += "-" + time.Now().Format("20060102150405")
201 }
Steven Moreland552432e2017-03-29 19:26:09 -0700202
203 *outDir = filepath.Join(config.OutDir(), name)
204
Dan Willemsenf624fb92017-05-19 16:39:04 -0700205 // Ensure the empty files exist in the output directory
206 // containing our output directory too. This is mostly for
207 // safety, but also triggers the ninja_build file so that our
208 // build servers know that they can parse the output as if it
209 // was ninja output.
210 build.SetupOutDir(buildCtx, config)
211
Steven Moreland552432e2017-03-29 19:26:09 -0700212 if err := os.MkdirAll(*outDir, 0777); err != nil {
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800213 log.Fatalf("Failed to create tempdir: %v", err)
214 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800215 }
216 config.Environment().Set("OUT_DIR", *outDir)
217 log.Println("Output directory:", *outDir)
218
Dan Willemsene3480762017-11-07 11:23:27 -0800219 logsDir := filepath.Join(config.OutDir(), "logs")
220 os.MkdirAll(logsDir, 0777)
221
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800222 build.SetupOutDir(buildCtx, config)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700223 if *alternateResultDir {
Dan Willemsene3480762017-11-07 11:23:27 -0800224 distLogsDir := filepath.Join(config.DistDir(), "logs")
225 os.MkdirAll(distLogsDir, 0777)
226 log.SetOutput(filepath.Join(distLogsDir, "soong.log"))
227 trace.SetOutput(filepath.Join(distLogsDir, "build.trace"))
Dan Willemsenf624fb92017-05-19 16:39:04 -0700228 } else {
229 log.SetOutput(filepath.Join(config.OutDir(), "soong.log"))
230 trace.SetOutput(filepath.Join(config.OutDir(), "build.trace"))
231 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800232
Dan Willemsen22de2162017-12-11 14:35:23 -0800233 setMaxFiles(log)
234
Dan Willemsen04d76ef2018-05-15 00:52:29 -0700235 finder := build.NewSourceFinder(buildCtx, config)
236 defer finder.Shutdown()
237
238 build.FindSources(buildCtx, config, finder)
239
Dan Willemsenb2e6c2e2017-07-13 17:24:44 -0700240 vars, err := build.DumpMakeVars(buildCtx, config, nil, []string{"all_named_products"})
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800241 if err != nil {
242 log.Fatal(err)
243 }
Jeff Gastonb61e3f72017-10-25 15:02:45 -0700244 var productsList []string
245 allProducts := strings.Fields(vars["all_named_products"])
246
247 if *includeProducts != "" {
248 missingProducts := []string{}
249 for _, product := range strings.Split(*includeProducts, ",") {
250 if inList(product, allProducts) {
251 productsList = append(productsList, product)
252 } else {
253 missingProducts = append(missingProducts, product)
254 }
255 }
256 if len(missingProducts) > 0 {
257 log.Fatalf("Products don't exist: %s\n", missingProducts)
258 }
259 } else {
260 productsList = allProducts
261 }
Dan Willemsen9957b9c2017-10-06 15:05:05 -0700262
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700263 finalProductsList := make([]string, 0, len(productsList))
Dan Willemsen9957b9c2017-10-06 15:05:05 -0700264 skipList := strings.Split(*skipProducts, ",")
265 skipProduct := func(p string) bool {
266 for _, s := range skipList {
267 if p == s {
268 return true
269 }
270 }
271 return false
272 }
273 for _, product := range productsList {
274 if !skipProduct(product) {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700275 finalProductsList = append(finalProductsList, product)
Dan Willemsen9957b9c2017-10-06 15:05:05 -0700276 } else {
277 log.Verbose("Skipping: ", product)
278 }
279 }
280
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700281 log.Verbose("Got product list: ", finalProductsList)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800282
Dan Willemsenb82471a2018-05-17 16:37:09 -0700283 s := buildCtx.Status.StartTool()
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700284 s.SetTotalActions(len(finalProductsList))
Dan Willemsena4e43a72017-05-06 16:58:26 -0700285
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700286 mpCtx := &mpContext{
287 Context: ctx,
288 Logger: log,
289 Status: s,
290 Tracer: trace,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800291
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700292 Finder: finder,
293 Config: config,
Dan Willemsenf624fb92017-05-19 16:39:04 -0700294
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700295 LogsDir: logsDir,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800296 }
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700297
298 products := make(chan string, len(productsList))
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800299 go func() {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700300 defer close(products)
301 for _, product := range finalProductsList {
302 products <- product
303 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800304 }()
305
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700306 var wg sync.WaitGroup
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800307 for i := 0; i < *numJobs; i++ {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700308 wg.Add(1)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800309 go func() {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700310 defer wg.Done()
311 for {
312 select {
313 case product := <-products:
314 if product == "" {
315 return
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800316 }
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700317 buildProduct(mpCtx, product)
318 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800319 }
320 }()
321 }
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700322 wg.Wait()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800323
Dan Willemsene3480762017-11-07 11:23:27 -0800324 if *alternateResultDir {
325 args := zip.ZipArgs{
326 FileArgs: []zip.FileArg{
327 {GlobDir: logsDir, SourcePrefixToStrip: logsDir},
328 },
329 OutputFilePath: filepath.Join(config.DistDir(), "logs.zip"),
330 NumParallelJobs: runtime.NumCPU(),
331 CompressionLevel: 5,
332 }
Colin Cross05518bc2018-09-27 15:06:19 -0700333 if err := zip.Zip(args); err != nil {
Dan Willemsene3480762017-11-07 11:23:27 -0800334 log.Fatalf("Error zipping logs: %v", err)
335 }
336 }
337
Dan Willemsenb82471a2018-05-17 16:37:09 -0700338 s.Finish()
Dan Willemsen34218ec2018-07-19 22:57:18 -0700339
340 if failures == 1 {
341 log.Fatal("1 failure")
342 } else if failures > 1 {
343 log.Fatalf("%d failures", failures)
344 } else {
Colin Crosse0df1a32019-06-09 19:40:08 -0700345 fmt.Fprintln(output, "Success")
Dan Willemsen34218ec2018-07-19 22:57:18 -0700346 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800347}
Dan Willemsen34218ec2018-07-19 22:57:18 -0700348
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700349func buildProduct(mpctx *mpContext, product string) {
350 var stdLog string
351
352 outDir := filepath.Join(mpctx.Config.OutDir(), product)
353 logsDir := filepath.Join(mpctx.LogsDir, product)
354
355 if err := os.MkdirAll(outDir, 0777); err != nil {
356 mpctx.Logger.Fatalf("Error creating out directory: %v", err)
357 }
358 if err := os.MkdirAll(logsDir, 0777); err != nil {
359 mpctx.Logger.Fatalf("Error creating log directory: %v", err)
360 }
361
362 stdLog = filepath.Join(logsDir, "std.log")
363 f, err := os.Create(stdLog)
364 if err != nil {
365 mpctx.Logger.Fatalf("Error creating std.log: %v", err)
366 }
367 defer f.Close()
368
369 log := logger.New(f)
370 defer log.Cleanup()
371 log.SetOutput(filepath.Join(logsDir, "soong.log"))
372
373 action := &status.Action{
374 Description: product,
375 Outputs: []string{product},
376 }
377 mpctx.Status.StartAction(action)
378 defer logger.Recover(func(err error) {
379 mpctx.Status.FinishAction(status.ActionResult{
380 Action: action,
381 Error: err,
382 Output: errMsgFromLog(stdLog),
383 })
384 })
385
386 ctx := build.Context{ContextImpl: &build.ContextImpl{
387 Context: mpctx.Context,
388 Logger: log,
389 Tracer: mpctx.Tracer,
Colin Cross097ed2a2019-06-08 21:48:58 -0700390 Writer: f,
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700391 Thread: mpctx.Tracer.NewThread(product),
392 Status: &status.Status{},
393 }}
Sasha Smundakc0c9ef92019-01-23 09:52:57 -0800394 ctx.Status.AddOutput(terminal.NewStatusOutput(ctx.Writer, "",
395 build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD")))
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700396
397 config := build.NewConfig(ctx, flag.Args()...)
398 config.Environment().Set("OUT_DIR", outDir)
Dan Willemsenf99915f2018-10-25 22:04:42 -0700399 if !*keepArtifacts {
400 config.Environment().Set("EMPTY_NINJA_FILE", "true")
401 }
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700402 build.FindSources(ctx, config, mpctx.Finder)
403 config.Lunch(ctx, product, *buildVariant)
404
405 defer func() {
406 if *keepArtifacts {
407 args := zip.ZipArgs{
408 FileArgs: []zip.FileArg{
409 {
410 GlobDir: outDir,
411 SourcePrefixToStrip: outDir,
412 },
413 },
414 OutputFilePath: filepath.Join(mpctx.Config.OutDir(), product+".zip"),
415 NumParallelJobs: runtime.NumCPU(),
416 CompressionLevel: 5,
417 }
Colin Cross05518bc2018-09-27 15:06:19 -0700418 if err := zip.Zip(args); err != nil {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700419 log.Fatalf("Error zipping artifacts: %v", err)
420 }
421 }
Dan Willemsenf99915f2018-10-25 22:04:42 -0700422 if !*incremental {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700423 os.RemoveAll(outDir)
424 }
425 }()
426
427 buildWhat := build.BuildProductConfig
428 if !*onlyConfig {
429 buildWhat |= build.BuildSoong
430 if !*onlySoong {
431 buildWhat |= build.BuildKati
432 }
433 }
434
435 before := time.Now()
436 build.Build(ctx, config, buildWhat)
437
438 // Save std_full.log if Kati re-read the makefiles
439 if buildWhat&build.BuildKati != 0 {
Dan Willemsen29971232018-09-26 14:58:30 -0700440 if after, err := os.Stat(config.KatiBuildNinjaFile()); err == nil && after.ModTime().After(before) {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700441 err := copyFile(stdLog, filepath.Join(filepath.Dir(stdLog), "std_full.log"))
442 if err != nil {
443 log.Fatalf("Error copying log file: %s", err)
444 }
445 }
446 }
447
448 mpctx.Status.FinishAction(status.ActionResult{
449 Action: action,
450 })
451}
452
Dan Willemsen34218ec2018-07-19 22:57:18 -0700453type failureCount int
454
455func (f *failureCount) StartAction(action *status.Action, counts status.Counts) {}
456
457func (f *failureCount) FinishAction(result status.ActionResult, counts status.Counts) {
458 if result.Error != nil {
459 *f += 1
460 }
461}
462
463func (f *failureCount) Message(level status.MsgLevel, message string) {
464 if level >= status.ErrorLvl {
465 *f += 1
466 }
467}
468
469func (f *failureCount) Flush() {}
Colin Crosse0df1a32019-06-09 19:40:08 -0700470
471func (f *failureCount) Write(p []byte) (int, error) {
472 // discard writes
473 return len(p), nil
474}