• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2015 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 main
16
17import (
18	"bytes"
19	"flag"
20	"fmt"
21	"os"
22	"path/filepath"
23	"strings"
24	"time"
25
26	"android/soong/android"
27	"android/soong/android/allowlists"
28	"android/soong/bazel"
29	"android/soong/bp2build"
30	"android/soong/shared"
31	"android/soong/ui/metrics/bp2build_metrics_proto"
32
33	"github.com/google/blueprint/bootstrap"
34	"github.com/google/blueprint/deptools"
35	"github.com/google/blueprint/metrics"
36	androidProtobuf "google.golang.org/protobuf/android"
37)
38
39var (
40	topDir           string
41	availableEnvFile string
42	usedEnvFile      string
43
44	globFile    string
45	globListDir string
46	delveListen string
47	delvePath   string
48
49	cmdlineArgs android.CmdArgs
50)
51
52func init() {
53	// Flags that make sense in every mode
54	flag.StringVar(&topDir, "top", "", "Top directory of the Android source tree")
55	flag.StringVar(&cmdlineArgs.SoongOutDir, "soong_out", "", "Soong output directory (usually $TOP/out/soong)")
56	flag.StringVar(&availableEnvFile, "available_env", "", "File containing available environment variables")
57	flag.StringVar(&usedEnvFile, "used_env", "", "File containing used environment variables")
58	flag.StringVar(&globFile, "globFile", "build-globs.ninja", "the Ninja file of globs to output")
59	flag.StringVar(&globListDir, "globListDir", "", "the directory containing the glob list files")
60	flag.StringVar(&cmdlineArgs.OutDir, "out", "", "the ninja builddir directory")
61	flag.StringVar(&cmdlineArgs.ModuleListFile, "l", "", "file that lists filepaths to parse")
62
63	// Debug flags
64	flag.StringVar(&delveListen, "delve_listen", "", "Delve port to listen on for debugging")
65	flag.StringVar(&delvePath, "delve_path", "", "Path to Delve. Only used if --delve_listen is set")
66	flag.StringVar(&cmdlineArgs.Cpuprofile, "cpuprofile", "", "write cpu profile to file")
67	flag.StringVar(&cmdlineArgs.TraceFile, "trace", "", "write trace to file")
68	flag.StringVar(&cmdlineArgs.Memprofile, "memprofile", "", "write memory profile to file")
69	flag.BoolVar(&cmdlineArgs.NoGC, "nogc", false, "turn off GC for debugging")
70
71	// Flags representing various modes soong_build can run in
72	flag.StringVar(&cmdlineArgs.ModuleGraphFile, "module_graph_file", "", "JSON module graph file to output")
73	flag.StringVar(&cmdlineArgs.ModuleActionsFile, "module_actions_file", "", "JSON file to output inputs/outputs of actions of modules")
74	flag.StringVar(&cmdlineArgs.DocFile, "soong_docs", "", "build documentation file to output")
75	flag.StringVar(&cmdlineArgs.BazelQueryViewDir, "bazel_queryview_dir", "", "path to the bazel queryview directory relative to --top")
76	flag.StringVar(&cmdlineArgs.BazelApiBp2buildDir, "bazel_api_bp2build_dir", "", "path to the bazel api_bp2build directory relative to --top")
77	flag.StringVar(&cmdlineArgs.Bp2buildMarker, "bp2build_marker", "", "If set, run bp2build, touch the specified marker file then exit")
78	flag.StringVar(&cmdlineArgs.SymlinkForestMarker, "symlink_forest_marker", "", "If set, create the bp2build symlink forest, touch the specified marker file, then exit")
79	flag.StringVar(&cmdlineArgs.OutFile, "o", "build.ninja", "the Ninja file to output")
80	flag.StringVar(&cmdlineArgs.BazelForceEnabledModules, "bazel-force-enabled-modules", "", "additional modules to build with Bazel. Comma-delimited")
81	flag.BoolVar(&cmdlineArgs.EmptyNinjaFile, "empty-ninja-file", false, "write out a 0-byte ninja file")
82	flag.BoolVar(&cmdlineArgs.MultitreeBuild, "multitree-build", false, "this is a multitree build")
83	flag.BoolVar(&cmdlineArgs.BazelMode, "bazel-mode", false, "use bazel for analysis of certain modules")
84	flag.BoolVar(&cmdlineArgs.BazelModeStaging, "bazel-mode-staging", false, "use bazel for analysis of certain near-ready modules")
85	flag.BoolVar(&cmdlineArgs.BazelModeDev, "bazel-mode-dev", false, "use bazel for analysis of a large number of modules (less stable)")
86	flag.BoolVar(&cmdlineArgs.UseBazelProxy, "use-bazel-proxy", false, "communicate with bazel using unix socket proxy instead of spawning subprocesses")
87	flag.BoolVar(&cmdlineArgs.BuildFromTextStub, "build-from-text-stub", false, "build Java stubs from API text files instead of source files")
88
89	// Flags that probably shouldn't be flags of soong_build, but we haven't found
90	// the time to remove them yet
91	flag.BoolVar(&cmdlineArgs.RunGoTests, "t", false, "build and run go tests during bootstrap")
92
93	// Disable deterministic randomization in the protobuf package, so incremental
94	// builds with unrelated Soong changes don't trigger large rebuilds (since we
95	// write out text protos in command lines, and command line changes trigger
96	// rebuilds).
97	androidProtobuf.DisableRand()
98}
99
100func newNameResolver(config android.Config) *android.NameResolver {
101	return android.NewNameResolver(config)
102}
103
104func newContext(configuration android.Config) *android.Context {
105	ctx := android.NewContext(configuration)
106	ctx.SetNameInterface(newNameResolver(configuration))
107	ctx.SetAllowMissingDependencies(configuration.AllowMissingDependencies())
108	ctx.AddIncludeTags(configuration.IncludeTags()...)
109	ctx.AddSourceRootDirs(configuration.SourceRootDirs()...)
110	return ctx
111}
112
113// Bazel-enabled mode. Attaches a mutator to queue Bazel requests, adds a
114// BeforePrepareBuildActionsHook to invoke Bazel, and then uses Bazel metadata
115// for modules that should be handled by Bazel.
116func runMixedModeBuild(ctx *android.Context, extraNinjaDeps []string) string {
117	ctx.EventHandler.Begin("mixed_build")
118	defer ctx.EventHandler.End("mixed_build")
119
120	bazelHook := func() error {
121		return ctx.Config().BazelContext.InvokeBazel(ctx.Config(), ctx)
122	}
123	ctx.SetBeforePrepareBuildActionsHook(bazelHook)
124	ninjaDeps := bootstrap.RunBlueprint(cmdlineArgs.Args, bootstrap.DoEverything, ctx.Context, ctx.Config())
125	ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
126
127	bazelPaths, err := readFileLines(ctx.Config().Getenv("BAZEL_DEPS_FILE"))
128	if err != nil {
129		panic("Bazel deps file not found: " + err.Error())
130	}
131	ninjaDeps = append(ninjaDeps, bazelPaths...)
132	ninjaDeps = append(ninjaDeps, writeBuildGlobsNinjaFile(ctx)...)
133
134	writeDepFile(cmdlineArgs.OutFile, ctx.EventHandler, ninjaDeps)
135	return cmdlineArgs.OutFile
136}
137
138// Run the code-generation phase to convert BazelTargetModules to BUILD files.
139func runQueryView(queryviewDir, queryviewMarker string, ctx *android.Context) {
140	ctx.EventHandler.Begin("queryview")
141	defer ctx.EventHandler.End("queryview")
142	codegenContext := bp2build.NewCodegenContext(ctx.Config(), ctx, bp2build.QueryView, topDir)
143	err := createBazelWorkspace(codegenContext, shared.JoinPath(topDir, queryviewDir), false)
144	maybeQuit(err, "")
145	touch(shared.JoinPath(topDir, queryviewMarker))
146}
147
148// Run the code-generation phase to convert API contributions to BUILD files.
149// Return marker file for the new synthetic workspace
150func runApiBp2build(ctx *android.Context, extraNinjaDeps []string) string {
151	ctx.EventHandler.Begin("api_bp2build")
152	defer ctx.EventHandler.End("api_bp2build")
153	// api_bp2build does not run the typical pipeline of soong mutators.
154	// Hoevever, it still runs the defaults mutator which can create dependencies.
155	// These dependencies might not always exist (e.g. in tests)
156	ctx.SetAllowMissingDependencies(ctx.Config().AllowMissingDependencies())
157	ctx.RegisterForApiBazelConversion()
158
159	// Register the Android.bp files in the tree
160	// Add them to the workspace's .d file
161	ctx.SetModuleListFile(cmdlineArgs.ModuleListFile)
162	if paths, err := ctx.ListModulePaths("."); err == nil {
163		extraNinjaDeps = append(extraNinjaDeps, paths...)
164	} else {
165		panic(err)
166	}
167
168	// Run the loading and analysis phase
169	ninjaDeps := bootstrap.RunBlueprint(cmdlineArgs.Args,
170		bootstrap.StopBeforePrepareBuildActions,
171		ctx.Context,
172		ctx.Config())
173	ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
174
175	// Add the globbed dependencies
176	ninjaDeps = append(ninjaDeps, writeBuildGlobsNinjaFile(ctx)...)
177
178	// Run codegen to generate BUILD files
179	codegenContext := bp2build.NewCodegenContext(ctx.Config(), ctx, bp2build.ApiBp2build, topDir)
180	absoluteApiBp2buildDir := shared.JoinPath(topDir, cmdlineArgs.BazelApiBp2buildDir)
181	// Always generate bp2build_all_srcs filegroups in api_bp2build.
182	// This is necessary to force each Android.bp file to create an equivalent BUILD file
183	// and prevent package boundray issues.
184	// e.g.
185	// Source
186	// f/b/Android.bp
187	// java_library{
188	//   name: "foo",
189	//   api: "api/current.txt",
190	// }
191	//
192	// f/b/api/Android.bp <- will cause package boundary issues
193	//
194	// Gen
195	// f/b/BUILD
196	// java_contribution{
197	//   name: "foo.contribution",
198	//   api: "//f/b/api:current.txt",
199	// }
200	//
201	// If we don't generate f/b/api/BUILD, foo.contribution will be unbuildable.
202	err := createBazelWorkspace(codegenContext, absoluteApiBp2buildDir, true)
203	maybeQuit(err, "")
204	ninjaDeps = append(ninjaDeps, codegenContext.AdditionalNinjaDeps()...)
205
206	// Create soong_injection repository
207	soongInjectionFiles, err := bp2build.CreateSoongInjectionDirFiles(codegenContext, bp2build.CreateCodegenMetrics())
208	maybeQuit(err, "")
209	absoluteSoongInjectionDir := shared.JoinPath(topDir, ctx.Config().SoongOutDir(), bazel.SoongInjectionDirName)
210	for _, file := range soongInjectionFiles {
211		// The API targets in api_bp2build workspace do not have any dependency on api_bp2build.
212		// But we need to create these files to prevent errors during Bazel analysis.
213		// These need to be created in Read-Write mode.
214		// This is because the subsequent step (bp2build in api domain analysis) creates them in Read-Write mode
215		// to allow users to edit/experiment in the synthetic workspace.
216		writeReadWriteFile(absoluteSoongInjectionDir, file)
217	}
218
219	workspace := shared.JoinPath(ctx.Config().SoongOutDir(), "api_bp2build")
220	// Create the symlink forest
221	symlinkDeps, _, _ := bp2build.PlantSymlinkForest(
222		ctx.Config().IsEnvTrue("BP2BUILD_VERBOSE"),
223		topDir,
224		workspace,
225		cmdlineArgs.BazelApiBp2buildDir,
226		apiBuildFileExcludes(ctx))
227	ninjaDeps = append(ninjaDeps, symlinkDeps...)
228
229	workspaceMarkerFile := workspace + ".marker"
230	writeDepFile(workspaceMarkerFile, ctx.EventHandler, ninjaDeps)
231	touch(shared.JoinPath(topDir, workspaceMarkerFile))
232	return workspaceMarkerFile
233}
234
235// With some exceptions, api_bp2build does not have any dependencies on the checked-in BUILD files
236// Exclude them from the generated workspace to prevent unrelated errors during the loading phase
237func apiBuildFileExcludes(ctx *android.Context) []string {
238	ret := bazelArtifacts()
239	srcs, err := getExistingBazelRelatedFiles(topDir)
240	maybeQuit(err, "Error determining existing Bazel-related files")
241	for _, src := range srcs {
242		// Exclude all src BUILD files
243		if src != "WORKSPACE" &&
244			src != "BUILD" &&
245			src != "BUILD.bazel" &&
246			!strings.HasPrefix(src, "build/bazel") &&
247			!strings.HasPrefix(src, "external/bazel-skylib") &&
248			!strings.HasPrefix(src, "prebuilts/clang") {
249			ret = append(ret, src)
250		}
251	}
252	// Android.bp files for api surfaces are mounted to out/, but out/ should not be a
253	// dep for api_bp2build. Otherwise, api_bp2build will be run every single time
254	ret = append(ret, ctx.Config().OutDir())
255	return ret
256}
257
258func writeNinjaHint(ctx *android.Context) error {
259	wantModules := make([]string, len(allowlists.HugeModulesMap))
260	i := 0
261	for k := range allowlists.HugeModulesMap {
262		wantModules[i] = k
263		i += 1
264	}
265	outputsMap := ctx.Context.GetOutputsFromModuleNames(wantModules)
266	var outputBuilder strings.Builder
267	for k, v := range allowlists.HugeModulesMap {
268		for _, output := range outputsMap[k] {
269			outputBuilder.WriteString(fmt.Sprintf("%s,%d\n", output, v))
270		}
271	}
272	weightListFile := filepath.Join(topDir, ctx.Config().OutDir(), ".ninja_weight_list")
273
274	err := os.WriteFile(weightListFile, []byte(outputBuilder.String()), 0644)
275	if err != nil {
276		return fmt.Errorf("could not write ninja weight list file %s", err)
277	}
278	return nil
279}
280
281func writeMetrics(configuration android.Config, eventHandler *metrics.EventHandler, metricsDir string) {
282	if len(metricsDir) < 1 {
283		fmt.Fprintf(os.Stderr, "\nMissing required env var for generating soong metrics: LOG_DIR\n")
284		os.Exit(1)
285	}
286	metricsFile := filepath.Join(metricsDir, "soong_build_metrics.pb")
287	err := android.WriteMetrics(configuration, eventHandler, metricsFile)
288	maybeQuit(err, "error writing soong_build metrics %s", metricsFile)
289}
290
291func writeJsonModuleGraphAndActions(ctx *android.Context, cmdArgs android.CmdArgs) {
292	graphFile, graphErr := os.Create(shared.JoinPath(topDir, cmdArgs.ModuleGraphFile))
293	maybeQuit(graphErr, "graph err")
294	defer graphFile.Close()
295	actionsFile, actionsErr := os.Create(shared.JoinPath(topDir, cmdArgs.ModuleActionsFile))
296	maybeQuit(actionsErr, "actions err")
297	defer actionsFile.Close()
298	ctx.Context.PrintJSONGraphAndActions(graphFile, actionsFile)
299}
300
301func writeBuildGlobsNinjaFile(ctx *android.Context) []string {
302	ctx.EventHandler.Begin("globs_ninja_file")
303	defer ctx.EventHandler.End("globs_ninja_file")
304
305	globDir := bootstrap.GlobDirectory(ctx.Config().SoongOutDir(), globListDir)
306	bootstrap.WriteBuildGlobsNinjaFile(&bootstrap.GlobSingleton{
307		GlobLister: ctx.Globs,
308		GlobFile:   globFile,
309		GlobDir:    globDir,
310		SrcDir:     ctx.SrcDir(),
311	}, ctx.Config())
312	return bootstrap.GlobFileListFiles(globDir)
313}
314
315func writeDepFile(outputFile string, eventHandler *metrics.EventHandler, ninjaDeps []string) {
316	eventHandler.Begin("ninja_deps")
317	defer eventHandler.End("ninja_deps")
318	depFile := shared.JoinPath(topDir, outputFile+".d")
319	err := deptools.WriteDepFile(depFile, outputFile, ninjaDeps)
320	maybeQuit(err, "error writing depfile '%s'", depFile)
321}
322
323// runSoongOnlyBuild runs the standard Soong build in a number of different modes.
324func runSoongOnlyBuild(ctx *android.Context, extraNinjaDeps []string) string {
325	ctx.EventHandler.Begin("soong_build")
326	defer ctx.EventHandler.End("soong_build")
327
328	var stopBefore bootstrap.StopBefore
329	switch ctx.Config().BuildMode {
330	case android.GenerateModuleGraph:
331		stopBefore = bootstrap.StopBeforeWriteNinja
332	case android.GenerateQueryView, android.GenerateDocFile:
333		stopBefore = bootstrap.StopBeforePrepareBuildActions
334	default:
335		stopBefore = bootstrap.DoEverything
336	}
337
338	ninjaDeps := bootstrap.RunBlueprint(cmdlineArgs.Args, stopBefore, ctx.Context, ctx.Config())
339	ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
340
341	globListFiles := writeBuildGlobsNinjaFile(ctx)
342	ninjaDeps = append(ninjaDeps, globListFiles...)
343
344	// Convert the Soong module graph into Bazel BUILD files.
345	switch ctx.Config().BuildMode {
346	case android.GenerateQueryView:
347		queryviewMarkerFile := cmdlineArgs.BazelQueryViewDir + ".marker"
348		runQueryView(cmdlineArgs.BazelQueryViewDir, queryviewMarkerFile, ctx)
349		writeDepFile(queryviewMarkerFile, ctx.EventHandler, ninjaDeps)
350		return queryviewMarkerFile
351	case android.GenerateModuleGraph:
352		writeJsonModuleGraphAndActions(ctx, cmdlineArgs)
353		writeDepFile(cmdlineArgs.ModuleGraphFile, ctx.EventHandler, ninjaDeps)
354		return cmdlineArgs.ModuleGraphFile
355	case android.GenerateDocFile:
356		// TODO: we could make writeDocs() return the list of documentation files
357		// written and add them to the .d file. Then soong_docs would be re-run
358		// whenever one is deleted.
359		err := writeDocs(ctx, shared.JoinPath(topDir, cmdlineArgs.DocFile))
360		maybeQuit(err, "error building Soong documentation")
361		writeDepFile(cmdlineArgs.DocFile, ctx.EventHandler, ninjaDeps)
362		return cmdlineArgs.DocFile
363	default:
364		// The actual output (build.ninja) was written in the RunBlueprint() call
365		// above
366		writeDepFile(cmdlineArgs.OutFile, ctx.EventHandler, ninjaDeps)
367		return cmdlineArgs.OutFile
368	}
369}
370
371// soong_ui dumps the available environment variables to
372// soong.environment.available . Then soong_build itself is run with an empty
373// environment so that the only way environment variables can be accessed is
374// using Config, which tracks access to them.
375
376// At the end of the build, a file called soong.environment.used is written
377// containing the current value of all used environment variables. The next
378// time soong_ui is run, it checks whether any environment variables that was
379// used had changed and if so, it deletes soong.environment.used to cause a
380// rebuild.
381//
382// The dependency of build.ninja on soong.environment.used is declared in
383// build.ninja.d
384func parseAvailableEnv() map[string]string {
385	if availableEnvFile == "" {
386		fmt.Fprintf(os.Stderr, "--available_env not set\n")
387		os.Exit(1)
388	}
389	result, err := shared.EnvFromFile(shared.JoinPath(topDir, availableEnvFile))
390	maybeQuit(err, "error reading available environment file '%s'", availableEnvFile)
391	return result
392}
393
394func main() {
395	flag.Parse()
396
397	shared.ReexecWithDelveMaybe(delveListen, delvePath)
398	android.InitSandbox(topDir)
399
400	availableEnv := parseAvailableEnv()
401	configuration, err := android.NewConfig(cmdlineArgs, availableEnv)
402	maybeQuit(err, "")
403	if configuration.Getenv("ALLOW_MISSING_DEPENDENCIES") == "true" {
404		configuration.SetAllowMissingDependencies()
405	}
406
407	extraNinjaDeps := []string{configuration.ProductVariablesFileName, usedEnvFile}
408	if shared.IsDebugging() {
409		// Add a non-existent file to the dependencies so that soong_build will rerun when the debugger is
410		// enabled even if it completed successfully.
411		extraNinjaDeps = append(extraNinjaDeps, filepath.Join(configuration.SoongOutDir(), "always_rerun_for_delve"))
412	}
413
414	// Bypass configuration.Getenv, as LOG_DIR does not need to be dependency tracked. By definition, it will
415	// change between every CI build, so tracking it would require re-running Soong for every build.
416	metricsDir := availableEnv["LOG_DIR"]
417
418	ctx := newContext(configuration)
419
420	var finalOutputFile string
421
422	// Run Soong for a specific activity, like bp2build, queryview
423	// or the actual Soong build for the build.ninja file.
424	switch configuration.BuildMode {
425	case android.SymlinkForest:
426		finalOutputFile = runSymlinkForestCreation(ctx, extraNinjaDeps, metricsDir)
427	case android.Bp2build:
428		// Run the alternate pipeline of bp2build mutators and singleton to convert
429		// Blueprint to BUILD files before everything else.
430		finalOutputFile = runBp2Build(ctx, extraNinjaDeps, metricsDir)
431	case android.ApiBp2build:
432		finalOutputFile = runApiBp2build(ctx, extraNinjaDeps)
433		writeMetrics(configuration, ctx.EventHandler, metricsDir)
434	default:
435		ctx.Register()
436		if configuration.IsMixedBuildsEnabled() {
437			finalOutputFile = runMixedModeBuild(ctx, extraNinjaDeps)
438		} else {
439			finalOutputFile = runSoongOnlyBuild(ctx, extraNinjaDeps)
440		}
441		if ctx.Config().IsEnvTrue("SOONG_GENERATES_NINJA_HINT") {
442			writeNinjaHint(ctx)
443		}
444		writeMetrics(configuration, ctx.EventHandler, metricsDir)
445	}
446	writeUsedEnvironmentFile(configuration)
447
448	// Touch the output file so that it's the newest file created by soong_build.
449	// This is necessary because, if soong_build generated any files which
450	// are ninja inputs to the main output file, then ninja would superfluously
451	// rebuild this output file on the next build invocation.
452	touch(shared.JoinPath(topDir, finalOutputFile))
453}
454
455func writeUsedEnvironmentFile(configuration android.Config) {
456	if usedEnvFile == "" {
457		return
458	}
459
460	path := shared.JoinPath(topDir, usedEnvFile)
461	data, err := shared.EnvFileContents(configuration.EnvDeps())
462	maybeQuit(err, "error writing used environment file '%s'\n", usedEnvFile)
463
464	if preexistingData, err := os.ReadFile(path); err != nil {
465		if !os.IsNotExist(err) {
466			maybeQuit(err, "error reading used environment file '%s'", usedEnvFile)
467		}
468	} else if bytes.Equal(preexistingData, data) {
469		// used environment file is unchanged
470		return
471	}
472	err = os.WriteFile(path, data, 0666)
473	maybeQuit(err, "error writing used environment file '%s'", usedEnvFile)
474}
475
476func touch(path string) {
477	f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
478	maybeQuit(err, "Error touching '%s'", path)
479	err = f.Close()
480	maybeQuit(err, "Error touching '%s'", path)
481
482	currentTime := time.Now().Local()
483	err = os.Chtimes(path, currentTime, currentTime)
484	maybeQuit(err, "error touching '%s'", path)
485}
486
487// Read the bazel.list file that the Soong Finder already dumped earlier (hopefully)
488// It contains the locations of BUILD files, BUILD.bazel files, etc. in the source dir
489func getExistingBazelRelatedFiles(topDir string) ([]string, error) {
490	bazelFinderFile := filepath.Join(filepath.Dir(cmdlineArgs.ModuleListFile), "bazel.list")
491	if !filepath.IsAbs(bazelFinderFile) {
492		// Assume this was a relative path under topDir
493		bazelFinderFile = filepath.Join(topDir, bazelFinderFile)
494	}
495	return readFileLines(bazelFinderFile)
496}
497
498func bazelArtifacts() []string {
499	return []string{
500		"bazel-bin",
501		"bazel-genfiles",
502		"bazel-out",
503		"bazel-testlogs",
504		"bazel-workspace",
505		"bazel-" + filepath.Base(topDir),
506	}
507}
508
509// This could in theory easily be separated into a binary that generically
510// merges two directories into a symlink tree. The main obstacle is that this
511// function currently depends on both Bazel-specific knowledge (the existence
512// of bazel-* symlinks) and configuration (the set of BUILD.bazel files that
513// should and should not be kept)
514//
515// Ideally, bp2build would write a file that contains instructions to the
516// symlink tree creation binary. Then the latter would not need to depend on
517// the very heavy-weight machinery of soong_build .
518func runSymlinkForestCreation(ctx *android.Context, extraNinjaDeps []string, metricsDir string) string {
519	var ninjaDeps []string
520	var mkdirCount, symlinkCount uint64
521
522	ctx.EventHandler.Do("symlink_forest", func() {
523		ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
524		verbose := ctx.Config().IsEnvTrue("BP2BUILD_VERBOSE")
525
526		// PlantSymlinkForest() returns all the directories that were readdir()'ed.
527		// Such a directory SHOULD be added to `ninjaDeps` so that a child directory
528		// or file created/deleted under it would trigger an update of the symlink forest.
529		generatedRoot := shared.JoinPath(ctx.Config().SoongOutDir(), "bp2build")
530		workspaceRoot := shared.JoinPath(ctx.Config().SoongOutDir(), "workspace")
531		var symlinkForestDeps []string
532		ctx.EventHandler.Do("plant", func() {
533			symlinkForestDeps, mkdirCount, symlinkCount = bp2build.PlantSymlinkForest(
534				verbose, topDir, workspaceRoot, generatedRoot, excludedFromSymlinkForest(ctx, verbose))
535		})
536		ninjaDeps = append(ninjaDeps, symlinkForestDeps...)
537	})
538
539	writeDepFile(cmdlineArgs.SymlinkForestMarker, ctx.EventHandler, ninjaDeps)
540	touch(shared.JoinPath(topDir, cmdlineArgs.SymlinkForestMarker))
541	codegenMetrics := bp2build.ReadCodegenMetrics(metricsDir)
542	if codegenMetrics == nil {
543		m := bp2build.CreateCodegenMetrics()
544		codegenMetrics = &m
545	} else {
546		//TODO (usta) we cannot determine if we loaded a stale file, i.e. from an unrelated prior
547		//invocation of codegen. We should simply use a separate .pb file
548	}
549	codegenMetrics.SetSymlinkCount(symlinkCount)
550	codegenMetrics.SetMkDirCount(mkdirCount)
551	writeBp2BuildMetrics(codegenMetrics, ctx.EventHandler, metricsDir)
552	return cmdlineArgs.SymlinkForestMarker
553}
554
555func excludedFromSymlinkForest(ctx *android.Context, verbose bool) []string {
556	excluded := bazelArtifacts()
557	if cmdlineArgs.OutDir[0] != '/' {
558		excluded = append(excluded, cmdlineArgs.OutDir)
559	}
560
561	// Find BUILD files in the srcDir which are not in the allowlist
562	// (android.Bp2BuildConversionAllowlist#ShouldKeepExistingBuildFileForDir)
563	// and return their paths so they can be left out of the Bazel workspace dir (i.e. ignored)
564	existingBazelFiles, err := getExistingBazelRelatedFiles(topDir)
565	maybeQuit(err, "Error determining existing Bazel-related files")
566
567	for _, path := range existingBazelFiles {
568		fullPath := shared.JoinPath(topDir, path)
569		fileInfo, err2 := os.Stat(fullPath)
570		if err2 != nil {
571			// Warn about error, but continue trying to check files
572			fmt.Fprintf(os.Stderr, "WARNING: Error accessing path '%s', err: %s\n", fullPath, err2)
573			continue
574		}
575		// Exclude only files named 'BUILD' or 'BUILD.bazel' and unless forcibly kept
576		if fileInfo.IsDir() ||
577			(fileInfo.Name() != "BUILD" && fileInfo.Name() != "BUILD.bazel") ||
578			ctx.Config().Bp2buildPackageConfig.ShouldKeepExistingBuildFileForDir(filepath.Dir(path)) {
579			// Don't ignore this existing build file
580			continue
581		}
582		if verbose {
583			fmt.Fprintf(os.Stderr, "Ignoring existing BUILD file: %s\n", path)
584		}
585		excluded = append(excluded, path)
586	}
587
588	// Temporarily exclude stuff to make `bazel build //external/...` (and `bazel build //frameworks/...`)  work
589	excluded = append(excluded,
590		// FIXME: 'autotest_lib' is a symlink back to external/autotest, and this causes an infinite
591		// symlink expansion error for Bazel
592		"external/autotest/venv/autotest_lib",
593		"external/autotest/autotest_lib",
594		"external/autotest/client/autotest_lib/client",
595
596		// FIXME: The external/google-fruit/extras/bazel_root/third_party/fruit dir is poison
597		// It contains several symlinks back to real source dirs, and those source dirs contain
598		// BUILD files we want to ignore
599		"external/google-fruit/extras/bazel_root/third_party/fruit",
600
601		// FIXME: 'frameworks/compile/slang' has a filegroup error due to an escaping issue
602		"frameworks/compile/slang",
603
604		// FIXME(b/260809113): 'prebuilts/clang/host/linux-x86/clang-dev' is a tool-generated symlink
605		// directory that contains a BUILD file. The bazel files finder code doesn't traverse into symlink dirs,
606		// and hence is not aware of this BUILD file and exclude it accordingly during symlink forest generation
607		// when checking against keepExistingBuildFiles allowlist.
608		//
609		// This is necessary because globs in //prebuilts/clang/host/linux-x86/BUILD
610		// currently assume no subpackages (keepExistingBuildFile is not recursive for that directory).
611		//
612		// This is a bandaid until we the symlink forest logic can intelligently exclude BUILD files found in
613		// source symlink dirs according to the keepExistingBuildFile allowlist.
614		"prebuilts/clang/host/linux-x86/clang-dev",
615	)
616	return excluded
617}
618
619// Run Soong in the bp2build mode. This creates a standalone context that registers
620// an alternate pipeline of mutators and singletons specifically for generating
621// Bazel BUILD files instead of Ninja files.
622func runBp2Build(ctx *android.Context, extraNinjaDeps []string, metricsDir string) string {
623	var codegenMetrics *bp2build.CodegenMetrics
624	ctx.EventHandler.Do("bp2build", func() {
625
626		// Propagate "allow misssing dependencies" bit. This is normally set in
627		// newContext(), but we create ctx without calling that method.
628		ctx.SetAllowMissingDependencies(ctx.Config().AllowMissingDependencies())
629		ctx.SetNameInterface(newNameResolver(ctx.Config()))
630		ctx.RegisterForBazelConversion()
631		ctx.SetModuleListFile(cmdlineArgs.ModuleListFile)
632
633		var ninjaDeps []string
634		ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
635
636		// Run the loading and analysis pipeline to prepare the graph of regular
637		// Modules parsed from Android.bp files, and the BazelTargetModules mapped
638		// from the regular Modules.
639		ctx.EventHandler.Do("bootstrap", func() {
640			blueprintArgs := cmdlineArgs
641			bootstrapDeps := bootstrap.RunBlueprint(blueprintArgs.Args,
642				bootstrap.StopBeforePrepareBuildActions, ctx.Context, ctx.Config())
643			ninjaDeps = append(ninjaDeps, bootstrapDeps...)
644		})
645
646		globListFiles := writeBuildGlobsNinjaFile(ctx)
647		ninjaDeps = append(ninjaDeps, globListFiles...)
648
649		// Run the code-generation phase to convert BazelTargetModules to BUILD files
650		// and print conversion codegenMetrics to the user.
651		codegenContext := bp2build.NewCodegenContext(ctx.Config(), ctx, bp2build.Bp2Build, topDir)
652		ctx.EventHandler.Do("codegen", func() {
653			codegenMetrics = bp2build.Codegen(codegenContext)
654		})
655
656		ninjaDeps = append(ninjaDeps, codegenContext.AdditionalNinjaDeps()...)
657
658		writeDepFile(cmdlineArgs.Bp2buildMarker, ctx.EventHandler, ninjaDeps)
659		touch(shared.JoinPath(topDir, cmdlineArgs.Bp2buildMarker))
660	})
661
662	// Only report metrics when in bp2build mode. The metrics aren't relevant
663	// for queryview, since that's a total repo-wide conversion and there's a
664	// 1:1 mapping for each module.
665	if ctx.Config().IsEnvTrue("BP2BUILD_VERBOSE") {
666		codegenMetrics.Print()
667	}
668	writeBp2BuildMetrics(codegenMetrics, ctx.EventHandler, metricsDir)
669	return cmdlineArgs.Bp2buildMarker
670}
671
672// Write Bp2Build metrics into $LOG_DIR
673func writeBp2BuildMetrics(codegenMetrics *bp2build.CodegenMetrics, eventHandler *metrics.EventHandler, metricsDir string) {
674	for _, event := range eventHandler.CompletedEvents() {
675		codegenMetrics.AddEvent(&bp2build_metrics_proto.Event{
676			Name:      event.Id,
677			StartTime: uint64(event.Start.UnixNano()),
678			RealTime:  event.RuntimeNanoseconds(),
679		})
680	}
681	if len(metricsDir) < 1 {
682		fmt.Fprintf(os.Stderr, "\nMissing required env var for generating bp2build metrics: LOG_DIR\n")
683		os.Exit(1)
684	}
685	codegenMetrics.Write(metricsDir)
686}
687
688func readFileLines(path string) ([]string, error) {
689	data, err := os.ReadFile(path)
690	if err == nil {
691		return strings.Split(strings.TrimSpace(string(data)), "\n"), nil
692	}
693	return nil, err
694
695}
696func maybeQuit(err error, format string, args ...interface{}) {
697	if err == nil {
698		return
699	}
700	if format != "" {
701		fmt.Fprintln(os.Stderr, fmt.Sprintf(format, args...)+": "+err.Error())
702	} else {
703		fmt.Fprintln(os.Stderr, err)
704	}
705	os.Exit(1)
706}
707