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