blob: 97d4cfafc61af797ea63ddea1b3613fadf0dad51 [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 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 Willemsenc2af0be2017-01-20 14:10:01 -080032)
33
34// We default to number of cpus / 4, which seems to be the sweet spot for my
35// system. I suspect this is mostly due to memory or disk bandwidth though, and
36// may depend on the size ofthe source tree, so this probably isn't a great
37// default.
38func detectNumJobs() int {
39 if runtime.NumCPU() < 4 {
40 return 1
41 }
42 return runtime.NumCPU() / 4
43}
44
45var numJobs = flag.Int("j", detectNumJobs(), "number of parallel kati jobs")
46
47var keep = flag.Bool("keep", false, "keep successful output files")
48
49var outDir = flag.String("out", "", "path to store output directories (defaults to tmpdir under $OUT when empty)")
50
51var onlyConfig = flag.Bool("only-config", false, "Only run product config (not Soong or Kati)")
52var onlySoong = flag.Bool("only-soong", false, "Only run product config and Soong (not Kati)")
53
54type Product struct {
55 ctx build.Context
56 config build.Config
57}
58
Dan Willemsena4e43a72017-05-06 16:58:26 -070059type Status struct {
60 cur int
61 total int
62 failed int
63
64 ctx build.Context
65 haveBlankLine bool
66 smartTerminal bool
67
68 lock sync.Mutex
69}
70
71func NewStatus(ctx build.Context) *Status {
72 return &Status{
73 ctx: ctx,
74 haveBlankLine: true,
75 smartTerminal: ctx.IsTerminal(),
76 }
77}
78
79func (s *Status) SetTotal(total int) {
80 s.total = total
81}
82
83func (s *Status) Fail(product string, err error) {
84 s.Finish(product)
85
86 s.lock.Lock()
87 defer s.lock.Unlock()
88
89 if s.smartTerminal && !s.haveBlankLine {
90 fmt.Fprintln(s.ctx.Stdout())
91 s.haveBlankLine = true
92 }
93
94 s.failed++
95 fmt.Fprintln(s.ctx.Stderr(), "FAILED:", product)
96 s.ctx.Verboseln("FAILED:", product)
97 s.ctx.Println(err)
98}
99
100func (s *Status) Finish(product string) {
101 s.lock.Lock()
102 defer s.lock.Unlock()
103
104 s.cur++
105 line := fmt.Sprintf("[%d/%d] %s", s.cur, s.total, product)
106
107 if s.smartTerminal {
108 if max, ok := s.ctx.TermWidth(); ok {
109 if len(line) > max {
110 line = line[:max]
111 }
112 }
113
114 fmt.Fprint(s.ctx.Stdout(), "\r", line, "\x1b[K")
115 s.haveBlankLine = false
116 } else {
117 s.ctx.Println(line)
118 }
119}
120
121func (s *Status) Finished() int {
122 s.lock.Lock()
123 defer s.lock.Unlock()
124
125 if !s.haveBlankLine {
126 fmt.Fprintln(s.ctx.Stdout())
127 s.haveBlankLine = true
128 }
129 return s.failed
130}
131
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800132func main() {
133 log := logger.New(os.Stderr)
134 defer log.Cleanup()
135
136 flag.Parse()
137
138 ctx, cancel := context.WithCancel(context.Background())
139 defer cancel()
140
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700141 trace := tracer.New(log)
142 defer trace.Close()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800143
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700144 build.SetupSignals(log, cancel, func() {
145 trace.Close()
146 log.Cleanup()
147 })
148
149 buildCtx := build.Context{&build.ContextImpl{
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800150 Context: ctx,
151 Logger: log,
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700152 Tracer: trace,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800153 StdioInterface: build.StdioImpl{},
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700154 }}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800155
Dan Willemsena4e43a72017-05-06 16:58:26 -0700156 status := NewStatus(buildCtx)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800157
158 config := build.NewConfig(buildCtx)
159 if *outDir == "" {
Steven Moreland552432e2017-03-29 19:26:09 -0700160 name := "multiproduct-" + time.Now().Format("20060102150405")
161
162 *outDir = filepath.Join(config.OutDir(), name)
163
164 if err := os.MkdirAll(*outDir, 0777); err != nil {
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800165 log.Fatalf("Failed to create tempdir: %v", err)
166 }
167
168 if !*keep {
169 defer func() {
Dan Willemsena4e43a72017-05-06 16:58:26 -0700170 if status.Finished() == 0 {
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800171 os.RemoveAll(*outDir)
172 }
173 }()
174 }
175 }
176 config.Environment().Set("OUT_DIR", *outDir)
177 log.Println("Output directory:", *outDir)
178
179 build.SetupOutDir(buildCtx, config)
Dan Willemsen8a073a82017-02-04 17:30:44 -0800180 log.SetOutput(filepath.Join(config.OutDir(), "soong.log"))
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700181 trace.SetOutput(filepath.Join(config.OutDir(), "build.trace"))
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800182
183 vars, err := build.DumpMakeVars(buildCtx, config, nil, nil, []string{"all_named_products"})
184 if err != nil {
185 log.Fatal(err)
186 }
187 products := strings.Fields(vars["all_named_products"])
188 log.Verbose("Got product list:", products)
189
Dan Willemsena4e43a72017-05-06 16:58:26 -0700190 status.SetTotal(len(products))
191
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800192 var wg sync.WaitGroup
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800193 productConfigs := make(chan Product, len(products))
194
195 // Run the product config for every product in parallel
196 for _, product := range products {
197 wg.Add(1)
198 go func(product string) {
199 defer wg.Done()
200 defer logger.Recover(func(err error) {
Dan Willemsena4e43a72017-05-06 16:58:26 -0700201 status.Fail(product, err)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800202 })
203
204 productOutDir := filepath.Join(config.OutDir(), product)
205
206 if err := os.MkdirAll(productOutDir, 0777); err != nil {
207 log.Fatalf("Error creating out directory: %v", err)
208 }
209
210 f, err := os.Create(filepath.Join(productOutDir, "std.log"))
211 if err != nil {
212 log.Fatalf("Error creating std.log: %v", err)
213 }
214
215 productLog := logger.New(&bytes.Buffer{})
Dan Willemsen8a073a82017-02-04 17:30:44 -0800216 productLog.SetOutput(filepath.Join(productOutDir, "soong.log"))
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800217
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700218 productCtx := build.Context{&build.ContextImpl{
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800219 Context: ctx,
220 Logger: productLog,
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700221 Tracer: trace,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800222 StdioInterface: build.NewCustomStdio(nil, f, f),
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700223 Thread: trace.NewThread(product),
224 }}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800225
226 productConfig := build.NewConfig(productCtx)
227 productConfig.Environment().Set("OUT_DIR", productOutDir)
228 productConfig.Lunch(productCtx, product, "eng")
229
230 build.Build(productCtx, productConfig, build.BuildProductConfig)
231 productConfigs <- Product{productCtx, productConfig}
232 }(product)
233 }
234 go func() {
235 defer close(productConfigs)
236 wg.Wait()
237 }()
238
239 var wg2 sync.WaitGroup
240 // Then run up to numJobs worth of Soong and Kati
241 for i := 0; i < *numJobs; i++ {
242 wg2.Add(1)
243 go func() {
244 defer wg2.Done()
245 for product := range productConfigs {
246 func() {
247 defer logger.Recover(func(err error) {
Dan Willemsena4e43a72017-05-06 16:58:26 -0700248 status.Fail(product.config.TargetProduct(), err)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800249 })
250
251 buildWhat := 0
252 if !*onlyConfig {
253 buildWhat |= build.BuildSoong
254 if !*onlySoong {
255 buildWhat |= build.BuildKati
256 }
257 }
258 build.Build(product.ctx, product.config, buildWhat)
259 if !*keep {
Dan Willemsenc38d3662017-02-24 10:53:23 -0800260 os.RemoveAll(product.config.OutDir())
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800261 }
Dan Willemsena4e43a72017-05-06 16:58:26 -0700262 status.Finish(product.config.TargetProduct())
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800263 }()
264 }
265 }()
266 }
Dan Willemsena4e43a72017-05-06 16:58:26 -0700267 wg2.Wait()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800268
Dan Willemsena4e43a72017-05-06 16:58:26 -0700269 if count := status.Finished(); count > 0 {
270 log.Fatalln(count, "products failed")
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800271 }
272}