blob: 2fee1f78cab02d406b4dfb0a0ceea00798c5200d [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"
Steven Moreland552432e2017-03-29 19:26:09 -070027 "time"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080028
29 "android/soong/ui/build"
30 "android/soong/ui/logger"
Dan Willemsend9f6fa22016-08-21 15:17:17 -070031 "android/soong/ui/tracer"
Dan Willemsene3480762017-11-07 11:23:27 -080032 "android/soong/zip"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080033)
34
35// We default to number of cpus / 4, which seems to be the sweet spot for my
36// system. I suspect this is mostly due to memory or disk bandwidth though, and
37// may depend on the size ofthe source tree, so this probably isn't a great
38// default.
39func detectNumJobs() int {
40 if runtime.NumCPU() < 4 {
41 return 1
42 }
43 return runtime.NumCPU() / 4
44}
45
46var numJobs = flag.Int("j", detectNumJobs(), "number of parallel kati jobs")
47
Dan Willemsene3480762017-11-07 11:23:27 -080048var keepArtifacts = flag.Bool("keep", false, "keep archives of artifacts")
Dan Willemsenc2af0be2017-01-20 14:10:01 -080049
50var outDir = flag.String("out", "", "path to store output directories (defaults to tmpdir under $OUT when empty)")
Dan Willemsenf624fb92017-05-19 16:39:04 -070051var alternateResultDir = flag.Bool("dist", false, "write select results to $DIST_DIR (or <out>/dist when empty)")
Dan Willemsenc2af0be2017-01-20 14:10:01 -080052
53var onlyConfig = flag.Bool("only-config", false, "Only run product config (not Soong or Kati)")
54var onlySoong = flag.Bool("only-soong", false, "Only run product config and Soong (not Kati)")
55
Dan Willemsen5ed900b2017-05-07 11:40:30 -070056var buildVariant = flag.String("variant", "eng", "build variant to use")
57
Dan Willemsen9957b9c2017-10-06 15:05:05 -070058var skipProducts = flag.String("skip-products", "", "comma-separated list of products to skip (known failures, etc)")
Jeff Gastonb61e3f72017-10-25 15:02:45 -070059var includeProducts = flag.String("products", "", "comma-separated list of products to build")
Dan Willemsen9957b9c2017-10-06 15:05:05 -070060
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
Jeff Gastonb61e3f72017-10-25 15:02:45 -0700162func inList(str string, list []string) bool {
163 for _, other := range list {
164 if str == other {
165 return true
166 }
167 }
168 return false
169}
170
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800171func main() {
172 log := logger.New(os.Stderr)
173 defer log.Cleanup()
174
175 flag.Parse()
176
177 ctx, cancel := context.WithCancel(context.Background())
178 defer cancel()
179
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700180 trace := tracer.New(log)
181 defer trace.Close()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800182
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700183 build.SetupSignals(log, cancel, func() {
184 trace.Close()
185 log.Cleanup()
186 })
187
188 buildCtx := build.Context{&build.ContextImpl{
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800189 Context: ctx,
190 Logger: log,
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700191 Tracer: trace,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800192 StdioInterface: build.StdioImpl{},
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700193 }}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800194
Dan Willemsena4e43a72017-05-06 16:58:26 -0700195 status := NewStatus(buildCtx)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800196
197 config := build.NewConfig(buildCtx)
198 if *outDir == "" {
Steven Moreland552432e2017-03-29 19:26:09 -0700199 name := "multiproduct-" + time.Now().Format("20060102150405")
200
201 *outDir = filepath.Join(config.OutDir(), name)
202
Dan Willemsenf624fb92017-05-19 16:39:04 -0700203 // Ensure the empty files exist in the output directory
204 // containing our output directory too. This is mostly for
205 // safety, but also triggers the ninja_build file so that our
206 // build servers know that they can parse the output as if it
207 // was ninja output.
208 build.SetupOutDir(buildCtx, config)
209
Steven Moreland552432e2017-03-29 19:26:09 -0700210 if err := os.MkdirAll(*outDir, 0777); err != nil {
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800211 log.Fatalf("Failed to create tempdir: %v", err)
212 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800213 }
214 config.Environment().Set("OUT_DIR", *outDir)
215 log.Println("Output directory:", *outDir)
216
Dan Willemsene3480762017-11-07 11:23:27 -0800217 logsDir := filepath.Join(config.OutDir(), "logs")
218 os.MkdirAll(logsDir, 0777)
219
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800220 build.SetupOutDir(buildCtx, config)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700221 if *alternateResultDir {
Dan Willemsene3480762017-11-07 11:23:27 -0800222 distLogsDir := filepath.Join(config.DistDir(), "logs")
223 os.MkdirAll(distLogsDir, 0777)
224 log.SetOutput(filepath.Join(distLogsDir, "soong.log"))
225 trace.SetOutput(filepath.Join(distLogsDir, "build.trace"))
Dan Willemsenf624fb92017-05-19 16:39:04 -0700226 } else {
227 log.SetOutput(filepath.Join(config.OutDir(), "soong.log"))
228 trace.SetOutput(filepath.Join(config.OutDir(), "build.trace"))
229 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800230
Dan Willemsenb2e6c2e2017-07-13 17:24:44 -0700231 vars, err := build.DumpMakeVars(buildCtx, config, nil, []string{"all_named_products"})
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800232 if err != nil {
233 log.Fatal(err)
234 }
Jeff Gastonb61e3f72017-10-25 15:02:45 -0700235 var productsList []string
236 allProducts := strings.Fields(vars["all_named_products"])
237
238 if *includeProducts != "" {
239 missingProducts := []string{}
240 for _, product := range strings.Split(*includeProducts, ",") {
241 if inList(product, allProducts) {
242 productsList = append(productsList, product)
243 } else {
244 missingProducts = append(missingProducts, product)
245 }
246 }
247 if len(missingProducts) > 0 {
248 log.Fatalf("Products don't exist: %s\n", missingProducts)
249 }
250 } else {
251 productsList = allProducts
252 }
Dan Willemsen9957b9c2017-10-06 15:05:05 -0700253
254 products := make([]string, 0, len(productsList))
255 skipList := strings.Split(*skipProducts, ",")
256 skipProduct := func(p string) bool {
257 for _, s := range skipList {
258 if p == s {
259 return true
260 }
261 }
262 return false
263 }
264 for _, product := range productsList {
265 if !skipProduct(product) {
266 products = append(products, product)
267 } else {
268 log.Verbose("Skipping: ", product)
269 }
270 }
271
272 log.Verbose("Got product list: ", products)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800273
Dan Willemsena4e43a72017-05-06 16:58:26 -0700274 status.SetTotal(len(products))
275
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800276 var wg sync.WaitGroup
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800277 productConfigs := make(chan Product, len(products))
278
Jeff Gastonb64fc1c2017-08-04 12:30:12 -0700279 finder := build.NewSourceFinder(buildCtx, config)
280 defer finder.Shutdown()
281
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800282 // 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()
289 defer logger.Recover(func(err error) {
Dan Willemsenf624fb92017-05-19 16:39:04 -0700290 status.Fail(product, err, stdLog)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800291 })
292
293 productOutDir := filepath.Join(config.OutDir(), product)
Dan Willemsene3480762017-11-07 11:23:27 -0800294 productLogDir := filepath.Join(logsDir, product)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800295
296 if err := os.MkdirAll(productOutDir, 0777); err != nil {
297 log.Fatalf("Error creating out directory: %v", err)
298 }
Dan Willemsene3480762017-11-07 11:23:27 -0800299 if err := os.MkdirAll(productLogDir, 0777); err != nil {
300 log.Fatalf("Error creating log directory: %v", err)
301 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800302
Dan Willemsenf624fb92017-05-19 16:39:04 -0700303 stdLog = filepath.Join(productLogDir, "std.log")
304 f, err := os.Create(stdLog)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800305 if err != nil {
306 log.Fatalf("Error creating std.log: %v", err)
307 }
308
Dan Willemsenf1963962017-11-11 15:44:51 -0800309 productLog := logger.New(f)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700310 productLog.SetOutput(filepath.Join(productLogDir, "soong.log"))
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800311
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700312 productCtx := build.Context{&build.ContextImpl{
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800313 Context: ctx,
314 Logger: productLog,
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700315 Tracer: trace,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800316 StdioInterface: build.NewCustomStdio(nil, f, f),
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700317 Thread: trace.NewThread(product),
318 }}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800319
320 productConfig := build.NewConfig(productCtx)
321 productConfig.Environment().Set("OUT_DIR", productOutDir)
Jeff Gaston743e29e2017-08-17 14:09:23 -0700322 build.FindSources(productCtx, productConfig, finder)
Dan Willemsen5ed900b2017-05-07 11:40:30 -0700323 productConfig.Lunch(productCtx, product, *buildVariant)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800324
325 build.Build(productCtx, productConfig, build.BuildProductConfig)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700326 productConfigs <- Product{productCtx, productConfig, stdLog}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800327 }(product)
328 }
329 go func() {
330 defer close(productConfigs)
331 wg.Wait()
332 }()
333
334 var wg2 sync.WaitGroup
335 // Then run up to numJobs worth of Soong and Kati
336 for i := 0; i < *numJobs; i++ {
337 wg2.Add(1)
338 go func() {
339 defer wg2.Done()
340 for product := range productConfigs {
341 func() {
342 defer logger.Recover(func(err error) {
Dan Willemsenf624fb92017-05-19 16:39:04 -0700343 status.Fail(product.config.TargetProduct(), err, product.logFile)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800344 })
345
Dan Willemsene3480762017-11-07 11:23:27 -0800346 defer func() {
347 if *keepArtifacts {
348 args := zip.ZipArgs{
349 FileArgs: []zip.FileArg{
350 {
351 GlobDir: product.config.OutDir(),
352 SourcePrefixToStrip: product.config.OutDir(),
353 },
354 },
355 OutputFilePath: filepath.Join(config.OutDir(), product.config.TargetProduct()+".zip"),
356 NumParallelJobs: runtime.NumCPU(),
357 CompressionLevel: 5,
358 }
359 if err := zip.Run(args); err != nil {
360 log.Fatalf("Error zipping artifacts: %v", err)
361 }
362 }
363 os.RemoveAll(product.config.OutDir())
364 }()
365
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800366 buildWhat := 0
367 if !*onlyConfig {
368 buildWhat |= build.BuildSoong
369 if !*onlySoong {
370 buildWhat |= build.BuildKati
371 }
372 }
373 build.Build(product.ctx, product.config, buildWhat)
Dan Willemsena4e43a72017-05-06 16:58:26 -0700374 status.Finish(product.config.TargetProduct())
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800375 }()
376 }
377 }()
378 }
Dan Willemsena4e43a72017-05-06 16:58:26 -0700379 wg2.Wait()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800380
Dan Willemsene3480762017-11-07 11:23:27 -0800381 if *alternateResultDir {
382 args := zip.ZipArgs{
383 FileArgs: []zip.FileArg{
384 {GlobDir: logsDir, SourcePrefixToStrip: logsDir},
385 },
386 OutputFilePath: filepath.Join(config.DistDir(), "logs.zip"),
387 NumParallelJobs: runtime.NumCPU(),
388 CompressionLevel: 5,
389 }
390 if err := zip.Run(args); err != nil {
391 log.Fatalf("Error zipping logs: %v", err)
392 }
393 }
394
Dan Willemsena4e43a72017-05-06 16:58:26 -0700395 if count := status.Finished(); count > 0 {
396 log.Fatalln(count, "products failed")
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800397 }
398}