Merge "Add android.SdkModule to Sdk Versions that have AIDL traces generated"
diff --git a/android/allowlists/allowlists.go b/android/allowlists/allowlists.go
index 16f144e..b82ea4f 100644
--- a/android/allowlists/allowlists.go
+++ b/android/allowlists/allowlists.go
@@ -347,6 +347,7 @@
 		".":/*recursive = */ false,
 
 		"build/bazel":/* recursive = */ true,
+		"build/make/core":/* recursive = */ false,
 		"build/bazel_common_rules":/* recursive = */ true,
 		// build/make/tools/signapk BUILD file is generated, so build/make/tools is not recursive.
 		"build/make/tools":/* recursive = */ false,
@@ -382,6 +383,8 @@
 		"prebuilts/sdk":/* recursive = */ false,
 		"prebuilts/sdk/tools":/* recursive = */ false,
 		"prebuilts/r8":/* recursive = */ false,
+
+		"tools/asuite/atest/":/* recursive = */ true,
 	}
 
 	Bp2buildModuleAlwaysConvertList = []string{
diff --git a/android/arch.go b/android/arch.go
index 086e945..6acf9cf 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -16,6 +16,7 @@
 
 import (
 	"encoding"
+	"encoding/json"
 	"fmt"
 	"reflect"
 	"runtime"
@@ -1681,10 +1682,10 @@
 
 // archConfig describes a built-in configuration.
 type archConfig struct {
-	arch        string
-	archVariant string
-	cpuVariant  string
-	abi         []string
+	Arch        string   `json:"arch"`
+	ArchVariant string   `json:"arch_variant"`
+	CpuVariant  string   `json:"cpu_variant"`
+	Abi         []string `json:"abis"`
 }
 
 // getNdkAbisConfig returns the list of archConfigs that are used for building
@@ -1713,8 +1714,8 @@
 	var ret []Target
 
 	for _, config := range archConfigs {
-		arch, err := decodeArch(Android, config.arch, &config.archVariant,
-			&config.cpuVariant, config.abi)
+		arch, err := decodeArch(Android, config.Arch, &config.ArchVariant,
+			&config.CpuVariant, config.Abi)
 		if err != nil {
 			return nil, err
 		}
@@ -2284,6 +2285,14 @@
 	return starlark_fmt.PrintDict(valDict, 0)
 }
 
+func printArchConfigList(arches []archConfig) string {
+	jsonOut, err := json.MarshalIndent(arches, "", starlark_fmt.Indention(1))
+	if err != nil {
+		panic(fmt.Errorf("Error converting arch configs %#v to json: %q", arches, err))
+	}
+	return fmt.Sprintf("json.decode('''%s''')", string(jsonOut))
+}
+
 func StarlarkArchConfigurations() string {
 	return fmt.Sprintf(`
 _arch_to_variants = %s
@@ -2294,13 +2303,21 @@
 
 _android_arch_feature_for_arch_variant = %s
 
+_aml_arches = %s
+
+_ndk_arches = %s
+
 arch_to_variants = _arch_to_variants
 arch_to_cpu_variants = _arch_to_cpu_variants
 arch_to_features = _arch_to_features
 android_arch_feature_for_arch_variants = _android_arch_feature_for_arch_variant
+aml_arches = _aml_arches
+ndk_arches = _ndk_arches
 `, printArchTypeStarlarkDict(archVariants),
 		printArchTypeStarlarkDict(cpuVariants),
 		printArchTypeStarlarkDict(archFeatures),
 		printArchTypeNestedStarlarkDict(androidArchFeatureMap),
+		printArchConfigList(getAmlAbisConfig()),
+		printArchConfigList(getNdkAbisConfig()),
 	)
 }
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index d014fc6..eec78d2 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -317,7 +317,7 @@
 func (bazelCtx *bazelContext) GetApexInfo(label string, cfgKey configKey) (cquery.ApexInfo, error) {
 	key := makeCqueryKey(label, cquery.GetApexInfo, cfgKey)
 	if rawString, ok := bazelCtx.results[key]; ok {
-		return cquery.GetApexInfo.ParseResult(strings.TrimSpace(rawString)), nil
+		return cquery.GetApexInfo.ParseResult(strings.TrimSpace(rawString))
 	}
 	return cquery.ApexInfo{}, fmt.Errorf("no bazel response found for %v", key)
 }
@@ -325,7 +325,7 @@
 func (bazelCtx *bazelContext) GetCcUnstrippedInfo(label string, cfgKey configKey) (cquery.CcUnstrippedInfo, error) {
 	key := makeCqueryKey(label, cquery.GetCcUnstrippedInfo, cfgKey)
 	if rawString, ok := bazelCtx.results[key]; ok {
-		return cquery.GetCcUnstrippedInfo.ParseResult(strings.TrimSpace(rawString)), nil
+		return cquery.GetCcUnstrippedInfo.ParseResult(strings.TrimSpace(rawString))
 	}
 	return cquery.CcUnstrippedInfo{}, fmt.Errorf("no bazel response for %s", key)
 }
diff --git a/android/config.go b/android/config.go
index 0c3e2d1..1deb7d4 100644
--- a/android/config.go
+++ b/android/config.go
@@ -1272,10 +1272,6 @@
 	return coverage
 }
 
-func (c *deviceConfig) AfdoAdditionalProfileDirs() []string {
-	return c.config.productVariables.AfdoAdditionalProfileDirs
-}
-
 func (c *deviceConfig) PgoAdditionalProfileDirs() []string {
 	return c.config.productVariables.PgoAdditionalProfileDirs
 }
diff --git a/android/fixture.go b/android/fixture.go
index f33e718..3f01f5a 100644
--- a/android/fixture.go
+++ b/android/fixture.go
@@ -658,7 +658,7 @@
 
 func createFixture(t *testing.T, buildDir string, preparers []*simpleFixturePreparer) Fixture {
 	config := TestConfig(buildDir, nil, "", nil)
-	ctx := NewTestContext(config)
+	ctx := newTestContextForFixture(config)
 	fixture := &fixture{
 		preparers: preparers,
 		t:         t,
@@ -790,6 +790,16 @@
 		}
 	}
 
+	// Create and set the Context's NameInterface. It needs to be created here as it depends on the
+	// configuration that has been prepared for this fixture.
+	resolver := NewNameResolver(ctx.config)
+
+	// Set the NameInterface in the main Context.
+	ctx.SetNameInterface(resolver)
+
+	// Set the NameResolver in the TestContext.
+	ctx.NameResolver = resolver
+
 	ctx.Register()
 	var ninjaDeps []string
 	extraNinjaDeps, errs := ctx.ParseBlueprintsFiles("ignored")
diff --git a/android/gen_notice.go b/android/gen_notice.go
index 008aac5..28fddbc 100644
--- a/android/gen_notice.go
+++ b/android/gen_notice.go
@@ -61,6 +61,9 @@
 				if mod == nil {
 					continue
 				}
+				if !mod.Enabled() { // don't depend on variants without build rules
+					continue
+				}
 				modules = append(modules, mod)
 			}
 		}
diff --git a/android/namespace.go b/android/namespace.go
index a3ff761..b43ffdf 100644
--- a/android/namespace.go
+++ b/android/namespace.go
@@ -91,7 +91,27 @@
 	namespaceExportFilter func(*Namespace) bool
 }
 
-func NewNameResolver(namespaceExportFilter func(*Namespace) bool) *NameResolver {
+// NameResolverConfig provides the subset of the Config interface needed by the
+// NewNameResolver function.
+type NameResolverConfig interface {
+	// ExportedNamespaces is the list of namespaces that Soong must export to
+	// make.
+	ExportedNamespaces() []string
+}
+
+func NewNameResolver(config NameResolverConfig) *NameResolver {
+	namespacePathsToExport := make(map[string]bool)
+
+	for _, namespaceName := range config.ExportedNamespaces() {
+		namespacePathsToExport[namespaceName] = true
+	}
+
+	namespacePathsToExport["."] = true // always export the root namespace
+
+	namespaceExportFilter := func(namespace *Namespace) bool {
+		return namespacePathsToExport[namespace.Path]
+	}
+
 	r := &NameResolver{
 		namespacesByDir:       sync.Map{},
 		namespaceExportFilter: namespaceExportFilter,
diff --git a/android/namespace_test.go b/android/namespace_test.go
index 87d1320..7a387a0 100644
--- a/android/namespace_test.go
+++ b/android/namespace_test.go
@@ -602,6 +602,36 @@
 	// RunTest will report any errors
 }
 
+func TestNamespace_Exports(t *testing.T) {
+	result := GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		FixtureModifyProductVariables(func(variables FixtureProductVariables) {
+			variables.NamespacesToExport = []string{"dir1"}
+		}),
+		dirBpToPreparer(map[string]string{
+			"dir1": `
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+				}
+			`,
+			"dir2": `
+				soong_namespace {
+				}
+				test_module {
+					name: "b",
+				}
+			`,
+		}),
+	).RunTest(t)
+
+	aModule := result.Module("a", "")
+	AssertBoolEquals(t, "a exported", true, aModule.ExportedToMake())
+	bModule := result.Module("b", "")
+	AssertBoolEquals(t, "b not exported", false, bModule.ExportedToMake())
+}
+
 // some utils to support the tests
 
 var prepareForTestWithNamespace = GroupFixturePreparers(
diff --git a/android/sdk.go b/android/sdk.go
index bd2f5d1..fc0a84e 100644
--- a/android/sdk.go
+++ b/android/sdk.go
@@ -232,12 +232,6 @@
 	// relative path) and add the dest to the zip.
 	CopyToSnapshot(src Path, dest string)
 
-	// EmptyFile returns the path to an empty file.
-	//
-	// This can be used by sdk member types that need to create an empty file in the snapshot, simply
-	// pass the value returned from this to the CopyToSnapshot() method.
-	EmptyFile() Path
-
 	// UnzipToSnapshot generates a rule that will unzip the supplied zip into the snapshot relative
 	// directory destDir.
 	UnzipToSnapshot(zipPath Path, destDir string)
@@ -264,6 +258,14 @@
 	// See sdk/update.go for more information.
 	AddPrebuiltModule(member SdkMember, moduleType string) BpModule
 
+	// AddInternalModule creates a new module in the generated Android.bp file that can only be
+	// referenced by one of the other modules in the snapshot.
+	//
+	// The created module's name is constructed by concatenating the name of this member and the
+	// nameSuffix, separated by "-". It also has the visibility property set to "//visibility:private"
+	// to prevent it from being inadvertently accessed from outside the snapshot.
+	AddInternalModule(properties SdkMemberProperties, moduleType string, nameSuffix string) BpModule
+
 	// SdkMemberReferencePropertyTag returns a property tag to use when adding a property to a
 	// BpModule that contains references to other sdk members.
 	//
@@ -922,6 +924,12 @@
 //
 // Contains common properties that apply across many different member types.
 type SdkMemberPropertiesBase struct {
+	// The name of the member.
+	//
+	// Ignore this property during optimization. This is needed because this property is the same for
+	// all variants of a member and so would be optimized away if it was not ignored.
+	MemberName string `sdk:"ignore"`
+
 	// The number of unique os types supported by the member variants.
 	//
 	// If a member has a variant with more than one os type then it will need to differentiate
@@ -945,6 +953,10 @@
 	Compile_multilib string `android:"arch_variant"`
 }
 
+func (b *SdkMemberPropertiesBase) Name() string {
+	return b.MemberName
+}
+
 // OsPrefix returns the os prefix to use for any file paths in the sdk.
 //
 // Is an empty string if the member only provides variants for a single os type, otherwise
@@ -970,6 +982,8 @@
 	// Base returns the base structure.
 	Base() *SdkMemberPropertiesBase
 
+	Name() string
+
 	// PopulateFromVariant populates this structure with information from a module variant.
 	//
 	// It will typically be called once for each variant of a member module that the SDK depends upon.
diff --git a/android/testing.go b/android/testing.go
index 2256c96..8fcf440 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -30,19 +30,11 @@
 	"github.com/google/blueprint/proptools"
 )
 
-func NewTestContext(config Config) *TestContext {
-	namespaceExportFilter := func(namespace *Namespace) bool {
-		return true
-	}
-
-	nameResolver := NewNameResolver(namespaceExportFilter)
+func newTestContextForFixture(config Config) *TestContext {
 	ctx := &TestContext{
-		Context:      &Context{blueprint.NewContext(), config},
-		NameResolver: nameResolver,
+		Context: &Context{blueprint.NewContext(), config},
 	}
 
-	ctx.SetNameInterface(nameResolver)
-
 	ctx.postDeps = append(ctx.postDeps, registerPathDepsMutator)
 
 	ctx.SetFs(ctx.config.fs)
@@ -53,6 +45,16 @@
 	return ctx
 }
 
+func NewTestContext(config Config) *TestContext {
+	ctx := newTestContextForFixture(config)
+
+	nameResolver := NewNameResolver(config)
+	ctx.NameResolver = nameResolver
+	ctx.SetNameInterface(nameResolver)
+
+	return ctx
+}
+
 var PrepareForTestWithArchMutator = GroupFixturePreparers(
 	// Configure architecture targets in the fixture config.
 	FixtureModifyConfig(modifyTestConfigToSupportArchMutator),
diff --git a/android/variable.go b/android/variable.go
index 37ecab5..28f22c9 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -336,8 +336,7 @@
 
 	NamespacesToExport []string `json:",omitempty"`
 
-	AfdoAdditionalProfileDirs []string `json:",omitempty"`
-	PgoAdditionalProfileDirs  []string `json:",omitempty"`
+	PgoAdditionalProfileDirs []string `json:",omitempty"`
 
 	VndkUseCoreVariant         *bool `json:",omitempty"`
 	VndkSnapshotBuildArtifacts *bool `json:",omitempty"`
diff --git a/apex/apex.go b/apex/apex.go
index 72403f9..09de2d4 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -1911,6 +1911,7 @@
 		a.nativeApisBackedByModuleFile = android.ModuleOutPath(android.PathForBazelOut(ctx, outputs.BackingLibs))
 		// TODO(b/239084755): Generate the java api using.xml file from Bazel.
 		a.javaApisUsedByModuleFile = android.ModuleOutPath(android.PathForBazelOut(ctx, outputs.JavaSymbolsUsedByApex))
+		a.installedFilesFile = android.ModuleOutPath(android.PathForBazelOut(ctx, outputs.InstalledFiles))
 		installSuffix := imageApexSuffix
 		if a.isCompressed {
 			installSuffix = imageCapexSuffix
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 985ad59..ea3e734 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -9775,6 +9775,7 @@
 						SymbolsUsedByApex:     "foo_using.txt",
 						JavaSymbolsUsedByApex: "foo_using.xml",
 						BundleFile:            "apex_bundle.zip",
+						InstalledFiles:        "installed-files.txt",
 
 						// unused
 						PackageName:  "pkg_name",
@@ -9820,6 +9821,10 @@
 		t.Errorf("Expected output file %q, got %q", w, g)
 	}
 
+	if w, g := "out/bazel/execroot/__main__/installed-files.txt", ab.installedFilesFile.String(); w != g {
+		t.Errorf("Expected installed-files.txt %q, got %q", w, g)
+	}
+
 	mkData := android.AndroidMkDataForTest(t, result.TestContext, m)
 	var builder strings.Builder
 	mkData.Custom(&builder, "foo", "BAZEL_TARGET_", "", mkData)
@@ -9828,4 +9833,7 @@
 	if w := "ALL_MODULES.$(my_register_name).BUNDLE := out/bazel/execroot/__main__/apex_bundle.zip"; !strings.Contains(data, w) {
 		t.Errorf("Expected %q in androidmk data, but did not find %q", w, data)
 	}
+	if w := "$(call dist-for-goals,checkbuild,out/bazel/execroot/__main__/installed-files.txt:foo-installed-files.txt)"; !strings.Contains(data, w) {
+		t.Errorf("Expected %q in androidmk data, but did not find %q", w, data)
+	}
 }
diff --git a/bazel/cquery/request_type.go b/bazel/cquery/request_type.go
index 5bf9fb2..b675f17 100644
--- a/bazel/cquery/request_type.go
+++ b/bazel/cquery/request_type.go
@@ -194,7 +194,9 @@
 // Starlark given in StarlarkFunctionBody.
 func (g getCcInfoType) ParseResult(rawString string) (CcInfo, error) {
 	var ccInfo CcInfo
-	parseJson(rawString, &ccInfo)
+	if err := parseJson(rawString, &ccInfo); err != nil {
+		return ccInfo, err
+	}
 	return ccInfo, nil
 }
 
@@ -230,6 +232,7 @@
     "java_symbols_used_by_apex": info.java_symbols_used_by_apex.path,
     "backing_libs": info.backing_libs.path,
     "bundle_file": info.base_with_config_zip.path,
+    "installed_files": info.installed_files.path,
 })`
 }
 
@@ -245,15 +248,16 @@
 	JavaSymbolsUsedByApex string   `json:"java_symbols_used_by_apex"`
 	BackingLibs           string   `json:"backing_libs"`
 	BundleFile            string   `json:"bundle_file"`
+	InstalledFiles        string   `json:"installed_files"`
 }
 
 // ParseResult returns a value obtained by parsing the result of the request's Starlark function.
 // The given rawString must correspond to the string output which was created by evaluating the
 // Starlark given in StarlarkFunctionBody.
-func (g getApexInfoType) ParseResult(rawString string) ApexInfo {
+func (g getApexInfoType) ParseResult(rawString string) (ApexInfo, error) {
 	var info ApexInfo
-	parseJson(rawString, &info)
-	return info
+	err := parseJson(rawString, &info)
+	return info, err
 }
 
 // getCcUnstrippedInfoType implements cqueryRequest interface. It handles the
@@ -282,10 +286,10 @@
 // ParseResult returns a value obtained by parsing the result of the request's Starlark function.
 // The given rawString must correspond to the string output which was created by evaluating the
 // Starlark given in StarlarkFunctionBody.
-func (g getCcUnstippedInfoType) ParseResult(rawString string) CcUnstrippedInfo {
+func (g getCcUnstippedInfoType) ParseResult(rawString string) (CcUnstrippedInfo, error) {
 	var info CcUnstrippedInfo
-	parseJson(rawString, &info)
-	return info
+	err := parseJson(rawString, &info)
+	return info, err
 }
 
 type CcUnstrippedInfo struct {
@@ -305,10 +309,12 @@
 
 // parseJson decodes json string into the fields of the receiver.
 // Unknown attribute name causes panic.
-func parseJson(jsonString string, info interface{}) {
+func parseJson(jsonString string, info interface{}) error {
 	decoder := json.NewDecoder(strings.NewReader(jsonString))
 	decoder.DisallowUnknownFields() //useful to detect typos, e.g. in unit tests
-	if err := decoder.Decode(info); err != nil {
-		panic(fmt.Errorf("cannot parse cquery result '%s': %s", jsonString, err))
+	err := decoder.Decode(info)
+	if err != nil {
+		return fmt.Errorf("cannot parse cquery result '%s': %s", jsonString, err)
 	}
+	return nil
 }
diff --git a/bazel/cquery/request_type_test.go b/bazel/cquery/request_type_test.go
index 86f128e..1d30535 100644
--- a/bazel/cquery/request_type_test.go
+++ b/bazel/cquery/request_type_test.go
@@ -3,10 +3,12 @@
 import (
 	"encoding/json"
 	"reflect"
+	"strings"
 	"testing"
 )
 
 func TestGetOutputFilesParseResults(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		description    string
 		input          string
@@ -29,14 +31,17 @@
 		},
 	}
 	for _, tc := range testCases {
-		actualOutput := GetOutputFiles.ParseResult(tc.input)
-		if !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
-			t.Errorf("%q: expected %#v != actual %#v", tc.description, tc.expectedOutput, actualOutput)
-		}
+		t.Run(tc.description, func(t *testing.T) {
+			actualOutput := GetOutputFiles.ParseResult(tc.input)
+			if !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
+				t.Errorf("expected %#v != actual %#v", tc.expectedOutput, actualOutput)
+			}
+		})
 	}
 }
 
 func TestGetPythonBinaryParseResults(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		description    string
 		input          string
@@ -54,14 +59,17 @@
 		},
 	}
 	for _, tc := range testCases {
-		actualOutput := GetPythonBinary.ParseResult(tc.input)
-		if !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
-			t.Errorf("%q: expected %#v != actual %#v", tc.description, tc.expectedOutput, actualOutput)
-		}
+		t.Run(tc.description, func(t *testing.T) {
+			actualOutput := GetPythonBinary.ParseResult(tc.input)
+			if !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
+				t.Errorf("expected %#v != actual %#v", tc.expectedOutput, actualOutput)
+			}
+		})
 	}
 }
 
 func TestGetCcInfoParseResults(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		description    string
 		inputCcInfo    CcInfo
@@ -73,24 +81,6 @@
 			expectedOutput: CcInfo{},
 		},
 		{
-			description: "only output",
-			inputCcInfo: CcInfo{
-				OutputFiles: []string{"test", "test3"},
-			},
-			expectedOutput: CcInfo{
-				OutputFiles: []string{"test", "test3"},
-			},
-		},
-		{
-			description: "only ToC",
-			inputCcInfo: CcInfo{
-				TocFile: "test",
-			},
-			expectedOutput: CcInfo{
-				TocFile: "test",
-			},
-		},
-		{
 			description: "all items set",
 			inputCcInfo: CcInfo{
 				OutputFiles:          []string{"out1", "out2"},
@@ -119,17 +109,51 @@
 		},
 	}
 	for _, tc := range testCases {
-		jsonInput, _ := json.Marshal(tc.inputCcInfo)
-		actualOutput, err := GetCcInfo.ParseResult(string(jsonInput))
-		if err != nil {
-			t.Errorf("%q:\n test case get error: %q", tc.description, err)
-		} else if err == nil && !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
-			t.Errorf("%q:\n expected %#v\n!= actual %#v", tc.description, tc.expectedOutput, actualOutput)
-		}
+		t.Run(tc.description, func(t *testing.T) {
+			jsonInput, _ := json.Marshal(tc.inputCcInfo)
+			actualOutput, err := GetCcInfo.ParseResult(string(jsonInput))
+			if err != nil {
+				t.Errorf("error parsing result: %q", err)
+			} else if err == nil && !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
+				t.Errorf("expected %#v\n!= actual %#v", tc.expectedOutput, actualOutput)
+			}
+		})
+	}
+}
+
+func TestGetCcInfoParseResultsError(t *testing.T) {
+	t.Parallel()
+	testCases := []struct {
+		description   string
+		input         string
+		expectedError string
+	}{
+		{
+			description:   "not json",
+			input:         ``,
+			expectedError: `cannot parse cquery result '': EOF`,
+		},
+		{
+			description: "invalid field",
+			input: `{
+	"toc_file": "dir/file.so.toc"
+}`,
+			expectedError: `json: unknown field "toc_file"`,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.description, func(t *testing.T) {
+			_, err := GetCcInfo.ParseResult(tc.input)
+			if !strings.Contains(err.Error(), tc.expectedError) {
+				t.Errorf("expected string %q in error message, got %q", tc.expectedError, err)
+			}
+		})
 	}
 }
 
 func TestGetApexInfoParseResults(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		description    string
 		input          string
@@ -152,6 +176,7 @@
 	"symbols_used_by_apex": "path/to/my.apex_using.txt",
 	"backing_libs":"path/to/backing.txt",
 	"bundle_file": "dir/bundlefile.zip",
+	"installed_files":"path/to/installed-files.txt",
 	"provides_native_libs":[]
 }`,
 			expectedOutput: ApexInfo{
@@ -165,18 +190,56 @@
 				SymbolsUsedByApex: "path/to/my.apex_using.txt",
 				BackingLibs:       "path/to/backing.txt",
 				BundleFile:        "dir/bundlefile.zip",
+				InstalledFiles:    "path/to/installed-files.txt",
 			},
 		},
 	}
 	for _, tc := range testCases {
-		actualOutput := GetApexInfo.ParseResult(tc.input)
-		if !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
-			t.Errorf("%q: expected %#v != actual %#v", tc.description, tc.expectedOutput, actualOutput)
-		}
+		t.Run(tc.description, func(t *testing.T) {
+			actualOutput, err := GetApexInfo.ParseResult(tc.input)
+			if err != nil {
+				t.Errorf("Unexpected error %q", err)
+			}
+			if !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
+				t.Errorf("expected %#v != actual %#v", tc.expectedOutput, actualOutput)
+			}
+		})
+	}
+}
+
+func TestGetApexInfoParseResultsError(t *testing.T) {
+	t.Parallel()
+	testCases := []struct {
+		description   string
+		input         string
+		expectedError string
+	}{
+		{
+			description:   "not json",
+			input:         ``,
+			expectedError: `cannot parse cquery result '': EOF`,
+		},
+		{
+			description: "invalid field",
+			input: `{
+	"fake_field": "path/to/file"
+}`,
+			expectedError: `json: unknown field "fake_field"`,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.description, func(t *testing.T) {
+			_, err := GetApexInfo.ParseResult(tc.input)
+			if !strings.Contains(err.Error(), tc.expectedError) {
+				t.Errorf("expected string %q in error message, got %q", tc.expectedError, err)
+			}
+		})
 	}
 }
 
 func TestGetCcUnstrippedParseResults(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		description    string
 		input          string
@@ -197,9 +260,45 @@
 		},
 	}
 	for _, tc := range testCases {
-		actualOutput := GetCcUnstrippedInfo.ParseResult(tc.input)
-		if !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
-			t.Errorf("%q: expected %#v != actual %#v", tc.description, tc.expectedOutput, actualOutput)
-		}
+		t.Run(tc.description, func(t *testing.T) {
+			actualOutput, err := GetCcUnstrippedInfo.ParseResult(tc.input)
+			if err != nil {
+				t.Errorf("Unexpected error %q", err)
+			}
+			if !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
+				t.Errorf("expected %#v != actual %#v", tc.expectedOutput, actualOutput)
+			}
+		})
+	}
+}
+
+func TestGetCcUnstrippedParseResultsErrors(t *testing.T) {
+	t.Parallel()
+	testCases := []struct {
+		description   string
+		input         string
+		expectedError string
+	}{
+		{
+			description:   "not json",
+			input:         ``,
+			expectedError: `cannot parse cquery result '': EOF`,
+		},
+		{
+			description: "invalid field",
+			input: `{
+	"fake_field": "path/to/file"
+}`,
+			expectedError: `json: unknown field "fake_field"`,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.description, func(t *testing.T) {
+			_, err := GetCcUnstrippedInfo.ParseResult(tc.input)
+			if !strings.Contains(err.Error(), tc.expectedError) {
+				t.Errorf("expected string %q in error message, got %q", tc.expectedError, err)
+			}
+		})
 	}
 }
diff --git a/bp2build/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go
index 0d6d5b8..4c86374 100644
--- a/bp2build/cc_library_conversion_test.go
+++ b/bp2build/cc_library_conversion_test.go
@@ -3508,3 +3508,38 @@
 		},
 	})
 }
+
+func TestCcLibraryWithTidy(t *testing.T) {
+	runCcLibraryTestCase(t, Bp2buildTestCase{
+		Description:                "cc_library uses tidy properties",
+		ModuleTypeUnderTest:        "cc_library",
+		ModuleTypeUnderTestFactory: cc.LibraryFactory,
+		Blueprint: `
+cc_library_static {
+    name: "foo",
+    srcs: ["foo.cpp"],
+	tidy: true,
+	tidy_checks: ["check1", "check2"],
+	tidy_checks_as_errors: ["check1error", "check2error"],
+	tidy_disabled_srcs: ["bar.cpp"],
+	tidy_timeout_srcs: ["baz.cpp"],
+}`,
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget("cc_library_static", "foo", AttrNameToString{
+				"local_includes": `["."]`,
+				"srcs":           `["foo.cpp"]`,
+				"tidy":           `True`,
+				"tidy_checks": `[
+        "check1",
+        "check2",
+    ]`,
+				"tidy_checks_as_errors": `[
+        "check1error",
+        "check2error",
+    ]`,
+				"tidy_disabled_srcs": `["bar.cpp"]`,
+				"tidy_timeout_srcs":  `["baz.cpp"]`,
+			}),
+		},
+	})
+}
diff --git a/bpf/bpf.go b/bpf/bpf.go
index 60a410d..a840fa3 100644
--- a/bpf/bpf.go
+++ b/bpf/bpf.go
@@ -18,11 +18,13 @@
 	"fmt"
 	"io"
 	"path/filepath"
+	"runtime"
 	"strings"
 
 	"android/soong/android"
 	"android/soong/bazel"
 	"android/soong/bazel/cquery"
+	"android/soong/cc"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
@@ -31,6 +33,7 @@
 func init() {
 	registerBpfBuildComponents(android.InitRegistrationContext)
 	pctx.Import("android/soong/cc/config")
+	pctx.StaticVariable("relPwd", cc.PwdPrefix())
 }
 
 var (
@@ -40,7 +43,7 @@
 		blueprint.RuleParams{
 			Depfile:     "${out}.d",
 			Deps:        blueprint.DepsGCC,
-			Command:     "$ccCmd --target=bpf -c $cFlags -MD -MF ${out}.d -o $out $in",
+			Command:     "$relPwd $ccCmd --target=bpf -c $cFlags -MD -MF ${out}.d -o $out $in",
 			CommandDeps: []string{"$ccCmd"},
 		},
 		"ccCmd", "cFlags")
@@ -164,6 +167,9 @@
 
 	if proptools.Bool(bpf.properties.Btf) {
 		cflags = append(cflags, "-g")
+		if runtime.GOOS != "darwin" {
+			cflags = append(cflags, "-fdebug-prefix-map=/proc/self/cwd=")
+		}
 	}
 
 	srcs := android.PathsForModuleSrc(ctx, bpf.properties.Srcs)
diff --git a/cc/afdo.go b/cc/afdo.go
index fb66bbe..d36f4af 100644
--- a/cc/afdo.go
+++ b/cc/afdo.go
@@ -36,7 +36,7 @@
 
 func getAfdoProfileProjects(config android.DeviceConfig) []string {
 	return config.OnceStringSlice(afdoProfileProjectsConfigKey, func() []string {
-		return append(globalAfdoProfileProjects, config.AfdoAdditionalProfileDirs()...)
+		return globalAfdoProfileProjects
 	})
 }
 
diff --git a/cc/binary.go b/cc/binary.go
index d09e744..c2868e7 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -646,7 +646,7 @@
 		sdkAttributes: bp2BuildParseSdkAttributes(m),
 	}
 
-	m.convertTidyAttributes(&attrs.tidyAttributes)
+	m.convertTidyAttributes(ctx, &attrs.tidyAttributes)
 
 	return attrs
 }
diff --git a/cc/bp2build.go b/cc/bp2build.go
index 2f79cae..6caa854 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -75,9 +75,11 @@
 	Tidy_flags            []string
 	Tidy_checks           []string
 	Tidy_checks_as_errors []string
+	Tidy_disabled_srcs    bazel.LabelListAttribute
+	Tidy_timeout_srcs     bazel.LabelListAttribute
 }
 
-func (m *Module) convertTidyAttributes(moduleAttrs *tidyAttributes) {
+func (m *Module) convertTidyAttributes(ctx android.BaseMutatorContext, moduleAttrs *tidyAttributes) {
 	for _, f := range m.features {
 		if tidy, ok := f.(*tidyFeature); ok {
 			moduleAttrs.Tidy = tidy.Properties.Tidy
@@ -85,6 +87,18 @@
 			moduleAttrs.Tidy_checks = tidy.Properties.Tidy_checks
 			moduleAttrs.Tidy_checks_as_errors = tidy.Properties.Tidy_checks_as_errors
 		}
+
+	}
+	archVariantProps := m.GetArchVariantProperties(ctx, &BaseCompilerProperties{})
+	for axis, configToProps := range archVariantProps {
+		for config, _props := range configToProps {
+			if archProps, ok := _props.(*BaseCompilerProperties); ok {
+				archDisabledSrcs := android.BazelLabelForModuleSrc(ctx, archProps.Tidy_disabled_srcs)
+				moduleAttrs.Tidy_disabled_srcs.SetSelectValue(axis, config, archDisabledSrcs)
+				archTimeoutSrcs := android.BazelLabelForModuleSrc(ctx, archProps.Tidy_timeout_srcs)
+				moduleAttrs.Tidy_timeout_srcs.SetSelectValue(axis, config, archTimeoutSrcs)
+			}
+		}
 	}
 }
 
diff --git a/cc/builder.go b/cc/builder.go
index 75e4736..46cea0b 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -924,9 +924,9 @@
 }
 
 // sourceAbiDiff registers a build statement to compare linked sAbi dump files (.lsdump).
-func sourceAbiDiff(ctx android.ModuleContext, inputDump android.Path, referenceDump android.Path,
-	baseName, exportedHeaderFlags string, diffFlags []string, prevVersion int,
-	checkAllApis, isLlndk, isNdk, isVndkExt, previousVersionDiff bool) android.OptionalPath {
+func sourceAbiDiff(ctx android.ModuleContext, inputDump, referenceDump android.Path,
+	baseName string, diffFlags []string, prevVersion int,
+	checkAllApis, isLlndkOrNdk, isVndkExt, previousVersionDiff bool) android.OptionalPath {
 
 	var outputFile android.ModuleOutPath
 	if previousVersionDiff {
@@ -955,7 +955,7 @@
 		extraFlags = append(extraFlags, "-target-version", "current")
 	}
 
-	if isLlndk || isNdk {
+	if isLlndkOrNdk {
 		extraFlags = append(extraFlags, "-consider-opaque-types-different")
 	}
 	if isVndkExt || previousVersionDiff {
diff --git a/cc/config/arm_device.go b/cc/config/arm_device.go
index b53a097..981d1ea 100644
--- a/cc/config/arm_device.go
+++ b/cc/config/arm_device.go
@@ -222,6 +222,7 @@
 		"":               "${config.ArmGenericCflags}",
 		"cortex-a7":      "${config.ArmCortexA7Cflags}",
 		"cortex-a8":      "${config.ArmCortexA8Cflags}",
+		"cortex-a9":      "${config.ArmGenericCflags}",
 		"cortex-a15":     "${config.ArmCortexA15Cflags}",
 		"cortex-a53":     "${config.ArmCortexA53Cflags}",
 		"cortex-a53.a57": "${config.ArmCortexA53Cflags}",
diff --git a/cc/config/riscv64_device.go b/cc/config/riscv64_device.go
index 825be7f..67208b2 100644
--- a/cc/config/riscv64_device.go
+++ b/cc/config/riscv64_device.go
@@ -32,7 +32,6 @@
 
 	riscv64Ldflags = []string{
 		"-Wl,--hash-style=gnu",
-		"-Wl,-z,separate-code",
 	}
 
 	riscv64Lldflags = append(riscv64Ldflags,
diff --git a/cc/library.go b/cc/library.go
index ef10e7c..897f3c7 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -1910,26 +1910,28 @@
 
 		addLsdumpPath(classifySourceAbiDump(ctx) + ":" + library.sAbiOutputFile.String())
 
+		isNdk := ctx.isNdk(ctx.Config())
+		isLlndk := ctx.isImplementationForLLNDKPublic()
 		// If NDK or PLATFORM library, check against previous version ABI.
 		if !ctx.useVndk() {
 			prevRefAbiDumpFile := getRefAbiDumpFile(ctx, strconv.Itoa(prevVersion), fileName)
 			if prevRefAbiDumpFile != nil {
 				library.prevSAbiDiff = sourceAbiDiff(ctx, library.sAbiOutputFile.Path(),
-					prevRefAbiDumpFile, fileName, exportedHeaderFlags,
+					prevRefAbiDumpFile, fileName,
 					library.Properties.Header_abi_checker.Diff_flags, prevVersion,
 					Bool(library.Properties.Header_abi_checker.Check_all_apis),
-					ctx.IsLlndk(), ctx.isNdk(ctx.Config()), ctx.IsVndkExt(), true)
+					isLlndk || isNdk, ctx.IsVndkExt(), true)
 			}
 		}
 
 		refAbiDumpFile := getRefAbiDumpFile(ctx, version, fileName)
 		if refAbiDumpFile != nil {
 			library.sAbiDiff = sourceAbiDiff(ctx, library.sAbiOutputFile.Path(),
-				refAbiDumpFile, fileName, exportedHeaderFlags,
+				refAbiDumpFile, fileName,
 				library.Properties.Header_abi_checker.Diff_flags,
 				/* unused if not previousVersionDiff */ 0,
 				Bool(library.Properties.Header_abi_checker.Check_all_apis),
-				ctx.IsLlndk(), ctx.isNdk(ctx.Config()), ctx.IsVndkExt(), false)
+				isLlndk || isNdk, ctx.IsVndkExt(), false)
 		}
 	}
 }
@@ -2792,7 +2794,7 @@
 		Runtime_deps:                      linkerAttrs.runtimeDeps,
 	}
 
-	module.convertTidyAttributes(&commonAttrs.tidyAttributes)
+	module.convertTidyAttributes(ctx, &commonAttrs.tidyAttributes)
 
 	var attrs interface{}
 	if isStatic {
diff --git a/cc/library_stub.go b/cc/library_stub.go
index 2fb21a3..043c03c 100644
--- a/cc/library_stub.go
+++ b/cc/library_stub.go
@@ -288,8 +288,8 @@
 }
 
 type variantExporterProperties struct {
-	// Header directory or library to export
-	Export_headers []string
+	// Header directory to export
+	Export_headers []string `android:"arch_variant"`
 
 	// Export all headers as system include
 	Export_headers_as_system *bool
diff --git a/cc/strip.go b/cc/strip.go
index 5c32d8b..c60e135 100644
--- a/cc/strip.go
+++ b/cc/strip.go
@@ -56,9 +56,7 @@
 	forceEnable := Bool(stripper.StripProperties.Strip.All) ||
 		Bool(stripper.StripProperties.Strip.Keep_symbols) ||
 		Bool(stripper.StripProperties.Strip.Keep_symbols_and_debug_frame)
-	// create_minidebuginfo doesn't work for riscv64 yet, disable stripping for now
-	riscv64 := actx.Arch().ArchType == android.Riscv64
-	return !forceDisable && (forceEnable || defaultEnable) && !riscv64
+	return !forceDisable && (forceEnable || defaultEnable)
 }
 
 // Keep this consistent with //build/bazel/rules/stripped_shared_library.bzl.
diff --git a/cc/test.go b/cc/test.go
index 92055fa..536210b 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -678,7 +678,7 @@
 		}
 	}
 
-	m.convertTidyAttributes(&testBinaryAttrs.tidyAttributes)
+	m.convertTidyAttributes(ctx, &testBinaryAttrs.tidyAttributes)
 
 	for _, propIntf := range m.GetProperties() {
 		if testLinkerProps, ok := propIntf.(*TestLinkerProperties); ok {
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 3fed1a1..e158814 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -106,24 +106,11 @@
 }
 
 func newNameResolver(config android.Config) *android.NameResolver {
-	namespacePathsToExport := make(map[string]bool)
-
-	for _, namespaceName := range config.ExportedNamespaces() {
-		namespacePathsToExport[namespaceName] = true
-	}
-
-	namespacePathsToExport["."] = true // always export the root namespace
-
-	exportFilter := func(namespace *android.Namespace) bool {
-		return namespacePathsToExport[namespace.Path]
-	}
-
-	return android.NewNameResolver(exportFilter)
+	return android.NewNameResolver(config)
 }
 
 func newContext(configuration android.Config) *android.Context {
 	ctx := android.NewContext(configuration)
-	ctx.Register()
 	ctx.SetNameInterface(newNameResolver(configuration))
 	ctx.SetAllowMissingDependencies(configuration.AllowMissingDependencies())
 	return ctx
@@ -165,7 +152,7 @@
 // Bazel-enabled mode. Attaches a mutator to queue Bazel requests, adds a
 // BeforePrepareBuildActionsHook to invoke Bazel, and then uses Bazel metadata
 // for modules that should be handled by Bazel.
-func runMixedModeBuild(configuration android.Config, ctx *android.Context, extraNinjaDeps []string) {
+func runMixedModeBuild(configuration android.Config, ctx *android.Context, extraNinjaDeps []string) string {
 	ctx.EventHandler.Begin("mixed_build")
 	defer ctx.EventHandler.End("mixed_build")
 
@@ -188,6 +175,7 @@
 	ninjaDeps = append(ninjaDeps, globListFiles...)
 
 	writeDepFile(cmdlineArgs.OutFile, ctx.EventHandler, ninjaDeps)
+	return cmdlineArgs.OutFile
 }
 
 // Run the code-generation phase to convert BazelTargetModules to BUILD files.
@@ -206,12 +194,11 @@
 
 // Run the code-generation phase to convert API contributions to BUILD files.
 // Return marker file for the new synthetic workspace
-func runApiBp2build(configuration android.Config, extraNinjaDeps []string) string {
-	// Create a new context and register mutators that are only meaningful to API export
-	ctx := android.NewContext(configuration)
+func runApiBp2build(configuration android.Config, ctx *android.Context, extraNinjaDeps []string) string {
 	ctx.EventHandler.Begin("api_bp2build")
 	defer ctx.EventHandler.End("api_bp2build")
-	ctx.SetNameInterface(newNameResolver(configuration))
+	// Do not allow missing dependencies.
+	ctx.SetAllowMissingDependencies(false)
 	ctx.RegisterForApiBazelConversion()
 
 	// Register the Android.bp files in the tree
@@ -349,61 +336,77 @@
 // output file of the specific activity.
 func doChosenActivity(ctx *android.Context, configuration android.Config, extraNinjaDeps []string, metricsDir string) string {
 	if configuration.BuildMode == android.SymlinkForest {
-		runSymlinkForestCreation(configuration, extraNinjaDeps, metricsDir)
-		return symlinkForestMarker
+		return runSymlinkForestCreation(configuration, ctx, extraNinjaDeps, metricsDir)
 	} else if configuration.BuildMode == android.Bp2build {
 		// Run the alternate pipeline of bp2build mutators and singleton to convert
 		// Blueprint to BUILD files before everything else.
-		runBp2Build(configuration, extraNinjaDeps, metricsDir)
-		return bp2buildMarker
-	} else if configuration.IsMixedBuildsEnabled() {
-		runMixedModeBuild(configuration, ctx, extraNinjaDeps)
+		return runBp2Build(configuration, ctx, extraNinjaDeps, metricsDir)
 	} else if configuration.BuildMode == android.ApiBp2build {
-		return runApiBp2build(configuration, extraNinjaDeps)
+		outputFile := runApiBp2build(configuration, ctx, extraNinjaDeps)
+		writeMetrics(configuration, ctx.EventHandler, metricsDir)
+		return outputFile
 	} else {
-		var stopBefore bootstrap.StopBefore
-		if configuration.BuildMode == android.GenerateModuleGraph {
-			stopBefore = bootstrap.StopBeforeWriteNinja
-		} else if configuration.BuildMode == android.GenerateQueryView || configuration.BuildMode == android.GenerateDocFile {
-			stopBefore = bootstrap.StopBeforePrepareBuildActions
+		ctx.Register()
+
+		var outputFile string
+		if configuration.IsMixedBuildsEnabled() {
+			outputFile = runMixedModeBuild(configuration, ctx, extraNinjaDeps)
 		} else {
-			stopBefore = bootstrap.DoEverything
+			outputFile = runSoongOnlyBuild(configuration, ctx, extraNinjaDeps)
 		}
 
-		ninjaDeps := bootstrap.RunBlueprint(cmdlineArgs, stopBefore, ctx.Context, configuration)
-		ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
+		writeMetrics(configuration, ctx.EventHandler, metricsDir)
 
-		globListFiles := writeBuildGlobsNinjaFile(ctx, configuration.SoongOutDir(), configuration)
-		ninjaDeps = append(ninjaDeps, globListFiles...)
+		return outputFile
+	}
+}
 
-		// Convert the Soong module graph into Bazel BUILD files.
-		if configuration.BuildMode == android.GenerateQueryView {
-			queryviewMarkerFile := bazelQueryViewDir + ".marker"
-			runQueryView(bazelQueryViewDir, queryviewMarkerFile, configuration, ctx)
-			writeDepFile(queryviewMarkerFile, ctx.EventHandler, ninjaDeps)
-			return queryviewMarkerFile
-		} else if configuration.BuildMode == android.GenerateModuleGraph {
-			writeJsonModuleGraphAndActions(ctx, moduleGraphFile, moduleActionsFile)
-			writeDepFile(moduleGraphFile, ctx.EventHandler, ninjaDeps)
-			return moduleGraphFile
-		} else if configuration.BuildMode == android.GenerateDocFile {
-			// TODO: we could make writeDocs() return the list of documentation files
-			// written and add them to the .d file. Then soong_docs would be re-run
-			// whenever one is deleted.
-			if err := writeDocs(ctx, shared.JoinPath(topDir, docFile)); err != nil {
-				fmt.Fprintf(os.Stderr, "error building Soong documentation: %s\n", err)
-				os.Exit(1)
-			}
-			writeDepFile(docFile, ctx.EventHandler, ninjaDeps)
-			return docFile
-		} else {
-			// The actual output (build.ninja) was written in the RunBlueprint() call
-			// above
-			writeDepFile(cmdlineArgs.OutFile, ctx.EventHandler, ninjaDeps)
-		}
+// runSoongOnlyBuild runs the standard Soong build in a number of different modes.
+func runSoongOnlyBuild(configuration android.Config, ctx *android.Context, extraNinjaDeps []string) string {
+	ctx.EventHandler.Begin("soong_build")
+	defer ctx.EventHandler.End("soong_build")
+
+	var stopBefore bootstrap.StopBefore
+	if configuration.BuildMode == android.GenerateModuleGraph {
+		stopBefore = bootstrap.StopBeforeWriteNinja
+	} else if configuration.BuildMode == android.GenerateQueryView || configuration.BuildMode == android.GenerateDocFile {
+		stopBefore = bootstrap.StopBeforePrepareBuildActions
+	} else {
+		stopBefore = bootstrap.DoEverything
 	}
 
-	return cmdlineArgs.OutFile
+	ninjaDeps := bootstrap.RunBlueprint(cmdlineArgs, stopBefore, ctx.Context, configuration)
+	ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
+
+	globListFiles := writeBuildGlobsNinjaFile(ctx, configuration.SoongOutDir(), configuration)
+	ninjaDeps = append(ninjaDeps, globListFiles...)
+
+	// Convert the Soong module graph into Bazel BUILD files.
+	if configuration.BuildMode == android.GenerateQueryView {
+		queryviewMarkerFile := bazelQueryViewDir + ".marker"
+		runQueryView(bazelQueryViewDir, queryviewMarkerFile, configuration, ctx)
+		writeDepFile(queryviewMarkerFile, ctx.EventHandler, ninjaDeps)
+		return queryviewMarkerFile
+	} else if configuration.BuildMode == android.GenerateModuleGraph {
+		writeJsonModuleGraphAndActions(ctx, moduleGraphFile, moduleActionsFile)
+		writeDepFile(moduleGraphFile, ctx.EventHandler, ninjaDeps)
+		return moduleGraphFile
+	} else if configuration.BuildMode == android.GenerateDocFile {
+		// TODO: we could make writeDocs() return the list of documentation files
+		// written and add them to the .d file. Then soong_docs would be re-run
+		// whenever one is deleted.
+		if err := writeDocs(ctx, shared.JoinPath(topDir, docFile)); err != nil {
+			fmt.Fprintf(os.Stderr, "error building Soong documentation: %s\n", err)
+			os.Exit(1)
+		}
+		writeDepFile(docFile, ctx.EventHandler, ninjaDeps)
+		return docFile
+	} else {
+		// The actual output (build.ninja) was written in the RunBlueprint() call
+		// above
+		writeDepFile(cmdlineArgs.OutFile, ctx.EventHandler, ninjaDeps)
+		return cmdlineArgs.OutFile
+	}
 }
 
 // soong_ui dumps the available environment variables to
@@ -463,13 +466,9 @@
 	logDir := availableEnv["LOG_DIR"]
 
 	ctx := newContext(configuration)
-	ctx.EventHandler.Begin("soong_build")
 
 	finalOutputFile := doChosenActivity(ctx, configuration, extraNinjaDeps, logDir)
 
-	ctx.EventHandler.End("soong_build")
-	writeMetrics(configuration, ctx.EventHandler, logDir)
-
 	writeUsedEnvironmentFile(configuration, finalOutputFile)
 }
 
@@ -614,9 +613,7 @@
 // Ideally, bp2build would write a file that contains instructions to the
 // symlink tree creation binary. Then the latter would not need to depend on
 // the very heavy-weight machinery of soong_build .
-func runSymlinkForestCreation(configuration android.Config, extraNinjaDeps []string, metricsDir string) {
-	eventHandler := &metrics.EventHandler{}
-
+func runSymlinkForestCreation(configuration android.Config, ctx *android.Context, extraNinjaDeps []string, metricsDir string) string {
 	var ninjaDeps []string
 	ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
 
@@ -643,13 +640,13 @@
 	// Such a directory SHOULD be added to `ninjaDeps` so that a child directory
 	// or file created/deleted under it would trigger an update of the symlink
 	// forest.
-	eventHandler.Do("symlink_forest", func() {
+	ctx.EventHandler.Do("symlink_forest", func() {
 		symlinkForestDeps := bp2build.PlantSymlinkForest(
 			configuration.IsEnvTrue("BP2BUILD_VERBOSE"), topDir, workspaceRoot, generatedRoot, excludes)
 		ninjaDeps = append(ninjaDeps, symlinkForestDeps...)
 	})
 
-	writeDepFile(symlinkForestMarker, eventHandler, ninjaDeps)
+	writeDepFile(symlinkForestMarker, ctx.EventHandler, ninjaDeps)
 	touch(shared.JoinPath(topDir, symlinkForestMarker))
 	codegenMetrics := bp2build.ReadCodegenMetrics(metricsDir)
 	if codegenMetrics == nil {
@@ -659,27 +656,24 @@
 		//TODO (usta) we cannot determine if we loaded a stale file, i.e. from an unrelated prior
 		//invocation of codegen. We should simply use a separate .pb file
 	}
-	writeBp2BuildMetrics(codegenMetrics, eventHandler, metricsDir)
+	writeBp2BuildMetrics(codegenMetrics, ctx.EventHandler, metricsDir)
+
+	return symlinkForestMarker
 }
 
 // Run Soong in the bp2build mode. This creates a standalone context that registers
 // an alternate pipeline of mutators and singletons specifically for generating
 // Bazel BUILD files instead of Ninja files.
-func runBp2Build(configuration android.Config, extraNinjaDeps []string, metricsDir string) {
+func runBp2Build(configuration android.Config, ctx *android.Context, extraNinjaDeps []string, metricsDir string) string {
 	var codegenMetrics *bp2build.CodegenMetrics
-	eventHandler := &metrics.EventHandler{}
-	eventHandler.Do("bp2build", func() {
-
-		// Register an alternate set of singletons and mutators for bazel
-		// conversion for Bazel conversion.
-		bp2buildCtx := android.NewContext(configuration)
+	ctx.EventHandler.Do("bp2build", func() {
 
 		// Propagate "allow misssing dependencies" bit. This is normally set in
-		// newContext(), but we create bp2buildCtx without calling that method.
-		bp2buildCtx.SetAllowMissingDependencies(configuration.AllowMissingDependencies())
-		bp2buildCtx.SetNameInterface(newNameResolver(configuration))
-		bp2buildCtx.RegisterForBazelConversion()
-		bp2buildCtx.SetModuleListFile(cmdlineArgs.ModuleListFile)
+		// newContext(), but we create ctx without calling that method.
+		ctx.SetAllowMissingDependencies(configuration.AllowMissingDependencies())
+		ctx.SetNameInterface(newNameResolver(configuration))
+		ctx.RegisterForBazelConversion()
+		ctx.SetModuleListFile(cmdlineArgs.ModuleListFile)
 
 		var ninjaDeps []string
 		ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
@@ -687,25 +681,25 @@
 		// Run the loading and analysis pipeline to prepare the graph of regular
 		// Modules parsed from Android.bp files, and the BazelTargetModules mapped
 		// from the regular Modules.
-		eventHandler.Do("bootstrap", func() {
+		ctx.EventHandler.Do("bootstrap", func() {
 			blueprintArgs := cmdlineArgs
-			bootstrapDeps := bootstrap.RunBlueprint(blueprintArgs, bootstrap.StopBeforePrepareBuildActions, bp2buildCtx.Context, configuration)
+			bootstrapDeps := bootstrap.RunBlueprint(blueprintArgs, bootstrap.StopBeforePrepareBuildActions, ctx.Context, configuration)
 			ninjaDeps = append(ninjaDeps, bootstrapDeps...)
 		})
 
-		globListFiles := writeBuildGlobsNinjaFile(bp2buildCtx, configuration.SoongOutDir(), configuration)
+		globListFiles := writeBuildGlobsNinjaFile(ctx, configuration.SoongOutDir(), configuration)
 		ninjaDeps = append(ninjaDeps, globListFiles...)
 
 		// Run the code-generation phase to convert BazelTargetModules to BUILD files
 		// and print conversion codegenMetrics to the user.
-		codegenContext := bp2build.NewCodegenContext(configuration, bp2buildCtx, bp2build.Bp2Build)
-		eventHandler.Do("codegen", func() {
+		codegenContext := bp2build.NewCodegenContext(configuration, ctx, bp2build.Bp2Build)
+		ctx.EventHandler.Do("codegen", func() {
 			codegenMetrics = bp2build.Codegen(codegenContext)
 		})
 
 		ninjaDeps = append(ninjaDeps, codegenContext.AdditionalNinjaDeps()...)
 
-		writeDepFile(bp2buildMarker, eventHandler, ninjaDeps)
+		writeDepFile(bp2buildMarker, ctx.EventHandler, ninjaDeps)
 		touch(shared.JoinPath(topDir, bp2buildMarker))
 	})
 
@@ -715,7 +709,8 @@
 	if configuration.IsEnvTrue("BP2BUILD_VERBOSE") {
 		codegenMetrics.Print()
 	}
-	writeBp2BuildMetrics(codegenMetrics, eventHandler, metricsDir)
+	writeBp2BuildMetrics(codegenMetrics, ctx.EventHandler, metricsDir)
+	return bp2buildMarker
 }
 
 // Write Bp2Build metrics into $LOG_DIR
diff --git a/java/invalid_implementation_jar.sh b/java/invalid_implementation_jar.sh
new file mode 100755
index 0000000..3820058
--- /dev/null
+++ b/java/invalid_implementation_jar.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+
+# Copyright 2022 Google Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Script to detect and report an attempt to access an invalid implementation
+# jar.
+
+MOD=$1
+
+cat <<EOF
+
+    $MOD is a java_library that generates a jar file which must not be accessed
+    from outside the mainline module that provides it. If you are seeing this
+    message it means that you are incorrectly attempting to use the jar file
+    from a java_import prebuilt of $MOD.
+
+    This is most likely due to an incorrect dependency on $MOD in an Android.mk
+    or Android.bp file. Please remove that dependency and replace with
+    something more appropriate, e.g. a dependency on an API provided by the
+    module.
+
+    If you do not know where the extraneous dependency was added then you can
+    run the following command to find a list of all the paths from the target
+    which you are trying to build to the target which produced this error.
+
+        prebuilts/build-tools/linux-x86/bin/ninja -f out/combined-\${TARGET_PRODUCT}.ninja -t path <target> <invalid-jar>
+
+    Where <target> is the build target you specified on the command line which
+    produces this error and <invalid-jar> is the rule that failed with this
+    message. If you are specifying multiple build targets then you will need to
+    run the above command for every target until you find the cause.
+
+    The command will output one (of the possibly many) dependency paths from
+    <target> to <invalid-jar>, one file/phony target per line. e.g. it may
+    output something like this:
+
+        ....
+        out/soong/.intermediates/acme/broken/android_common/combined/broken.jar
+        out/soong/.intermediates/prebuilts/module_sdk/art/current/sdk/prebuilt_core-libart/android_common/combined/core-libart.jar
+        out/soong/.intermediates/prebuilts/module_sdk/art/current/sdk/art-module-sdk_core-libart-error/gen/this-file-will-never-be-created.jar
+
+    The last line is the failing target, the second to last line is a dependency
+    from the core-libart java_import onto the failing target, the third to last
+    line is the source of the dependency so you should look in acme/Android.bp
+    file for the "broken" module.
+
+EOF
+
+exit 1
diff --git a/java/java.go b/java/java.go
index b6fc6b8..3471abb 100644
--- a/java/java.go
+++ b/java/java.go
@@ -86,11 +86,11 @@
 var (
 	// Supports adding java header libraries to module_exports and sdk.
 	javaHeaderLibsSdkMemberType = &librarySdkMemberType{
-		android.SdkMemberTypeBase{
+		SdkMemberTypeBase: android.SdkMemberTypeBase{
 			PropertyName: "java_header_libs",
 			SupportsSdk:  true,
 		},
-		func(_ android.SdkMemberContext, j *Library) android.Path {
+		jarToExportGetter: func(_ android.SdkMemberContext, j *Library) android.Path {
 			headerJars := j.HeaderJars()
 			if len(headerJars) != 1 {
 				panic(fmt.Errorf("there must be only one header jar from %q", j.Name()))
@@ -98,8 +98,8 @@
 
 			return headerJars[0]
 		},
-		sdkSnapshotFilePathForJar,
-		copyEverythingToSnapshot,
+		snapshotPathGetter:    sdkSnapshotFilePathForJar,
+		onlyCopyJarToSnapshot: copyEverythingToSnapshot,
 	}
 
 	// Export implementation classes jar as part of the sdk.
@@ -113,12 +113,12 @@
 
 	// Supports adding java implementation libraries to module_exports but not sdk.
 	javaLibsSdkMemberType = &librarySdkMemberType{
-		android.SdkMemberTypeBase{
+		SdkMemberTypeBase: android.SdkMemberTypeBase{
 			PropertyName: "java_libs",
 		},
-		exportImplementationClassesJar,
-		sdkSnapshotFilePathForJar,
-		copyEverythingToSnapshot,
+		jarToExportGetter:     exportImplementationClassesJar,
+		snapshotPathGetter:    sdkSnapshotFilePathForJar,
+		onlyCopyJarToSnapshot: copyEverythingToSnapshot,
 	}
 
 	snapshotRequiresImplementationJar = func(ctx android.SdkMemberContext) bool {
@@ -143,11 +143,11 @@
 	// necessary. The java_boot_libs property to allow those modules to be exported as part of the
 	// sdk/module_exports without exposing any unnecessary information.
 	javaBootLibsSdkMemberType = &librarySdkMemberType{
-		android.SdkMemberTypeBase{
+		SdkMemberTypeBase: android.SdkMemberTypeBase{
 			PropertyName: "java_boot_libs",
 			SupportsSdk:  true,
 		},
-		func(ctx android.SdkMemberContext, j *Library) android.Path {
+		jarToExportGetter: func(ctx android.SdkMemberContext, j *Library) android.Path {
 			if snapshotRequiresImplementationJar(ctx) {
 				return exportImplementationClassesJar(ctx, j)
 			}
@@ -156,9 +156,9 @@
 			// jar for use by dexpreopting and boot jars package check. They do not need to provide an
 			// actual implementation jar but the java_import will need a file that exists so just copy an
 			// empty file. Any attempt to use that file as a jar will cause a build error.
-			return ctx.SnapshotBuilder().EmptyFile()
+			return nil
 		},
-		func(ctx android.SdkMemberContext, osPrefix, name string) string {
+		snapshotPathGetter: func(ctx android.SdkMemberContext, osPrefix, name string) string {
 			if snapshotRequiresImplementationJar(ctx) {
 				return sdkSnapshotFilePathForJar(ctx, osPrefix, name)
 			}
@@ -168,7 +168,7 @@
 			// TODO(b/175714559): Provide a proper error message in Soong not ninja.
 			return filepath.Join(osPrefix, "java_boot_libs", "snapshot", "jars", "are", "invalid", name+jarFileSuffix)
 		},
-		onlyCopyJarToSnapshot,
+		onlyCopyJarToSnapshot: onlyCopyJarToSnapshot,
 	}
 
 	// Supports adding java systemserver libraries to module_exports and sdk.
@@ -182,27 +182,27 @@
 	// necessary. The java_systemserver_libs property to allow those modules to be exported as part of
 	// the sdk/module_exports without exposing any unnecessary information.
 	javaSystemserverLibsSdkMemberType = &librarySdkMemberType{
-		android.SdkMemberTypeBase{
+		SdkMemberTypeBase: android.SdkMemberTypeBase{
 			PropertyName: "java_systemserver_libs",
 			SupportsSdk:  true,
 
 			// This was only added in Tiramisu.
 			SupportedBuildReleaseSpecification: "Tiramisu+",
 		},
-		func(ctx android.SdkMemberContext, j *Library) android.Path {
+		jarToExportGetter: func(ctx android.SdkMemberContext, j *Library) android.Path {
 			// Java systemserver libs are only provided in the SDK to provide access to their dex
 			// implementation jar for use by dexpreopting. They do not need to provide an actual
 			// implementation jar but the java_import will need a file that exists so just copy an empty
 			// file. Any attempt to use that file as a jar will cause a build error.
-			return ctx.SnapshotBuilder().EmptyFile()
+			return nil
 		},
-		func(_ android.SdkMemberContext, osPrefix, name string) string {
+		snapshotPathGetter: func(_ android.SdkMemberContext, osPrefix, name string) string {
 			// Create a special name for the implementation jar to try and provide some useful information
 			// to a developer that attempts to compile against this.
 			// TODO(b/175714559): Provide a proper error message in Soong not ninja.
 			return filepath.Join(osPrefix, "java_systemserver_libs", "snapshot", "jars", "are", "invalid", name+jarFileSuffix)
 		},
-		onlyCopyJarToSnapshot,
+		onlyCopyJarToSnapshot: onlyCopyJarToSnapshot,
 	}
 
 	// Supports adding java test libraries to module_exports but not sdk.
@@ -232,7 +232,7 @@
 	ImplementationAndResourcesJars android.Paths
 
 	// ImplementationJars is a list of jars that contain the implementations of classes in the
-	//module.
+	// module.
 	ImplementationJars android.Paths
 
 	// ResourceJars is a list of jars that contain the resources included in the module.
@@ -718,7 +718,8 @@
 	android.SdkMemberTypeBase
 
 	// Function to retrieve the appropriate output jar (implementation or header) from
-	// the library.
+	// the library, if this returns nil then it is assumed that the snapshot must not provide access
+	// to the jar.
 	jarToExportGetter func(ctx android.SdkMemberContext, j *Library) android.Path
 
 	// Function to compute the snapshot relative path to which the named library's
@@ -755,7 +756,11 @@
 type librarySdkMemberProperties struct {
 	android.SdkMemberPropertiesBase
 
-	JarToExport     android.Path `android:"arch_variant"`
+	JarToExport android.Path `android:"arch_variant"`
+
+	// The path to a script to use when the jar is invalid.
+	InvalidJarScript android.Path
+
 	AidlIncludeDirs android.Paths
 
 	// The list of permitted packages that need to be passed to the prebuilts as they are used to
@@ -766,7 +771,15 @@
 func (p *librarySdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) {
 	j := variant.(*Library)
 
-	p.JarToExport = ctx.MemberType().(*librarySdkMemberType).jarToExportGetter(ctx, j)
+	memberType := ctx.MemberType().(*librarySdkMemberType)
+	p.JarToExport = memberType.jarToExportGetter(ctx, j)
+
+	// If no jar was provided for export then disallow access to it completely.
+	if p.JarToExport == nil {
+		// Copy the script to prevent access to the jar into the snapshot.
+		p.InvalidJarScript = android.PathForSource(ctx.SdkModuleContext(),
+			"build/soong/java/invalid_implementation_jar.sh")
+	}
 
 	p.AidlIncludeDirs = j.AidlIncludeDirs()
 
@@ -789,6 +802,21 @@
 		propertySet.AddProperty("jars", []string{snapshotRelativeJavaLibPath})
 	}
 
+	if scriptSrc := p.InvalidJarScript; scriptSrc != nil {
+		// Copy the script to prevent access to the jar into the snapshot.
+		scriptDest := filepath.Join("scripts", scriptSrc.Base())
+		builder.CopyToSnapshot(scriptSrc, scriptDest)
+
+		// Generate a genrule module that will invoke the script passing in the module name.
+		genrule := builder.AddInternalModule(p, "genrule", "error")
+		genRuleName := genrule.Name()
+		genrule.AddProperty("out", []string{"this-file-will-never-be-created.jar"})
+		genrule.AddProperty("tool_files", []string{scriptDest})
+		genrule.AddProperty("cmd", fmt.Sprintf("$(location %s) %s", scriptDest, p.Name()))
+
+		propertySet.AddPropertyWithTag("jars", []string{":" + genRuleName}, builder.SdkMemberReferencePropertyTag(true))
+	}
+
 	if len(p.PermittedPackages) > 0 {
 		propertySet.AddProperty("permitted_packages", p.PermittedPackages)
 	}
@@ -1650,7 +1678,7 @@
 }
 
 func (j *Import) commonBuildActions(ctx android.ModuleContext) {
-	//TODO(b/231322772) these should come from Bazel once available
+	// TODO(b/231322772) these should come from Bazel once available
 	j.sdkVersion = j.SdkVersion(ctx)
 	j.minSdkVersion = j.MinSdkVersion(ctx)
 
@@ -2253,7 +2281,7 @@
 		resources.Append(android.BazelLabelForModuleSrc(ctx, m.properties.Java_resources))
 	}
 
-	//TODO(b/179889880) handle case where glob includes files outside package
+	// TODO(b/179889880) handle case where glob includes files outside package
 	resDeps := ResourceDirsToFiles(
 		ctx,
 		m.properties.Java_resource_dirs,
@@ -2401,7 +2429,7 @@
 	}
 
 	epEnabled := m.properties.Errorprone.Enabled
-	//TODO(b/227504307) add configuration that depends on RUN_ERROR_PRONE environment variable
+	// TODO(b/227504307) add configuration that depends on RUN_ERROR_PRONE environment variable
 	if Bool(epEnabled) {
 		javacopts = append(javacopts, m.properties.Errorprone.Javacflags...)
 	}
@@ -2637,7 +2665,7 @@
 		HeaderJars:                     android.PathsIfNonNil(i.combinedClasspathFile),
 		ImplementationAndResourcesJars: android.PathsIfNonNil(i.combinedClasspathFile),
 		ImplementationJars:             android.PathsIfNonNil(i.combinedClasspathFile),
-		//TODO(b/240308299) include AIDL information from Bazel
+		// TODO(b/240308299) include AIDL information from Bazel
 	})
 
 	i.maybeInstall(ctx, jarName, outputFile)
diff --git a/sdk/bootclasspath_fragment_sdk_test.go b/sdk/bootclasspath_fragment_sdk_test.go
index 1b64130..58c1647 100644
--- a/sdk/bootclasspath_fragment_sdk_test.go
+++ b/sdk/bootclasspath_fragment_sdk_test.go
@@ -169,7 +169,15 @@
     prefer: false,
     visibility: ["//visibility:public"],
     apex_available: ["com.android.art"],
-    jars: ["java_boot_libs/snapshot/jars/are/invalid/core1.jar"],
+    jars: [":mysdk_core1-error"],
+}
+
+genrule {
+    name: "mysdk_core1-error",
+    visibility: ["//visibility:private"],
+    out: ["this-file-will-never-be-created.jar"],
+    tool_files: ["scripts/invalid_implementation_jar.sh"],
+    cmd: "$(location scripts/invalid_implementation_jar.sh) core1",
 }
 
 java_import {
@@ -177,7 +185,15 @@
     prefer: false,
     visibility: ["//visibility:public"],
     apex_available: ["com.android.art"],
-    jars: ["java_boot_libs/snapshot/jars/are/invalid/core2.jar"],
+    jars: [":mysdk_core2-error"],
+}
+
+genrule {
+    name: "mysdk_core2-error",
+    visibility: ["//visibility:private"],
+    out: ["this-file-will-never-be-created.jar"],
+    tool_files: ["scripts/invalid_implementation_jar.sh"],
+    cmd: "$(location scripts/invalid_implementation_jar.sh) core2",
 }
 `),
 		checkAllCopyRules(`
@@ -187,8 +203,7 @@
 .intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/signature-patterns.csv -> hiddenapi/signature-patterns.csv
 .intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/filtered-stub-flags.csv -> hiddenapi/filtered-stub-flags.csv
 .intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/filtered-flags.csv -> hiddenapi/filtered-flags.csv
-.intermediates/mysdk/common_os/empty -> java_boot_libs/snapshot/jars/are/invalid/core1.jar
-.intermediates/mysdk/common_os/empty -> java_boot_libs/snapshot/jars/are/invalid/core2.jar
+build/soong/java/invalid_implementation_jar.sh -> scripts/invalid_implementation_jar.sh
 		`),
 		snapshotTestPreparer(checkSnapshotWithoutSource, preparerForSnapshot),
 
@@ -357,10 +372,18 @@
     prefer: false,
     visibility: ["//visibility:public"],
     apex_available: ["myapex"],
-    jars: ["java_boot_libs/snapshot/jars/are/invalid/mybootlib.jar"],
+    jars: [":mysdk_mybootlib-error"],
     permitted_packages: ["mybootlib"],
 }
 
+genrule {
+    name: "mysdk_mybootlib-error",
+    visibility: ["//visibility:private"],
+    out: ["this-file-will-never-be-created.jar"],
+    tool_files: ["scripts/invalid_implementation_jar.sh"],
+    cmd: "$(location scripts/invalid_implementation_jar.sh) mybootlib",
+}
+
 java_sdk_library_import {
     name: "myothersdklibrary",
     prefer: false,
@@ -467,7 +490,7 @@
 .intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/signature-patterns.csv -> hiddenapi/signature-patterns.csv
 .intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/filtered-stub-flags.csv -> hiddenapi/filtered-stub-flags.csv
 .intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/filtered-flags.csv -> hiddenapi/filtered-flags.csv
-.intermediates/mysdk/common_os/empty -> java_boot_libs/snapshot/jars/are/invalid/mybootlib.jar
+build/soong/java/invalid_implementation_jar.sh -> scripts/invalid_implementation_jar.sh
 .intermediates/myothersdklibrary.stubs/android_common/javac/myothersdklibrary.stubs.jar -> sdk_library/public/myothersdklibrary-stubs.jar
 .intermediates/myothersdklibrary.stubs.source/android_common/metalava/myothersdklibrary.stubs.source_api.txt -> sdk_library/public/myothersdklibrary.txt
 .intermediates/myothersdklibrary.stubs.source/android_common/metalava/myothersdklibrary.stubs.source_removed.txt -> sdk_library/public/myothersdklibrary-removed.txt
@@ -487,7 +510,7 @@
 .intermediates/mybootclasspathfragment/android_common_myapex/modular-hiddenapi/signature-patterns.csv -> hiddenapi/signature-patterns.csv
 .intermediates/mybootclasspathfragment/android_common_myapex/modular-hiddenapi/filtered-stub-flags.csv -> hiddenapi/filtered-stub-flags.csv
 .intermediates/mybootclasspathfragment/android_common_myapex/modular-hiddenapi/filtered-flags.csv -> hiddenapi/filtered-flags.csv
-.intermediates/mysdk/common_os/empty -> java_boot_libs/snapshot/jars/are/invalid/mybootlib.jar
+build/soong/java/invalid_implementation_jar.sh -> scripts/invalid_implementation_jar.sh
 .intermediates/myothersdklibrary.stubs/android_common/javac/myothersdklibrary.stubs.jar -> sdk_library/public/myothersdklibrary-stubs.jar
 .intermediates/myothersdklibrary.stubs.source/android_common/metalava/myothersdklibrary.stubs.source_api.txt -> sdk_library/public/myothersdklibrary.txt
 .intermediates/myothersdklibrary.stubs.source/android_common/metalava/myothersdklibrary.stubs.source_removed.txt -> sdk_library/public/myothersdklibrary-removed.txt
@@ -876,10 +899,18 @@
     prefer: false,
     visibility: ["//visibility:public"],
     apex_available: ["myapex"],
-    jars: ["java_boot_libs/snapshot/jars/are/invalid/mybootlib.jar"],
+    jars: [":mysdk_mybootlib-error"],
     permitted_packages: ["mybootlib"],
 }
 
+genrule {
+    name: "mysdk_mybootlib-error",
+    visibility: ["//visibility:private"],
+    out: ["this-file-will-never-be-created.jar"],
+    tool_files: ["scripts/invalid_implementation_jar.sh"],
+    cmd: "$(location scripts/invalid_implementation_jar.sh) mybootlib",
+}
+
 java_sdk_library_import {
     name: "mynewlibrary",
     prefer: false,
@@ -930,7 +961,7 @@
 .intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/signature-patterns.csv -> hiddenapi/signature-patterns.csv
 .intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/filtered-stub-flags.csv -> hiddenapi/filtered-stub-flags.csv
 .intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/filtered-flags.csv -> hiddenapi/filtered-flags.csv
-.intermediates/mysdk/common_os/empty -> java_boot_libs/snapshot/jars/are/invalid/mybootlib.jar
+build/soong/java/invalid_implementation_jar.sh -> scripts/invalid_implementation_jar.sh
 .intermediates/mynewlibrary.stubs/android_common/javac/mynewlibrary.stubs.jar -> sdk_library/public/mynewlibrary-stubs.jar
 .intermediates/mynewlibrary.stubs.source/android_common/metalava/mynewlibrary.stubs.source_api.txt -> sdk_library/public/mynewlibrary.txt
 .intermediates/mynewlibrary.stubs.source/android_common/metalava/mynewlibrary.stubs.source_removed.txt -> sdk_library/public/mynewlibrary-removed.txt
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index 51903ce3..c6cb6c2 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -19,11 +19,13 @@
 	"testing"
 
 	"android/soong/android"
+	"android/soong/genrule"
 	"android/soong/java"
 )
 
 var prepareForSdkTestWithJava = android.GroupFixturePreparers(
 	java.PrepareForTestWithJavaBuildComponents,
+	genrule.PrepareForTestWithGenRuleBuildComponents,
 	PrepareForTestWithSdkBuildComponents,
 
 	// Ensure that all source paths are provided. This helps ensure that the snapshot generation is
@@ -34,6 +36,7 @@
 	// Files needs by most of the tests.
 	android.MockFS{
 		"Test.java": nil,
+		"build/soong/java/invalid_implementation_jar.sh": nil,
 	}.AddToFixture(),
 )
 
@@ -288,18 +291,26 @@
     prefer: false,
     visibility: ["//visibility:public"],
     apex_available: ["//apex_available:platform"],
-    jars: ["java_boot_libs/snapshot/jars/are/invalid/myjavalib.jar"],
+    jars: [":mysdk_myjavalib-error"],
     permitted_packages: ["pkg.myjavalib"],
 }
+
+genrule {
+    name: "mysdk_myjavalib-error",
+    visibility: ["//visibility:private"],
+    out: ["this-file-will-never-be-created.jar"],
+    tool_files: ["scripts/invalid_implementation_jar.sh"],
+    cmd: "$(location scripts/invalid_implementation_jar.sh) myjavalib",
+}
 `),
 		checkAllCopyRules(`
-.intermediates/mysdk/common_os/empty -> java_boot_libs/snapshot/jars/are/invalid/myjavalib.jar
+build/soong/java/invalid_implementation_jar.sh -> scripts/invalid_implementation_jar.sh
 `),
 	)
 }
 
 func TestSnapshotWithJavaBootLibrary_UpdatableMedia(t *testing.T) {
-	runTest := func(t *testing.T, targetBuildRelease, expectedJarPath, expectedCopyRule string) {
+	runTest := func(t *testing.T, targetBuildRelease, expectedJarPath, expectedGenRule, expectedCopyRule string) {
 		result := android.GroupFixturePreparers(
 			prepareForSdkTestWithJava,
 			android.FixtureMergeEnv(map[string]string{
@@ -334,20 +345,27 @@
     jars: ["%s"],
     permitted_packages: ["pkg.media"],
 }
-`, expectedJarPath)),
+%s`, expectedJarPath, expectedGenRule)),
 			checkAllCopyRules(expectedCopyRule),
 		)
 	}
 
 	t.Run("updatable-media in S", func(t *testing.T) {
-		runTest(t, "S", "java/updatable-media.jar", `
+		runTest(t, "S", "java/updatable-media.jar", "", `
 .intermediates/updatable-media/android_common/package-check/updatable-media.jar -> java/updatable-media.jar
 `)
 	})
 
 	t.Run("updatable-media in T", func(t *testing.T) {
-		runTest(t, "Tiramisu", "java_boot_libs/snapshot/jars/are/invalid/updatable-media.jar", `
-.intermediates/mysdk/common_os/empty -> java_boot_libs/snapshot/jars/are/invalid/updatable-media.jar
+		runTest(t, "Tiramisu", ":mysdk_updatable-media-error", `
+genrule {
+    name: "mysdk_updatable-media-error",
+    visibility: ["//visibility:private"],
+    out: ["this-file-will-never-be-created.jar"],
+    tool_files: ["scripts/invalid_implementation_jar.sh"],
+    cmd: "$(location scripts/invalid_implementation_jar.sh) updatable-media",
+}`, `
+build/soong/java/invalid_implementation_jar.sh -> scripts/invalid_implementation_jar.sh
 `)
 	})
 }
@@ -389,12 +407,20 @@
     prefer: false,
     visibility: ["//visibility:public"],
     apex_available: ["//apex_available:platform"],
-    jars: ["java_systemserver_libs/snapshot/jars/are/invalid/myjavalib.jar"],
+    jars: [":myexports_myjavalib-error"],
     permitted_packages: ["pkg.myjavalib"],
 }
+
+genrule {
+    name: "myexports_myjavalib-error",
+    visibility: ["//visibility:private"],
+    out: ["this-file-will-never-be-created.jar"],
+    tool_files: ["scripts/invalid_implementation_jar.sh"],
+    cmd: "$(location scripts/invalid_implementation_jar.sh) myjavalib",
+}
 `),
 		checkAllCopyRules(`
-.intermediates/myexports/common_os/empty -> java_systemserver_libs/snapshot/jars/are/invalid/myjavalib.jar
+build/soong/java/invalid_implementation_jar.sh -> scripts/invalid_implementation_jar.sh
 `),
 	)
 }
diff --git a/sdk/systemserverclasspath_fragment_sdk_test.go b/sdk/systemserverclasspath_fragment_sdk_test.go
index 1ac405d..9540a6b 100644
--- a/sdk/systemserverclasspath_fragment_sdk_test.go
+++ b/sdk/systemserverclasspath_fragment_sdk_test.go
@@ -119,10 +119,18 @@
     prefer: false,
     visibility: ["//visibility:public"],
     apex_available: ["myapex"],
-    jars: ["java_systemserver_libs/snapshot/jars/are/invalid/mylib.jar"],
+    jars: [":mysdk_mylib-error"],
     permitted_packages: ["mylib"],
 }
 
+genrule {
+    name: "mysdk_mylib-error",
+    visibility: ["//visibility:private"],
+    out: ["this-file-will-never-be-created.jar"],
+    tool_files: ["scripts/invalid_implementation_jar.sh"],
+    cmd: "$(location scripts/invalid_implementation_jar.sh) mylib",
+}
+
 prebuilt_systemserverclasspath_fragment {
     name: "mysystemserverclasspathfragment",
     prefer: false,
@@ -180,10 +188,18 @@
     prefer: false,
     visibility: ["//visibility:public"],
     apex_available: ["myapex"],
-    jars: ["java_systemserver_libs/snapshot/jars/are/invalid/mylib.jar"],
+    jars: [":mysdk_mylib-error"],
     permitted_packages: ["mylib"],
 }
 
+genrule {
+    name: "mysdk_mylib-error",
+    visibility: ["//visibility:private"],
+    out: ["this-file-will-never-be-created.jar"],
+    tool_files: ["scripts/invalid_implementation_jar.sh"],
+    cmd: "$(location scripts/invalid_implementation_jar.sh) mylib",
+}
+
 prebuilt_systemserverclasspath_fragment {
     name: "mysystemserverclasspathfragment",
     prefer: false,
diff --git a/sdk/update.go b/sdk/update.go
index 92a13fa..6ebbf09 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -1049,9 +1049,6 @@
 	filesToZip  android.Paths
 	zipsToMerge android.Paths
 
-	// The path to an empty file.
-	emptyFile android.WritablePath
-
 	prebuiltModules map[string]*bpModule
 	prebuiltOrder   []*bpModule
 
@@ -1111,19 +1108,6 @@
 	s.zipsToMerge = append(s.zipsToMerge, tmpZipPath)
 }
 
-func (s *snapshotBuilder) EmptyFile() android.Path {
-	if s.emptyFile == nil {
-		ctx := s.ctx
-		s.emptyFile = android.PathForModuleOut(ctx, "empty")
-		s.ctx.Build(pctx, android.BuildParams{
-			Rule:   android.Touch,
-			Output: s.emptyFile,
-		})
-	}
-
-	return s.emptyFile
-}
-
 func (s *snapshotBuilder) AddPrebuiltModule(member android.SdkMember, moduleType string) android.BpModule {
 	name := member.Name()
 	if s.prebuiltModules[name] != nil {
@@ -1200,6 +1184,24 @@
 	return m
 }
 
+func (s *snapshotBuilder) AddInternalModule(properties android.SdkMemberProperties, moduleType string, nameSuffix string) android.BpModule {
+	name := properties.Name() + "-" + nameSuffix
+
+	if s.prebuiltModules[name] != nil {
+		panic(fmt.Sprintf("Duplicate module detected, module %s has already been added", name))
+	}
+
+	m := s.bpFile.newModule(moduleType)
+	m.AddProperty("name", name)
+	m.AddProperty("visibility", []string{"//visibility:private"})
+
+	s.prebuiltModules[name] = m
+	s.prebuiltOrder = append(s.prebuiltOrder, m)
+
+	s.allMembersByName[name] = struct{}{}
+	return m
+}
+
 func addHostDeviceSupportedProperties(deviceSupported bool, hostSupported bool, bpModule *bpModule) {
 	// If neither device or host is supported then this module does not support either so will not
 	// recognize the properties.
@@ -1230,18 +1232,23 @@
 // Get a name for sdk snapshot member. If the member is private then generate a snapshot specific
 // name. As part of the processing this checks to make sure that any required members are part of
 // the snapshot.
-func (s *snapshotBuilder) snapshotSdkMemberName(name string, required bool) string {
+func (s *snapshotBuilder) snapshotSdkMemberName(reference string, required bool) string {
+	prefix := ""
+	name := strings.TrimPrefix(reference, ":")
+	if name != reference {
+		prefix = ":"
+	}
 	if _, ok := s.allMembersByName[name]; !ok {
 		if required {
 			s.ctx.ModuleErrorf("Required member reference %s is not a member of the sdk", name)
 		}
-		return name
+		return reference
 	}
 
 	if s.isInternalMember(name) {
-		return s.ctx.ModuleName() + "_" + name
+		return prefix + s.ctx.ModuleName() + "_" + name
 	} else {
-		return name
+		return reference
 	}
 }
 
@@ -2057,6 +2064,7 @@
 	variantPropertiesFactory := func() android.SdkMemberProperties {
 		properties := memberType.CreateVariantPropertiesStruct()
 		base := properties.Base()
+		base.MemberName = member.Name()
 		base.Os_count = osCount
 		return properties
 	}
diff --git a/ui/build/config.go b/ui/build/config.go
index de10112..896a854 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -896,6 +896,10 @@
 	return filepath.Join(c.OutDir(), "bazel")
 }
 
+func (c *configImpl) bazelOutputBase() string {
+	return filepath.Join(c.BazelOutDir(), "output")
+}
+
 func (c *configImpl) SoongOutDir() string {
 	return filepath.Join(c.OutDir(), "soong")
 }
diff --git a/ui/build/soong.go b/ui/build/soong.go
index 4aded17..ee2b7c8 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -418,7 +418,7 @@
 	// Bazel's HOME var is set to an output subdirectory which doesn't exist. This
 	// prevents Bazel from file I/O in the actual user HOME directory.
 	soongBuildEnv.Set("BAZEL_HOME", absPath(ctx, filepath.Join(config.BazelOutDir(), "bazelhome")))
-	soongBuildEnv.Set("BAZEL_OUTPUT_BASE", filepath.Join(config.BazelOutDir(), "output"))
+	soongBuildEnv.Set("BAZEL_OUTPUT_BASE", config.bazelOutputBase())
 	soongBuildEnv.Set("BAZEL_WORKSPACE", absPath(ctx, "."))
 	soongBuildEnv.Set("BAZEL_METRICS_DIR", config.BazelMetricsDir())
 	soongBuildEnv.Set("LOG_DIR", config.LogsDir())
diff --git a/ui/build/test_build.go b/ui/build/test_build.go
index 86c8568..2efc732 100644
--- a/ui/build/test_build.go
+++ b/ui/build/test_build.go
@@ -18,14 +18,44 @@
 	"bufio"
 	"fmt"
 	"path/filepath"
+	"regexp"
 	"runtime"
 	"sort"
 	"strings"
+	"sync"
 
 	"android/soong/ui/metrics"
 	"android/soong/ui/status"
 )
 
+var (
+	// bazel output paths are in __main__/bazel-out/<config-specific-path>/bin
+	bazelOutputPathRegexOnce sync.Once
+	bazelOutputPathRegexp    *regexp.Regexp
+)
+
+func bazelOutputPathPattern(config Config) *regexp.Regexp {
+	bazelOutputPathRegexOnce.Do(func() {
+		// Bazel output files are in <Bazel output base>/execroot/__main__/bazel-out/<config>/bin
+		bazelOutRoot := filepath.Join(regexp.QuoteMeta(config.bazelOutputBase()), "execroot", "__main__", "bazel-out")
+		bazelOutputPathRegexp = regexp.MustCompile(bazelOutRoot + "/[^/]+/bin")
+	})
+	return bazelOutputPathRegexp
+}
+
+func ignoreBazelPath(config Config, path string) bool {
+	bazelRoot := filepath.Join(config.bazelOutputBase(), "execroot")
+	// Don't check bazel output regexp unless it is Bazel path
+	if strings.HasPrefix(path, bazelRoot) {
+		bazelOutputRegexp := bazelOutputPathPattern(config)
+		// if the file is a bazel path that is _not_ a Bazel generated file output, we rely on Bazel to
+		// ensure the paths to exist. If it _is_ a Bazel output path, we expect that it should be built
+		// by Ninja.
+		return !bazelOutputRegexp.MatchString(path)
+	}
+	return false
+}
+
 // Checks for files in the out directory that have a rule that depends on them but no rule to
 // create them. This catches a common set of build failures where a rule to generate a file is
 // deleted (either by deleting a module in an Android.mk file, or by modifying the build system
@@ -97,6 +127,10 @@
 			// full build rules in the primary build.ninja file.
 			continue
 		}
+
+		if ignoreBazelPath(config, line) {
+			continue
+		}
 		danglingRules[line] = true
 	}