blob: 0570c17befa917e09bc1304e288b0a4371abee7e [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"
22 "io/ioutil"
23 "os"
24 "path/filepath"
25 "runtime"
26 "strings"
27 "sync"
28
29 "android/soong/ui/build"
30 "android/soong/ui/logger"
31)
32
33// We default to number of cpus / 4, which seems to be the sweet spot for my
34// system. I suspect this is mostly due to memory or disk bandwidth though, and
35// may depend on the size ofthe source tree, so this probably isn't a great
36// default.
37func detectNumJobs() int {
38 if runtime.NumCPU() < 4 {
39 return 1
40 }
41 return runtime.NumCPU() / 4
42}
43
44var numJobs = flag.Int("j", detectNumJobs(), "number of parallel kati jobs")
45
46var keep = flag.Bool("keep", false, "keep successful output files")
47
48var outDir = flag.String("out", "", "path to store output directories (defaults to tmpdir under $OUT when empty)")
49
50var onlyConfig = flag.Bool("only-config", false, "Only run product config (not Soong or Kati)")
51var onlySoong = flag.Bool("only-soong", false, "Only run product config and Soong (not Kati)")
52
53type Product struct {
54 ctx build.Context
55 config build.Config
56}
57
58func main() {
59 log := logger.New(os.Stderr)
60 defer log.Cleanup()
61
62 flag.Parse()
63
64 ctx, cancel := context.WithCancel(context.Background())
65 defer cancel()
66
67 build.SetupSignals(log, cancel, log.Cleanup)
68
69 buildCtx := &build.ContextImpl{
70 Context: ctx,
71 Logger: log,
72 StdioInterface: build.StdioImpl{},
73 }
74
75 failed := false
76
77 config := build.NewConfig(buildCtx)
78 if *outDir == "" {
79 var err error
80 *outDir, err = ioutil.TempDir(config.OutDir(), "multiproduct")
81 if err != nil {
82 log.Fatalf("Failed to create tempdir: %v", err)
83 }
84
85 if !*keep {
86 defer func() {
87 if !failed {
88 os.RemoveAll(*outDir)
89 }
90 }()
91 }
92 }
93 config.Environment().Set("OUT_DIR", *outDir)
94 log.Println("Output directory:", *outDir)
95
96 build.SetupOutDir(buildCtx, config)
97 log.SetOutput(filepath.Join(config.OutDir(), "build.log"))
98
99 vars, err := build.DumpMakeVars(buildCtx, config, nil, nil, []string{"all_named_products"})
100 if err != nil {
101 log.Fatal(err)
102 }
103 products := strings.Fields(vars["all_named_products"])
104 log.Verbose("Got product list:", products)
105
106 var wg sync.WaitGroup
107 errs := make(chan error, len(products))
108 productConfigs := make(chan Product, len(products))
109
110 // Run the product config for every product in parallel
111 for _, product := range products {
112 wg.Add(1)
113 go func(product string) {
114 defer wg.Done()
115 defer logger.Recover(func(err error) {
116 errs <- fmt.Errorf("Error building %s: %v", product, err)
117 })
118
119 productOutDir := filepath.Join(config.OutDir(), product)
120
121 if err := os.MkdirAll(productOutDir, 0777); err != nil {
122 log.Fatalf("Error creating out directory: %v", err)
123 }
124
125 f, err := os.Create(filepath.Join(productOutDir, "std.log"))
126 if err != nil {
127 log.Fatalf("Error creating std.log: %v", err)
128 }
129
130 productLog := logger.New(&bytes.Buffer{})
131 productLog.SetOutput(filepath.Join(productOutDir, "build.log"))
132
133 productCtx := &build.ContextImpl{
134 Context: ctx,
135 Logger: productLog,
136 StdioInterface: build.NewCustomStdio(nil, f, f),
137 }
138
139 productConfig := build.NewConfig(productCtx)
140 productConfig.Environment().Set("OUT_DIR", productOutDir)
141 productConfig.Lunch(productCtx, product, "eng")
142
143 build.Build(productCtx, productConfig, build.BuildProductConfig)
144 productConfigs <- Product{productCtx, productConfig}
145 }(product)
146 }
147 go func() {
148 defer close(productConfigs)
149 wg.Wait()
150 }()
151
152 var wg2 sync.WaitGroup
153 // Then run up to numJobs worth of Soong and Kati
154 for i := 0; i < *numJobs; i++ {
155 wg2.Add(1)
156 go func() {
157 defer wg2.Done()
158 for product := range productConfigs {
159 func() {
160 defer logger.Recover(func(err error) {
161 errs <- fmt.Errorf("Error building %s: %v", product.config.TargetProduct(), err)
162 })
163
164 buildWhat := 0
165 if !*onlyConfig {
166 buildWhat |= build.BuildSoong
167 if !*onlySoong {
168 buildWhat |= build.BuildKati
169 }
170 }
171 build.Build(product.ctx, product.config, buildWhat)
172 if !*keep {
173 // TODO: kati aborts from opendir while setting up the find emulator
174 //os.RemoveAll(product.config.OutDir())
175 }
176 log.Println("Finished running for", product.config.TargetProduct())
177 }()
178 }
179 }()
180 }
181 go func() {
182 wg2.Wait()
183 close(errs)
184 }()
185
186 for err := range errs {
187 failed = true
188 log.Print(err)
189 }
190
191 if failed {
192 log.Fatalln("Failed")
193 }
194}