blob: 813c060865f4eaa59ed9ef25a0f4e1b973eef54b [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 Willemsenb82471a2018-05-17 16:37:09 -070032 "android/soong/ui/status"
33 "android/soong/ui/terminal"
Dan Willemsend9f6fa22016-08-21 15:17:17 -070034 "android/soong/ui/tracer"
Dan Willemsene3480762017-11-07 11:23:27 -080035 "android/soong/zip"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080036)
37
38// We default to number of cpus / 4, which seems to be the sweet spot for my
39// system. I suspect this is mostly due to memory or disk bandwidth though, and
40// may depend on the size ofthe source tree, so this probably isn't a great
41// default.
42func detectNumJobs() int {
43 if runtime.NumCPU() < 4 {
44 return 1
45 }
46 return runtime.NumCPU() / 4
47}
48
49var numJobs = flag.Int("j", detectNumJobs(), "number of parallel kati jobs")
50
Dan Willemsene3480762017-11-07 11:23:27 -080051var keepArtifacts = flag.Bool("keep", false, "keep archives of artifacts")
Dan Willemsenc2af0be2017-01-20 14:10:01 -080052
53var outDir = flag.String("out", "", "path to store output directories (defaults to tmpdir under $OUT when empty)")
Dan Willemsenf624fb92017-05-19 16:39:04 -070054var alternateResultDir = flag.Bool("dist", false, "write select results to $DIST_DIR (or <out>/dist when empty)")
Dan Willemsenc2af0be2017-01-20 14:10:01 -080055
56var onlyConfig = flag.Bool("only-config", false, "Only run product config (not Soong or Kati)")
57var onlySoong = flag.Bool("only-soong", false, "Only run product config and Soong (not Kati)")
58
Dan Willemsen5ed900b2017-05-07 11:40:30 -070059var buildVariant = flag.String("variant", "eng", "build variant to use")
60
Dan Willemsen9957b9c2017-10-06 15:05:05 -070061var skipProducts = flag.String("skip-products", "", "comma-separated list of products to skip (known failures, etc)")
Jeff Gastonb61e3f72017-10-25 15:02:45 -070062var includeProducts = flag.String("products", "", "comma-separated list of products to build")
Dan Willemsen9957b9c2017-10-06 15:05:05 -070063
Dan Willemsenf624fb92017-05-19 16:39:04 -070064const errorLeadingLines = 20
65const errorTrailingLines = 20
66
Dan Willemsenc2af0be2017-01-20 14:10:01 -080067type Product struct {
Dan Willemsenf624fb92017-05-19 16:39:04 -070068 ctx build.Context
69 config build.Config
70 logFile string
Dan Willemsenb82471a2018-05-17 16:37:09 -070071 action *status.Action
Dan Willemsenc2af0be2017-01-20 14:10:01 -080072}
73
Dan Willemsenb82471a2018-05-17 16:37:09 -070074func errMsgFromLog(filename string) string {
75 if filename == "" {
76 return ""
Dan Willemsena4e43a72017-05-06 16:58:26 -070077 }
78
Dan Willemsenb82471a2018-05-17 16:37:09 -070079 data, err := ioutil.ReadFile(filename)
80 if err != nil {
81 return ""
Dan Willemsenf624fb92017-05-19 16:39:04 -070082 }
83
Dan Willemsenb82471a2018-05-17 16:37:09 -070084 lines := strings.Split(strings.TrimSpace(string(data)), "\n")
85 if len(lines) > errorLeadingLines+errorTrailingLines+1 {
86 lines[errorLeadingLines] = fmt.Sprintf("... skipping %d lines ...",
87 len(lines)-errorLeadingLines-errorTrailingLines)
Dan Willemsena4e43a72017-05-06 16:58:26 -070088
Dan Willemsenb82471a2018-05-17 16:37:09 -070089 lines = append(lines[:errorLeadingLines+1],
90 lines[len(lines)-errorTrailingLines:]...)
Dan Willemsena4e43a72017-05-06 16:58:26 -070091 }
Dan Willemsenb82471a2018-05-17 16:37:09 -070092 var buf strings.Builder
93 for _, line := range lines {
94 buf.WriteString("> ")
95 buf.WriteString(line)
96 buf.WriteString("\n")
Dan Willemsena4e43a72017-05-06 16:58:26 -070097 }
Dan Willemsenb82471a2018-05-17 16:37:09 -070098 return buf.String()
Dan Willemsena4e43a72017-05-06 16:58:26 -070099}
100
Dan Willemsen22de2162017-12-11 14:35:23 -0800101// TODO(b/70370883): This tool uses a lot of open files -- over the default
102// soft limit of 1024 on some systems. So bump up to the hard limit until I fix
103// the algorithm.
104func setMaxFiles(log logger.Logger) {
105 var limits syscall.Rlimit
106
107 err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limits)
108 if err != nil {
109 log.Println("Failed to get file limit:", err)
110 return
111 }
112
113 log.Verbosef("Current file limits: %d soft, %d hard", limits.Cur, limits.Max)
114 if limits.Cur == limits.Max {
115 return
116 }
117
118 limits.Cur = limits.Max
119 err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limits)
120 if err != nil {
121 log.Println("Failed to increase file limit:", err)
122 }
123}
124
Jeff Gastonb61e3f72017-10-25 15:02:45 -0700125func inList(str string, list []string) bool {
126 for _, other := range list {
127 if str == other {
128 return true
129 }
130 }
131 return false
132}
133
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800134func main() {
Dan Willemsenb82471a2018-05-17 16:37:09 -0700135 writer := terminal.NewWriter(terminal.StdioImpl{})
136 defer writer.Finish()
137
Dan Willemsen34218ec2018-07-19 22:57:18 -0700138 log := logger.New(writer)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800139 defer log.Cleanup()
140
141 flag.Parse()
142
143 ctx, cancel := context.WithCancel(context.Background())
144 defer cancel()
145
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700146 trace := tracer.New(log)
147 defer trace.Close()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800148
Dan Willemsenb82471a2018-05-17 16:37:09 -0700149 stat := &status.Status{}
150 defer stat.Finish()
151 stat.AddOutput(terminal.NewStatusOutput(writer, ""))
152
Dan Willemsen34218ec2018-07-19 22:57:18 -0700153 var failures failureCount
154 stat.AddOutput(&failures)
155
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700156 build.SetupSignals(log, cancel, func() {
157 trace.Close()
158 log.Cleanup()
Dan Willemsenb82471a2018-05-17 16:37:09 -0700159 stat.Finish()
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700160 })
161
Dan Willemsen59339a22018-07-22 21:18:45 -0700162 buildCtx := build.Context{ContextImpl: &build.ContextImpl{
Dan Willemsenb82471a2018-05-17 16:37:09 -0700163 Context: ctx,
164 Logger: log,
165 Tracer: trace,
166 Writer: writer,
167 Status: stat,
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700168 }}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800169
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800170 config := build.NewConfig(buildCtx)
171 if *outDir == "" {
Steven Moreland552432e2017-03-29 19:26:09 -0700172 name := "multiproduct-" + time.Now().Format("20060102150405")
173
174 *outDir = filepath.Join(config.OutDir(), name)
175
Dan Willemsenf624fb92017-05-19 16:39:04 -0700176 // Ensure the empty files exist in the output directory
177 // containing our output directory too. This is mostly for
178 // safety, but also triggers the ninja_build file so that our
179 // build servers know that they can parse the output as if it
180 // was ninja output.
181 build.SetupOutDir(buildCtx, config)
182
Steven Moreland552432e2017-03-29 19:26:09 -0700183 if err := os.MkdirAll(*outDir, 0777); err != nil {
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800184 log.Fatalf("Failed to create tempdir: %v", err)
185 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800186 }
187 config.Environment().Set("OUT_DIR", *outDir)
188 log.Println("Output directory:", *outDir)
189
Dan Willemsene3480762017-11-07 11:23:27 -0800190 logsDir := filepath.Join(config.OutDir(), "logs")
191 os.MkdirAll(logsDir, 0777)
192
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800193 build.SetupOutDir(buildCtx, config)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700194 if *alternateResultDir {
Dan Willemsene3480762017-11-07 11:23:27 -0800195 distLogsDir := filepath.Join(config.DistDir(), "logs")
196 os.MkdirAll(distLogsDir, 0777)
197 log.SetOutput(filepath.Join(distLogsDir, "soong.log"))
198 trace.SetOutput(filepath.Join(distLogsDir, "build.trace"))
Dan Willemsenf624fb92017-05-19 16:39:04 -0700199 } else {
200 log.SetOutput(filepath.Join(config.OutDir(), "soong.log"))
201 trace.SetOutput(filepath.Join(config.OutDir(), "build.trace"))
202 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800203
Dan Willemsen22de2162017-12-11 14:35:23 -0800204 setMaxFiles(log)
205
Dan Willemsen04d76ef2018-05-15 00:52:29 -0700206 finder := build.NewSourceFinder(buildCtx, config)
207 defer finder.Shutdown()
208
209 build.FindSources(buildCtx, config, finder)
210
Dan Willemsenb2e6c2e2017-07-13 17:24:44 -0700211 vars, err := build.DumpMakeVars(buildCtx, config, nil, []string{"all_named_products"})
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800212 if err != nil {
213 log.Fatal(err)
214 }
Jeff Gastonb61e3f72017-10-25 15:02:45 -0700215 var productsList []string
216 allProducts := strings.Fields(vars["all_named_products"])
217
218 if *includeProducts != "" {
219 missingProducts := []string{}
220 for _, product := range strings.Split(*includeProducts, ",") {
221 if inList(product, allProducts) {
222 productsList = append(productsList, product)
223 } else {
224 missingProducts = append(missingProducts, product)
225 }
226 }
227 if len(missingProducts) > 0 {
228 log.Fatalf("Products don't exist: %s\n", missingProducts)
229 }
230 } else {
231 productsList = allProducts
232 }
Dan Willemsen9957b9c2017-10-06 15:05:05 -0700233
234 products := make([]string, 0, len(productsList))
235 skipList := strings.Split(*skipProducts, ",")
236 skipProduct := func(p string) bool {
237 for _, s := range skipList {
238 if p == s {
239 return true
240 }
241 }
242 return false
243 }
244 for _, product := range productsList {
245 if !skipProduct(product) {
246 products = append(products, product)
247 } else {
248 log.Verbose("Skipping: ", product)
249 }
250 }
251
252 log.Verbose("Got product list: ", products)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800253
Dan Willemsenb82471a2018-05-17 16:37:09 -0700254 s := buildCtx.Status.StartTool()
255 s.SetTotalActions(len(products))
Dan Willemsena4e43a72017-05-06 16:58:26 -0700256
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800257 var wg sync.WaitGroup
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800258 productConfigs := make(chan Product, len(products))
259
260 // Run the product config for every product in parallel
261 for _, product := range products {
262 wg.Add(1)
263 go func(product string) {
Dan Willemsenf624fb92017-05-19 16:39:04 -0700264 var stdLog string
265
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800266 defer wg.Done()
Dan Willemsenb82471a2018-05-17 16:37:09 -0700267
268 action := &status.Action{
269 Description: product,
270 Outputs: []string{product},
271 }
272 s.StartAction(action)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800273 defer logger.Recover(func(err error) {
Dan Willemsenb82471a2018-05-17 16:37:09 -0700274 s.FinishAction(status.ActionResult{
275 Action: action,
276 Error: err,
277 Output: errMsgFromLog(stdLog),
278 })
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800279 })
280
281 productOutDir := filepath.Join(config.OutDir(), product)
Dan Willemsene3480762017-11-07 11:23:27 -0800282 productLogDir := filepath.Join(logsDir, product)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800283
284 if err := os.MkdirAll(productOutDir, 0777); err != nil {
285 log.Fatalf("Error creating out directory: %v", err)
286 }
Dan Willemsene3480762017-11-07 11:23:27 -0800287 if err := os.MkdirAll(productLogDir, 0777); err != nil {
288 log.Fatalf("Error creating log directory: %v", err)
289 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800290
Dan Willemsenf624fb92017-05-19 16:39:04 -0700291 stdLog = filepath.Join(productLogDir, "std.log")
292 f, err := os.Create(stdLog)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800293 if err != nil {
294 log.Fatalf("Error creating std.log: %v", err)
295 }
296
Dan Willemsenf1963962017-11-11 15:44:51 -0800297 productLog := logger.New(f)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700298 productLog.SetOutput(filepath.Join(productLogDir, "soong.log"))
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800299
Dan Willemsen59339a22018-07-22 21:18:45 -0700300 productCtx := build.Context{ContextImpl: &build.ContextImpl{
Dan Willemsenb82471a2018-05-17 16:37:09 -0700301 Context: ctx,
302 Logger: productLog,
303 Tracer: trace,
304 Writer: terminal.NewWriter(terminal.NewCustomStdio(nil, f, f)),
305 Thread: trace.NewThread(product),
306 Status: &status.Status{},
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700307 }}
Dan Willemsenb82471a2018-05-17 16:37:09 -0700308 productCtx.Status.AddOutput(terminal.NewStatusOutput(productCtx.Writer, ""))
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800309
Anton Hansson43da16a2018-08-20 16:16:02 +0100310 productConfig := build.NewConfig(productCtx, flag.Args()...)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800311 productConfig.Environment().Set("OUT_DIR", productOutDir)
Jeff Gaston743e29e2017-08-17 14:09:23 -0700312 build.FindSources(productCtx, productConfig, finder)
Dan Willemsen5ed900b2017-05-07 11:40:30 -0700313 productConfig.Lunch(productCtx, product, *buildVariant)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800314
315 build.Build(productCtx, productConfig, build.BuildProductConfig)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700316 productConfigs <- Product{productCtx, productConfig, stdLog, action}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800317 }(product)
318 }
319 go func() {
320 defer close(productConfigs)
321 wg.Wait()
322 }()
323
324 var wg2 sync.WaitGroup
325 // Then run up to numJobs worth of Soong and Kati
326 for i := 0; i < *numJobs; i++ {
327 wg2.Add(1)
328 go func() {
329 defer wg2.Done()
330 for product := range productConfigs {
331 func() {
332 defer logger.Recover(func(err error) {
Dan Willemsenb82471a2018-05-17 16:37:09 -0700333 s.FinishAction(status.ActionResult{
334 Action: product.action,
335 Error: err,
336 Output: errMsgFromLog(product.logFile),
337 })
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800338 })
339
Dan Willemsene3480762017-11-07 11:23:27 -0800340 defer func() {
341 if *keepArtifacts {
342 args := zip.ZipArgs{
343 FileArgs: []zip.FileArg{
344 {
345 GlobDir: product.config.OutDir(),
346 SourcePrefixToStrip: product.config.OutDir(),
347 },
348 },
349 OutputFilePath: filepath.Join(config.OutDir(), product.config.TargetProduct()+".zip"),
350 NumParallelJobs: runtime.NumCPU(),
351 CompressionLevel: 5,
352 }
353 if err := zip.Run(args); err != nil {
354 log.Fatalf("Error zipping artifacts: %v", err)
355 }
356 }
357 os.RemoveAll(product.config.OutDir())
358 }()
359
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800360 buildWhat := 0
361 if !*onlyConfig {
362 buildWhat |= build.BuildSoong
363 if !*onlySoong {
364 buildWhat |= build.BuildKati
365 }
366 }
367 build.Build(product.ctx, product.config, buildWhat)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700368 s.FinishAction(status.ActionResult{
369 Action: product.action,
370 })
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800371 }()
372 }
373 }()
374 }
Dan Willemsena4e43a72017-05-06 16:58:26 -0700375 wg2.Wait()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800376
Dan Willemsene3480762017-11-07 11:23:27 -0800377 if *alternateResultDir {
378 args := zip.ZipArgs{
379 FileArgs: []zip.FileArg{
380 {GlobDir: logsDir, SourcePrefixToStrip: logsDir},
381 },
382 OutputFilePath: filepath.Join(config.DistDir(), "logs.zip"),
383 NumParallelJobs: runtime.NumCPU(),
384 CompressionLevel: 5,
385 }
386 if err := zip.Run(args); err != nil {
387 log.Fatalf("Error zipping logs: %v", err)
388 }
389 }
390
Dan Willemsenb82471a2018-05-17 16:37:09 -0700391 s.Finish()
Dan Willemsen34218ec2018-07-19 22:57:18 -0700392
393 if failures == 1 {
394 log.Fatal("1 failure")
395 } else if failures > 1 {
396 log.Fatalf("%d failures", failures)
397 } else {
398 writer.Print("Success")
399 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800400}
Dan Willemsen34218ec2018-07-19 22:57:18 -0700401
402type failureCount int
403
404func (f *failureCount) StartAction(action *status.Action, counts status.Counts) {}
405
406func (f *failureCount) FinishAction(result status.ActionResult, counts status.Counts) {
407 if result.Error != nil {
408 *f += 1
409 }
410}
411
412func (f *failureCount) Message(level status.MsgLevel, message string) {
413 if level >= status.ErrorLvl {
414 *f += 1
415 }
416}
417
418func (f *failureCount) Flush() {}