blob: b12628e76edb2b422cb7fd993b5ba645109b7cfd [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
Dan Willemsen5ed900b2017-05-07 11:40:30 -070054var buildVariant = flag.String("variant", "eng", "build variant to use")
55
Dan Willemsenc2af0be2017-01-20 14:10:01 -080056type Product struct {
57 ctx build.Context
58 config build.Config
59}
60
Dan Willemsena4e43a72017-05-06 16:58:26 -070061type Status struct {
62 cur int
63 total int
64 failed int
65
66 ctx build.Context
67 haveBlankLine bool
68 smartTerminal bool
69
70 lock sync.Mutex
71}
72
73func NewStatus(ctx build.Context) *Status {
74 return &Status{
75 ctx: ctx,
76 haveBlankLine: true,
77 smartTerminal: ctx.IsTerminal(),
78 }
79}
80
81func (s *Status) SetTotal(total int) {
82 s.total = total
83}
84
85func (s *Status) Fail(product string, err error) {
86 s.Finish(product)
87
88 s.lock.Lock()
89 defer s.lock.Unlock()
90
91 if s.smartTerminal && !s.haveBlankLine {
92 fmt.Fprintln(s.ctx.Stdout())
93 s.haveBlankLine = true
94 }
95
96 s.failed++
97 fmt.Fprintln(s.ctx.Stderr(), "FAILED:", product)
98 s.ctx.Verboseln("FAILED:", product)
99 s.ctx.Println(err)
100}
101
102func (s *Status) Finish(product string) {
103 s.lock.Lock()
104 defer s.lock.Unlock()
105
106 s.cur++
107 line := fmt.Sprintf("[%d/%d] %s", s.cur, s.total, product)
108
109 if s.smartTerminal {
110 if max, ok := s.ctx.TermWidth(); ok {
111 if len(line) > max {
112 line = line[:max]
113 }
114 }
115
116 fmt.Fprint(s.ctx.Stdout(), "\r", line, "\x1b[K")
117 s.haveBlankLine = false
118 } else {
119 s.ctx.Println(line)
120 }
121}
122
123func (s *Status) Finished() int {
124 s.lock.Lock()
125 defer s.lock.Unlock()
126
127 if !s.haveBlankLine {
128 fmt.Fprintln(s.ctx.Stdout())
129 s.haveBlankLine = true
130 }
131 return s.failed
132}
133
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800134func main() {
135 log := logger.New(os.Stderr)
136 defer log.Cleanup()
137
138 flag.Parse()
139
140 ctx, cancel := context.WithCancel(context.Background())
141 defer cancel()
142
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700143 trace := tracer.New(log)
144 defer trace.Close()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800145
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700146 build.SetupSignals(log, cancel, func() {
147 trace.Close()
148 log.Cleanup()
149 })
150
151 buildCtx := build.Context{&build.ContextImpl{
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800152 Context: ctx,
153 Logger: log,
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700154 Tracer: trace,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800155 StdioInterface: build.StdioImpl{},
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700156 }}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800157
Dan Willemsena4e43a72017-05-06 16:58:26 -0700158 status := NewStatus(buildCtx)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800159
160 config := build.NewConfig(buildCtx)
161 if *outDir == "" {
Steven Moreland552432e2017-03-29 19:26:09 -0700162 name := "multiproduct-" + time.Now().Format("20060102150405")
163
164 *outDir = filepath.Join(config.OutDir(), name)
165
166 if err := os.MkdirAll(*outDir, 0777); err != nil {
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800167 log.Fatalf("Failed to create tempdir: %v", err)
168 }
169
170 if !*keep {
171 defer func() {
Dan Willemsena4e43a72017-05-06 16:58:26 -0700172 if status.Finished() == 0 {
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800173 os.RemoveAll(*outDir)
174 }
175 }()
176 }
177 }
178 config.Environment().Set("OUT_DIR", *outDir)
179 log.Println("Output directory:", *outDir)
180
181 build.SetupOutDir(buildCtx, config)
Dan Willemsen8a073a82017-02-04 17:30:44 -0800182 log.SetOutput(filepath.Join(config.OutDir(), "soong.log"))
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700183 trace.SetOutput(filepath.Join(config.OutDir(), "build.trace"))
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800184
185 vars, err := build.DumpMakeVars(buildCtx, config, nil, nil, []string{"all_named_products"})
186 if err != nil {
187 log.Fatal(err)
188 }
189 products := strings.Fields(vars["all_named_products"])
190 log.Verbose("Got product list:", products)
191
Dan Willemsena4e43a72017-05-06 16:58:26 -0700192 status.SetTotal(len(products))
193
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800194 var wg sync.WaitGroup
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800195 productConfigs := make(chan Product, len(products))
196
197 // Run the product config for every product in parallel
198 for _, product := range products {
199 wg.Add(1)
200 go func(product string) {
201 defer wg.Done()
202 defer logger.Recover(func(err error) {
Dan Willemsena4e43a72017-05-06 16:58:26 -0700203 status.Fail(product, err)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800204 })
205
206 productOutDir := filepath.Join(config.OutDir(), product)
207
208 if err := os.MkdirAll(productOutDir, 0777); err != nil {
209 log.Fatalf("Error creating out directory: %v", err)
210 }
211
212 f, err := os.Create(filepath.Join(productOutDir, "std.log"))
213 if err != nil {
214 log.Fatalf("Error creating std.log: %v", err)
215 }
216
217 productLog := logger.New(&bytes.Buffer{})
Dan Willemsen8a073a82017-02-04 17:30:44 -0800218 productLog.SetOutput(filepath.Join(productOutDir, "soong.log"))
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800219
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700220 productCtx := build.Context{&build.ContextImpl{
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800221 Context: ctx,
222 Logger: productLog,
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700223 Tracer: trace,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800224 StdioInterface: build.NewCustomStdio(nil, f, f),
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700225 Thread: trace.NewThread(product),
226 }}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800227
228 productConfig := build.NewConfig(productCtx)
229 productConfig.Environment().Set("OUT_DIR", productOutDir)
Dan Willemsen5ed900b2017-05-07 11:40:30 -0700230 productConfig.Lunch(productCtx, product, *buildVariant)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800231
232 build.Build(productCtx, productConfig, build.BuildProductConfig)
233 productConfigs <- Product{productCtx, productConfig}
234 }(product)
235 }
236 go func() {
237 defer close(productConfigs)
238 wg.Wait()
239 }()
240
241 var wg2 sync.WaitGroup
242 // Then run up to numJobs worth of Soong and Kati
243 for i := 0; i < *numJobs; i++ {
244 wg2.Add(1)
245 go func() {
246 defer wg2.Done()
247 for product := range productConfigs {
248 func() {
249 defer logger.Recover(func(err error) {
Dan Willemsena4e43a72017-05-06 16:58:26 -0700250 status.Fail(product.config.TargetProduct(), err)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800251 })
252
253 buildWhat := 0
254 if !*onlyConfig {
255 buildWhat |= build.BuildSoong
256 if !*onlySoong {
257 buildWhat |= build.BuildKati
258 }
259 }
260 build.Build(product.ctx, product.config, buildWhat)
261 if !*keep {
Dan Willemsenc38d3662017-02-24 10:53:23 -0800262 os.RemoveAll(product.config.OutDir())
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800263 }
Dan Willemsena4e43a72017-05-06 16:58:26 -0700264 status.Finish(product.config.TargetProduct())
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800265 }()
266 }
267 }()
268 }
Dan Willemsena4e43a72017-05-06 16:58:26 -0700269 wg2.Wait()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800270
Dan Willemsena4e43a72017-05-06 16:58:26 -0700271 if count := status.Finished(); count > 0 {
272 log.Fatalln(count, "products failed")
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800273 }
274}