• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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