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