blob: 183f800a256763b652ab1267ef09d3a31a80d7a4 [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)")
59
Dan Willemsenf624fb92017-05-19 16:39:04 -070060const errorLeadingLines = 20
61const errorTrailingLines = 20
62
Dan Willemsenc2af0be2017-01-20 14:10:01 -080063type Product struct {
Dan Willemsenf624fb92017-05-19 16:39:04 -070064 ctx build.Context
65 config build.Config
66 logFile string
Dan Willemsenc2af0be2017-01-20 14:10:01 -080067}
68
Dan Willemsena4e43a72017-05-06 16:58:26 -070069type Status struct {
70 cur int
71 total int
72 failed int
73
74 ctx build.Context
75 haveBlankLine bool
76 smartTerminal bool
77
78 lock sync.Mutex
79}
80
81func NewStatus(ctx build.Context) *Status {
82 return &Status{
83 ctx: ctx,
84 haveBlankLine: true,
85 smartTerminal: ctx.IsTerminal(),
86 }
87}
88
89func (s *Status) SetTotal(total int) {
90 s.total = total
91}
92
Dan Willemsenf624fb92017-05-19 16:39:04 -070093func (s *Status) Fail(product string, err error, logFile string) {
Dan Willemsena4e43a72017-05-06 16:58:26 -070094 s.Finish(product)
95
96 s.lock.Lock()
97 defer s.lock.Unlock()
98
99 if s.smartTerminal && !s.haveBlankLine {
100 fmt.Fprintln(s.ctx.Stdout())
101 s.haveBlankLine = true
102 }
103
104 s.failed++
105 fmt.Fprintln(s.ctx.Stderr(), "FAILED:", product)
106 s.ctx.Verboseln("FAILED:", product)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700107
108 if logFile != "" {
109 data, err := ioutil.ReadFile(logFile)
110 if err == nil {
111 lines := strings.Split(strings.TrimSpace(string(data)), "\n")
112 if len(lines) > errorLeadingLines+errorTrailingLines+1 {
113 lines[errorLeadingLines] = fmt.Sprintf("... skipping %d lines ...",
114 len(lines)-errorLeadingLines-errorTrailingLines)
115
116 lines = append(lines[:errorLeadingLines+1],
117 lines[len(lines)-errorTrailingLines:]...)
118 }
119 for _, line := range lines {
120 fmt.Fprintln(s.ctx.Stderr(), "> ", line)
121 s.ctx.Verboseln(line)
122 }
123 }
124 }
125
126 s.ctx.Print(err)
Dan Willemsena4e43a72017-05-06 16:58:26 -0700127}
128
129func (s *Status) Finish(product string) {
130 s.lock.Lock()
131 defer s.lock.Unlock()
132
133 s.cur++
134 line := fmt.Sprintf("[%d/%d] %s", s.cur, s.total, product)
135
136 if s.smartTerminal {
137 if max, ok := s.ctx.TermWidth(); ok {
138 if len(line) > max {
139 line = line[:max]
140 }
141 }
142
143 fmt.Fprint(s.ctx.Stdout(), "\r", line, "\x1b[K")
144 s.haveBlankLine = false
145 } else {
146 s.ctx.Println(line)
147 }
148}
149
150func (s *Status) Finished() int {
151 s.lock.Lock()
152 defer s.lock.Unlock()
153
154 if !s.haveBlankLine {
155 fmt.Fprintln(s.ctx.Stdout())
156 s.haveBlankLine = true
157 }
158 return s.failed
159}
160
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800161func main() {
162 log := logger.New(os.Stderr)
163 defer log.Cleanup()
164
165 flag.Parse()
166
167 ctx, cancel := context.WithCancel(context.Background())
168 defer cancel()
169
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700170 trace := tracer.New(log)
171 defer trace.Close()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800172
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700173 build.SetupSignals(log, cancel, func() {
174 trace.Close()
175 log.Cleanup()
176 })
177
178 buildCtx := build.Context{&build.ContextImpl{
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800179 Context: ctx,
180 Logger: log,
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700181 Tracer: trace,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800182 StdioInterface: build.StdioImpl{},
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700183 }}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800184
Dan Willemsena4e43a72017-05-06 16:58:26 -0700185 status := NewStatus(buildCtx)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800186
187 config := build.NewConfig(buildCtx)
188 if *outDir == "" {
Steven Moreland552432e2017-03-29 19:26:09 -0700189 name := "multiproduct-" + time.Now().Format("20060102150405")
190
191 *outDir = filepath.Join(config.OutDir(), name)
192
Dan Willemsenf624fb92017-05-19 16:39:04 -0700193 // Ensure the empty files exist in the output directory
194 // containing our output directory too. This is mostly for
195 // safety, but also triggers the ninja_build file so that our
196 // build servers know that they can parse the output as if it
197 // was ninja output.
198 build.SetupOutDir(buildCtx, config)
199
Steven Moreland552432e2017-03-29 19:26:09 -0700200 if err := os.MkdirAll(*outDir, 0777); err != nil {
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800201 log.Fatalf("Failed to create tempdir: %v", err)
202 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800203 }
204 config.Environment().Set("OUT_DIR", *outDir)
205 log.Println("Output directory:", *outDir)
206
Dan Willemsene3480762017-11-07 11:23:27 -0800207 logsDir := filepath.Join(config.OutDir(), "logs")
208 os.MkdirAll(logsDir, 0777)
209
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800210 build.SetupOutDir(buildCtx, config)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700211 if *alternateResultDir {
Dan Willemsene3480762017-11-07 11:23:27 -0800212 distLogsDir := filepath.Join(config.DistDir(), "logs")
213 os.MkdirAll(distLogsDir, 0777)
214 log.SetOutput(filepath.Join(distLogsDir, "soong.log"))
215 trace.SetOutput(filepath.Join(distLogsDir, "build.trace"))
Dan Willemsenf624fb92017-05-19 16:39:04 -0700216 } else {
217 log.SetOutput(filepath.Join(config.OutDir(), "soong.log"))
218 trace.SetOutput(filepath.Join(config.OutDir(), "build.trace"))
219 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800220
Dan Willemsenb2e6c2e2017-07-13 17:24:44 -0700221 vars, err := build.DumpMakeVars(buildCtx, config, nil, []string{"all_named_products"})
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800222 if err != nil {
223 log.Fatal(err)
224 }
Dan Willemsen9957b9c2017-10-06 15:05:05 -0700225 productsList := strings.Fields(vars["all_named_products"])
226
227 products := make([]string, 0, len(productsList))
228 skipList := strings.Split(*skipProducts, ",")
229 skipProduct := func(p string) bool {
230 for _, s := range skipList {
231 if p == s {
232 return true
233 }
234 }
235 return false
236 }
237 for _, product := range productsList {
238 if !skipProduct(product) {
239 products = append(products, product)
240 } else {
241 log.Verbose("Skipping: ", product)
242 }
243 }
244
245 log.Verbose("Got product list: ", products)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800246
Dan Willemsena4e43a72017-05-06 16:58:26 -0700247 status.SetTotal(len(products))
248
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800249 var wg sync.WaitGroup
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800250 productConfigs := make(chan Product, len(products))
251
Jeff Gastonb64fc1c2017-08-04 12:30:12 -0700252 finder := build.NewSourceFinder(buildCtx, config)
253 defer finder.Shutdown()
254
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800255 // Run the product config for every product in parallel
256 for _, product := range products {
257 wg.Add(1)
258 go func(product string) {
Dan Willemsenf624fb92017-05-19 16:39:04 -0700259 var stdLog string
260
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800261 defer wg.Done()
262 defer logger.Recover(func(err error) {
Dan Willemsenf624fb92017-05-19 16:39:04 -0700263 status.Fail(product, err, stdLog)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800264 })
265
266 productOutDir := filepath.Join(config.OutDir(), product)
Dan Willemsene3480762017-11-07 11:23:27 -0800267 productLogDir := filepath.Join(logsDir, product)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800268
269 if err := os.MkdirAll(productOutDir, 0777); err != nil {
270 log.Fatalf("Error creating out directory: %v", err)
271 }
Dan Willemsene3480762017-11-07 11:23:27 -0800272 if err := os.MkdirAll(productLogDir, 0777); err != nil {
273 log.Fatalf("Error creating log directory: %v", err)
274 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800275
Dan Willemsenf624fb92017-05-19 16:39:04 -0700276 stdLog = filepath.Join(productLogDir, "std.log")
277 f, err := os.Create(stdLog)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800278 if err != nil {
279 log.Fatalf("Error creating std.log: %v", err)
280 }
281
Dan Willemsenf1963962017-11-11 15:44:51 -0800282 productLog := logger.New(f)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700283 productLog.SetOutput(filepath.Join(productLogDir, "soong.log"))
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800284
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700285 productCtx := build.Context{&build.ContextImpl{
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800286 Context: ctx,
287 Logger: productLog,
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700288 Tracer: trace,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800289 StdioInterface: build.NewCustomStdio(nil, f, f),
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700290 Thread: trace.NewThread(product),
291 }}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800292
293 productConfig := build.NewConfig(productCtx)
294 productConfig.Environment().Set("OUT_DIR", productOutDir)
Jeff Gaston743e29e2017-08-17 14:09:23 -0700295 build.FindSources(productCtx, productConfig, finder)
Dan Willemsen5ed900b2017-05-07 11:40:30 -0700296 productConfig.Lunch(productCtx, product, *buildVariant)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800297
298 build.Build(productCtx, productConfig, build.BuildProductConfig)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700299 productConfigs <- Product{productCtx, productConfig, stdLog}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800300 }(product)
301 }
302 go func() {
303 defer close(productConfigs)
304 wg.Wait()
305 }()
306
307 var wg2 sync.WaitGroup
308 // Then run up to numJobs worth of Soong and Kati
309 for i := 0; i < *numJobs; i++ {
310 wg2.Add(1)
311 go func() {
312 defer wg2.Done()
313 for product := range productConfigs {
314 func() {
315 defer logger.Recover(func(err error) {
Dan Willemsenf624fb92017-05-19 16:39:04 -0700316 status.Fail(product.config.TargetProduct(), err, product.logFile)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800317 })
318
Dan Willemsene3480762017-11-07 11:23:27 -0800319 defer func() {
320 if *keepArtifacts {
321 args := zip.ZipArgs{
322 FileArgs: []zip.FileArg{
323 {
324 GlobDir: product.config.OutDir(),
325 SourcePrefixToStrip: product.config.OutDir(),
326 },
327 },
328 OutputFilePath: filepath.Join(config.OutDir(), product.config.TargetProduct()+".zip"),
329 NumParallelJobs: runtime.NumCPU(),
330 CompressionLevel: 5,
331 }
332 if err := zip.Run(args); err != nil {
333 log.Fatalf("Error zipping artifacts: %v", err)
334 }
335 }
336 os.RemoveAll(product.config.OutDir())
337 }()
338
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800339 buildWhat := 0
340 if !*onlyConfig {
341 buildWhat |= build.BuildSoong
342 if !*onlySoong {
343 buildWhat |= build.BuildKati
344 }
345 }
346 build.Build(product.ctx, product.config, buildWhat)
Dan Willemsena4e43a72017-05-06 16:58:26 -0700347 status.Finish(product.config.TargetProduct())
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800348 }()
349 }
350 }()
351 }
Dan Willemsena4e43a72017-05-06 16:58:26 -0700352 wg2.Wait()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800353
Dan Willemsene3480762017-11-07 11:23:27 -0800354 if *alternateResultDir {
355 args := zip.ZipArgs{
356 FileArgs: []zip.FileArg{
357 {GlobDir: logsDir, SourcePrefixToStrip: logsDir},
358 },
359 OutputFilePath: filepath.Join(config.DistDir(), "logs.zip"),
360 NumParallelJobs: runtime.NumCPU(),
361 CompressionLevel: 5,
362 }
363 if err := zip.Run(args); err != nil {
364 log.Fatalf("Error zipping logs: %v", err)
365 }
366 }
367
Dan Willemsena4e43a72017-05-06 16:58:26 -0700368 if count := status.Finished(); count > 0 {
369 log.Fatalln(count, "products failed")
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800370 }
371}