1// 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 "android/soong/ui/tracer" 32) 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 59func main() { 60 log := logger.New(os.Stderr) 61 defer log.Cleanup() 62 63 flag.Parse() 64 65 ctx, cancel := context.WithCancel(context.Background()) 66 defer cancel() 67 68 trace := tracer.New(log) 69 defer trace.Close() 70 71 build.SetupSignals(log, cancel, func() { 72 trace.Close() 73 log.Cleanup() 74 }) 75 76 buildCtx := build.Context{&build.ContextImpl{ 77 Context: ctx, 78 Logger: log, 79 Tracer: trace, 80 StdioInterface: build.StdioImpl{}, 81 }} 82 83 failed := false 84 85 config := build.NewConfig(buildCtx) 86 if *outDir == "" { 87 var err error 88 *outDir, err = ioutil.TempDir(config.OutDir(), "multiproduct") 89 if err != nil { 90 log.Fatalf("Failed to create tempdir: %v", err) 91 } 92 93 if !*keep { 94 defer func() { 95 if !failed { 96 os.RemoveAll(*outDir) 97 } 98 }() 99 } 100 } 101 config.Environment().Set("OUT_DIR", *outDir) 102 log.Println("Output directory:", *outDir) 103 104 build.SetupOutDir(buildCtx, config) 105 log.SetOutput(filepath.Join(config.OutDir(), "soong.log")) 106 trace.SetOutput(filepath.Join(config.OutDir(), "build.trace")) 107 108 vars, err := build.DumpMakeVars(buildCtx, config, nil, nil, []string{"all_named_products"}) 109 if err != nil { 110 log.Fatal(err) 111 } 112 products := strings.Fields(vars["all_named_products"]) 113 log.Verbose("Got product list:", products) 114 115 var wg sync.WaitGroup 116 errs := make(chan error, len(products)) 117 productConfigs := make(chan Product, len(products)) 118 119 // Run the product config for every product in parallel 120 for _, product := range products { 121 wg.Add(1) 122 go func(product string) { 123 defer wg.Done() 124 defer logger.Recover(func(err error) { 125 errs <- fmt.Errorf("Error building %s: %v", product, err) 126 }) 127 128 productOutDir := filepath.Join(config.OutDir(), product) 129 130 if err := os.MkdirAll(productOutDir, 0777); err != nil { 131 log.Fatalf("Error creating out directory: %v", err) 132 } 133 134 f, err := os.Create(filepath.Join(productOutDir, "std.log")) 135 if err != nil { 136 log.Fatalf("Error creating std.log: %v", err) 137 } 138 139 productLog := logger.New(&bytes.Buffer{}) 140 productLog.SetOutput(filepath.Join(productOutDir, "soong.log")) 141 142 productCtx := build.Context{&build.ContextImpl{ 143 Context: ctx, 144 Logger: productLog, 145 Tracer: trace, 146 StdioInterface: build.NewCustomStdio(nil, f, f), 147 Thread: trace.NewThread(product), 148 }} 149 150 productConfig := build.NewConfig(productCtx) 151 productConfig.Environment().Set("OUT_DIR", productOutDir) 152 productConfig.Lunch(productCtx, product, "eng") 153 154 build.Build(productCtx, productConfig, build.BuildProductConfig) 155 productConfigs <- Product{productCtx, productConfig} 156 }(product) 157 } 158 go func() { 159 defer close(productConfigs) 160 wg.Wait() 161 }() 162 163 var wg2 sync.WaitGroup 164 // Then run up to numJobs worth of Soong and Kati 165 for i := 0; i < *numJobs; i++ { 166 wg2.Add(1) 167 go func() { 168 defer wg2.Done() 169 for product := range productConfigs { 170 func() { 171 defer logger.Recover(func(err error) { 172 errs <- fmt.Errorf("Error building %s: %v", product.config.TargetProduct(), err) 173 }) 174 175 buildWhat := 0 176 if !*onlyConfig { 177 buildWhat |= build.BuildSoong 178 if !*onlySoong { 179 buildWhat |= build.BuildKati 180 } 181 } 182 build.Build(product.ctx, product.config, buildWhat) 183 if !*keep { 184 os.RemoveAll(product.config.OutDir()) 185 } 186 log.Println("Finished running for", product.config.TargetProduct()) 187 }() 188 } 189 }() 190 } 191 go func() { 192 wg2.Wait() 193 close(errs) 194 }() 195 196 for err := range errs { 197 failed = true 198 log.Print(err) 199 } 200 201 if failed { 202 log.Fatalln("Failed") 203 } 204} 205