blob: e771c158809b8c930ece6ba90f34355c10494486 [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 (
18 "bytes"
19 "context"
20 "flag"
21 "fmt"
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"
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 Willemsend9f6fa22016-08-21 15:17:17 -070032 "android/soong/ui/tracer"
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
48var keep = flag.Bool("keep", false, "keep successful output files")
49
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 Willemsenf624fb92017-05-19 16:39:04 -070058const errorLeadingLines = 20
59const errorTrailingLines = 20
60
Dan Willemsenc2af0be2017-01-20 14:10:01 -080061type Product struct {
Dan Willemsenf624fb92017-05-19 16:39:04 -070062 ctx build.Context
63 config build.Config
64 logFile string
Dan Willemsenc2af0be2017-01-20 14:10:01 -080065}
66
Dan Willemsena4e43a72017-05-06 16:58:26 -070067type Status struct {
68 cur int
69 total int
70 failed int
71
72 ctx build.Context
73 haveBlankLine bool
74 smartTerminal bool
75
76 lock sync.Mutex
77}
78
79func NewStatus(ctx build.Context) *Status {
80 return &Status{
81 ctx: ctx,
82 haveBlankLine: true,
83 smartTerminal: ctx.IsTerminal(),
84 }
85}
86
87func (s *Status) SetTotal(total int) {
88 s.total = total
89}
90
Dan Willemsenf624fb92017-05-19 16:39:04 -070091func (s *Status) Fail(product string, err error, logFile string) {
Dan Willemsena4e43a72017-05-06 16:58:26 -070092 s.Finish(product)
93
94 s.lock.Lock()
95 defer s.lock.Unlock()
96
97 if s.smartTerminal && !s.haveBlankLine {
98 fmt.Fprintln(s.ctx.Stdout())
99 s.haveBlankLine = true
100 }
101
102 s.failed++
103 fmt.Fprintln(s.ctx.Stderr(), "FAILED:", product)
104 s.ctx.Verboseln("FAILED:", product)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700105
106 if logFile != "" {
107 data, err := ioutil.ReadFile(logFile)
108 if err == nil {
109 lines := strings.Split(strings.TrimSpace(string(data)), "\n")
110 if len(lines) > errorLeadingLines+errorTrailingLines+1 {
111 lines[errorLeadingLines] = fmt.Sprintf("... skipping %d lines ...",
112 len(lines)-errorLeadingLines-errorTrailingLines)
113
114 lines = append(lines[:errorLeadingLines+1],
115 lines[len(lines)-errorTrailingLines:]...)
116 }
117 for _, line := range lines {
118 fmt.Fprintln(s.ctx.Stderr(), "> ", line)
119 s.ctx.Verboseln(line)
120 }
121 }
122 }
123
124 s.ctx.Print(err)
Dan Willemsena4e43a72017-05-06 16:58:26 -0700125}
126
127func (s *Status) Finish(product string) {
128 s.lock.Lock()
129 defer s.lock.Unlock()
130
131 s.cur++
132 line := fmt.Sprintf("[%d/%d] %s", s.cur, s.total, product)
133
134 if s.smartTerminal {
135 if max, ok := s.ctx.TermWidth(); ok {
136 if len(line) > max {
137 line = line[:max]
138 }
139 }
140
141 fmt.Fprint(s.ctx.Stdout(), "\r", line, "\x1b[K")
142 s.haveBlankLine = false
143 } else {
144 s.ctx.Println(line)
145 }
146}
147
148func (s *Status) Finished() int {
149 s.lock.Lock()
150 defer s.lock.Unlock()
151
152 if !s.haveBlankLine {
153 fmt.Fprintln(s.ctx.Stdout())
154 s.haveBlankLine = true
155 }
156 return s.failed
157}
158
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800159func main() {
160 log := logger.New(os.Stderr)
161 defer log.Cleanup()
162
163 flag.Parse()
164
165 ctx, cancel := context.WithCancel(context.Background())
166 defer cancel()
167
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700168 trace := tracer.New(log)
169 defer trace.Close()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800170
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700171 build.SetupSignals(log, cancel, func() {
172 trace.Close()
173 log.Cleanup()
174 })
175
176 buildCtx := build.Context{&build.ContextImpl{
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800177 Context: ctx,
178 Logger: log,
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700179 Tracer: trace,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800180 StdioInterface: build.StdioImpl{},
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700181 }}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800182
Dan Willemsena4e43a72017-05-06 16:58:26 -0700183 status := NewStatus(buildCtx)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800184
185 config := build.NewConfig(buildCtx)
186 if *outDir == "" {
Steven Moreland552432e2017-03-29 19:26:09 -0700187 name := "multiproduct-" + time.Now().Format("20060102150405")
188
189 *outDir = filepath.Join(config.OutDir(), name)
190
Dan Willemsenf624fb92017-05-19 16:39:04 -0700191 // Ensure the empty files exist in the output directory
192 // containing our output directory too. This is mostly for
193 // safety, but also triggers the ninja_build file so that our
194 // build servers know that they can parse the output as if it
195 // was ninja output.
196 build.SetupOutDir(buildCtx, config)
197
Steven Moreland552432e2017-03-29 19:26:09 -0700198 if err := os.MkdirAll(*outDir, 0777); err != nil {
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800199 log.Fatalf("Failed to create tempdir: %v", err)
200 }
201
202 if !*keep {
203 defer func() {
Dan Willemsena4e43a72017-05-06 16:58:26 -0700204 if status.Finished() == 0 {
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800205 os.RemoveAll(*outDir)
206 }
207 }()
208 }
209 }
210 config.Environment().Set("OUT_DIR", *outDir)
211 log.Println("Output directory:", *outDir)
212
213 build.SetupOutDir(buildCtx, config)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700214 if *alternateResultDir {
215 logsDir := filepath.Join(config.DistDir(), "logs")
216 os.MkdirAll(logsDir, 0777)
217 log.SetOutput(filepath.Join(logsDir, "soong.log"))
218 trace.SetOutput(filepath.Join(logsDir, "build.trace"))
219 } else {
220 log.SetOutput(filepath.Join(config.OutDir(), "soong.log"))
221 trace.SetOutput(filepath.Join(config.OutDir(), "build.trace"))
222 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800223
224 vars, err := build.DumpMakeVars(buildCtx, config, nil, nil, []string{"all_named_products"})
225 if err != nil {
226 log.Fatal(err)
227 }
228 products := strings.Fields(vars["all_named_products"])
229 log.Verbose("Got product list:", products)
230
Dan Willemsena4e43a72017-05-06 16:58:26 -0700231 status.SetTotal(len(products))
232
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800233 var wg sync.WaitGroup
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800234 productConfigs := make(chan Product, len(products))
235
Jeff Gastonb64fc1c2017-08-04 12:30:12 -0700236 finder := build.NewSourceFinder(buildCtx, config)
237 defer finder.Shutdown()
238
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800239 // Run the product config for every product in parallel
240 for _, product := range products {
241 wg.Add(1)
242 go func(product string) {
Dan Willemsenf624fb92017-05-19 16:39:04 -0700243 var stdLog string
244
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800245 defer wg.Done()
246 defer logger.Recover(func(err error) {
Dan Willemsenf624fb92017-05-19 16:39:04 -0700247 status.Fail(product, err, stdLog)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800248 })
249
250 productOutDir := filepath.Join(config.OutDir(), product)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700251 productLogDir := productOutDir
252 if *alternateResultDir {
253 productLogDir = filepath.Join(config.DistDir(), product)
254 if err := os.MkdirAll(productLogDir, 0777); err != nil {
255 log.Fatalf("Error creating log directory: %v", err)
256 }
257 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800258
259 if err := os.MkdirAll(productOutDir, 0777); err != nil {
260 log.Fatalf("Error creating out directory: %v", err)
261 }
262
Dan Willemsenf624fb92017-05-19 16:39:04 -0700263 stdLog = filepath.Join(productLogDir, "std.log")
264 f, err := os.Create(stdLog)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800265 if err != nil {
266 log.Fatalf("Error creating std.log: %v", err)
267 }
268
269 productLog := logger.New(&bytes.Buffer{})
Dan Willemsenf624fb92017-05-19 16:39:04 -0700270 productLog.SetOutput(filepath.Join(productLogDir, "soong.log"))
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800271
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700272 productCtx := build.Context{&build.ContextImpl{
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800273 Context: ctx,
274 Logger: productLog,
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700275 Tracer: trace,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800276 StdioInterface: build.NewCustomStdio(nil, f, f),
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700277 Thread: trace.NewThread(product),
278 }}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800279
280 productConfig := build.NewConfig(productCtx)
281 productConfig.Environment().Set("OUT_DIR", productOutDir)
Jeff Gaston743e29e2017-08-17 14:09:23 -0700282 build.FindSources(productCtx, productConfig, finder)
Dan Willemsen5ed900b2017-05-07 11:40:30 -0700283 productConfig.Lunch(productCtx, product, *buildVariant)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800284
285 build.Build(productCtx, productConfig, build.BuildProductConfig)
Dan Willemsenf624fb92017-05-19 16:39:04 -0700286 productConfigs <- Product{productCtx, productConfig, stdLog}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800287 }(product)
288 }
289 go func() {
290 defer close(productConfigs)
291 wg.Wait()
292 }()
293
294 var wg2 sync.WaitGroup
295 // Then run up to numJobs worth of Soong and Kati
296 for i := 0; i < *numJobs; i++ {
297 wg2.Add(1)
298 go func() {
299 defer wg2.Done()
300 for product := range productConfigs {
301 func() {
302 defer logger.Recover(func(err error) {
Dan Willemsenf624fb92017-05-19 16:39:04 -0700303 status.Fail(product.config.TargetProduct(), err, product.logFile)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800304 })
305
306 buildWhat := 0
307 if !*onlyConfig {
308 buildWhat |= build.BuildSoong
309 if !*onlySoong {
310 buildWhat |= build.BuildKati
311 }
312 }
313 build.Build(product.ctx, product.config, buildWhat)
314 if !*keep {
Dan Willemsenc38d3662017-02-24 10:53:23 -0800315 os.RemoveAll(product.config.OutDir())
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800316 }
Dan Willemsena4e43a72017-05-06 16:58:26 -0700317 status.Finish(product.config.TargetProduct())
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800318 }()
319 }
320 }()
321 }
Dan Willemsena4e43a72017-05-06 16:58:26 -0700322 wg2.Wait()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800323
Dan Willemsena4e43a72017-05-06 16:58:26 -0700324 if count := status.Finished(); count > 0 {
325 log.Fatalln(count, "products failed")
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800326 }
327}