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 build 16 17import ( 18 "io/ioutil" 19 "os" 20 "path/filepath" 21 "sync" 22 "text/template" 23 24 "android/soong/ui/metrics" 25) 26 27// SetupOutDir ensures the out directory exists, and has the proper files to 28// prevent kati from recursing into it. 29func SetupOutDir(ctx Context, config Config) { 30 ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "Android.mk")) 31 ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "CleanSpec.mk")) 32 33 // Potentially write a marker file for whether kati is enabled. This is used by soong_build to 34 // potentially run the AndroidMk singleton and postinstall commands. 35 // Note that the absence of the file does not not preclude running Kati for product 36 // configuration purposes. 37 katiEnabledMarker := filepath.Join(config.SoongOutDir(), ".soong.kati_enabled") 38 if config.SkipKatiNinja() { 39 os.Remove(katiEnabledMarker) 40 // Note that we can not remove the file for SkipKati builds yet -- some continuous builds 41 // --skip-make builds rely on kati targets being defined. 42 } else if !config.SkipKati() { 43 ensureEmptyFileExists(ctx, katiEnabledMarker) 44 } 45 46 // The ninja_build file is used by our buildbots to understand that the output 47 // can be parsed as ninja output. 48 ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "ninja_build")) 49 ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), ".out-dir")) 50 51 if buildDateTimeFile, ok := config.environ.Get("BUILD_DATETIME_FILE"); ok { 52 err := ioutil.WriteFile(buildDateTimeFile, []byte(config.buildDateTime), 0666) // a+rw 53 if err != nil { 54 ctx.Fatalln("Failed to write BUILD_DATETIME to file:", err) 55 } 56 } else { 57 ctx.Fatalln("Missing BUILD_DATETIME_FILE") 58 } 59} 60 61var combinedBuildNinjaTemplate = template.Must(template.New("combined").Parse(` 62builddir = {{.OutDir}} 63{{if .UseRemoteBuild }}pool local_pool 64 depth = {{.Parallel}} 65{{end -}} 66pool highmem_pool 67 depth = {{.HighmemParallel}} 68{{if and (not .SkipKatiNinja) .HasKatiSuffix}}subninja {{.KatiBuildNinjaFile}} 69subninja {{.KatiPackageNinjaFile}} 70{{end -}} 71subninja {{.SoongNinjaFile}} 72`)) 73 74func createCombinedBuildNinjaFile(ctx Context, config Config) { 75 // If we're in SkipKati mode but want to run kati ninja, skip creating this file if it already exists 76 if config.SkipKati() && !config.SkipKatiNinja() { 77 if _, err := os.Stat(config.CombinedNinjaFile()); err == nil || !os.IsNotExist(err) { 78 return 79 } 80 } 81 82 file, err := os.Create(config.CombinedNinjaFile()) 83 if err != nil { 84 ctx.Fatalln("Failed to create combined ninja file:", err) 85 } 86 defer file.Close() 87 88 if err := combinedBuildNinjaTemplate.Execute(file, config); err != nil { 89 ctx.Fatalln("Failed to write combined ninja file:", err) 90 } 91} 92 93// These are bitmasks which can be used to check whether various flags are set 94const ( 95 _ = iota 96 // Whether to run the kati config step. 97 RunProductConfig = 1 << iota 98 // Whether to run soong to generate a ninja file. 99 RunSoong = 1 << iota 100 // Whether to run kati to generate a ninja file. 101 RunKati = 1 << iota 102 // Whether to include the kati-generated ninja file in the combined ninja. 103 RunKatiNinja = 1 << iota 104 // Whether to run ninja on the combined ninja. 105 RunNinja = 1 << iota 106 RunDistActions = 1 << iota 107 RunBuildTests = 1 << iota 108) 109 110// checkBazelMode fails the build if there are conflicting arguments for which bazel 111// build mode to use. 112func checkBazelMode(ctx Context, config Config) { 113 count := 0 114 if config.bazelProdMode { 115 count++ 116 } 117 if config.bazelDevMode { 118 count++ 119 } 120 if config.bazelStagingMode { 121 count++ 122 } 123 if count > 1 { 124 ctx.Fatalln("Conflicting bazel mode.\n" + 125 "Do not specify more than one of --bazel-mode and --bazel-mode-dev and --bazel-mode-staging ") 126 } 127} 128 129// checkProblematicFiles fails the build if existing Android.mk or CleanSpec.mk files are found at the root of the tree. 130func checkProblematicFiles(ctx Context) { 131 files := []string{"Android.mk", "CleanSpec.mk"} 132 for _, file := range files { 133 if _, err := os.Stat(file); !os.IsNotExist(err) { 134 absolute := absPath(ctx, file) 135 ctx.Printf("Found %s in tree root. This file needs to be removed to build.\n", file) 136 ctx.Fatalf(" rm %s\n", absolute) 137 } 138 } 139} 140 141// checkCaseSensitivity issues a warning if a case-insensitive file system is being used. 142func checkCaseSensitivity(ctx Context, config Config) { 143 outDir := config.OutDir() 144 lowerCase := filepath.Join(outDir, "casecheck.txt") 145 upperCase := filepath.Join(outDir, "CaseCheck.txt") 146 lowerData := "a" 147 upperData := "B" 148 149 if err := ioutil.WriteFile(lowerCase, []byte(lowerData), 0666); err != nil { // a+rw 150 ctx.Fatalln("Failed to check case sensitivity:", err) 151 } 152 153 if err := ioutil.WriteFile(upperCase, []byte(upperData), 0666); err != nil { // a+rw 154 ctx.Fatalln("Failed to check case sensitivity:", err) 155 } 156 157 res, err := ioutil.ReadFile(lowerCase) 158 if err != nil { 159 ctx.Fatalln("Failed to check case sensitivity:", err) 160 } 161 162 if string(res) != lowerData { 163 ctx.Println("************************************************************") 164 ctx.Println("You are building on a case-insensitive filesystem.") 165 ctx.Println("Please move your source tree to a case-sensitive filesystem.") 166 ctx.Println("************************************************************") 167 ctx.Fatalln("Case-insensitive filesystems not supported") 168 } 169} 170 171// help prints a help/usage message, via the build/make/help.sh script. 172func help(ctx Context, config Config) { 173 cmd := Command(ctx, config, "help.sh", "build/make/help.sh") 174 cmd.Sandbox = dumpvarsSandbox 175 cmd.RunAndPrintOrFatal() 176} 177 178// checkRAM warns if there probably isn't enough RAM to complete a build. 179func checkRAM(ctx Context, config Config) { 180 if totalRAM := config.TotalRAM(); totalRAM != 0 { 181 ram := float32(totalRAM) / (1024 * 1024 * 1024) 182 ctx.Verbosef("Total RAM: %.3vGB", ram) 183 184 if ram <= 16 { 185 ctx.Println("************************************************************") 186 ctx.Printf("You are building on a machine with %.3vGB of RAM\n", ram) 187 ctx.Println("") 188 ctx.Println("The minimum required amount of free memory is around 16GB,") 189 ctx.Println("and even with that, some configurations may not work.") 190 ctx.Println("") 191 ctx.Println("If you run into segfaults or other errors, try reducing your") 192 ctx.Println("-j value.") 193 ctx.Println("************************************************************") 194 } else if ram <= float32(config.Parallel()) { 195 // Want at least 1GB of RAM per job. 196 ctx.Printf("Warning: high -j%d count compared to %.3vGB of RAM", config.Parallel(), ram) 197 ctx.Println("If you run into segfaults or other errors, try a lower -j value") 198 } 199 } 200} 201 202// Build the tree. Various flags in `config` govern which components of 203// the build to run. 204func Build(ctx Context, config Config) { 205 ctx.Verboseln("Starting build with args:", config.Arguments()) 206 ctx.Verboseln("Environment:", config.Environment().Environ()) 207 208 ctx.BeginTrace(metrics.Total, "total") 209 defer ctx.EndTrace() 210 211 if inList("help", config.Arguments()) { 212 help(ctx, config) 213 return 214 } 215 216 // Make sure that no other Soong process is running with the same output directory 217 buildLock := BecomeSingletonOrFail(ctx, config) 218 defer buildLock.Unlock() 219 220 logArgsOtherThan := func(specialTargets ...string) { 221 var ignored []string 222 for _, a := range config.Arguments() { 223 if !inList(a, specialTargets) { 224 ignored = append(ignored, a) 225 } 226 } 227 if len(ignored) > 0 { 228 ctx.Printf("ignoring arguments %q", ignored) 229 } 230 } 231 232 if inList("clean", config.Arguments()) || inList("clobber", config.Arguments()) { 233 logArgsOtherThan("clean", "clobber") 234 clean(ctx, config) 235 return 236 } 237 238 defer waitForDist(ctx) 239 240 checkBazelMode(ctx, config) 241 242 // checkProblematicFiles aborts the build if Android.mk or CleanSpec.mk are found at the root of the tree. 243 checkProblematicFiles(ctx) 244 245 checkRAM(ctx, config) 246 247 SetupOutDir(ctx, config) 248 249 // checkCaseSensitivity issues a warning if a case-insensitive file system is being used. 250 checkCaseSensitivity(ctx, config) 251 252 ensureEmptyDirectoriesExist(ctx, config.TempDir()) 253 254 SetupPath(ctx, config) 255 256 what := evaluateWhatToRun(config, ctx.Verboseln) 257 258 if config.StartGoma() { 259 startGoma(ctx, config) 260 } 261 262 rbeCh := make(chan bool) 263 if config.StartRBE() { 264 cleanupRBELogsDir(ctx, config) 265 go func() { 266 startRBE(ctx, config) 267 close(rbeCh) 268 }() 269 defer DumpRBEMetrics(ctx, config, filepath.Join(config.LogsDir(), "rbe_metrics.pb")) 270 } else { 271 close(rbeCh) 272 } 273 274 if what&RunProductConfig != 0 { 275 runMakeProductConfig(ctx, config) 276 } 277 278 // Everything below here depends on product config. 279 280 if inList("installclean", config.Arguments()) || 281 inList("install-clean", config.Arguments()) { 282 logArgsOtherThan("installclean", "install-clean") 283 installClean(ctx, config) 284 ctx.Println("Deleted images and staging directories.") 285 return 286 } 287 288 if inList("dataclean", config.Arguments()) || 289 inList("data-clean", config.Arguments()) { 290 logArgsOtherThan("dataclean", "data-clean") 291 dataClean(ctx, config) 292 ctx.Println("Deleted data files.") 293 return 294 } 295 296 if what&RunSoong != 0 { 297 runSoong(ctx, config) 298 } 299 300 if what&RunKati != 0 { 301 genKatiSuffix(ctx, config) 302 runKatiCleanSpec(ctx, config) 303 runKatiBuild(ctx, config) 304 runKatiPackage(ctx, config) 305 306 ioutil.WriteFile(config.LastKatiSuffixFile(), []byte(config.KatiSuffix()), 0666) // a+rw 307 } else if what&RunKatiNinja != 0 { 308 // Load last Kati Suffix if it exists 309 if katiSuffix, err := ioutil.ReadFile(config.LastKatiSuffixFile()); err == nil { 310 ctx.Verboseln("Loaded previous kati config:", string(katiSuffix)) 311 config.SetKatiSuffix(string(katiSuffix)) 312 } 313 } 314 315 // Write combined ninja file 316 createCombinedBuildNinjaFile(ctx, config) 317 318 distGzipFile(ctx, config, config.CombinedNinjaFile()) 319 320 if what&RunBuildTests != 0 { 321 testForDanglingRules(ctx, config) 322 } 323 324 <-rbeCh 325 if what&RunNinja != 0 { 326 if what&RunKati != 0 { 327 installCleanIfNecessary(ctx, config) 328 } 329 runNinjaForBuild(ctx, config) 330 } 331 332 if what&RunDistActions != 0 { 333 runDistActions(ctx, config) 334 } 335} 336 337func evaluateWhatToRun(config Config, verboseln func(v ...interface{})) int { 338 //evaluate what to run 339 what := 0 340 if config.Checkbuild() { 341 what |= RunBuildTests 342 } 343 if !config.SkipConfig() { 344 what |= RunProductConfig 345 } else { 346 verboseln("Skipping Config as requested") 347 } 348 if !config.SkipSoong() { 349 what |= RunSoong 350 } else { 351 verboseln("Skipping use of Soong as requested") 352 } 353 if !config.SkipKati() { 354 what |= RunKati 355 } else { 356 verboseln("Skipping Kati as requested") 357 } 358 if !config.SkipKatiNinja() { 359 what |= RunKatiNinja 360 } else { 361 verboseln("Skipping use of Kati ninja as requested") 362 } 363 if !config.SkipNinja() { 364 what |= RunNinja 365 } else { 366 verboseln("Skipping Ninja as requested") 367 } 368 369 if !config.SoongBuildInvocationNeeded() { 370 // This means that the output of soong_build is not needed and thus it would 371 // run unnecessarily. In addition, if this code wasn't there invocations 372 // with only special-cased target names like "m bp2build" would result in 373 // passing Ninja the empty target list and it would then build the default 374 // targets which is not what the user asked for. 375 what = what &^ RunNinja 376 what = what &^ RunKati 377 } 378 379 if config.Dist() { 380 what |= RunDistActions 381 } 382 383 return what 384} 385 386var distWaitGroup sync.WaitGroup 387 388// waitForDist waits for all backgrounded distGzipFile and distFile writes to finish 389func waitForDist(ctx Context) { 390 ctx.BeginTrace("soong_ui", "dist") 391 defer ctx.EndTrace() 392 393 distWaitGroup.Wait() 394} 395 396// distGzipFile writes a compressed copy of src to the distDir if dist is enabled. Failures 397// are printed but non-fatal. Uses the distWaitGroup func for backgrounding (optimization). 398func distGzipFile(ctx Context, config Config, src string, subDirs ...string) { 399 if !config.Dist() { 400 return 401 } 402 403 subDir := filepath.Join(subDirs...) 404 destDir := filepath.Join(config.RealDistDir(), "soong_ui", subDir) 405 406 if err := os.MkdirAll(destDir, 0777); err != nil { // a+rwx 407 ctx.Printf("failed to mkdir %s: %s", destDir, err.Error()) 408 } 409 410 distWaitGroup.Add(1) 411 go func() { 412 defer distWaitGroup.Done() 413 if err := gzipFileToDir(src, destDir); err != nil { 414 ctx.Printf("failed to dist %s: %s", filepath.Base(src), err.Error()) 415 } 416 }() 417} 418 419// distFile writes a copy of src to the distDir if dist is enabled. Failures are printed but 420// non-fatal. Uses the distWaitGroup func for backgrounding (optimization). 421func distFile(ctx Context, config Config, src string, subDirs ...string) { 422 if !config.Dist() { 423 return 424 } 425 426 subDir := filepath.Join(subDirs...) 427 destDir := filepath.Join(config.RealDistDir(), "soong_ui", subDir) 428 429 if err := os.MkdirAll(destDir, 0777); err != nil { // a+rwx 430 ctx.Printf("failed to mkdir %s: %s", destDir, err.Error()) 431 } 432 433 distWaitGroup.Add(1) 434 go func() { 435 defer distWaitGroup.Done() 436 if _, err := copyFile(src, filepath.Join(destDir, filepath.Base(src))); err != nil { 437 ctx.Printf("failed to dist %s: %s", filepath.Base(src), err.Error()) 438 } 439 }() 440} 441 442// Actions to run on every build where 'dist' is in the actions. 443// Be careful, anything added here slows down EVERY CI build 444func runDistActions(ctx Context, config Config) { 445 runStagingSnapshot(ctx, config) 446} 447