blob: ea671bcf4bfd3ac2145ea04a3bd3522c19484ee5 [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
31 "android/soong/ui/build"
32 "android/soong/ui/logger"
Dan Willemsenb82471a2018-05-17 16:37:09 -070033 "android/soong/ui/status"
34 "android/soong/ui/terminal"
Dan Willemsend9f6fa22016-08-21 15:17:17 -070035 "android/soong/ui/tracer"
Dan Willemsene3480762017-11-07 11:23:27 -080036 "android/soong/zip"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080037)
38
39// We default to number of cpus / 4, which seems to be the sweet spot for my
40// system. I suspect this is mostly due to memory or disk bandwidth though, and
41// may depend on the size ofthe source tree, so this probably isn't a great
42// default.
43func detectNumJobs() int {
44 if runtime.NumCPU() < 4 {
45 return 1
46 }
47 return runtime.NumCPU() / 4
48}
49
50var numJobs = flag.Int("j", detectNumJobs(), "number of parallel kati jobs")
51
Dan Willemsene3480762017-11-07 11:23:27 -080052var keepArtifacts = flag.Bool("keep", false, "keep archives of artifacts")
Dan Willemsen41538382018-08-31 19:51:35 -070053var incremental = flag.Bool("incremental", false, "run in incremental mode (saving intermediates)")
Dan Willemsenc2af0be2017-01-20 14:10:01 -080054
55var outDir = flag.String("out", "", "path to store output directories (defaults to tmpdir under $OUT when empty)")
Dan Willemsenf624fb92017-05-19 16:39:04 -070056var alternateResultDir = flag.Bool("dist", false, "write select results to $DIST_DIR (or <out>/dist when empty)")
Dan Willemsenc2af0be2017-01-20 14:10:01 -080057
58var onlyConfig = flag.Bool("only-config", false, "Only run product config (not Soong or Kati)")
59var onlySoong = flag.Bool("only-soong", false, "Only run product config and Soong (not Kati)")
60
Dan Willemsen5ed900b2017-05-07 11:40:30 -070061var buildVariant = flag.String("variant", "eng", "build variant to use")
62
Dan Willemsen9957b9c2017-10-06 15:05:05 -070063var skipProducts = flag.String("skip-products", "", "comma-separated list of products to skip (known failures, etc)")
Jeff Gastonb61e3f72017-10-25 15:02:45 -070064var includeProducts = flag.String("products", "", "comma-separated list of products to build")
Dan Willemsen9957b9c2017-10-06 15:05:05 -070065
Dan Willemsenf624fb92017-05-19 16:39:04 -070066const errorLeadingLines = 20
67const errorTrailingLines = 20
68
Dan Willemsenc2af0be2017-01-20 14:10:01 -080069type Product struct {
Dan Willemsenf624fb92017-05-19 16:39:04 -070070 ctx build.Context
71 config build.Config
72 logFile string
Dan Willemsenb82471a2018-05-17 16:37:09 -070073 action *status.Action
Dan Willemsenc2af0be2017-01-20 14:10:01 -080074}
75
Dan Willemsenb82471a2018-05-17 16:37:09 -070076func errMsgFromLog(filename string) string {
77 if filename == "" {
78 return ""
Dan Willemsena4e43a72017-05-06 16:58:26 -070079 }
80
Dan Willemsenb82471a2018-05-17 16:37:09 -070081 data, err := ioutil.ReadFile(filename)
82 if err != nil {
83 return ""
Dan Willemsenf624fb92017-05-19 16:39:04 -070084 }
85
Dan Willemsenb82471a2018-05-17 16:37:09 -070086 lines := strings.Split(strings.TrimSpace(string(data)), "\n")
87 if len(lines) > errorLeadingLines+errorTrailingLines+1 {
88 lines[errorLeadingLines] = fmt.Sprintf("... skipping %d lines ...",
89 len(lines)-errorLeadingLines-errorTrailingLines)
Dan Willemsena4e43a72017-05-06 16:58:26 -070090
Dan Willemsenb82471a2018-05-17 16:37:09 -070091 lines = append(lines[:errorLeadingLines+1],
92 lines[len(lines)-errorTrailingLines:]...)
Dan Willemsena4e43a72017-05-06 16:58:26 -070093 }
Dan Willemsenb82471a2018-05-17 16:37:09 -070094 var buf strings.Builder
95 for _, line := range lines {
96 buf.WriteString("> ")
97 buf.WriteString(line)
98 buf.WriteString("\n")
Dan Willemsena4e43a72017-05-06 16:58:26 -070099 }
Dan Willemsenb82471a2018-05-17 16:37:09 -0700100 return buf.String()
Dan Willemsena4e43a72017-05-06 16:58:26 -0700101}
102
Dan Willemsen22de2162017-12-11 14:35:23 -0800103// TODO(b/70370883): This tool uses a lot of open files -- over the default
104// soft limit of 1024 on some systems. So bump up to the hard limit until I fix
105// the algorithm.
106func setMaxFiles(log logger.Logger) {
107 var limits syscall.Rlimit
108
109 err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limits)
110 if err != nil {
111 log.Println("Failed to get file limit:", err)
112 return
113 }
114
115 log.Verbosef("Current file limits: %d soft, %d hard", limits.Cur, limits.Max)
116 if limits.Cur == limits.Max {
117 return
118 }
119
120 limits.Cur = limits.Max
121 err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limits)
122 if err != nil {
123 log.Println("Failed to increase file limit:", err)
124 }
125}
126
Jeff Gastonb61e3f72017-10-25 15:02:45 -0700127func inList(str string, list []string) bool {
128 for _, other := range list {
129 if str == other {
130 return true
131 }
132 }
133 return false
134}
135
Dan Willemsen41538382018-08-31 19:51:35 -0700136func copyFile(from, to string) error {
137 fromFile, err := os.Open(from)
138 if err != nil {
139 return err
140 }
141 defer fromFile.Close()
142
143 toFile, err := os.Create(to)
144 if err != nil {
145 return err
146 }
147 defer toFile.Close()
148
149 _, err = io.Copy(toFile, fromFile)
150 return err
151}
152
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800153func main() {
Dan Willemsenb82471a2018-05-17 16:37:09 -0700154 writer := terminal.NewWriter(terminal.StdioImpl{})
155 defer writer.Finish()
156
Dan Willemsen34218ec2018-07-19 22:57:18 -0700157 log := logger.New(writer)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800158 defer log.Cleanup()
159
160 flag.Parse()
161
162 ctx, cancel := context.WithCancel(context.Background())
163 defer cancel()
164
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700165 trace := tracer.New(log)
166 defer trace.Close()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800167
Dan Willemsenb82471a2018-05-17 16:37:09 -0700168 stat := &status.Status{}
169 defer stat.Finish()
170 stat.AddOutput(terminal.NewStatusOutput(writer, ""))
171
Dan Willemsen34218ec2018-07-19 22:57:18 -0700172 var failures failureCount
173 stat.AddOutput(&failures)
174
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700175 build.SetupSignals(log, cancel, func() {
176 trace.Close()
177 log.Cleanup()
Dan Willemsenb82471a2018-05-17 16:37:09 -0700178 stat.Finish()
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700179 })
180
Dan Willemsen59339a22018-07-22 21:18:45 -0700181 buildCtx := build.Context{ContextImpl: &build.ContextImpl{
Dan Willemsenb82471a2018-05-17 16:37:09 -0700182 Context: ctx,
183 Logger: log,
184 Tracer: trace,
185 Writer: writer,
186 Status: stat,
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700187 }}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800188
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800189 config := build.NewConfig(buildCtx)
190 if *outDir == "" {
Dan Willemsen41538382018-08-31 19:51:35 -0700191 name := "multiproduct"
192 if !*incremental {
193 name += "-" + time.Now().Format("20060102150405")
194 }
Steven Moreland552432e2017-03-29 19:26:09 -0700195
196 *outDir = filepath.Join(config.OutDir(), name)
197
Dan Willemsenf624fb92017-05-19 16:39:04 -0700198 // Ensure the empty files exist in the output directory
199 // containing our output directory too. This is mostly for
200 // safety, but also triggers the ninja_build file so that our
201 // build servers know that they can parse the output as if it
202 // was ninja output.
203 build.SetupOutDir(buildCtx, config)
204
Steven Moreland552432e2017-03-29 19:26:09 -0700205 if err := os.MkdirAll(*outDir, 0777); err != nil {
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800206 log.Fatalf("Failed to create tempdir: %v", err)
207 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800208 }
209 config.Environment().Set("OUT_DIR", *outDir)
210 log.Println("Output directory:", *outDir)
211
Dan Willemsene3480762017-11-07 11:23:27 -0800212 logsDir := filepath.Join(config.OutDir(), "logs")
213 os.MkdirAll(logsDir, 0777)
214
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800215 build.SetupOutDir(buildCtx, config)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700216 if *alternateResultDir {
Dan Willemsene3480762017-11-07 11:23:27 -0800217 distLogsDir := filepath.Join(config.DistDir(), "logs")
218 os.MkdirAll(distLogsDir, 0777)
219 log.SetOutput(filepath.Join(distLogsDir, "soong.log"))
220 trace.SetOutput(filepath.Join(distLogsDir, "build.trace"))
Dan Willemsenf624fb92017-05-19 16:39:04 -0700221 } else {
222 log.SetOutput(filepath.Join(config.OutDir(), "soong.log"))
223 trace.SetOutput(filepath.Join(config.OutDir(), "build.trace"))
224 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800225
Dan Willemsen22de2162017-12-11 14:35:23 -0800226 setMaxFiles(log)
227
Dan Willemsen04d76ef2018-05-15 00:52:29 -0700228 finder := build.NewSourceFinder(buildCtx, config)
229 defer finder.Shutdown()
230
231 build.FindSources(buildCtx, config, finder)
232
Dan Willemsenb2e6c2e2017-07-13 17:24:44 -0700233 vars, err := build.DumpMakeVars(buildCtx, config, nil, []string{"all_named_products"})
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800234 if err != nil {
235 log.Fatal(err)
236 }
Jeff Gastonb61e3f72017-10-25 15:02:45 -0700237 var productsList []string
238 allProducts := strings.Fields(vars["all_named_products"])
239
240 if *includeProducts != "" {
241 missingProducts := []string{}
242 for _, product := range strings.Split(*includeProducts, ",") {
243 if inList(product, allProducts) {
244 productsList = append(productsList, product)
245 } else {
246 missingProducts = append(missingProducts, product)
247 }
248 }
249 if len(missingProducts) > 0 {
250 log.Fatalf("Products don't exist: %s\n", missingProducts)
251 }
252 } else {
253 productsList = allProducts
254 }
Dan Willemsen9957b9c2017-10-06 15:05:05 -0700255
256 products := make([]string, 0, len(productsList))
257 skipList := strings.Split(*skipProducts, ",")
258 skipProduct := func(p string) bool {
259 for _, s := range skipList {
260 if p == s {
261 return true
262 }
263 }
264 return false
265 }
266 for _, product := range productsList {
267 if !skipProduct(product) {
268 products = append(products, product)
269 } else {
270 log.Verbose("Skipping: ", product)
271 }
272 }
273
274 log.Verbose("Got product list: ", products)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800275
Dan Willemsenb82471a2018-05-17 16:37:09 -0700276 s := buildCtx.Status.StartTool()
277 s.SetTotalActions(len(products))
Dan Willemsena4e43a72017-05-06 16:58:26 -0700278
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800279 var wg sync.WaitGroup
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800280 productConfigs := make(chan Product, len(products))
281
282 // Run the product config for every product in parallel
283 for _, product := range products {
284 wg.Add(1)
285 go func(product string) {
Dan Willemsenf624fb92017-05-19 16:39:04 -0700286 var stdLog string
287
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800288 defer wg.Done()
Dan Willemsenb82471a2018-05-17 16:37:09 -0700289
290 action := &status.Action{
291 Description: product,
292 Outputs: []string{product},
293 }
294 s.StartAction(action)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800295 defer logger.Recover(func(err error) {
Dan Willemsenb82471a2018-05-17 16:37:09 -0700296 s.FinishAction(status.ActionResult{
297 Action: action,
298 Error: err,
299 Output: errMsgFromLog(stdLog),
300 })
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800301 })
302
303 productOutDir := filepath.Join(config.OutDir(), product)
Dan Willemsene3480762017-11-07 11:23:27 -0800304 productLogDir := filepath.Join(logsDir, product)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800305
306 if err := os.MkdirAll(productOutDir, 0777); err != nil {
307 log.Fatalf("Error creating out directory: %v", err)
308 }
Dan Willemsene3480762017-11-07 11:23:27 -0800309 if err := os.MkdirAll(productLogDir, 0777); err != nil {
310 log.Fatalf("Error creating log directory: %v", err)
311 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800312
Dan Willemsenf624fb92017-05-19 16:39:04 -0700313 stdLog = filepath.Join(productLogDir, "std.log")
314 f, err := os.Create(stdLog)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800315 if err != nil {
316 log.Fatalf("Error creating std.log: %v", err)
317 }
318
Dan Willemsenf1963962017-11-11 15:44:51 -0800319 productLog := logger.New(f)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700320 productLog.SetOutput(filepath.Join(productLogDir, "soong.log"))
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800321
Dan Willemsen59339a22018-07-22 21:18:45 -0700322 productCtx := build.Context{ContextImpl: &build.ContextImpl{
Dan Willemsenb82471a2018-05-17 16:37:09 -0700323 Context: ctx,
324 Logger: productLog,
325 Tracer: trace,
326 Writer: terminal.NewWriter(terminal.NewCustomStdio(nil, f, f)),
327 Thread: trace.NewThread(product),
328 Status: &status.Status{},
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700329 }}
Dan Willemsenb82471a2018-05-17 16:37:09 -0700330 productCtx.Status.AddOutput(terminal.NewStatusOutput(productCtx.Writer, ""))
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800331
Anton Hansson43da16a2018-08-20 16:16:02 +0100332 productConfig := build.NewConfig(productCtx, flag.Args()...)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800333 productConfig.Environment().Set("OUT_DIR", productOutDir)
Jeff Gaston743e29e2017-08-17 14:09:23 -0700334 build.FindSources(productCtx, productConfig, finder)
Dan Willemsen5ed900b2017-05-07 11:40:30 -0700335 productConfig.Lunch(productCtx, product, *buildVariant)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800336
337 build.Build(productCtx, productConfig, build.BuildProductConfig)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700338 productConfigs <- Product{productCtx, productConfig, stdLog, action}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800339 }(product)
340 }
341 go func() {
342 defer close(productConfigs)
343 wg.Wait()
344 }()
345
346 var wg2 sync.WaitGroup
347 // Then run up to numJobs worth of Soong and Kati
348 for i := 0; i < *numJobs; i++ {
349 wg2.Add(1)
350 go func() {
351 defer wg2.Done()
352 for product := range productConfigs {
353 func() {
354 defer logger.Recover(func(err error) {
Dan Willemsenb82471a2018-05-17 16:37:09 -0700355 s.FinishAction(status.ActionResult{
356 Action: product.action,
357 Error: err,
358 Output: errMsgFromLog(product.logFile),
359 })
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800360 })
361
Dan Willemsene3480762017-11-07 11:23:27 -0800362 defer func() {
363 if *keepArtifacts {
364 args := zip.ZipArgs{
365 FileArgs: []zip.FileArg{
366 {
367 GlobDir: product.config.OutDir(),
368 SourcePrefixToStrip: product.config.OutDir(),
369 },
370 },
371 OutputFilePath: filepath.Join(config.OutDir(), product.config.TargetProduct()+".zip"),
372 NumParallelJobs: runtime.NumCPU(),
373 CompressionLevel: 5,
374 }
375 if err := zip.Run(args); err != nil {
376 log.Fatalf("Error zipping artifacts: %v", err)
377 }
378 }
Dan Willemsen41538382018-08-31 19:51:35 -0700379 if *incremental {
380 // Save space, Kati doesn't notice
381 if f := product.config.KatiNinjaFile(); f != "" {
382 os.Truncate(f, 0)
383 }
384 } else {
385 os.RemoveAll(product.config.OutDir())
386 }
Dan Willemsene3480762017-11-07 11:23:27 -0800387 }()
388
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800389 buildWhat := 0
390 if !*onlyConfig {
391 buildWhat |= build.BuildSoong
392 if !*onlySoong {
393 buildWhat |= build.BuildKati
394 }
395 }
Dan Willemsen41538382018-08-31 19:51:35 -0700396
397 before := time.Now()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800398 build.Build(product.ctx, product.config, buildWhat)
Dan Willemsen41538382018-08-31 19:51:35 -0700399
400 // Save std_full.log if Kati re-read the makefiles
401 if buildWhat&build.BuildKati != 0 {
402 if after, err := os.Stat(product.config.KatiNinjaFile()); err == nil && after.ModTime().After(before) {
403 err := copyFile(product.logFile, filepath.Join(filepath.Dir(product.logFile), "std_full.log"))
404 if err != nil {
405 log.Fatalf("Error copying log file: %s", err)
406 }
407 }
408 }
409
Dan Willemsenb82471a2018-05-17 16:37:09 -0700410 s.FinishAction(status.ActionResult{
411 Action: product.action,
412 })
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800413 }()
414 }
415 }()
416 }
Dan Willemsena4e43a72017-05-06 16:58:26 -0700417 wg2.Wait()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800418
Dan Willemsene3480762017-11-07 11:23:27 -0800419 if *alternateResultDir {
420 args := zip.ZipArgs{
421 FileArgs: []zip.FileArg{
422 {GlobDir: logsDir, SourcePrefixToStrip: logsDir},
423 },
424 OutputFilePath: filepath.Join(config.DistDir(), "logs.zip"),
425 NumParallelJobs: runtime.NumCPU(),
426 CompressionLevel: 5,
427 }
428 if err := zip.Run(args); err != nil {
429 log.Fatalf("Error zipping logs: %v", err)
430 }
431 }
432
Dan Willemsenb82471a2018-05-17 16:37:09 -0700433 s.Finish()
Dan Willemsen34218ec2018-07-19 22:57:18 -0700434
435 if failures == 1 {
436 log.Fatal("1 failure")
437 } else if failures > 1 {
438 log.Fatalf("%d failures", failures)
439 } else {
440 writer.Print("Success")
441 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800442}
Dan Willemsen34218ec2018-07-19 22:57:18 -0700443
444type failureCount int
445
446func (f *failureCount) StartAction(action *status.Action, counts status.Counts) {}
447
448func (f *failureCount) FinishAction(result status.ActionResult, counts status.Counts) {
449 if result.Error != nil {
450 *f += 1
451 }
452}
453
454func (f *failureCount) Message(level status.MsgLevel, message string) {
455 if level >= status.ErrorLvl {
456 *f += 1
457 }
458}
459
460func (f *failureCount) Flush() {}