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 "encoding/json" 19 "errors" 20 "fmt" 21 "io/fs" 22 "os" 23 "path/filepath" 24 "runtime" 25 "slices" 26 "strconv" 27 "strings" 28 "sync" 29 "sync/atomic" 30 "syscall" 31 "time" 32 33 "android/soong/ui/tracer" 34 35 "android/soong/ui/metrics" 36 "android/soong/ui/metrics/metrics_proto" 37 "android/soong/ui/status" 38 39 "android/soong/shared" 40 41 "github.com/google/blueprint" 42 "github.com/google/blueprint/bootstrap" 43 "github.com/google/blueprint/microfactory" 44 "github.com/google/blueprint/pathtools" 45 46 "google.golang.org/protobuf/proto" 47) 48 49const ( 50 availableEnvFile = "soong.environment.available" 51 usedEnvFile = "soong.environment.used" 52 53 soongBuildTag = "build" 54 jsonModuleGraphTag = "modulegraph" 55 soongDocsTag = "soong_docs" 56 57 // bootstrapEpoch is used to determine if an incremental build is incompatible with the current 58 // version of bootstrap and needs cleaning before continuing the build. Increment this for 59 // incompatible changes, for example when moving the location of a microfactory binary that is 60 // executed during bootstrap before the primary builder has had a chance to update the path. 61 bootstrapEpoch = 1 62) 63 64var ( 65 // Used during parallel update of symlinks in out directory to reflect new 66 // TOP dir. 67 symlinkWg sync.WaitGroup 68 numFound, numUpdated uint32 69) 70 71func writeEnvironmentFile(_ Context, envFile string, envDeps map[string]string) error { 72 data, err := shared.EnvFileContents(envDeps) 73 if err != nil { 74 return err 75 } 76 77 return os.WriteFile(envFile, data, 0644) 78} 79 80// This uses Android.bp files and various tools to generate <builddir>/build.ninja. 81// 82// However, the execution of <builddir>/build.ninja happens later in 83// build/soong/ui/build/build.go#Build() 84// 85// We want to rely on as few prebuilts as possible, so we need to bootstrap 86// Soong. The process is as follows: 87// 88// 1. We use "Microfactory", a simple tool to compile Go code, to build 89// first itself, then soong_ui from soong_ui.bash. This binary contains 90// parts of soong_build that are needed to build itself. 91// 2. This simplified version of soong_build then reads the Blueprint files 92// that describe itself and emits .bootstrap/build.ninja that describes 93// how to build its full version and use that to produce the final Ninja 94// file Soong emits. 95// 3. soong_ui executes .bootstrap/build.ninja 96// 97// (After this, Kati is executed to parse the Makefiles, but that's not part of 98// bootstrapping Soong) 99 100// A tiny struct used to tell Blueprint that it's in bootstrap mode. It would 101// probably be nicer to use a flag in bootstrap.Args instead. 102type BlueprintConfig struct { 103 toolDir string 104 soongOutDir string 105 outDir string 106 runGoTests bool 107 debugCompilation bool 108 subninjas []string 109 primaryBuilderInvocations []bootstrap.PrimaryBuilderInvocation 110} 111 112func (c BlueprintConfig) HostToolDir() string { 113 return c.toolDir 114} 115 116func (c BlueprintConfig) SoongOutDir() string { 117 return c.soongOutDir 118} 119 120func (c BlueprintConfig) OutDir() string { 121 return c.outDir 122} 123 124func (c BlueprintConfig) RunGoTests() bool { 125 return c.runGoTests 126} 127 128func (c BlueprintConfig) DebugCompilation() bool { 129 return c.debugCompilation 130} 131 132func (c BlueprintConfig) Subninjas() []string { 133 return c.subninjas 134} 135 136func (c BlueprintConfig) PrimaryBuilderInvocations() []bootstrap.PrimaryBuilderInvocation { 137 return c.primaryBuilderInvocations 138} 139 140func environmentArgs(config Config, tag string) []string { 141 return []string{ 142 "--available_env", shared.JoinPath(config.SoongOutDir(), availableEnvFile), 143 "--used_env", config.UsedEnvFile(tag), 144 } 145} 146 147func writeEmptyFile(ctx Context, path string) { 148 err := os.MkdirAll(filepath.Dir(path), 0777) 149 if err != nil { 150 ctx.Fatalf("Failed to create parent directories of empty file '%s': %s", path, err) 151 } 152 153 if exists, err := fileExists(path); err != nil { 154 ctx.Fatalf("Failed to check if file '%s' exists: %s", path, err) 155 } else if !exists { 156 err = os.WriteFile(path, nil, 0666) 157 if err != nil { 158 ctx.Fatalf("Failed to create empty file '%s': %s", path, err) 159 } 160 } 161} 162 163func fileExists(path string) (bool, error) { 164 if _, err := os.Stat(path); os.IsNotExist(err) { 165 return false, nil 166 } else if err != nil { 167 return false, err 168 } 169 return true, nil 170} 171 172type PrimaryBuilderFactory struct { 173 name string 174 description string 175 config Config 176 output string 177 specificArgs []string 178 debugPort string 179} 180 181func getGlobPathName(config Config) string { 182 globPathName, ok := config.TargetProductOrErr() 183 if ok != nil { 184 globPathName = soongBuildTag 185 } 186 return globPathName 187} 188 189func getGlobPathNameFromPrimaryBuilderFactory(config Config, pb PrimaryBuilderFactory) string { 190 if pb.name == soongBuildTag { 191 // Glob path for soong build would be separated per product target 192 return getGlobPathName(config) 193 } 194 return pb.name 195} 196 197func (pb PrimaryBuilderFactory) primaryBuilderInvocation(config Config) bootstrap.PrimaryBuilderInvocation { 198 commonArgs := make([]string, 0, 0) 199 200 commonArgs = append(commonArgs, "--kati_suffix", config.KatiSuffix()) 201 202 if !pb.config.skipSoongTests { 203 commonArgs = append(commonArgs, "-t") 204 } 205 206 if pb.config.buildFromSourceStub { 207 commonArgs = append(commonArgs, "--build-from-source-stub") 208 } 209 210 if pb.config.moduleDebugFile != "" { 211 commonArgs = append(commonArgs, "--soong_module_debug") 212 commonArgs = append(commonArgs, pb.config.moduleDebugFile) 213 } 214 215 commonArgs = append(commonArgs, "-l", filepath.Join(pb.config.FileListDir(), "Android.bp.list")) 216 invocationEnv := make(map[string]string) 217 if pb.debugPort != "" { 218 //debug mode 219 commonArgs = append(commonArgs, "--delve_listen", pb.debugPort, 220 "--delve_path", shared.ResolveDelveBinary()) 221 // GODEBUG=asyncpreemptoff=1 disables the preemption of goroutines. This 222 // is useful because the preemption happens by sending SIGURG to the OS 223 // thread hosting the goroutine in question and each signal results in 224 // work that needs to be done by Delve; it uses ptrace to debug the Go 225 // process and the tracer process must deal with every signal (it is not 226 // possible to selectively ignore SIGURG). This makes debugging slower, 227 // sometimes by an order of magnitude depending on luck. 228 // The original reason for adding async preemption to Go is here: 229 // https://github.com/golang/proposal/blob/master/design/24543-non-cooperative-preemption.md 230 invocationEnv["GODEBUG"] = "asyncpreemptoff=1" 231 } 232 233 var allArgs []string 234 allArgs = append(allArgs, pb.specificArgs...) 235 236 allArgs = append(allArgs, commonArgs...) 237 allArgs = append(allArgs, environmentArgs(pb.config, pb.name)...) 238 if profileCpu := os.Getenv("SOONG_PROFILE_CPU"); profileCpu != "" { 239 allArgs = append(allArgs, "--cpuprofile", profileCpu+"."+pb.name) 240 } 241 if profileMem := os.Getenv("SOONG_PROFILE_MEM"); profileMem != "" { 242 allArgs = append(allArgs, "--memprofile", profileMem+"."+pb.name) 243 } 244 allArgs = append(allArgs, "Android.bp") 245 246 return bootstrap.PrimaryBuilderInvocation{ 247 Implicits: []string{pb.output + ".glob_results"}, 248 Outputs: []string{pb.output}, 249 Args: allArgs, 250 Description: pb.description, 251 // NB: Changing the value of this environment variable will not result in a 252 // rebuild. The bootstrap Ninja file will change, but apparently Ninja does 253 // not consider changing the pool specified in a statement a change that's 254 // worth rebuilding for. 255 Console: os.Getenv("SOONG_UNBUFFERED_OUTPUT") == "1", 256 Env: invocationEnv, 257 } 258} 259 260// bootstrapEpochCleanup deletes files used by bootstrap during incremental builds across 261// incompatible changes. Incompatible changes are marked by incrementing the bootstrapEpoch 262// constant. A tree is considered out of date for the current epoch of the 263// .soong.bootstrap.epoch.<epoch> file doesn't exist. 264func bootstrapEpochCleanup(ctx Context, config Config) { 265 epochFile := fmt.Sprintf(".soong.bootstrap.epoch.%d", bootstrapEpoch) 266 epochPath := filepath.Join(config.SoongOutDir(), epochFile) 267 if exists, err := fileExists(epochPath); err != nil { 268 ctx.Fatalf("failed to check if bootstrap epoch file %q exists: %q", epochPath, err) 269 } else if !exists { 270 // The tree is out of date for the current epoch, delete files used by bootstrap 271 // and force the primary builder to rerun. 272 soongNinjaFile := config.SoongNinjaFile() 273 os.Remove(soongNinjaFile) 274 for _, file := range blueprint.GetNinjaShardFiles(soongNinjaFile) { 275 if ok, _ := fileExists(file); ok { 276 os.Remove(file) 277 } 278 } 279 os.Remove(soongNinjaFile + ".globs") 280 os.Remove(soongNinjaFile + ".globs_time") 281 os.Remove(soongNinjaFile + ".glob_results") 282 283 // Mark the tree as up to date with the current epoch by writing the epoch marker file. 284 writeEmptyFile(ctx, epochPath) 285 } 286} 287 288func bootstrapBlueprint(ctx Context, config Config) { 289 ctx.BeginTrace(metrics.RunSoong, "blueprint bootstrap") 290 defer ctx.EndTrace() 291 292 st := ctx.Status.StartTool() 293 defer st.Finish() 294 st.SetTotalActions(1) 295 action := &status.Action{ 296 Description: "bootstrap blueprint", 297 Outputs: []string{"bootstrap blueprint"}, 298 } 299 st.StartAction(action) 300 301 // Clean up some files for incremental builds across incompatible changes. 302 bootstrapEpochCleanup(ctx, config) 303 304 baseArgs := []string{"--soong_variables", config.SoongVarsFile()} 305 306 mainSoongBuildExtraArgs := append(baseArgs, "-o", config.SoongNinjaFile()) 307 if config.EmptyNinjaFile() { 308 mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--empty-ninja-file") 309 } 310 if config.buildFromSourceStub { 311 mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--build-from-source-stub") 312 } 313 if config.ensureAllowlistIntegrity { 314 mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--ensure-allowlist-integrity") 315 } 316 if config.incrementalBuildActions { 317 mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--incremental-build-actions") 318 } 319 320 pbfs := []PrimaryBuilderFactory{ 321 { 322 name: soongBuildTag, 323 description: fmt.Sprintf("analyzing Android.bp files and generating ninja file at %s", config.SoongNinjaFile()), 324 config: config, 325 output: config.SoongNinjaFile(), 326 specificArgs: mainSoongBuildExtraArgs, 327 }, 328 { 329 name: jsonModuleGraphTag, 330 description: fmt.Sprintf("generating the Soong module graph at %s", config.ModuleGraphFile()), 331 config: config, 332 output: config.ModuleGraphFile(), 333 specificArgs: append(baseArgs, 334 "--module_graph_file", config.ModuleGraphFile(), 335 "--module_actions_file", config.ModuleActionsFile(), 336 ), 337 }, 338 { 339 name: soongDocsTag, 340 description: fmt.Sprintf("generating Soong docs at %s", config.SoongDocsHtml()), 341 config: config, 342 output: config.SoongDocsHtml(), 343 specificArgs: append(baseArgs, 344 "--soong_docs", config.SoongDocsHtml(), 345 ), 346 }, 347 } 348 349 // Figure out which invocations will be run under the debugger: 350 // * SOONG_DELVE if set specifies listening port 351 // * SOONG_DELVE_STEPS if set specifies specific invocations to be debugged, otherwise all are 352 debuggedInvocations := make(map[string]bool) 353 delvePort := os.Getenv("SOONG_DELVE") 354 if delvePort != "" { 355 if steps := os.Getenv("SOONG_DELVE_STEPS"); steps != "" { 356 var validSteps []string 357 for _, pbf := range pbfs { 358 debuggedInvocations[pbf.name] = false 359 validSteps = append(validSteps, pbf.name) 360 361 } 362 for _, step := range strings.Split(steps, ",") { 363 if _, ok := debuggedInvocations[step]; ok { 364 debuggedInvocations[step] = true 365 } else { 366 ctx.Fatalf("SOONG_DELVE_STEPS contains unknown soong_build step %s\n"+ 367 "Valid steps are %v", step, validSteps) 368 } 369 } 370 } else { 371 // SOONG_DELVE_STEPS is not set, run all steps in the debugger 372 for _, pbf := range pbfs { 373 debuggedInvocations[pbf.name] = true 374 } 375 } 376 } 377 378 var invocations []bootstrap.PrimaryBuilderInvocation 379 for _, pbf := range pbfs { 380 if debuggedInvocations[pbf.name] { 381 pbf.debugPort = delvePort 382 } 383 pbi := pbf.primaryBuilderInvocation(config) 384 invocations = append(invocations, pbi) 385 } 386 387 blueprintArgs := bootstrap.Args{ 388 ModuleListFile: filepath.Join(config.FileListDir(), "Android.bp.list"), 389 OutFile: shared.JoinPath(config.SoongOutDir(), "bootstrap.ninja"), 390 EmptyNinjaFile: false, 391 } 392 393 blueprintCtx := blueprint.NewContext() 394 blueprintCtx.AddSourceRootDirs(config.GetSourceRootDirs()...) 395 blueprintCtx.SetIgnoreUnknownModuleTypes(true) 396 blueprintConfig := BlueprintConfig{ 397 soongOutDir: config.SoongOutDir(), 398 toolDir: config.HostToolDir(), 399 outDir: config.OutDir(), 400 runGoTests: !config.skipSoongTests, 401 // If we want to debug soong_build, we need to compile it for debugging 402 debugCompilation: delvePort != "", 403 primaryBuilderInvocations: invocations, 404 } 405 406 // since `bootstrap.ninja` is regenerated unconditionally, we ignore the deps, i.e. little 407 // reason to write a `bootstrap.ninja.d` file 408 _, err := bootstrap.RunBlueprint(blueprintArgs, bootstrap.DoEverything, blueprintCtx, blueprintConfig) 409 410 result := status.ActionResult{ 411 Action: action, 412 } 413 if err != nil { 414 result.Error = err 415 result.Output = err.Error() 416 } 417 st.FinishAction(result) 418 if err != nil { 419 ctx.Fatalf("bootstrap failed") 420 } 421} 422 423func checkEnvironmentFile(ctx Context, currentEnv *Environment, envFile string) { 424 getenv := func(k string) string { 425 v, _ := currentEnv.Get(k) 426 return v 427 } 428 429 // Log the changed environment variables to ChangedEnvironmentVariable field 430 if stale, changedEnvironmentVariableList, _ := shared.StaleEnvFile(envFile, getenv); stale { 431 for _, changedEnvironmentVariable := range changedEnvironmentVariableList { 432 ctx.Metrics.AddChangedEnvironmentVariable(changedEnvironmentVariable) 433 } 434 os.Remove(envFile) 435 } 436} 437 438func updateSymlinks(ctx Context, dir, prevCWD, cwd string, updateSemaphore chan struct{}) error { 439 defer symlinkWg.Done() 440 441 visit := func(path string, d fs.DirEntry, err error) error { 442 if d.IsDir() && path != dir { 443 symlinkWg.Add(1) 444 go updateSymlinks(ctx, path, prevCWD, cwd, updateSemaphore) 445 return filepath.SkipDir 446 } 447 f, err := d.Info() 448 if err != nil { 449 return err 450 } 451 // If the file is not a symlink, we don't have to update it. 452 if f.Mode()&os.ModeSymlink != os.ModeSymlink { 453 return nil 454 } 455 456 atomic.AddUint32(&numFound, 1) 457 target, err := os.Readlink(path) 458 if err != nil { 459 return err 460 } 461 if strings.HasPrefix(target, prevCWD) && 462 (len(target) == len(prevCWD) || target[len(prevCWD)] == '/') { 463 target = filepath.Join(cwd, target[len(prevCWD):]) 464 if err := os.Remove(path); err != nil { 465 return err 466 } 467 if err := os.Symlink(target, path); err != nil { 468 return err 469 } 470 atomic.AddUint32(&numUpdated, 1) 471 } 472 return nil 473 } 474 475 <-updateSemaphore 476 defer func() { updateSemaphore <- struct{}{} }() 477 if err := filepath.WalkDir(dir, visit); err != nil { 478 return err 479 } 480 return nil 481} 482 483// b/376466642: If the concurrency of updateSymlinks is unbounded, Go's runtime spawns a 484// theoretically unbounded number of threads to handle blocking syscalls. This causes the runtime to 485// panic due to hitting thread limits in rare cases. Limiting to GOMAXPROCS concurrent symlink 486// updates should make this a non-issue. 487func newUpdateSemaphore() chan struct{} { 488 numPermits := runtime.GOMAXPROCS(0) 489 c := make(chan struct{}, numPermits) 490 for i := 0; i < numPermits; i++ { 491 c <- struct{}{} 492 } 493 return c 494} 495 496func fixOutDirSymlinks(ctx Context, config Config, outDir string) error { 497 cwd, err := os.Getwd() 498 if err != nil { 499 return err 500 } 501 502 // Record the .top as the very last thing in the function. 503 tf := filepath.Join(outDir, ".top") 504 defer func() { 505 if err := os.WriteFile(tf, []byte(cwd), 0644); err != nil { 506 fmt.Fprintf(os.Stderr, "Unable to log CWD: %v", err) 507 } 508 }() 509 510 // Find the previous working directory if it was recorded. 511 var prevCWD string 512 pcwd, err := os.ReadFile(tf) 513 if err != nil { 514 if os.IsNotExist(err) { 515 // No previous working directory recorded, nothing to do. 516 return nil 517 } 518 return err 519 } 520 prevCWD = strings.Trim(string(pcwd), "\n") 521 522 if prevCWD == cwd { 523 // We are in the same source dir, nothing to update. 524 return nil 525 } 526 527 symlinkWg.Add(1) 528 if err := updateSymlinks(ctx, outDir, prevCWD, cwd, newUpdateSemaphore()); err != nil { 529 return err 530 } 531 symlinkWg.Wait() 532 ctx.Println(fmt.Sprintf("Updated %d/%d symlinks in dir %v", numUpdated, numFound, outDir)) 533 return nil 534} 535 536func migrateOutputSymlinks(ctx Context, config Config) error { 537 // Figure out the real out directory ("out" could be a symlink). 538 outDir := config.OutDir() 539 s, err := os.Lstat(outDir) 540 if err != nil { 541 if os.IsNotExist(err) { 542 // No out dir exists, no symlinks to migrate. 543 return nil 544 } 545 return err 546 } 547 if s.Mode()&os.ModeSymlink == os.ModeSymlink { 548 target, err := filepath.EvalSymlinks(outDir) 549 if err != nil { 550 return err 551 } 552 outDir = target 553 } 554 return fixOutDirSymlinks(ctx, config, outDir) 555} 556 557func runSoong(ctx Context, config Config) { 558 ctx.BeginTrace(metrics.RunSoong, "soong") 559 defer ctx.EndTrace() 560 561 if err := migrateOutputSymlinks(ctx, config); err != nil { 562 ctx.Fatalf("failed to migrate output directory to current TOP dir: %v", err) 563 } 564 565 // We have two environment files: .available is the one with every variable, 566 // .used with the ones that were actually used. The latter is used to 567 // determine whether Soong needs to be re-run since why re-run it if only 568 // unused variables were changed? 569 envFile := filepath.Join(config.SoongOutDir(), availableEnvFile) 570 571 // This is done unconditionally, but does not take a measurable amount of time 572 bootstrapBlueprint(ctx, config) 573 574 soongBuildEnv := config.Environment().Copy() 575 soongBuildEnv.Set("TOP", os.Getenv("TOP")) 576 soongBuildEnv.Set("LOG_DIR", config.LogsDir()) 577 578 // For Soong bootstrapping tests 579 if os.Getenv("ALLOW_MISSING_DEPENDENCIES") == "true" { 580 soongBuildEnv.Set("ALLOW_MISSING_DEPENDENCIES", "true") 581 } 582 583 err := writeEnvironmentFile(ctx, envFile, soongBuildEnv.AsMap()) 584 if err != nil { 585 ctx.Fatalf("failed to write environment file %s: %s", envFile, err) 586 } 587 588 func() { 589 ctx.BeginTrace(metrics.RunSoong, "environment check") 590 defer ctx.EndTrace() 591 592 checkEnvironmentFile(ctx, soongBuildEnv, config.UsedEnvFile(soongBuildTag)) 593 594 if config.JsonModuleGraph() { 595 checkEnvironmentFile(ctx, soongBuildEnv, config.UsedEnvFile(jsonModuleGraphTag)) 596 } 597 598 if config.SoongDocs() { 599 checkEnvironmentFile(ctx, soongBuildEnv, config.UsedEnvFile(soongDocsTag)) 600 } 601 }() 602 603 ninja := func(targets ...string) { 604 ctx.BeginTrace(metrics.RunSoong, "bootstrap") 605 defer ctx.EndTrace() 606 607 fifo := filepath.Join(config.OutDir(), ".ninja_fifo") 608 nr := status.NewNinjaReader(ctx, ctx.Status.StartTool(), fifo) 609 defer nr.Close() 610 611 var ninjaCmd string 612 var ninjaArgs []string 613 switch config.ninjaCommand { 614 case NINJA_N2: 615 ninjaCmd = config.N2Bin() 616 ninjaArgs = []string{ 617 // TODO: implement these features, or remove them. 618 //"-d", "keepdepfile", 619 //"-d", "stats", 620 //"-o", "usesphonyoutputs=yes", 621 //"-o", "preremoveoutputs=yes", 622 //"-w", "dupbuild=err", 623 //"-w", "outputdir=err", 624 //"-w", "missingoutfile=err", 625 "-v", 626 "-j", strconv.Itoa(config.Parallel()), 627 "--frontend-file", fifo, 628 "-f", filepath.Join(config.SoongOutDir(), "bootstrap.ninja"), 629 } 630 case NINJA_SISO: 631 ninjaCmd = config.SisoBin() 632 ninjaArgs = []string{ 633 "ninja", 634 // TODO: implement these features, or remove them. 635 //"-d", "keepdepfile", 636 //"-d", "stats", 637 //"-o", "usesphonyoutputs=yes", 638 //"-o", "preremoveoutputs=yes", 639 //"-w", "dupbuild=err", 640 //"-w", "outputdir=err", 641 //"-w", "missingoutfile=err", 642 "-v", 643 "-j", strconv.Itoa(config.Parallel()), 644 //"--frontend-file", fifo, 645 "--log_dir", config.SoongOutDir(), 646 "-f", filepath.Join(config.SoongOutDir(), "bootstrap.ninja"), 647 } 648 default: 649 // NINJA_NINJA is the default. 650 ninjaCmd = config.NinjaBin() 651 ninjaArgs = []string{ 652 "-d", "keepdepfile", 653 "-d", "stats", 654 "-o", "usesphonyoutputs=yes", 655 "-o", "preremoveoutputs=yes", 656 "-w", "dupbuild=err", 657 "-w", "outputdir=err", 658 "-w", "missingoutfile=err", 659 "-j", strconv.Itoa(config.Parallel()), 660 "--frontend_file", fifo, 661 "-f", filepath.Join(config.SoongOutDir(), "bootstrap.ninja"), 662 } 663 } 664 665 if extra, ok := config.Environment().Get("SOONG_UI_NINJA_ARGS"); ok { 666 ctx.Printf(`CAUTION: arguments in $SOONG_UI_NINJA_ARGS=%q, e.g. "-n", can make soong_build FAIL or INCORRECT`, extra) 667 ninjaArgs = append(ninjaArgs, strings.Fields(extra)...) 668 } 669 670 ninjaArgs = append(ninjaArgs, targets...) 671 672 cmd := Command(ctx, config, "soong bootstrap", 673 ninjaCmd, ninjaArgs...) 674 675 var ninjaEnv Environment 676 677 // This is currently how the command line to invoke soong_build finds the 678 // root of the source tree and the output root 679 ninjaEnv.Set("TOP", os.Getenv("TOP")) 680 681 cmd.Environment = &ninjaEnv 682 cmd.Sandbox = soongSandbox 683 cmd.RunAndStreamOrFatal() 684 } 685 686 targets := make([]string, 0, 0) 687 688 if config.JsonModuleGraph() { 689 targets = append(targets, config.ModuleGraphFile()) 690 } 691 692 if config.SoongDocs() { 693 targets = append(targets, config.SoongDocsHtml()) 694 } 695 696 if config.SoongBuildInvocationNeeded() { 697 // This build generates <builddir>/build.ninja, which is used later by build/soong/ui/build/build.go#Build(). 698 targets = append(targets, config.SoongNinjaFile()) 699 } 700 701 for _, target := range targets { 702 if err := checkGlobs(ctx, target); err != nil { 703 ctx.Fatalf("Error checking globs: %s", err.Error()) 704 } 705 } 706 707 beforeSoongTimestamp := time.Now() 708 709 ninja(targets...) 710 711 loadSoongBuildMetrics(ctx, config, beforeSoongTimestamp) 712 713 soongNinjaFile := config.SoongNinjaFile() 714 distGzipFile(ctx, config, soongNinjaFile, "soong") 715 for _, file := range blueprint.GetNinjaShardFiles(soongNinjaFile) { 716 if ok, _ := fileExists(file); ok { 717 distGzipFile(ctx, config, file, "soong") 718 } 719 } 720 distFile(ctx, config, config.SoongVarsFile(), "soong") 721 distFile(ctx, config, config.SoongExtraVarsFile(), "soong") 722 723 if !config.SkipKati() { 724 distGzipFile(ctx, config, config.SoongAndroidMk(), "soong") 725 distGzipFile(ctx, config, config.SoongMakeVarsMk(), "soong") 726 } 727 728 if config.JsonModuleGraph() { 729 distGzipFile(ctx, config, config.ModuleGraphFile(), "soong") 730 } 731} 732 733// checkGlobs manages the globs that cause soong to rerun. 734// 735// When soong_build runs, it will run globs. It will write all the globs 736// it ran into the "{finalOutFile}.globs" file. Then every build, 737// soong_ui will check that file, rerun the globs, and if they changed 738// from the results that soong_build got, update the ".glob_results" 739// file, causing soong_build to rerun. The ".glob_results" file will 740// be empty on the first run of soong_build, because we don't know 741// what the globs are yet, but also remain empty until the globs change 742// so that we don't run soong_build a second time unnecessarily. 743// Both soong_build and soong_ui will also update a ".globs_time" file 744// with the time that they ran at every build. When soong_ui checks 745// globs, it only reruns globs whose dependencies are newer than the 746// time in the ".globs_time" file. 747func checkGlobs(ctx Context, finalOutFile string) error { 748 ctx.BeginTrace(metrics.RunSoong, "check_globs") 749 defer ctx.EndTrace() 750 st := ctx.Status.StartTool() 751 st.Status("Running globs...") 752 defer st.Finish() 753 754 globsFile, err := os.Open(finalOutFile + ".globs") 755 if errors.Is(err, fs.ErrNotExist) { 756 // if the glob file doesn't exist, make sure the glob_results file exists and is empty. 757 if err := os.MkdirAll(filepath.Dir(finalOutFile), 0777); err != nil { 758 return err 759 } 760 f, err := os.Create(finalOutFile + ".glob_results") 761 if err != nil { 762 return err 763 } 764 return f.Close() 765 } else if err != nil { 766 return err 767 } 768 defer globsFile.Close() 769 globsFileDecoder := json.NewDecoder(globsFile) 770 771 globsTimeBytes, err := os.ReadFile(finalOutFile + ".globs_time") 772 if err != nil { 773 return err 774 } 775 globsTimeMicros, err := strconv.ParseInt(strings.TrimSpace(string(globsTimeBytes)), 10, 64) 776 if err != nil { 777 return err 778 } 779 globCheckStartTime := time.Now().UnixMicro() 780 781 globsChan := make(chan pathtools.GlobResult) 782 errorsChan := make(chan error) 783 wg := sync.WaitGroup{} 784 785 hasChangedGlobs := false 786 var changedGlobNameMutex sync.Mutex 787 var changedGlobName string 788 789 for i := 0; i < runtime.NumCPU()*2; i++ { 790 wg.Add(1) 791 go func() { 792 for cachedGlob := range globsChan { 793 // If we've already determined we have changed globs, just finish consuming 794 // the channel without doing any more checks. 795 if hasChangedGlobs { 796 continue 797 } 798 // First, check if any of the deps are newer than the last time globs were checked. 799 // If not, we don't need to rerun the glob. 800 hasNewDep := false 801 for _, dep := range cachedGlob.Deps { 802 info, err := os.Stat(dep) 803 if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) { 804 hasNewDep = true 805 break 806 } else if err != nil { 807 errorsChan <- err 808 continue 809 } 810 if info.ModTime().UnixMicro() > globsTimeMicros { 811 hasNewDep = true 812 break 813 } 814 } 815 if !hasNewDep { 816 continue 817 } 818 819 // Then rerun the glob and check if we got the same result as before. 820 result, err := pathtools.Glob(cachedGlob.Pattern, cachedGlob.Excludes, pathtools.FollowSymlinks) 821 if err != nil { 822 errorsChan <- err 823 } else { 824 if !slices.Equal(result.Matches, cachedGlob.Matches) { 825 hasChangedGlobs = true 826 827 changedGlobNameMutex.Lock() 828 defer changedGlobNameMutex.Unlock() 829 changedGlobName = result.Pattern 830 if len(result.Excludes) > 2 { 831 changedGlobName += fmt.Sprintf(" (excluding %d other patterns)", len(result.Excludes)) 832 } else if len(result.Excludes) > 0 { 833 changedGlobName += " (excluding " + strings.Join(result.Excludes, " and ") + ")" 834 } 835 } 836 } 837 } 838 wg.Done() 839 }() 840 } 841 go func() { 842 wg.Wait() 843 close(errorsChan) 844 }() 845 846 errorsWg := sync.WaitGroup{} 847 errorsWg.Add(1) 848 var errFromGoRoutines error 849 go func() { 850 for result := range errorsChan { 851 if errFromGoRoutines == nil { 852 errFromGoRoutines = result 853 } 854 } 855 errorsWg.Done() 856 }() 857 858 var cachedGlob pathtools.GlobResult 859 for globsFileDecoder.More() { 860 if err := globsFileDecoder.Decode(&cachedGlob); err != nil { 861 return err 862 } 863 // Need to clone the GlobResult because the json decoder will 864 // reuse the same slice allocations. 865 globsChan <- cachedGlob.Clone() 866 } 867 close(globsChan) 868 errorsWg.Wait() 869 if errFromGoRoutines != nil { 870 return errFromGoRoutines 871 } 872 873 // Update the globs_time file whether or not we found changed globs, 874 // so that we don't rerun globs in the future that we just saw didn't change. 875 err = os.WriteFile( 876 finalOutFile+".globs_time", 877 []byte(fmt.Sprintf("%d\n", globCheckStartTime)), 878 0666, 879 ) 880 if err != nil { 881 return err 882 } 883 884 if hasChangedGlobs { 885 fmt.Fprintf(os.Stdout, "Globs changed, rerunning soong...\n") 886 fmt.Fprintf(os.Stdout, "One culprit glob (may be more): %s\n", changedGlobName) 887 // Write the current time to the glob_results file. We just need 888 // some unique value to trigger a rerun, it doesn't matter what it is. 889 err = os.WriteFile( 890 finalOutFile+".glob_results", 891 []byte(fmt.Sprintf("%d\n", globCheckStartTime)), 892 0666, 893 ) 894 if err != nil { 895 return err 896 } 897 } 898 return nil 899} 900 901// loadSoongBuildMetrics reads out/soong_build_metrics.pb if it was generated by soong_build and copies the 902// events stored in it into the soong_ui trace to provide introspection into how long the different phases of 903// soong_build are taking. 904func loadSoongBuildMetrics(ctx Context, config Config, oldTimestamp time.Time) { 905 soongBuildMetricsFile := config.SoongBuildMetrics() 906 if metricsStat, err := os.Stat(soongBuildMetricsFile); err != nil { 907 ctx.Verbosef("Failed to stat %s: %s", soongBuildMetricsFile, err) 908 return 909 } else if !metricsStat.ModTime().After(oldTimestamp) { 910 ctx.Verbosef("%s timestamp not later after running soong, expected %s > %s", 911 soongBuildMetricsFile, metricsStat.ModTime(), oldTimestamp) 912 return 913 } 914 915 metricsData, err := os.ReadFile(soongBuildMetricsFile) 916 if err != nil { 917 ctx.Verbosef("Failed to read %s: %s", soongBuildMetricsFile, err) 918 return 919 } 920 921 soongBuildMetrics := metrics_proto.SoongBuildMetrics{} 922 err = proto.Unmarshal(metricsData, &soongBuildMetrics) 923 if err != nil { 924 ctx.Verbosef("Failed to unmarshal %s: %s", soongBuildMetricsFile, err) 925 return 926 } 927 for _, event := range soongBuildMetrics.Events { 928 desc := event.GetDescription() 929 if dot := strings.LastIndexByte(desc, '.'); dot >= 0 { 930 desc = desc[dot+1:] 931 } 932 ctx.Tracer.Complete(desc, ctx.Thread, 933 event.GetStartTime(), event.GetStartTime()+event.GetRealTime()) 934 } 935 for _, event := range soongBuildMetrics.PerfCounters { 936 timestamp := event.GetTime() 937 for _, group := range event.Groups { 938 counters := make([]tracer.Counter, 0, len(group.Counters)) 939 for _, counter := range group.Counters { 940 counters = append(counters, tracer.Counter{ 941 Name: counter.GetName(), 942 Value: counter.GetValue(), 943 }) 944 } 945 ctx.Tracer.CountersAtTime(group.GetName(), ctx.Thread, timestamp, counters) 946 } 947 } 948} 949 950func runMicrofactory(ctx Context, config Config, name string, pkg string, mapping map[string]string) { 951 ctx.BeginTrace(metrics.RunSoong, name) 952 defer ctx.EndTrace() 953 cfg := microfactory.Config{TrimPath: absPath(ctx, ".")} 954 for pkgPrefix, pathPrefix := range mapping { 955 cfg.Map(pkgPrefix, pathPrefix) 956 } 957 958 exePath := filepath.Join(config.SoongOutDir(), name) 959 dir := filepath.Dir(exePath) 960 if err := os.MkdirAll(dir, 0777); err != nil { 961 ctx.Fatalf("cannot create %s: %s", dir, err) 962 } 963 if _, err := microfactory.Build(&cfg, exePath, pkg); err != nil { 964 ctx.Fatalf("failed to build %s: %s", name, err) 965 } 966} 967