Merge "Fix empty item handling in Split function, rewrite Words function"
diff --git a/android/mutator.go b/android/mutator.go
index 7a10477..31edea3 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -44,6 +44,11 @@
 	}
 }
 
+func registerMutatorsForBazelConversion(ctx *blueprint.Context) {
+	// FIXME(b/171263886): Start bringing in mutators to make the Bionic
+	// module subgraph suitable for automated conversion.
+}
+
 func registerMutators(ctx *blueprint.Context, preArch, preDeps, postDeps, finalDeps []RegisterMutatorFunc) {
 	mctx := &registerMutatorsContext{}
 
diff --git a/android/prebuilt.go b/android/prebuilt.go
index 294a6e0..98cb572 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -178,6 +178,9 @@
 	srcPropertyName := proptools.PropertyNameForField(srcField)
 
 	srcsSupplier := func(ctx BaseModuleContext) []string {
+		if !ctx.Module().Enabled() {
+			return nil
+		}
 		value := srcPropsValue.FieldByIndex(srcFieldIndex)
 		if value.Kind() == reflect.Ptr {
 			value = value.Elem()
diff --git a/android/queryview.go b/android/queryview.go
index 970ae01..1b7e77d 100644
--- a/android/queryview.go
+++ b/android/queryview.go
@@ -26,15 +26,40 @@
 // for calling the soong_build primary builder in the main build.ninja file.
 func init() {
 	RegisterSingletonType("bazel_queryview", BazelQueryViewSingleton)
+	RegisterSingletonType("bazel_converter", BazelConverterSingleton)
 }
 
+// BazelQueryViewSingleton is the singleton responsible for registering the
+// soong_build build statement that will convert the Soong module graph after
+// applying *all* mutators, enabing the feature to query the final state of the
+// Soong graph. This mode is meant for querying the build graph state, and not meant
+// for generating BUILD files to be checked in.
 func BazelQueryViewSingleton() Singleton {
 	return &bazelQueryViewSingleton{}
 }
 
-type bazelQueryViewSingleton struct{}
+// BazelConverterSingleton is the singleton responsible for registering the soong_build
+// build statement that will convert the Soong module graph by applying an alternate
+// pipeline of mutators, with the goal of reaching semantic equivalence between the original
+// Blueprint and final BUILD files. Using this mode, the goal is to be able to
+// build with these BUILD files directly in the source tree.
+func BazelConverterSingleton() Singleton {
+	return &bazelConverterSingleton{}
+}
 
-func (c *bazelQueryViewSingleton) GenerateBuildActions(ctx SingletonContext) {
+type bazelQueryViewSingleton struct{}
+type bazelConverterSingleton struct{}
+
+func generateBuildActionsForBazelConversion(ctx SingletonContext, converterMode bool) {
+	name := "queryview"
+	additionalEnvVars := ""
+	descriptionTemplate := "[EXPERIMENTAL, PRE-PRODUCTION] Creating the Bazel QueryView workspace with %s at $outDir"
+	if converterMode {
+		name = "bp2build"
+		additionalEnvVars = "CONVERT_TO_BAZEL=true"
+		descriptionTemplate = "[EXPERIMENTAL, PRE-PRODUCTION] Converting all Android.bp to Bazel BUILD files with %s at $outDir"
+	}
+
 	// Create a build and rule statement, using the Bazel QueryView's WORKSPACE
 	// file as the output file marker.
 	var deps Paths
@@ -42,22 +67,23 @@
 	deps = append(deps, moduleListFilePath)
 	deps = append(deps, pathForBuildToolDep(ctx, ctx.Config().ProductVariablesFileName))
 
-	bazelQueryViewDirectory := PathForOutput(ctx, "queryview")
+	bazelQueryViewDirectory := PathForOutput(ctx, name)
 	bazelQueryViewWorkspaceFile := bazelQueryViewDirectory.Join(ctx, "WORKSPACE")
 	primaryBuilder := primaryBuilderPath(ctx)
 	bazelQueryView := ctx.Rule(pctx, "bazelQueryView",
 		blueprint.RuleParams{
 			Command: fmt.Sprintf(
 				"rm -rf ${outDir}/* && "+
-					"%s --bazel_queryview_dir ${outDir} %s && "+
+					"%s %s --bazel_queryview_dir ${outDir} %s && "+
 					"echo WORKSPACE: `cat %s` > ${outDir}/.queryview-depfile.d",
+				additionalEnvVars,
 				primaryBuilder.String(),
 				strings.Join(os.Args[1:], " "),
 				moduleListFilePath.String(), // Use the contents of Android.bp.list as the depfile.
 			),
 			CommandDeps: []string{primaryBuilder.String()},
 			Description: fmt.Sprintf(
-				"[EXPERIMENTAL, PRE-PRODUCTION] Creating the Bazel QueryView workspace with %s at $outDir",
+				descriptionTemplate,
 				primaryBuilder.Base()),
 			Deps:    blueprint.DepsGCC,
 			Depfile: "${outDir}/.queryview-depfile.d",
@@ -73,6 +99,14 @@
 		},
 	})
 
-	// Add a phony target for building the Bazel QueryView
-	ctx.Phony("queryview", bazelQueryViewWorkspaceFile)
+	// Add a phony target for generating the workspace
+	ctx.Phony(name, bazelQueryViewWorkspaceFile)
+}
+
+func (c *bazelQueryViewSingleton) GenerateBuildActions(ctx SingletonContext) {
+	generateBuildActionsForBazelConversion(ctx, false)
+}
+
+func (c *bazelConverterSingleton) GenerateBuildActions(ctx SingletonContext) {
+	generateBuildActionsForBazelConversion(ctx, true)
 }
diff --git a/android/register.go b/android/register.go
index 08e47b3..b26f9b9 100644
--- a/android/register.go
+++ b/android/register.go
@@ -90,6 +90,21 @@
 	return ctx
 }
 
+// RegisterForBazelConversion registers an alternate shadow pipeline of
+// singletons, module types and mutators to register for converting Blueprint
+// files to semantically equivalent BUILD files.
+func (ctx *Context) RegisterForBazelConversion() {
+	for _, t := range moduleTypes {
+		ctx.RegisterModuleType(t.name, ModuleFactoryAdaptor(t.factory))
+	}
+
+	bazelConverterSingleton := singleton{"bp2build", BazelConverterSingleton}
+	ctx.RegisterSingletonType(bazelConverterSingleton.name,
+		SingletonFactoryAdaptor(ctx, bazelConverterSingleton.factory))
+
+	registerMutatorsForBazelConversion(ctx.Context)
+}
+
 func (ctx *Context) Register() {
 	for _, t := range preSingletons {
 		ctx.RegisterPreSingletonType(t.name, SingletonFactoryAdaptor(ctx, t.factory))
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index d758de2..907bed3 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -51,10 +51,22 @@
 	return android.NewNameResolver(exportFilter)
 }
 
+// bazelConversionRequested checks that the user is intending to convert
+// Blueprint to Bazel BUILD files.
+func bazelConversionRequested(configuration android.Config) bool {
+	return configuration.IsEnvTrue("CONVERT_TO_BAZEL")
+}
+
 func newContext(srcDir string, configuration android.Config) *android.Context {
 	ctx := android.NewContext(configuration)
-	ctx.Register()
-	if !shouldPrepareBuildActions() {
+	if bazelConversionRequested(configuration) {
+		// Register an alternate set of singletons and mutators for bazel
+		// conversion for Bazel conversion.
+		ctx.RegisterForBazelConversion()
+	} else {
+		ctx.Register()
+	}
+	if !shouldPrepareBuildActions(configuration) {
 		configuration.SetStopBefore(bootstrap.StopBeforePrepareBuildActions)
 	}
 	ctx.SetNameInterface(newNameResolver(configuration))
@@ -114,6 +126,8 @@
 		ctx = newContext(srcDir, configuration)
 		bootstrap.Main(ctx.Context, configuration, extraNinjaDeps...)
 	}
+
+	// Convert the Soong module graph into Bazel BUILD files.
 	if bazelQueryViewDir != "" {
 		if err := createBazelQueryView(ctx, bazelQueryViewDir); err != nil {
 			fmt.Fprintf(os.Stderr, "%s", err)
@@ -130,7 +144,7 @@
 
 	// TODO(ccross): make this a command line argument.  Requires plumbing through blueprint
 	//  to affect the command line of the primary builder.
-	if shouldPrepareBuildActions() {
+	if shouldPrepareBuildActions(configuration) {
 		metricsFile := filepath.Join(bootstrap.BuildDir, "soong_build_metrics.pb")
 		err := android.WriteMetrics(configuration, metricsFile)
 		if err != nil {
@@ -140,8 +154,19 @@
 	}
 }
 
-func shouldPrepareBuildActions() bool {
-	// If we're writing soong_docs or queryview, don't write build.ninja or
-	// collect metrics.
-	return docFile == "" && bazelQueryViewDir == ""
+// shouldPrepareBuildActions reads configuration and flags if build actions
+// should be generated.
+func shouldPrepareBuildActions(configuration android.Config) bool {
+	// Generating Soong docs
+	if docFile != "" {
+		return false
+	}
+
+	// Generating a directory for Soong query (queryview)
+	if bazelQueryViewDir != "" {
+		return false
+	}
+
+	// Generating a directory for converted Bazel BUILD files
+	return !bazelConversionRequested(configuration)
 }
diff --git a/cmd/soong_build/writedocs.go b/cmd/soong_build/writedocs.go
index 5fb6e6b..253979e 100644
--- a/cmd/soong_build/writedocs.go
+++ b/cmd/soong_build/writedocs.go
@@ -44,9 +44,10 @@
 	"name":             0,
 	"src":              1,
 	"srcs":             2,
-	"defaults":         3,
-	"host_supported":   4,
-	"device_supported": 5,
+	"exclude_srcs":     3,
+	"defaults":         4,
+	"host_supported":   5,
+	"device_supported": 6,
 }
 
 // For each module type, extract its documentation and convert it to the template data.
diff --git a/dexpreopt/class_loader_context.go b/dexpreopt/class_loader_context.go
index 3759217..deaf77f 100644
--- a/dexpreopt/class_loader_context.go
+++ b/dexpreopt/class_loader_context.go
@@ -437,7 +437,11 @@
 			if sdkVer == AnySdkVersion {
 				// Return error if dexpreopt doesn't know paths to one of the <uses-library>
 				// dependencies. In the future we may need to relax this and just disable dexpreopt.
-				return false, fmt.Errorf("invalid path for <uses-library> \"%s\"", clc.Name)
+				if clc.Host == nil {
+					return false, fmt.Errorf("invalid build path for <uses-library> \"%s\"", clc.Name)
+				} else {
+					return false, fmt.Errorf("invalid install path for <uses-library> \"%s\"", clc.Name)
+				}
 			} else {
 				// No error for compatibility libraries, as Soong doesn't know if they are needed
 				// (this depends on the targetSdkVersion in the manifest), but the CLC is invalid.
diff --git a/dexpreopt/class_loader_context_test.go b/dexpreopt/class_loader_context_test.go
index df68563..be7d4c6 100644
--- a/dexpreopt/class_loader_context_test.go
+++ b/dexpreopt/class_loader_context_test.go
@@ -195,7 +195,7 @@
 	// But class loader context in such cases should raise an error on validation.
 	t.Run("validate", func(t *testing.T) {
 		_, err := validateClassLoaderContext(m)
-		checkError(t, err, "invalid path for <uses-library> \"a\"")
+		checkError(t, err, "invalid build path for <uses-library> \"a\"")
 	})
 }
 
diff --git a/java/aar.go b/java/aar.go
index 1940d7f..3b6b34e 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -407,6 +407,7 @@
 
 	ctx.VisitDirectDeps(func(module android.Module) {
 		depName := ctx.OtherModuleName(module)
+		depTag := ctx.OtherModuleDependencyTag(module)
 
 		var exportPackage android.Path
 		aarDep, _ := module.(AndroidLibraryDependency)
@@ -414,7 +415,7 @@
 			exportPackage = aarDep.ExportPackage()
 		}
 
-		switch ctx.OtherModuleDependencyTag(module) {
+		switch depTag {
 		case instrumentationForTag:
 			// Nothing, instrumentationForTag is treated as libTag for javac but not for aapt2.
 		case libTag:
@@ -439,7 +440,6 @@
 				transitiveStaticLibs = append(transitiveStaticLibs, aarDep.ExportedStaticPackages()...)
 				transitiveStaticLibs = append(transitiveStaticLibs, exportPackage)
 				transitiveStaticLibManifests = append(transitiveStaticLibManifests, aarDep.ExportedManifests()...)
-				classLoaderContexts.AddContextMap(aarDep.ClassLoaderContexts(), depName)
 				if aarDep.ExportedAssets().Valid() {
 					assets = append(assets, aarDep.ExportedAssets().Path())
 				}
@@ -458,11 +458,8 @@
 			}
 		}
 
-		// Add nested dependencies after processing the direct dependency: if it is a <uses-library>,
-		// nested context is added as its subcontext, and should not be re-added at the top-level.
-		if dep, ok := module.(Dependency); ok {
-			classLoaderContexts.AddContextMap(dep.ClassLoaderContexts(), depName)
-		}
+		// Merge dep's CLC after processing the dep itself (which may add its own <uses-library>).
+		maybeAddCLCFromDep(module, depTag, depName, classLoaderContexts)
 	})
 
 	deps = append(deps, sharedLibs...)
diff --git a/java/app.go b/java/app.go
index 4bf9d33..e6d9550 100755
--- a/java/app.go
+++ b/java/app.go
@@ -1400,6 +1400,13 @@
 	archProps := reflect.ValueOf(a.archVariants).Elem().FieldByName("Arch")
 	archType := ctx.Config().AndroidFirstDeviceTarget.Arch.ArchType
 	MergePropertiesFromVariant(ctx, &a.properties, archProps, archType.Name)
+
+	if String(a.properties.Apk) == "" {
+		// Disable this module since the apk property is still empty after processing all matching
+		// variants. This likely means there is no matching variant, and the default variant doesn't
+		// have an apk property value either.
+		a.Disable()
+	}
 }
 
 func MergePropertiesFromVariant(ctx android.EarlyModuleContext,
diff --git a/java/app_test.go b/java/app_test.go
index 6efb0dc..ef5e84d 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -2524,6 +2524,24 @@
 			`,
 			expected: "prebuilts/apk/app.apk",
 		},
+		{
+			name: "no matching arch without default",
+			bp: `
+				android_app_import {
+					name: "foo",
+					arch: {
+						arm: {
+							apk: "prebuilts/apk/app_arm.apk",
+						},
+					},
+					presigned: true,
+					dex_preopt: {
+						enabled: true,
+					},
+				}
+			`,
+			expected: "",
+		},
 	}
 
 	jniRuleRe := regexp.MustCompile("^if \\(zipinfo (\\S+)")
@@ -2531,6 +2549,12 @@
 		ctx, _ := testJava(t, test.bp)
 
 		variant := ctx.ModuleForTests("foo", "android_common")
+		if test.expected == "" {
+			if variant.Module().Enabled() {
+				t.Error("module should have been disabled, but wasn't")
+			}
+			continue
+		}
 		jniRuleCommand := variant.Output("jnis-uncompressed/foo.apk").RuleParams.Command
 		matches := jniRuleRe.FindStringSubmatch(jniRuleCommand)
 		if len(matches) != 2 {
@@ -2730,6 +2754,13 @@
 		}
 
 		java_sdk_library {
+			name: "fred",
+			srcs: ["a.java"],
+			api_packages: ["fred"],
+			sdk_version: "current",
+		}
+
+		java_sdk_library {
 			name: "bar",
 			srcs: ["a.java"],
 			api_packages: ["bar"],
@@ -2753,7 +2784,12 @@
 			name: "app",
 			srcs: ["a.java"],
 			libs: ["qux", "quuz.stubs"],
-			static_libs: ["static-runtime-helper"],
+			static_libs: [
+				"static-runtime-helper",
+				// statically linked component libraries should not pull their SDK libraries,
+				// so "fred" should not be added to class loader context
+				"fred.stubs",
+			],
 			uses_libs: ["foo"],
 			sdk_version: "current",
 			optional_uses_libs: [
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index f16ddf1..da7f291 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -506,7 +506,7 @@
 			m := image.modules.Jar(i)
 			if ctx.Config().AllowMissingDependencies() {
 				missingDeps = append(missingDeps, m)
-				bootDexJars[i] = android.PathForOutput(ctx, "missing")
+				bootDexJars[i] = android.PathForOutput(ctx, "missing/module", m, "from/apex", image.modules.Apex(i))
 			} else {
 				ctx.Errorf("failed to find a dex jar path for module '%s'"+
 					", note that some jars may be filtered out by module constraints", m)
@@ -779,7 +779,7 @@
 			bootFrameworkProfile = path.Path()
 		} else {
 			missingDeps = append(missingDeps, defaultProfile)
-			bootFrameworkProfile = android.PathForOutput(ctx, "missing")
+			bootFrameworkProfile = android.PathForOutput(ctx, "missing", defaultProfile)
 		}
 
 		profile := image.dir.Join(ctx, "boot.bprof")
diff --git a/java/hiddenapi_singleton.go b/java/hiddenapi_singleton.go
index ce8410e..419dc34 100644
--- a/java/hiddenapi_singleton.go
+++ b/java/hiddenapi_singleton.go
@@ -177,12 +177,13 @@
 	for moduleList, pathList := range moduleListToPathList {
 		for i := range pathList {
 			if pathList[i] == nil {
-				pathList[i] = android.PathForOutput(ctx, "missing")
+				moduleName := (*moduleList)[i]
+				pathList[i] = android.PathForOutput(ctx, "missing/module", moduleName)
 				if ctx.Config().AllowMissingDependencies() {
-					missingDeps = append(missingDeps, (*moduleList)[i])
+					missingDeps = append(missingDeps, moduleName)
 				} else {
 					ctx.Errorf("failed to find dex jar path for module %q",
-						(*moduleList)[i])
+						moduleName)
 				}
 			}
 		}
diff --git a/java/java.go b/java/java.go
index 3d121cc..d44719e 100644
--- a/java/java.go
+++ b/java/java.go
@@ -1081,7 +1081,6 @@
 			switch tag {
 			case libTag:
 				deps.classpath = append(deps.classpath, dep.SdkHeaderJars(ctx, j.sdkVersion())...)
-				// names of sdk libs that are directly depended are exported
 				j.classLoaderContexts.MaybeAddContext(ctx, dep.OptionalImplicitSdkLibrary(),
 					dep.DexJarBuildPath(), dep.DexJarInstallPath())
 			case staticLibTag:
@@ -1093,7 +1092,6 @@
 				deps.bootClasspath = append(deps.bootClasspath, dep.HeaderJars()...)
 			case libTag, instrumentationForTag:
 				deps.classpath = append(deps.classpath, dep.HeaderJars()...)
-				// sdk lib names from dependencies are re-exported
 				j.classLoaderContexts.AddContextMap(dep.ClassLoaderContexts(), otherName)
 				deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs()...)
 				pluginJars, pluginClasses, disableTurbine := dep.ExportedPlugins()
@@ -1106,8 +1104,6 @@
 				deps.staticJars = append(deps.staticJars, dep.ImplementationJars()...)
 				deps.staticHeaderJars = append(deps.staticHeaderJars, dep.HeaderJars()...)
 				deps.staticResourceJars = append(deps.staticResourceJars, dep.ResourceJars()...)
-				// sdk lib names from dependencies are re-exported
-				j.classLoaderContexts.AddContextMap(dep.ClassLoaderContexts(), otherName)
 				deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs()...)
 				pluginJars, pluginClasses, disableTurbine := dep.ExportedPlugins()
 				addPlugins(&deps, pluginJars, pluginClasses...)
@@ -1182,6 +1178,9 @@
 				deps.systemModules = &systemModules{outputDir, outputDeps}
 			}
 		}
+
+		// Merge dep's CLC after processing the dep itself (which may add its own <uses-library>).
+		maybeAddCLCFromDep(module, tag, otherName, j.classLoaderContexts)
 	})
 
 	return deps
@@ -2815,8 +2814,6 @@
 			switch tag {
 			case libTag, staticLibTag:
 				flags.classpath = append(flags.classpath, dep.HeaderJars()...)
-				// sdk lib names from dependencies are re-exported
-				j.classLoaderContexts.AddContextMap(dep.ClassLoaderContexts(), otherName)
 			case bootClasspathTag:
 				flags.bootClasspath = append(flags.bootClasspath, dep.HeaderJars()...)
 			}
@@ -2824,10 +2821,12 @@
 			switch tag {
 			case libTag:
 				flags.classpath = append(flags.classpath, dep.SdkHeaderJars(ctx, j.sdkVersion())...)
-				// names of sdk libs that are directly depended are exported
 				j.classLoaderContexts.AddContext(ctx, otherName, dep.DexJarBuildPath(), dep.DexJarInstallPath())
 			}
 		}
+
+		// Merge dep's CLC after processing the dep itself (which may add its own <uses-library>).
+		maybeAddCLCFromDep(module, tag, otherName, j.classLoaderContexts)
 	})
 
 	var installFile android.Path
@@ -3248,3 +3247,31 @@
 var BoolDefault = proptools.BoolDefault
 var String = proptools.String
 var inList = android.InList
+
+// Add class loader context of a given dependency to the given class loader context, provided that
+// all the necessary conditions are met.
+func maybeAddCLCFromDep(depModule android.Module, depTag blueprint.DependencyTag,
+	depName string, clcMap dexpreopt.ClassLoaderContextMap) {
+
+	if dep, ok := depModule.(Dependency); ok {
+		if depTag == libTag {
+			// Ok, propagate <uses-library> through non-static library dependencies.
+		} else if depTag == staticLibTag {
+			// Propagate <uses-library> through static library dependencies, unless it is a
+			// component library (such as stubs). Component libraries have a dependency on their
+			// SDK library, which should not be pulled just because of a static component library.
+			if comp, isComp := depModule.(SdkLibraryComponentDependency); isComp {
+				if compName := comp.OptionalImplicitSdkLibrary(); compName != nil {
+					dep = nil
+				}
+			}
+		} else {
+			// Don't propagate <uses-library> for other dependency tags.
+			dep = nil
+		}
+
+		if dep != nil {
+			clcMap.AddContextMap(dep.ClassLoaderContexts(), depName)
+		}
+	}
+}
diff --git a/ui/build/bazel.go b/ui/build/bazel.go
index fac0461..23b14ee 100644
--- a/ui/build/bazel.go
+++ b/ui/build/bazel.go
@@ -15,6 +15,8 @@
 package build
 
 import (
+	"bytes"
+	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -24,7 +26,7 @@
 	"android/soong/ui/metrics"
 )
 
-func getBazelInfo(ctx Context, config Config, bazelExecutable string, query string) string {
+func getBazelInfo(ctx Context, config Config, bazelExecutable string, bazelEnv map[string]string, query string) string {
 	infoCmd := Command(ctx, config, "bazel", bazelExecutable)
 
 	if extraStartupArgs, ok := infoCmd.Environment.Get("BAZEL_STARTUP_ARGS"); ok {
@@ -37,8 +39,9 @@
 		query,
 	)
 
-	infoCmd.Environment.Set("DIST_DIR", config.DistDir())
-	infoCmd.Environment.Set("SHELL", "/bin/bash")
+	for k, v := range bazelEnv {
+		infoCmd.Environment.Set(k, v)
+	}
 
 	infoCmd.Dir = filepath.Join(config.OutDir(), "..")
 
@@ -65,14 +68,18 @@
 
 	// Environment variables are the primary mechanism to pass information from
 	// soong_ui configuration or context to Bazel.
-	//
+	bazelEnv := make(map[string]string)
+
 	// Use *_NINJA variables to pass the root-relative path of the combined,
 	// kati-generated, soong-generated, and packaging Ninja files to Bazel.
 	// Bazel reads these from the lunch() repository rule.
-	config.environ.Set("COMBINED_NINJA", config.CombinedNinjaFile())
-	config.environ.Set("KATI_NINJA", config.KatiBuildNinjaFile())
-	config.environ.Set("PACKAGE_NINJA", config.KatiPackageNinjaFile())
-	config.environ.Set("SOONG_NINJA", config.SoongNinjaFile())
+	bazelEnv["COMBINED_NINJA"] = config.CombinedNinjaFile()
+	bazelEnv["KATI_NINJA"] = config.KatiBuildNinjaFile()
+	bazelEnv["PACKAGE_NINJA"] = config.KatiPackageNinjaFile()
+	bazelEnv["SOONG_NINJA"] = config.SoongNinjaFile()
+
+	bazelEnv["DIST_DIR"] = config.DistDir()
+	bazelEnv["SHELL"] = "/bin/bash"
 
 	// `tools/bazel` is the default entry point for executing Bazel in the AOSP
 	// source tree.
@@ -126,7 +133,7 @@
 
 		// We need to calculate --RBE_exec_root ourselves
 		ctx.Println("Getting Bazel execution_root...")
-		cmd.Args = append(cmd.Args, "--action_env=RBE_exec_root="+getBazelInfo(ctx, config, bazelExecutable, "execution_root"))
+		cmd.Args = append(cmd.Args, "--action_env=RBE_exec_root="+getBazelInfo(ctx, config, bazelExecutable, bazelEnv, "execution_root"))
 	}
 
 	// Ensure that the PATH environment variable value used in the action
@@ -149,15 +156,21 @@
 		"//:"+config.TargetProduct()+"-"+config.TargetBuildVariant(),
 	)
 
-	cmd.Environment.Set("DIST_DIR", config.DistDir())
-	cmd.Environment.Set("SHELL", "/bin/bash")
-
-	// Print the full command line for debugging purposes.
-	ctx.Println(cmd.Cmd)
-
 	// Execute the command at the root of the directory.
 	cmd.Dir = filepath.Join(config.OutDir(), "..")
-	ctx.Status.Status("Starting Bazel..")
+
+	for k, v := range bazelEnv {
+		cmd.Environment.Set(k, v)
+	}
+
+	// Make a human-readable version of the bazelEnv map
+	bazelEnvStringBuffer := new(bytes.Buffer)
+	for k, v := range bazelEnv {
+		fmt.Fprintf(bazelEnvStringBuffer, "%s=%s ", k, v)
+	}
+
+	// Print the full command line (including environment variables) for debugging purposes.
+	ctx.Println("Bazel command line: " + bazelEnvStringBuffer.String() + cmd.Cmd.String() + "\n")
 
 	// Execute the build command.
 	cmd.RunAndStreamOrFatal()
@@ -168,11 +181,12 @@
 	// the files from the execution root's output direction into $OUT_DIR.
 
 	ctx.Println("Getting Bazel output_path...")
-	outputBasePath := getBazelInfo(ctx, config, bazelExecutable, "output_path")
+	outputBasePath := getBazelInfo(ctx, config, bazelExecutable, bazelEnv, "output_path")
 	// TODO: Don't hardcode out/ as the bazel output directory. This is
 	// currently hardcoded as ninja_build.output_root.
 	bazelNinjaBuildOutputRoot := filepath.Join(outputBasePath, "..", "out")
 
+	ctx.Println("Creating output symlinks..")
 	symlinkOutdir(ctx, config, bazelNinjaBuildOutputRoot, ".")
 }
 
@@ -185,27 +199,39 @@
 	if err != nil {
 		ctx.Fatal(err)
 	}
+
 	for _, f := range files {
+		// The original Bazel file path
 		destPath := filepath.Join(destDir, f.Name())
+
+		// The desired Soong file path
 		srcPath := filepath.Join(config.OutDir(), relativePath, f.Name())
-		if statResult, err := os.Stat(srcPath); err == nil {
-			if statResult.Mode().IsDir() && f.IsDir() {
-				// Directory under OutDir already exists, so recurse on its contents.
+
+		destLstatResult, destLstatErr := os.Lstat(destPath)
+		if destLstatErr != nil {
+			ctx.Fatalf("Unable to Lstat dest %s: %s", destPath, destLstatErr)
+		}
+
+		srcLstatResult, srcLstatErr := os.Lstat(srcPath)
+
+		if srcLstatErr == nil {
+			if srcLstatResult.IsDir() && destLstatResult.IsDir() {
+				// src and dest are both existing dirs - recurse on the dest dir contents...
 				symlinkOutdir(ctx, config, rootPath, filepath.Join(relativePath, f.Name()))
-			} else if !statResult.Mode().IsDir() && !f.IsDir() {
-				// File exists both in source and destination, and it's not a directory
-				// in either location. Do nothing.
-				// This can arise for files which are generated under OutDir outside of
-				// soong_build, such as .bootstrap files.
 			} else {
-				// File is a directory in one location but not the other. Raise an error.
-				ctx.Fatalf("Could not link %s to %s due to conflict", srcPath, destPath)
+				// Ignore other pre-existing src files (could be pre-existing files, directories, symlinks, ...)
+				// This can arise for files which are generated under OutDir outside of soong_build, such as .bootstrap files.
+				// FIXME: This might cause a problem later e.g. if a symlink in the build graph changes...
 			}
-		} else if os.IsNotExist(err) {
-			// Create symlink srcPath -> fullDestPath.
-			os.Symlink(destPath, srcPath)
 		} else {
-			ctx.Fatalf("Unable to stat %s: %s", srcPath, err)
+			if !os.IsNotExist(srcLstatErr) {
+				ctx.Fatalf("Unable to Lstat src %s: %s", srcPath, srcLstatErr)
+			}
+
+			// src does not exist, so try to create a src -> dest symlink (i.e. a Soong path -> Bazel path symlink)
+			if symlinkErr := os.Symlink(destPath, srcPath); symlinkErr != nil {
+				ctx.Fatalf("Unable to create symlink %s -> %s due to error %s", srcPath, destPath, symlinkErr)
+			}
 		}
 	}
 }
diff --git a/ui/build/ninja.go b/ui/build/ninja.go
index ffd1ab9..7799766 100644
--- a/ui/build/ninja.go
+++ b/ui/build/ninja.go
@@ -139,15 +139,6 @@
 			// b/147197813 - used by art-check-debug-apex-gen
 			"EMMA_INSTRUMENT_FRAMEWORK",
 
-			// Goma -- gomacc may not need all of these
-			"GOMA_DIR",
-			"GOMA_DISABLED",
-			"GOMA_FAIL_FAST",
-			"GOMA_FALLBACK",
-			"GOMA_GCE_SERVICE_ACCOUNT",
-			"GOMA_TMP_DIR",
-			"GOMA_USE_LOCAL",
-
 			// RBE client
 			"RBE_compare",
 			"RBE_exec_root",