Merge "Add test to TestJavaStableSdkVersion for legacy core platform"
diff --git a/android/bazel.go b/android/bazel.go
index facd116..8341e57 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -212,6 +212,13 @@
 		"libc_ndk",          // http://b/187013218, cc_library_static, depends on //bionic/libm:libm (http://b/183064661)
 		"libc_malloc_hooks", // http://b/187016307, cc_library, ld.lld: error: undefined symbol: __malloc_hook
 
+		// There are unexported symbols that don't surface on a shared library build,
+		// from the source static archive
+		// e.g. _Unwind_{GetRegionStart,GetLanguageSpecificData,GetIP,Set{IP,GR},Resume,{Raise,Delete}Exception}, pthread_atfork
+		// ... from: cxa_{personality,exception}.o, system_error.o, wrappers_c_bionic.o
+		// cf. http://b/198403271
+		"libc++",
+
 		// http://b/186823769: Needs C++ STL support, includes from unconverted standard libraries in //external/libcxx
 		// c++_static
 		"libbase_ndk", // http://b/186826477, cc_library, no such target '//build/bazel/platforms/os:darwin' when --platforms //build/bazel/platforms:android_x86 is added
@@ -223,6 +230,8 @@
 		"libbase",                  // Requires liblog. http://b/186826479, cc_library, fatal error: 'memory' file not found, from libcxx.
 		// Also depends on fmtlib.
 
+		"libfdtrack", // depends on STL
+
 		"libseccomp_policy", // depends on libbase
 
 		"gwp_asan_crash_handler", // cc_library, ld.lld: error: undefined symbol: memset
diff --git a/android/config.go b/android/config.go
index 3e41fbb..993aaa7 100644
--- a/android/config.go
+++ b/android/config.go
@@ -1659,6 +1659,20 @@
 	return ConfiguredJarList{apexes, jars}
 }
 
+// Append a list of (apex, jar) pairs to the list.
+func (l *ConfiguredJarList) AppendList(other ConfiguredJarList) ConfiguredJarList {
+	apexes := make([]string, 0, l.Len()+other.Len())
+	jars := make([]string, 0, l.Len()+other.Len())
+
+	apexes = append(apexes, l.apexes...)
+	jars = append(jars, l.jars...)
+
+	apexes = append(apexes, other.apexes...)
+	jars = append(jars, other.jars...)
+
+	return ConfiguredJarList{apexes, jars}
+}
+
 // RemoveList filters out a list of (apex, jar) pairs from the receiving list of pairs.
 func (l *ConfiguredJarList) RemoveList(list ConfiguredJarList) ConfiguredJarList {
 	apexes := make([]string, 0, l.Len())
diff --git a/android/deapexer.go b/android/deapexer.go
index de3f635..9290481 100644
--- a/android/deapexer.go
+++ b/android/deapexer.go
@@ -73,7 +73,7 @@
 	// exported file name is the apex relative path, e.g. javalib/core-libart.jar.
 	//
 	// See Prebuilt.ApexInfoMutator for more information.
-	exports map[string]Path
+	exports map[string]WritablePath
 }
 
 // PrebuiltExportPath provides the path, or nil if not available, of a file exported from the
@@ -82,7 +82,7 @@
 // The exported file is identified by the apex relative path, e.g. "javalib/core-libart.jar".
 //
 // See apex/deapexer.go for more information.
-func (i DeapexerInfo) PrebuiltExportPath(apexRelativePath string) Path {
+func (i DeapexerInfo) PrebuiltExportPath(apexRelativePath string) WritablePath {
 	path := i.exports[apexRelativePath]
 	return path
 }
@@ -95,7 +95,7 @@
 // for use with a prebuilt_apex module.
 //
 // See apex/deapexer.go for more information.
-func NewDeapexerInfo(exports map[string]Path) DeapexerInfo {
+func NewDeapexerInfo(exports map[string]WritablePath) DeapexerInfo {
 	return DeapexerInfo{
 		exports: exports,
 	}
diff --git a/android/mutator.go b/android/mutator.go
index c6b6676..b361c51 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -16,9 +16,7 @@
 
 import (
 	"android/soong/bazel"
-	"fmt"
 	"reflect"
-	"strings"
 	"sync"
 
 	"github.com/google/blueprint"
@@ -519,12 +517,6 @@
 	name string,
 	bazelProps bazel.BazelTargetModuleProperties,
 	attrs interface{}) {
-	if strings.HasPrefix(name, bazel.BazelTargetModuleNamePrefix) {
-		panic(fmt.Errorf(
-			"The %s name prefix is added automatically, do not set it manually: %s",
-			bazel.BazelTargetModuleNamePrefix,
-			name))
-	}
 
 	info := bp2buildInfo{
 		Name:       name,
diff --git a/android/paths.go b/android/paths.go
index 763cd7c..2e378ba 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -263,38 +263,56 @@
 
 // OptionalPath is a container that may or may not contain a valid Path.
 type OptionalPath struct {
-	valid bool
-	path  Path
+	path          Path   // nil if invalid.
+	invalidReason string // Not applicable if path != nil. "" if the reason is unknown.
 }
 
 // OptionalPathForPath returns an OptionalPath containing the path.
 func OptionalPathForPath(path Path) OptionalPath {
-	if path == nil {
-		return OptionalPath{}
-	}
-	return OptionalPath{valid: true, path: path}
+	return OptionalPath{path: path}
+}
+
+// InvalidOptionalPath returns an OptionalPath that is invalid with the given reason.
+func InvalidOptionalPath(reason string) OptionalPath {
+
+	return OptionalPath{invalidReason: reason}
 }
 
 // Valid returns whether there is a valid path
 func (p OptionalPath) Valid() bool {
-	return p.valid
+	return p.path != nil
 }
 
 // Path returns the Path embedded in this OptionalPath. You must be sure that
 // there is a valid path, since this method will panic if there is not.
 func (p OptionalPath) Path() Path {
-	if !p.valid {
-		panic("Requesting an invalid path")
+	if p.path == nil {
+		msg := "Requesting an invalid path"
+		if p.invalidReason != "" {
+			msg += ": " + p.invalidReason
+		}
+		panic(msg)
 	}
 	return p.path
 }
 
+// InvalidReason returns the reason that the optional path is invalid, or "" if it is valid.
+func (p OptionalPath) InvalidReason() string {
+	if p.path != nil {
+		return ""
+	}
+	if p.invalidReason == "" {
+		return "unknown"
+	}
+	return p.invalidReason
+}
+
 // AsPaths converts the OptionalPath into Paths.
 //
 // It returns nil if this is not valid, or a single length slice containing the Path embedded in
 // this OptionalPath.
 func (p OptionalPath) AsPaths() Paths {
-	if !p.valid {
+	if p.path == nil {
 		return nil
 	}
 	return Paths{p.path}
@@ -303,7 +321,7 @@
 // RelativeToTop returns an OptionalPath with the path that was embedded having been replaced by the
 // result of calling Path.RelativeToTop on it.
 func (p OptionalPath) RelativeToTop() OptionalPath {
-	if !p.valid {
+	if p.path == nil {
 		return p
 	}
 	p.path = p.path.RelativeToTop()
@@ -312,7 +330,7 @@
 
 // String returns the string version of the Path, or "" if it isn't valid.
 func (p OptionalPath) String() string {
-	if p.valid {
+	if p.path != nil {
 		return p.path.String()
 	} else {
 		return ""
@@ -1077,6 +1095,7 @@
 	path, err := pathForSource(ctx, pathComponents...)
 	if err != nil {
 		reportPathError(ctx, err)
+		// No need to put the error message into the returned path since it has been reported already.
 		return OptionalPath{}
 	}
 
@@ -1091,7 +1110,7 @@
 		return OptionalPath{}
 	}
 	if !exists {
-		return OptionalPath{}
+		return InvalidOptionalPath(path.String() + " does not exist")
 	}
 	return OptionalPathForPath(path)
 }
@@ -1127,6 +1146,7 @@
 		relDir = srcPath.path
 	} else {
 		ReportPathErrorf(ctx, "Cannot find relative path for %s(%s)", reflect.TypeOf(path).Name(), path)
+		// No need to put the error message into the returned path since it has been reported already.
 		return OptionalPath{}
 	}
 	dir := filepath.Join(p.srcDir, p.path, relDir)
@@ -1140,7 +1160,7 @@
 		return OptionalPath{}
 	}
 	if len(paths) == 0 {
-		return OptionalPath{}
+		return InvalidOptionalPath(dir + " does not exist")
 	}
 	relPath := Rel(ctx, p.srcDir, paths[0])
 	return OptionalPathForPath(PathForSource(ctx, relPath))
diff --git a/android/paths_test.go b/android/paths_test.go
index f4e4ce1..3f4625d 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -137,26 +137,35 @@
 
 func TestOptionalPath(t *testing.T) {
 	var path OptionalPath
-	checkInvalidOptionalPath(t, path)
+	checkInvalidOptionalPath(t, path, "unknown")
 
 	path = OptionalPathForPath(nil)
-	checkInvalidOptionalPath(t, path)
+	checkInvalidOptionalPath(t, path, "unknown")
+
+	path = InvalidOptionalPath("foo")
+	checkInvalidOptionalPath(t, path, "foo")
+
+	path = InvalidOptionalPath("")
+	checkInvalidOptionalPath(t, path, "unknown")
 
 	path = OptionalPathForPath(PathForTesting("path"))
 	checkValidOptionalPath(t, path, "path")
 }
 
-func checkInvalidOptionalPath(t *testing.T, path OptionalPath) {
+func checkInvalidOptionalPath(t *testing.T, path OptionalPath, expectedInvalidReason string) {
 	t.Helper()
 	if path.Valid() {
-		t.Errorf("Uninitialized OptionalPath should not be valid")
+		t.Errorf("Invalid OptionalPath should not be valid")
+	}
+	if path.InvalidReason() != expectedInvalidReason {
+		t.Errorf("Wrong invalid reason: expected %q, got %q", expectedInvalidReason, path.InvalidReason())
 	}
 	if path.String() != "" {
-		t.Errorf("Uninitialized OptionalPath String() should return \"\", not %q", path.String())
+		t.Errorf("Invalid OptionalPath String() should return \"\", not %q", path.String())
 	}
 	paths := path.AsPaths()
 	if len(paths) != 0 {
-		t.Errorf("Uninitialized OptionalPath AsPaths() should return empty Paths, not %q", paths)
+		t.Errorf("Invalid OptionalPath AsPaths() should return empty Paths, not %q", paths)
 	}
 	defer func() {
 		if r := recover(); r == nil {
@@ -171,6 +180,9 @@
 	if !path.Valid() {
 		t.Errorf("Initialized OptionalPath should not be invalid")
 	}
+	if path.InvalidReason() != "" {
+		t.Errorf("Initialized OptionalPath should not have an invalid reason, got: %q", path.InvalidReason())
+	}
 	if path.String() != expectedString {
 		t.Errorf("Initialized OptionalPath String() should return %q, not %q", expectedString, path.String())
 	}
diff --git a/android/sdk.go b/android/sdk.go
index 7c26fbf..100f63b 100644
--- a/android/sdk.go
+++ b/android/sdk.go
@@ -22,6 +22,8 @@
 	"github.com/google/blueprint/proptools"
 )
 
+// RequiredSdks provides access to the set of SDKs required by an APEX and its contents.
+//
 // Extracted from SdkAware to make it easier to define custom subsets of the
 // SdkAware interface and improve code navigation within the IDE.
 //
@@ -30,11 +32,11 @@
 // is expected to implement RequiredSdks() by reading its own properties like
 // `uses_sdks`.
 type RequiredSdks interface {
-	// The set of SDKs required by an APEX and its contents.
+	// RequiredSdks returns the set of SDKs required by an APEX and its contents.
 	RequiredSdks() SdkRefs
 }
 
-// Provided to improve code navigation with the IDE.
+// sdkAwareWithoutModule is provided simply to improve code navigation with the IDE.
 type sdkAwareWithoutModule interface {
 	RequiredSdks
 
@@ -233,75 +235,85 @@
 	return false
 }
 
-// Provide support for generating the build rules which will build the snapshot.
+// SnapshotBuilder provides support for generating the build rules which will build the snapshot.
 type SnapshotBuilder interface {
-	// Copy src to the dest (which is a snapshot relative path) and add the dest
-	// to the zip
+	// CopyToSnapshot generates a rule that will copy the src to the dest (which is a snapshot
+	// relative path) and add the dest to the zip.
 	CopyToSnapshot(src Path, dest string)
 
-	// Return the path to an empty file.
+	// 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
 
-	// Unzip the supplied zip into the snapshot relative directory destDir.
+	// UnzipToSnapshot generates a rule that will unzip the supplied zip into the snapshot relative
+	// directory destDir.
 	UnzipToSnapshot(zipPath Path, destDir string)
 
-	// Add a new prebuilt module to the snapshot. The returned module
-	// must be populated with the module type specific properties. The following
-	// properties will be automatically populated.
+	// AddPrebuiltModule adds a new prebuilt module to the snapshot.
+	//
+	// It is intended to be called from SdkMemberType.AddPrebuiltModule which can add module type
+	// specific properties that are not variant specific. The following properties will be
+	// automatically populated before returning.
 	//
 	// * name
 	// * sdk_member_name
 	// * prefer
 	//
-	// This will result in two Soong modules being generated in the Android. One
-	// that is versioned, coupled to the snapshot version and marked as
-	// prefer=true. And one that is not versioned, not marked as prefer=true and
-	// will only be used if the equivalently named non-prebuilt module is not
-	// present.
+	// Properties that are variant specific will be handled by SdkMemberProperties structure.
+	//
+	// Each module created by this method can be output to the generated Android.bp file in two
+	// different forms, depending on the setting of the SOONG_SDK_SNAPSHOT_VERSION build property.
+	// The two forms are:
+	// 1. A versioned Soong module that is referenced from a corresponding similarly versioned
+	//    snapshot module.
+	// 2. An unversioned Soong module that.
+	//
+	// See sdk/update.go for more information.
 	AddPrebuiltModule(member SdkMember, moduleType string) BpModule
 
-	// The property tag to use when adding a property to a BpModule that contains
-	// references to other sdk members. Using this will ensure that the reference
-	// is correctly output for both versioned and unversioned prebuilts in the
-	// snapshot.
+	// SdkMemberReferencePropertyTag returns a property tag to use when adding a property to a
+	// BpModule that contains references to other sdk members.
 	//
-	// "required: true" means that the property must only contain references
-	// to other members of the sdk. Passing a reference to a module that is not a
-	// member of the sdk will result in a build error.
+	// Using this will ensure that the reference is correctly output for both versioned and
+	// unversioned prebuilts in the snapshot.
 	//
-	// "required: false" means that the property can contain references to modules
-	// that are either members or not members of the sdk. If a reference is to a
-	// module that is a non member then the reference is left unchanged, i.e. it
-	// is not transformed as references to members are.
+	// "required: true" means that the property must only contain references to other members of the
+	// sdk. Passing a reference to a module that is not a member of the sdk will result in a build
+	// error.
 	//
-	// The handling of the member names is dependent on whether it is an internal or
-	// exported member. An exported member is one whose name is specified in one of
-	// the member type specific properties. An internal member is one that is added
-	// due to being a part of an exported (or other internal) member and is not itself
-	// an exported member.
+	// "required: false" means that the property can contain references to modules that are either
+	// members or not members of the sdk. If a reference is to a module that is a non member then the
+	// reference is left unchanged, i.e. it is not transformed as references to members are.
+	//
+	// The handling of the member names is dependent on whether it is an internal or exported member.
+	// An exported member is one whose name is specified in one of the member type specific
+	// properties. An internal member is one that is added due to being a part of an exported (or
+	// other internal) member and is not itself an exported member.
 	//
 	// Member names are handled as follows:
-	// * When creating the unversioned form of the module the name is left unchecked
-	//   unless the member is internal in which case it is transformed into an sdk
-	//   specific name, i.e. by prefixing with the sdk name.
+	// * When creating the unversioned form of the module the name is left unchecked unless the member
+	//   is internal in which case it is transformed into an sdk specific name, i.e. by prefixing with
+	//   the sdk name.
 	//
-	// * When creating the versioned form of the module the name is transformed into
-	//   a versioned sdk specific name, i.e. by prefixing with the sdk name and
-	//   suffixing with the version.
+	// * When creating the versioned form of the module the name is transformed into a versioned sdk
+	//   specific name, i.e. by prefixing with the sdk name and suffixing with the version.
 	//
 	// e.g.
 	// bpPropertySet.AddPropertyWithTag("libs", []string{"member1", "member2"}, builder.SdkMemberReferencePropertyTag(true))
 	SdkMemberReferencePropertyTag(required bool) BpPropertyTag
 }
 
+// BpPropertyTag is a marker interface that can be associated with properties in a BpPropertySet to
+// provide additional information which can be used to customize their behavior.
 type BpPropertyTag interface{}
 
-// A set of properties for use in a .bp file.
+// BpPropertySet is a set of properties for use in a .bp file.
 type BpPropertySet interface {
-	// Add a property, the value can be one of the following types:
+	// AddProperty adds a property.
+	//
+	// The value can be one of the following types:
 	// * string
 	// * array of the above
 	// * bool
@@ -326,18 +338,18 @@
 	// * Otherwise, if a property exists with the name then it is an error.
 	AddProperty(name string, value interface{})
 
-	// Add a property with an associated tag
+	// AddPropertyWithTag adds a property with an associated property tag.
 	AddPropertyWithTag(name string, value interface{}, tag BpPropertyTag)
 
-	// Add a property set with the specified name and return so that additional
-	// properties can be added.
+	// AddPropertySet adds a property set with the specified name and returns it so that additional
+	// properties can be added to it.
 	AddPropertySet(name string) BpPropertySet
 
-	// Add comment for property (or property set).
+	// AddCommentForProperty adds a comment for the named property (or property set).
 	AddCommentForProperty(name, text string)
 }
 
-// A .bp module definition.
+// BpModule represents a module definition in a .bp file.
 type BpModule interface {
 	BpPropertySet
 
@@ -364,13 +376,14 @@
 
 var _ BpPrintable = BpPrintableBase{}
 
-// An individual member of the SDK, includes all of the variants that the SDK
-// requires.
+// SdkMember is an individual member of the SDK.
+//
+// It includes all of the variants that the SDK depends upon.
 type SdkMember interface {
-	// The name of the member.
+	// Name returns the name of the member.
 	Name() string
 
-	// All the variants required by the SDK.
+	// Variants returns all the variants of this module depended upon by the SDK.
 	Variants() []SdkAware
 }
 
@@ -418,8 +431,8 @@
 	return t.export
 }
 
-// Prevent dependencies from the sdk/module_exports onto their members from being
-// replaced with a preferred prebuilt.
+// ReplaceSourceWithPrebuilt prevents dependencies from the sdk/module_exports onto their members
+// from being replaced with a preferred prebuilt.
 func (t *sdkMemberDependencyTag) ReplaceSourceWithPrebuilt() bool {
 	return false
 }
@@ -431,7 +444,7 @@
 	return &sdkMemberDependencyTag{memberType: memberType, export: export}
 }
 
-// Interface that must be implemented for every type that can be a member of an
+// SdkMemberType is the interface that must be implemented for every type that can be a member of an
 // sdk.
 //
 // The basic implementation should look something like this, where ModuleType is
@@ -452,43 +465,43 @@
 //    ...methods...
 //
 type SdkMemberType interface {
-	// The name of the member type property on an sdk module.
+	// SdkPropertyName returns the name of the member type property on an sdk module.
 	SdkPropertyName() string
 
 	// RequiresBpProperty returns true if this member type requires its property to be usable within
 	// an Android.bp file.
 	RequiresBpProperty() bool
 
-	// True if the member type supports the sdk/sdk_snapshot, false otherwise.
+	// UsableWithSdkAndSdkSnapshot returns true if the member type supports the sdk/sdk_snapshot,
+	// false otherwise.
 	UsableWithSdkAndSdkSnapshot() bool
 
-	// Return true if prebuilt host artifacts may be specific to the host OS. Only
-	// applicable to modules where HostSupported() is true. If this is true,
-	// snapshots will list each host OS variant explicitly and disable all other
-	// host OS'es.
+	// IsHostOsDependent returns true if prebuilt host artifacts may be specific to the host OS. Only
+	// applicable to modules where HostSupported() is true. If this is true, snapshots will list each
+	// host OS variant explicitly and disable all other host OS'es.
 	IsHostOsDependent() bool
 
-	// Add dependencies from the SDK module to all the module variants the member
-	// type contributes to the SDK. `names` is the list of module names given in
-	// the member type property (as returned by SdkPropertyName()) in the SDK
-	// module. The exact set of variants required is determined by the SDK and its
-	// properties. The dependencies must be added with the supplied tag.
+	// AddDependencies adds dependencies from the SDK module to all the module variants the member
+	// type contributes to the SDK. `names` is the list of module names given in the member type
+	// property (as returned by SdkPropertyName()) in the SDK module. The exact set of variants
+	// required is determined by the SDK and its properties. The dependencies must be added with the
+	// supplied tag.
 	//
 	// The BottomUpMutatorContext provided is for the SDK module.
 	AddDependencies(ctx SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string)
 
-	// Return true if the supplied module is an instance of this member type.
+	// IsInstance returns true if the supplied module is an instance of this member type.
 	//
-	// This is used to check the type of each variant before added to the
-	// SdkMember. Returning false will cause an error to be logged expaining that
-	// the module is not allowed in whichever sdk property it was added.
+	// This is used to check the type of each variant before added to the SdkMember. Returning false
+	// will cause an error to be logged explaining that the module is not allowed in whichever sdk
+	// property it was added.
 	IsInstance(module Module) bool
 
 	// UsesSourceModuleTypeInSnapshot returns true when the AddPrebuiltModule() method returns a
 	// source module type.
 	UsesSourceModuleTypeInSnapshot() bool
 
-	// Add a prebuilt module that the sdk will populate.
+	// AddPrebuiltModule is called to add a prebuilt module that the sdk will populate.
 	//
 	// The sdk module code generates the snapshot as follows:
 	//
@@ -525,7 +538,8 @@
 	//
 	AddPrebuiltModule(ctx SdkMemberContext, member SdkMember) BpModule
 
-	// Create a structure into which variant specific properties can be added.
+	// CreateVariantPropertiesStruct creates a structure into which variant specific properties can be
+	// added.
 	CreateVariantPropertiesStruct() SdkMemberProperties
 }
 
@@ -535,7 +549,8 @@
 	BottomUpMutatorContext
 }
 
-// Base type for SdkMemberType implementations.
+// SdkMemberTypeBase is the base type for SdkMemberType implementations and must be embedded in any
+// struct that implements SdkMemberType.
 type SdkMemberTypeBase struct {
 	PropertyName string
 
@@ -572,7 +587,7 @@
 	return b.UseSourceModuleTypeInSnapshot
 }
 
-// Encapsulates the information about registered SdkMemberTypes.
+// SdkMemberTypesRegistry encapsulates the information about registered SdkMemberTypes.
 type SdkMemberTypesRegistry struct {
 	// The list of types sorted by property name.
 	list []SdkMemberType
@@ -610,14 +625,15 @@
 	return NewCustomOnceKey(r)
 }
 
-// The set of registered SdkMemberTypes for module_exports modules.
+// ModuleExportsMemberTypes is the set of registered SdkMemberTypes for module_exports modules.
 var ModuleExportsMemberTypes = &SdkMemberTypesRegistry{}
 
-// The set of registered SdkMemberTypes for sdk modules.
+// SdkMemberTypes is the set of registered SdkMemberTypes for sdk modules.
 var SdkMemberTypes = &SdkMemberTypesRegistry{}
 
-// Register an SdkMemberType object to allow them to be used in the sdk and sdk_snapshot module
-// types.
+// RegisterSdkMemberType registers an SdkMemberType object to allow them to be used in the
+// module_exports, module_exports_snapshot and (depending on the value returned from
+// SdkMemberType.UsableWithSdkAndSdkSnapshot) the sdk and sdk_snapshot module types.
 func RegisterSdkMemberType(memberType SdkMemberType) {
 	// All member types are usable with module_exports.
 	ModuleExportsMemberTypes = ModuleExportsMemberTypes.copyAndAppend(memberType)
@@ -628,7 +644,8 @@
 	}
 }
 
-// Base structure for all implementations of SdkMemberProperties.
+// SdkMemberPropertiesBase is the base structure for all implementations of SdkMemberProperties and
+// must be embedded in any struct that implements SdkMemberProperties.
 //
 // Contains common properties that apply across many different member types.
 type SdkMemberPropertiesBase struct {
@@ -655,7 +672,7 @@
 	Compile_multilib string `android:"arch_variant"`
 }
 
-// The os prefix to use for any file paths in the sdk.
+// 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
 // is the OsType.Name.
@@ -671,35 +688,47 @@
 	return b
 }
 
-// Interface to be implemented on top of a structure that contains variant specific
-// information.
+// SdkMemberProperties is the interface to be implemented on top of a structure that contains
+// variant specific information.
 //
-// Struct fields that are capitalized are examined for common values to extract. Fields
-// that are not capitalized are assumed to be arch specific.
+// Struct fields that are capitalized are examined for common values to extract. Fields that are not
+// capitalized are assumed to be arch specific.
 type SdkMemberProperties interface {
-	// Access the base structure.
+	// Base returns the base structure.
 	Base() *SdkMemberPropertiesBase
 
-	// Populate this structure with information from the variant.
+	// 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.
 	PopulateFromVariant(ctx SdkMemberContext, variant Module)
 
-	// Add the information from this structure to the property set.
+	// AddToPropertySet adds the information from this structure to the property set.
+	//
+	// This will be called for each instance of this structure on which the PopulateFromVariant method
+	// was called and also on a number of different instances of this structure into which properties
+	// common to one or more variants have been copied. Therefore, implementations of this must handle
+	// the case when this structure is only partially populated.
 	AddToPropertySet(ctx SdkMemberContext, propertySet BpPropertySet)
 }
 
-// Provides access to information common to a specific member.
+// SdkMemberContext provides access to information common to a specific member.
 type SdkMemberContext interface {
 
-	// The module context of the sdk common os variant which is creating the snapshot.
+	// SdkModuleContext returns the module context of the sdk common os variant which is creating the
+	// snapshot.
+	//
+	// This is common to all members of the sdk and is not specific to the member being processed.
+	// If information about the member being processed needs to be obtained from this ModuleContext it
+	// must be obtained using one of the OtherModule... methods not the Module... methods.
 	SdkModuleContext() ModuleContext
 
-	// The builder of the snapshot.
+	// SnapshotBuilder the builder of the snapshot.
 	SnapshotBuilder() SnapshotBuilder
 
-	// The type of the member.
+	// MemberType returns the type of the member currently being processed.
 	MemberType() SdkMemberType
 
-	// The name of the member.
+	// Name returns the name of the member currently being processed.
 	//
 	// Provided for use by sdk members to create a member specific location within the snapshot
 	// into which to copy the prebuilt files.
diff --git a/android/testing.go b/android/testing.go
index bd2faa2..7a89fc4 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -491,6 +491,66 @@
 	ctx.preSingletons = append(ctx.preSingletons, newPreSingleton(name, factory))
 }
 
+// ModuleVariantForTests selects a specific variant of the module with the given
+// name by matching the variations map against the variations of each module
+// variant. A module variant matches the map if every variation that exists in
+// both have the same value. Both the module and the map are allowed to have
+// extra variations that the other doesn't have. Panics if not exactly one
+// module variant matches.
+func (ctx *TestContext) ModuleVariantForTests(name string, matchVariations map[string]string) TestingModule {
+	modules := []Module{}
+	ctx.VisitAllModules(func(m blueprint.Module) {
+		if ctx.ModuleName(m) == name {
+			am := m.(Module)
+			amMut := am.base().commonProperties.DebugMutators
+			amVar := am.base().commonProperties.DebugVariations
+			matched := true
+			for i, mut := range amMut {
+				if wantedVar, found := matchVariations[mut]; found && amVar[i] != wantedVar {
+					matched = false
+					break
+				}
+			}
+			if matched {
+				modules = append(modules, am)
+			}
+		}
+	})
+
+	if len(modules) == 0 {
+		// Show all the modules or module variants that do exist.
+		var allModuleNames []string
+		var allVariants []string
+		ctx.VisitAllModules(func(m blueprint.Module) {
+			allModuleNames = append(allModuleNames, ctx.ModuleName(m))
+			if ctx.ModuleName(m) == name {
+				allVariants = append(allVariants, m.(Module).String())
+			}
+		})
+
+		if len(allVariants) == 0 {
+			panic(fmt.Errorf("failed to find module %q. All modules:\n  %s",
+				name, strings.Join(SortedUniqueStrings(allModuleNames), "\n  ")))
+		} else {
+			sort.Strings(allVariants)
+			panic(fmt.Errorf("failed to find module %q matching %v. All variants:\n  %s",
+				name, matchVariations, strings.Join(allVariants, "\n  ")))
+		}
+	}
+
+	if len(modules) > 1 {
+		moduleStrings := []string{}
+		for _, m := range modules {
+			moduleStrings = append(moduleStrings, m.String())
+		}
+		sort.Strings(moduleStrings)
+		panic(fmt.Errorf("module %q has more than one variant that match %v:\n  %s",
+			name, matchVariations, strings.Join(moduleStrings, "\n  ")))
+	}
+
+	return newTestingModule(ctx.config, modules[0])
+}
+
 func (ctx *TestContext) ModuleForTests(name, variant string) TestingModule {
 	var module Module
 	ctx.VisitAllModules(func(m blueprint.Module) {
diff --git a/apex/apex.go b/apex/apex.go
index e3edc68..2d153e2 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -1569,6 +1569,11 @@
 	af.jacocoReportClassesFile = module.JacocoReportClassesFile()
 	af.lintDepSets = module.LintDepSets()
 	af.customStem = module.Stem() + ".jar"
+	if dexpreopter, ok := module.(java.DexpreopterInterface); ok {
+		for _, install := range dexpreopter.DexpreoptBuiltInstalledForApex() {
+			af.requiredModuleNames = append(af.requiredModuleNames, install.FullModuleName())
+		}
+	}
 	return af
 }
 
diff --git a/apex/deapexer.go b/apex/deapexer.go
index c70da15..2c1835a 100644
--- a/apex/deapexer.go
+++ b/apex/deapexer.go
@@ -97,7 +97,7 @@
 	// Create and remember the directory into which the .apex file's contents will be unpacked.
 	deapexerOutput := android.PathForModuleOut(ctx, "deapexer")
 
-	exports := make(map[string]android.Path)
+	exports := make(map[string]android.WritablePath)
 
 	// Create mappings from apex relative path to the extracted file's path.
 	exportedPaths := make(android.Paths, 0, len(exports))
diff --git a/apex/platform_bootclasspath_test.go b/apex/platform_bootclasspath_test.go
index 513ddc0..d8a4a7a 100644
--- a/apex/platform_bootclasspath_test.go
+++ b/apex/platform_bootclasspath_test.go
@@ -21,6 +21,7 @@
 
 	"android/soong/android"
 	"android/soong/java"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
@@ -325,31 +326,15 @@
 }
 
 // TestPlatformBootclasspath_AlwaysUsePrebuiltSdks verifies that the build does not fail when
-// AlwaysUsePrebuiltSdk() returns true. The structure of the modules in this test matches what
-// currently exists in some places in the Android build but it is not the intended structure. It is
-// in fact an invalid structure that should cause build failures. However, fixing that structure
-// will take too long so in the meantime this tests the workarounds to avoid build breakages.
-//
-// The main issues with this structure are:
-// 1. There is no prebuilt_bootclasspath_fragment referencing the "foo" java_sdk_library_import.
-// 2. There is no prebuilt_apex/apex_set which makes the dex implementation jar available to the
-//    prebuilt_bootclasspath_fragment and the "foo" java_sdk_library_import.
-//
-// Together these cause the following symptoms:
-// 1. The "foo" java_sdk_library_import does not have a dex implementation jar.
-// 2. The "foo" java_sdk_library_import does not have a myapex variant.
-//
-// TODO(b/179354495): Fix the structure in this test once the main Android build has been fixed.
+// AlwaysUsePrebuiltSdk() returns true.
 func TestPlatformBootclasspath_AlwaysUsePrebuiltSdks(t *testing.T) {
 	result := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
 		prepareForTestWithMyapex,
 		// Configure two libraries, the first is a java_sdk_library whose prebuilt will be used because
-		// of AlwaysUsePrebuiltsSdk() but does not have an appropriate apex variant and does not provide
-		// a boot dex jar. The second is a normal library that is unaffected. The order matters because
-		// if the dependency on myapex:foo is filtered out because of either of those conditions then
-		// the dependencies resolved by the platform_bootclasspath will not match the configured list
-		// and so will fail the test.
+		// of AlwaysUsePrebuiltsSdk(). The second is a normal library that is unaffected. The order
+		// matters, so that the dependencies resolved by the platform_bootclasspath matches the
+		// configured list.
 		java.FixtureConfigureApexBootJars("myapex:foo", "myapex:bar"),
 		java.PrepareForTestWithJavaSdkLibraryFiles,
 		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
@@ -394,6 +379,12 @@
 			permitted_packages: ["foo"],
 		}
 
+		prebuilt_apex {
+			name: "myapex",
+			src: "myapex.apex",
+			exported_bootclasspath_fragments: ["mybootclasspath-fragment"],
+		}
+
 		// A prebuilt java_sdk_library_import that is not preferred by default but will be preferred
 		// because AlwaysUsePrebuiltSdks() is true.
 		java_sdk_library_import {
@@ -423,6 +414,23 @@
 			],
 		}
 
+		prebuilt_bootclasspath_fragment {
+			name: "mybootclasspath-fragment",
+			apex_available: [
+				"myapex",
+			],
+			contents: [
+				"foo",
+			],
+			hidden_api: {
+				stub_flags: "",
+				annotation_flags: "",
+				metadata: "",
+				index: "",
+				all_flags: "",
+			},
+		}
+
 		platform_bootclasspath {
 			name: "myplatform-bootclasspath",
 			fragments: [
@@ -437,7 +445,7 @@
 
 	java.CheckPlatformBootclasspathModules(t, result, "myplatform-bootclasspath", []string{
 		// The configured contents of BootJars.
-		"platform:prebuilt_foo", // Note: This is the platform not myapex variant.
+		"myapex:prebuilt_foo",
 		"myapex:bar",
 	})
 
@@ -456,16 +464,15 @@
 
 		// The platform_bootclasspath intentionally adds dependencies on both source and prebuilt
 		// modules when available as it does not know which one will be preferred.
-		//
-		// The source module has an APEX variant but the prebuilt does not.
 		"myapex:foo",
-		"platform:prebuilt_foo",
+		"myapex:prebuilt_foo",
 
 		// Only a source module exists.
 		"myapex:bar",
 
 		// The fragments.
 		"myapex:mybootclasspath-fragment",
+		"myapex:prebuilt_mybootclasspath-fragment",
 	})
 }
 
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index c4794dc..4833a64 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -178,13 +178,19 @@
 			// If the exported java module provides a dex jar path then add it to the list of apexFiles.
 			path := child.(interface{ DexJarBuildPath() android.Path }).DexJarBuildPath()
 			if path != nil {
-				p.apexFilesForAndroidMk = append(p.apexFilesForAndroidMk, apexFile{
+				af := apexFile{
 					module:              child,
 					moduleDir:           ctx.OtherModuleDir(child),
 					androidMkModuleName: name,
 					builtFile:           path,
 					class:               javaSharedLib,
-				})
+				}
+				if module, ok := child.(java.DexpreopterInterface); ok {
+					for _, install := range module.DexpreoptBuiltInstalledForApex() {
+						af.requiredModuleNames = append(af.requiredModuleNames, install.FullModuleName())
+					}
+				}
+				p.apexFilesForAndroidMk = append(p.apexFilesForAndroidMk, af)
 			}
 		} else if tag == exportedBootclasspathFragmentTag {
 			// Visit the children of the bootclasspath_fragment.
@@ -195,6 +201,14 @@
 	})
 }
 
+func (p *prebuiltCommon) addRequiredModules(entries *android.AndroidMkEntries) {
+	for _, fi := range p.apexFilesForAndroidMk {
+		entries.AddStrings("LOCAL_REQUIRED_MODULES", fi.requiredModuleNames...)
+		entries.AddStrings("LOCAL_TARGET_REQUIRED_MODULES", fi.targetRequiredModuleNames...)
+		entries.AddStrings("LOCAL_HOST_REQUIRED_MODULES", fi.hostRequiredModuleNames...)
+	}
+}
+
 func (p *prebuiltCommon) AndroidMkEntries() []android.AndroidMkEntries {
 	entriesList := []android.AndroidMkEntries{
 		{
@@ -213,6 +227,7 @@
 					if len(postInstallCommands) > 0 {
 						entries.SetString("LOCAL_POST_INSTALL_CMD", strings.Join(postInstallCommands, " && "))
 					}
+					p.addRequiredModules(entries)
 				},
 			},
 		},
diff --git a/bazel/cquery/request_type.go b/bazel/cquery/request_type.go
index 131f0ec..d731f3e 100644
--- a/bazel/cquery/request_type.go
+++ b/bazel/cquery/request_type.go
@@ -25,6 +25,7 @@
 	// be a subset of OutputFiles. (or shared libraries, this will be equal to OutputFiles,
 	// but general cc_library will also have dynamic libraries in output files).
 	RootDynamicLibraries []string
+	TocFile              string
 }
 
 type getOutputFilesRequestType struct{}
@@ -100,14 +101,15 @@
 func (g getCcInfoType) StarlarkFunctionBody() string {
 	return `
 outputFiles = [f.path for f in target.files.to_list()]
+cc_info = providers(target)["CcInfo"]
 
-includes = providers(target)["CcInfo"].compilation_context.includes.to_list()
-system_includes = providers(target)["CcInfo"].compilation_context.system_includes.to_list()
+includes = cc_info.compilation_context.includes.to_list()
+system_includes = cc_info.compilation_context.system_includes.to_list()
 
 ccObjectFiles = []
 staticLibraries = []
 rootStaticArchives = []
-linker_inputs = providers(target)["CcInfo"].linking_context.linker_inputs.to_list()
+linker_inputs = cc_info.linking_context.linker_inputs.to_list()
 
 for linker_input in linker_inputs:
   for library in linker_input.libraries:
@@ -120,11 +122,17 @@
 
 rootDynamicLibraries = []
 
-if "@rules_cc//examples:experimental_cc_shared_library.bzl%CcSharedLibraryInfo" in providers(target):
-  shared_info = providers(target)["@rules_cc//examples:experimental_cc_shared_library.bzl%CcSharedLibraryInfo"]
+shared_info_tag = "@rules_cc//examples:experimental_cc_shared_library.bzl%CcSharedLibraryInfo"
+if shared_info_tag in providers(target):
+  shared_info = providers(target)[shared_info_tag]
   for lib in shared_info.linker_input.libraries:
     rootDynamicLibraries += [lib.dynamic_library.path]
 
+toc_file = ""
+toc_file_tag = "//build/bazel/rules:generate_toc.bzl%CcTocInfo"
+if toc_file_tag in providers(target):
+  toc_file = providers(target)[toc_file_tag].toc.path
+
 returns = [
   outputFiles,
   staticLibraries,
@@ -132,10 +140,10 @@
   includes,
   system_includes,
   rootStaticArchives,
-  rootDynamicLibraries
+  rootDynamicLibraries,
 ]
 
-return "|".join([", ".join(r) for r in returns])`
+return "|".join([", ".join(r) for r in returns] + [toc_file])`
 }
 
 // ParseResult returns a value obtained by parsing the result of the request's Starlark function.
@@ -146,7 +154,7 @@
 	var ccObjects []string
 
 	splitString := strings.Split(rawString, "|")
-	if expectedLen := 7; len(splitString) != expectedLen {
+	if expectedLen := 8; len(splitString) != expectedLen {
 		return CcInfo{}, fmt.Errorf("Expected %d items, got %q", expectedLen, splitString)
 	}
 	outputFilesString := splitString[0]
@@ -159,6 +167,7 @@
 	systemIncludes := splitOrEmpty(splitString[4], ", ")
 	rootStaticArchives := splitOrEmpty(splitString[5], ", ")
 	rootDynamicLibraries := splitOrEmpty(splitString[6], ", ")
+	tocFile := splitString[7] // NOTE: Will be the empty string if there wasn't
 	return CcInfo{
 		OutputFiles:          outputFiles,
 		CcObjectFiles:        ccObjects,
@@ -167,6 +176,7 @@
 		SystemIncludes:       systemIncludes,
 		RootStaticArchives:   rootStaticArchives,
 		RootDynamicLibraries: rootDynamicLibraries,
+		TocFile:              tocFile,
 	}, nil
 }
 
diff --git a/bazel/cquery/request_type_test.go b/bazel/cquery/request_type_test.go
index 49019ab..34d0832 100644
--- a/bazel/cquery/request_type_test.go
+++ b/bazel/cquery/request_type_test.go
@@ -71,7 +71,7 @@
 	}{
 		{
 			description: "no result",
-			input:       "||||||",
+			input:       "|||||||",
 			expectedOutput: CcInfo{
 				OutputFiles:          []string{},
 				CcObjectFiles:        []string{},
@@ -80,11 +80,12 @@
 				SystemIncludes:       []string{},
 				RootStaticArchives:   []string{},
 				RootDynamicLibraries: []string{},
+				TocFile:              "",
 			},
 		},
 		{
 			description: "only output",
-			input:       "test||||||",
+			input:       "test|||||||",
 			expectedOutput: CcInfo{
 				OutputFiles:          []string{"test"},
 				CcObjectFiles:        []string{},
@@ -93,11 +94,12 @@
 				SystemIncludes:       []string{},
 				RootStaticArchives:   []string{},
 				RootDynamicLibraries: []string{},
+				TocFile:              "",
 			},
 		},
 		{
 			description: "all items set",
-			input:       "out1, out2|static_lib1, static_lib2|object1, object2|., dir/subdir|system/dir, system/other/dir|rootstaticarchive1|rootdynamiclibrary1",
+			input:       "out1, out2|static_lib1, static_lib2|object1, object2|., dir/subdir|system/dir, system/other/dir|rootstaticarchive1|rootdynamiclibrary1|lib.so.toc",
 			expectedOutput: CcInfo{
 				OutputFiles:          []string{"out1", "out2"},
 				CcObjectFiles:        []string{"object1", "object2"},
@@ -106,19 +108,20 @@
 				SystemIncludes:       []string{"system/dir", "system/other/dir"},
 				RootStaticArchives:   []string{"rootstaticarchive1"},
 				RootDynamicLibraries: []string{"rootdynamiclibrary1"},
+				TocFile:              "lib.so.toc",
 			},
 		},
 		{
 			description:          "too few result splits",
 			input:                "|",
 			expectedOutput:       CcInfo{},
-			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", 7, []string{"", ""}),
+			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", 8, []string{"", ""}),
 		},
 		{
 			description:          "too many result splits",
 			input:                strings.Repeat("|", 8),
 			expectedOutput:       CcInfo{},
-			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", 7, make([]string, 9)),
+			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", 8, make([]string, 9)),
 		},
 	}
 	for _, tc := range testCases {
diff --git a/bazel/properties.go b/bazel/properties.go
index ed4e9fc..d3b40a2 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -19,7 +19,6 @@
 	"path/filepath"
 	"regexp"
 	"sort"
-	"strings"
 )
 
 // BazelTargetModuleProperties contain properties and metadata used for
@@ -32,12 +31,6 @@
 	Bzl_load_location string `blueprint:"mutated"`
 }
 
-const BazelTargetModuleNamePrefix = "__bp2build__"
-
-func StripNamePrefix(moduleName string) string {
-	return strings.TrimPrefix(moduleName, BazelTargetModuleNamePrefix)
-}
-
 var productVariableSubstitutionPattern = regexp.MustCompile("%(d|s)")
 
 // Label is used to represent a Bazel compatible Label. Also stores the original
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
index 5ee04f9..40526a6 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -11,7 +11,6 @@
         "build_conversion.go",
         "bzl_conversion.go",
         "configurability.go",
-        "compatibility.go",
         "constants.go",
         "conversion.go",
         "metrics.go",
@@ -37,6 +36,7 @@
         "cc_library_conversion_test.go",
         "cc_library_headers_conversion_test.go",
         "cc_library_static_conversion_test.go",
+        "cc_library_shared_conversion_test.go",
         "cc_object_conversion_test.go",
         "conversion_test.go",
         "filegroup_conversion_test.go",
diff --git a/bp2build/bp2build.go b/bp2build/bp2build.go
index 48b2945..45a3cb6 100644
--- a/bp2build/bp2build.go
+++ b/bp2build/bp2build.go
@@ -43,7 +43,7 @@
 	writeFiles(ctx, bp2buildDir, bp2buildFiles)
 
 	soongInjectionDir := android.PathForOutput(ctx, bazel.SoongInjectionDirName)
-	writeFiles(ctx, soongInjectionDir, CreateSoongInjectionFiles(res.compatLayer))
+	writeFiles(ctx, soongInjectionDir, CreateSoongInjectionFiles(res.metrics))
 
 	return res.metrics
 }
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index 10e2329..f7b392b 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -153,10 +153,10 @@
 }
 
 type CodegenContext struct {
-	config                  android.Config
-	context                 android.Context
-	mode                    CodegenMode
-	additionalDeps          []string
+	config             android.Config
+	context            android.Context
+	mode               CodegenMode
+	additionalDeps     []string
 	unconvertedDepMode unconvertedDepsMode
 }
 
@@ -249,7 +249,6 @@
 type conversionResults struct {
 	buildFileToTargets map[string]BazelTargets
 	metrics            CodegenMetrics
-	compatLayer        CodegenCompatLayer
 }
 
 func (r conversionResults) BuildDirToTargets() map[string]BazelTargets {
@@ -265,10 +264,6 @@
 		RuleClassCount: make(map[string]int),
 	}
 
-	compatLayer := CodegenCompatLayer{
-		NameToLabelMap: make(map[string]string),
-	}
-
 	dirs := make(map[string]bool)
 
 	var errs []error
@@ -285,7 +280,7 @@
 			if b, ok := m.(android.Bazelable); ok && b.HasHandcraftedLabel() {
 				metrics.handCraftedTargetCount += 1
 				metrics.TotalModuleCount += 1
-				compatLayer.AddNameToLabelEntry(m.Name(), b.HandcraftedLabel())
+				metrics.AddConvertedModule(m.Name())
 				pathToBuildFile := getBazelPackagePath(b)
 				// We are using the entire contents of handcrafted build file, so if multiple targets within
 				// a package have handcrafted targets, we only want to include the contents one time.
@@ -314,10 +309,8 @@
 				}
 				targets = generateBazelTargets(bpCtx, aModule)
 				for _, t := range targets {
-					if t.name == m.Name() {
-						// only add targets that exist in Soong to compatibility layer
-						compatLayer.AddNameToLabelEntry(m.Name(), t.Label())
-					}
+					// only add targets that exist in Soong to compatibility layer
+					metrics.AddConvertedModule(m.Name())
 					metrics.RuleClassCount[t.ruleClass] += 1
 				}
 			} else {
@@ -360,7 +353,6 @@
 	return conversionResults{
 		buildFileToTargets: buildFileToTargets,
 		metrics:            metrics,
-		compatLayer:        compatLayer,
 	}, errs
 }
 
@@ -710,10 +702,6 @@
 	return strings.Repeat("    ", indent)
 }
 
-func targetNameForBp2Build(c bpToBuildContext, logicModule blueprint.Module) string {
-	return strings.Replace(c.ModuleName(logicModule), bazel.BazelTargetModuleNamePrefix, "", 1)
-}
-
 func targetNameWithVariant(c bpToBuildContext, logicModule blueprint.Module) string {
 	name := ""
 	if c.ModuleSubDir(logicModule) != "" {
diff --git a/bp2build/cc_library_shared_conversion_test.go b/bp2build/cc_library_shared_conversion_test.go
new file mode 100644
index 0000000..52a07cc
--- /dev/null
+++ b/bp2build/cc_library_shared_conversion_test.go
@@ -0,0 +1,366 @@
+// Copyright 2021 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.
+
+package bp2build
+
+import (
+	"testing"
+
+	"android/soong/android"
+	"android/soong/cc"
+)
+
+const (
+	// See cc/testing.go for more context
+	// TODO(alexmarquez): Split out the preamble into common code?
+	soongCcLibrarySharedPreamble = soongCcLibraryStaticPreamble
+)
+
+func registerCcLibrarySharedModuleTypes(ctx android.RegistrationContext) {
+	cc.RegisterCCBuildComponents(ctx)
+	ctx.RegisterModuleType("toolchain_library", cc.ToolchainLibraryFactory)
+	ctx.RegisterModuleType("cc_library_headers", cc.LibraryHeaderFactory)
+	ctx.RegisterModuleType("cc_library_static", cc.LibraryStaticFactory)
+}
+
+func runCcLibrarySharedTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, registerCcLibrarySharedModuleTypes, tc)
+}
+
+func TestCcLibrarySharedSimple(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_shared simple overall test",
+		moduleTypeUnderTest:                "cc_library_shared",
+		moduleTypeUnderTestFactory:         cc.LibrarySharedFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibrarySharedBp2Build,
+		filesystem: map[string]string{
+			// NOTE: include_dir headers *should not* appear in Bazel hdrs later (?)
+			"include_dir_1/include_dir_1_a.h": "",
+			"include_dir_1/include_dir_1_b.h": "",
+			"include_dir_2/include_dir_2_a.h": "",
+			"include_dir_2/include_dir_2_b.h": "",
+			// NOTE: local_include_dir headers *should not* appear in Bazel hdrs later (?)
+			"local_include_dir_1/local_include_dir_1_a.h": "",
+			"local_include_dir_1/local_include_dir_1_b.h": "",
+			"local_include_dir_2/local_include_dir_2_a.h": "",
+			"local_include_dir_2/local_include_dir_2_b.h": "",
+			// NOTE: export_include_dir headers *should* appear in Bazel hdrs later
+			"export_include_dir_1/export_include_dir_1_a.h": "",
+			"export_include_dir_1/export_include_dir_1_b.h": "",
+			"export_include_dir_2/export_include_dir_2_a.h": "",
+			"export_include_dir_2/export_include_dir_2_b.h": "",
+			// NOTE: Soong implicitly includes headers in the current directory
+			"implicit_include_1.h": "",
+			"implicit_include_2.h": "",
+		},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_headers {
+    name: "header_lib_1",
+    export_include_dirs: ["header_lib_1"],
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_headers {
+    name: "header_lib_2",
+    export_include_dirs: ["header_lib_2"],
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_shared {
+    name: "shared_lib_1",
+    srcs: ["shared_lib_1.cc"],
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_shared {
+    name: "shared_lib_2",
+    srcs: ["shared_lib_2.cc"],
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_static {
+    name: "whole_static_lib_1",
+    srcs: ["whole_static_lib_1.cc"],
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_static {
+    name: "whole_static_lib_2",
+    srcs: ["whole_static_lib_2.cc"],
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_shared {
+    name: "foo_shared",
+    srcs: [
+        "foo_shared1.cc",
+        "foo_shared2.cc",
+    ],
+    cflags: [
+        "-Dflag1",
+        "-Dflag2"
+    ],
+    shared_libs: [
+        "shared_lib_1",
+        "shared_lib_2"
+    ],
+    whole_static_libs: [
+        "whole_static_lib_1",
+        "whole_static_lib_2"
+    ],
+    include_dirs: [
+        "include_dir_1",
+        "include_dir_2",
+    ],
+    local_include_dirs: [
+        "local_include_dir_1",
+        "local_include_dir_2",
+    ],
+    export_include_dirs: [
+        "export_include_dir_1",
+        "export_include_dir_2"
+    ],
+    header_libs: [
+        "header_lib_1",
+        "header_lib_2"
+    ],
+
+    // TODO: Also support export_header_lib_headers
+}`,
+		expectedBazelTargets: []string{`cc_library_shared(
+    name = "foo_shared",
+    absolute_includes = [
+        "include_dir_1",
+        "include_dir_2",
+    ],
+    copts = [
+        "-Dflag1",
+        "-Dflag2",
+    ],
+    dynamic_deps = [
+        ":shared_lib_1",
+        ":shared_lib_2",
+    ],
+    export_includes = [
+        "export_include_dir_1",
+        "export_include_dir_2",
+    ],
+    implementation_deps = [
+        ":header_lib_1",
+        ":header_lib_2",
+    ],
+    local_includes = [
+        "local_include_dir_1",
+        "local_include_dir_2",
+        ".",
+    ],
+    srcs = [
+        "foo_shared1.cc",
+        "foo_shared2.cc",
+    ],
+    whole_archive_deps = [
+        ":whole_static_lib_1",
+        ":whole_static_lib_2",
+    ],
+)`},
+	})
+}
+
+func TestCcLibrarySharedArchSpecificSharedLib(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_shared arch-specific shared_libs with whole_static_libs",
+		moduleTypeUnderTest:                "cc_library_shared",
+		moduleTypeUnderTestFactory:         cc.LibrarySharedFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibrarySharedBp2Build,
+		filesystem:                         map[string]string{},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_static {
+    name: "static_dep",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_shared {
+    name: "shared_dep",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_shared {
+    name: "foo_shared",
+    arch: { arm64: { shared_libs: ["shared_dep"], whole_static_libs: ["static_dep"] } },
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{`cc_library_shared(
+    name = "foo_shared",
+    dynamic_deps = select({
+        "//build/bazel/platforms/arch:arm64": [":shared_dep"],
+        "//conditions:default": [],
+    }),
+    whole_archive_deps = select({
+        "//build/bazel/platforms/arch:arm64": [":static_dep"],
+        "//conditions:default": [],
+    }),
+)`},
+	})
+}
+
+func TestCcLibrarySharedOsSpecificSharedLib(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_shared os-specific shared_libs",
+		moduleTypeUnderTest:                "cc_library_shared",
+		moduleTypeUnderTestFactory:         cc.LibrarySharedFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibrarySharedBp2Build,
+		filesystem:                         map[string]string{},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_shared {
+    name: "shared_dep",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_shared {
+    name: "foo_shared",
+    target: { android: { shared_libs: ["shared_dep"], } },
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{`cc_library_shared(
+    name = "foo_shared",
+    dynamic_deps = select({
+        "//build/bazel/platforms/os:android": [":shared_dep"],
+        "//conditions:default": [],
+    }),
+)`},
+	})
+}
+
+func TestCcLibrarySharedBaseArchOsSpecificSharedLib(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_shared base, arch, and os-specific shared_libs",
+		moduleTypeUnderTest:                "cc_library_shared",
+		moduleTypeUnderTestFactory:         cc.LibrarySharedFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibrarySharedBp2Build,
+		filesystem:                         map[string]string{},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_shared {
+    name: "shared_dep",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_shared {
+    name: "shared_dep2",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_shared {
+    name: "shared_dep3",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_shared {
+    name: "foo_shared",
+    shared_libs: ["shared_dep"],
+    target: { android: { shared_libs: ["shared_dep2"] } },
+    arch: { arm64: { shared_libs: ["shared_dep3"] } },
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{`cc_library_shared(
+    name = "foo_shared",
+    dynamic_deps = [":shared_dep"] + select({
+        "//build/bazel/platforms/arch:arm64": [":shared_dep3"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/platforms/os:android": [":shared_dep2"],
+        "//conditions:default": [],
+    }),
+)`},
+	})
+}
+
+func TestCcLibrarySharedSimpleExcludeSrcs(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_shared simple exclude_srcs",
+		moduleTypeUnderTest:                "cc_library_shared",
+		moduleTypeUnderTestFactory:         cc.LibrarySharedFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibrarySharedBp2Build,
+		filesystem: map[string]string{
+			"common.c":       "",
+			"foo-a.c":        "",
+			"foo-excluded.c": "",
+		},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_shared {
+    name: "foo_shared",
+    srcs: ["common.c", "foo-*.c"],
+    exclude_srcs: ["foo-excluded.c"],
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{`cc_library_shared(
+    name = "foo_shared",
+    srcs_c = [
+        "common.c",
+        "foo-a.c",
+    ],
+)`},
+	})
+}
+
+func TestCcLibrarySharedStrip(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_shared stripping",
+		moduleTypeUnderTest:                "cc_library_shared",
+		moduleTypeUnderTestFactory:         cc.LibrarySharedFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibrarySharedBp2Build,
+		filesystem:                         map[string]string{},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_shared {
+    name: "foo_shared",
+    strip: {
+        keep_symbols: false,
+        keep_symbols_and_debug_frame: true,
+        keep_symbols_list: ["sym", "sym2"],
+        all: true,
+        none: false,
+    },
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{`cc_library_shared(
+    name = "foo_shared",
+    strip = {
+        "all": True,
+        "keep_symbols": False,
+        "keep_symbols_and_debug_frame": True,
+        "keep_symbols_list": [
+            "sym",
+            "sym2",
+        ],
+        "none": False,
+    },
+)`},
+	})
+}
+
+func TestCcLibrarySharedVersionScript(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_shared version script",
+		moduleTypeUnderTest:                "cc_library_shared",
+		moduleTypeUnderTestFactory:         cc.LibrarySharedFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibrarySharedBp2Build,
+		filesystem: map[string]string{
+			"version_script": "",
+		},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_shared {
+    name: "foo_shared",
+    version_script: "version_script",
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{`cc_library_shared(
+    name = "foo_shared",
+    version_script = "version_script",
+)`},
+	})
+}
diff --git a/bp2build/cc_library_static_conversion_test.go b/bp2build/cc_library_static_conversion_test.go
index 72034fa..91b7478 100644
--- a/bp2build/cc_library_static_conversion_test.go
+++ b/bp2build/cc_library_static_conversion_test.go
@@ -202,7 +202,6 @@
         ":static_lib_1",
         ":static_lib_2",
     ],
-    linkstatic = True,
     local_includes = [
         "local_include_dir_1",
         "local_include_dir_2",
@@ -251,7 +250,6 @@
 		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
     absolute_includes = ["subpackage"],
-    linkstatic = True,
     local_includes = ["."],
 )`},
 	})
@@ -278,7 +276,6 @@
 		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
     export_includes = ["subpackage"],
-    linkstatic = True,
 )`},
 	})
 }
@@ -304,7 +301,6 @@
 		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
     export_system_includes = ["subpackage"],
-    linkstatic = True,
 )`},
 	})
 }
@@ -347,7 +343,6 @@
         "subpackage3/subsubpackage",
     ],
     export_includes = ["./exported_subsubpackage"],
-    linkstatic = True,
     local_includes = [
         "subsubpackage2",
         ".",
@@ -378,7 +373,6 @@
 		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
     absolute_includes = ["subpackage"],
-    linkstatic = True,
     local_includes = ["subpackage2"],
 )`},
 	})
@@ -408,7 +402,6 @@
 		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
     absolute_includes = ["subpackage"],
-    linkstatic = True,
     local_includes = [
         "subpackage2",
         ".",
@@ -444,7 +437,6 @@
         "//build/bazel/platforms/arch:arm64": [":static_dep"],
         "//conditions:default": [],
     }),
-    linkstatic = True,
     whole_archive_deps = select({
         "//build/bazel/platforms/arch:arm64": [":static_dep2"],
         "//conditions:default": [],
@@ -480,7 +472,6 @@
         "//build/bazel/platforms/os:android": [":static_dep"],
         "//conditions:default": [],
     }),
-    linkstatic = True,
     whole_archive_deps = select({
         "//build/bazel/platforms/os:android": [":static_dep2"],
         "//conditions:default": [],
@@ -530,7 +521,6 @@
         "//build/bazel/platforms/os:android": [":static_dep3"],
         "//conditions:default": [],
     }),
-    linkstatic = True,
     whole_archive_deps = [":static_dep2"],
 )`},
 	})
@@ -556,7 +546,6 @@
 }`,
 		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    linkstatic = True,
     srcs_c = [
         "common.c",
         "foo-a.c",
@@ -584,7 +573,6 @@
 }`,
 		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    linkstatic = True,
     srcs_c = ["common.c"] + select({
         "//build/bazel/platforms/arch:arm": ["foo-arm.c"],
         "//conditions:default": [],
@@ -617,7 +605,6 @@
 }`,
 		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    linkstatic = True,
     srcs_c = ["common.c"] + select({
         "//build/bazel/platforms/arch:arm": ["for-arm.c"],
         "//conditions:default": ["not-for-arm.c"],
@@ -652,7 +639,6 @@
 } `,
 		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    linkstatic = True,
     srcs_c = ["common.c"] + select({
         "//build/bazel/platforms/arch:arm": [
             "for-arm.c",
@@ -703,7 +689,6 @@
 } `,
 		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    linkstatic = True,
     srcs_c = ["common.c"] + select({
         "//build/bazel/platforms/arch:arm": [
             "for-arm.c",
@@ -763,7 +748,6 @@
 }`,
 		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    linkstatic = True,
     srcs = ["common.cc"] + select({
         "//build/bazel/platforms/arch:arm": [],
         "//conditions:default": ["foo-no-arm.cc"],
@@ -797,7 +781,6 @@
 }`,
 		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    linkstatic = True,
     srcs = ["common.cc"] + select({
         "//build/bazel/platforms/arch:arm": [],
         "//build/bazel/platforms/arch:x86": [
@@ -830,7 +813,6 @@
 		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
     implementation_deps = [":static_dep"],
-    linkstatic = True,
 )`},
 	})
 }
@@ -857,7 +839,6 @@
 } `,
 		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    linkstatic = True,
     srcs_c = ["common.c"] + select({
         "//build/bazel/platforms/arch:arm": ["for-lib32.c"],
         "//build/bazel/platforms/arch:x86": ["for-lib32.c"],
@@ -892,7 +873,6 @@
 } `,
 		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static2",
-    linkstatic = True,
     srcs_c = ["common.c"] + select({
         "//build/bazel/platforms/arch:arm": [
             "for-lib32.c",
@@ -960,7 +940,6 @@
 }`,
 		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static3",
-    linkstatic = True,
     srcs_c = ["common.c"] + select({
         "//build/bazel/platforms/arch:arm": [
             "for-arm.c",
@@ -1075,7 +1054,6 @@
 `,
 		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static3",
-    linkstatic = True,
     srcs = [
         "//dep:generated_hdr_other_pkg",
         "//dep:generated_src_other_pkg",
@@ -1131,7 +1109,6 @@
 }`,
 		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    linkstatic = True,
     srcs_c = select({
         "//build/bazel/platforms/os:android": ["android_src.c"],
         "//conditions:default": [],
@@ -1183,7 +1160,6 @@
         "//build/bazel/product_variables:malloc_zero_contents": ["-Wmalloc_zero_contents"],
         "//conditions:default": [],
     }),
-    linkstatic = True,
     srcs_c = ["common.c"],
 )`},
 	})
@@ -1252,7 +1228,6 @@
         "//build/bazel/product_variables:malloc_not_svelte-x86": ["-Wlib32_malloc_not_svelte"],
         "//conditions:default": [],
     }),
-    linkstatic = True,
     srcs_c = ["common.c"],
 )`},
 	})
@@ -1282,7 +1257,6 @@
         "//build/bazel/product_variables:platform_sdk_version": ["-DPLATFORM_SDK_VERSION=$(Platform_sdk_version)"],
         "//conditions:default": [],
     }),
-    linkstatic = True,
     srcs_as = ["common.S"],
 )`},
 	})
@@ -1303,7 +1277,6 @@
 `,
 		expectedBazelTargets: []string{`cc_library_static(
     name = "root_empty",
-    linkstatic = True,
     system_dynamic_deps = [],
 )`},
 	})
@@ -1330,7 +1303,6 @@
 `,
 		expectedBazelTargets: []string{`cc_library_static(
     name = "static_empty",
-    linkstatic = True,
     system_dynamic_deps = [],
 )`},
 	})
@@ -1355,7 +1327,6 @@
 `,
 		expectedBazelTargets: []string{`cc_library_static(
     name = "target_bionic_empty",
-    linkstatic = True,
     system_dynamic_deps = [],
 )`},
 	})
@@ -1384,7 +1355,6 @@
 `,
 		expectedBazelTargets: []string{`cc_library_static(
     name = "target_linux_bionic_empty",
-    linkstatic = True,
     system_dynamic_deps = [],
 )`},
 	})
@@ -1411,7 +1381,6 @@
 `,
 		expectedBazelTargets: []string{`cc_library_static(
     name = "target_bionic",
-    linkstatic = True,
     system_dynamic_deps = select({
         "//build/bazel/platforms/os:bionic": [":libc"],
         "//conditions:default": [],
@@ -1443,7 +1412,6 @@
 `,
 		expectedBazelTargets: []string{`cc_library_static(
     name = "target_linux_bionic",
-    linkstatic = True,
     system_dynamic_deps = [":libc"] + select({
         "//build/bazel/platforms/os:linux_bionic": [":libm"],
         "//conditions:default": [],
diff --git a/bp2build/compatibility.go b/bp2build/compatibility.go
deleted file mode 100644
index 5baa524..0000000
--- a/bp2build/compatibility.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package bp2build
-
-import (
-	"android/soong/bazel"
-	"fmt"
-)
-
-// Data from the code generation process that is used to improve compatibility
-// between build systems.
-type CodegenCompatLayer struct {
-	// A map from the original module name to the generated/handcrafted Bazel
-	// label for legacy build systems to be able to build a fully-qualified
-	// Bazel target from an unique module name.
-	NameToLabelMap map[string]string
-}
-
-// Log an entry of module name -> Bazel target label.
-func (compatLayer CodegenCompatLayer) AddNameToLabelEntry(name, label string) {
-	// The module name may be prefixed with bazel.BazelTargetModuleNamePrefix if
-	// generated from bp2build.
-	name = bazel.StripNamePrefix(name)
-	if existingLabel, ok := compatLayer.NameToLabelMap[name]; ok {
-		panic(fmt.Errorf(
-			"Module '%s' maps to more than one Bazel target label: %s, %s. "+
-				"This shouldn't happen. It probably indicates a bug with the bp2build internals.",
-			name,
-			existingLabel,
-			label))
-	}
-	compatLayer.NameToLabelMap[name] = label
-}
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
index 75bc2b4..354abf6 100644
--- a/bp2build/conversion.go
+++ b/bp2build/conversion.go
@@ -16,29 +16,19 @@
 	Contents string
 }
 
-func CreateSoongInjectionFiles(compatLayer CodegenCompatLayer) []BazelFile {
+func CreateSoongInjectionFiles(metrics CodegenMetrics) []BazelFile {
 	var files []BazelFile
 
 	files = append(files, newFile("cc_toolchain", GeneratedBuildFileName, "")) // Creates a //cc_toolchain package.
 	files = append(files, newFile("cc_toolchain", "constants.bzl", config.BazelCcToolchainVars()))
 
-	files = append(files, newFile("module_name_to_label", GeneratedBuildFileName, nameToLabelAliases(compatLayer.NameToLabelMap)))
+	files = append(files, newFile("metrics", "converted_modules.txt", strings.Join(metrics.convertedModules, "\n")))
 
 	return files
 }
 
-func nameToLabelAliases(nameToLabelMap map[string]string) string {
-	ret := make([]string, len(nameToLabelMap))
-
-	for k, v := range nameToLabelMap {
-		// v is the fully qualified label rooted at '//'
-		ret = append(ret, fmt.Sprintf(
-			`alias(
-    name = "%s",
-    actual = "@%s",
-)`, k, v))
-	}
-	return strings.Join(ret, "\n\n")
+func convertedModules(convertedModules []string) string {
+	return strings.Join(convertedModules, "\n")
 }
 
 func CreateBazelFiles(
diff --git a/bp2build/conversion_test.go b/bp2build/conversion_test.go
index 56ea589..dfa1a9e 100644
--- a/bp2build/conversion_test.go
+++ b/bp2build/conversion_test.go
@@ -80,7 +80,7 @@
 }
 
 func TestCreateBazelFiles_Bp2Build_CreatesDefaultFiles(t *testing.T) {
-	files := CreateSoongInjectionFiles(CodegenCompatLayer{})
+	files := CreateSoongInjectionFiles(CodegenMetrics{})
 
 	expectedFilePaths := []bazelFilepath{
 		{
@@ -92,8 +92,8 @@
 			basename: "constants.bzl",
 		},
 		{
-			dir:      "module_name_to_label",
-			basename: GeneratedBuildFileName,
+			dir:      "metrics",
+			basename: "converted_modules.txt",
 		},
 	}
 
@@ -107,9 +107,5 @@
 		if actualFile.Dir != expectedFile.dir || actualFile.Basename != expectedFile.basename {
 			t.Errorf("Did not find expected file %s/%s", actualFile.Dir, actualFile.Basename)
 		}
-
-		if expectedFile.basename != GeneratedBuildFileName && actualFile.Contents == "" {
-			t.Errorf("Contents of %s unexpected empty.", actualFile)
-		}
 	}
 }
diff --git a/bp2build/metrics.go b/bp2build/metrics.go
index 645ef2d..55b928b 100644
--- a/bp2build/metrics.go
+++ b/bp2build/metrics.go
@@ -19,6 +19,8 @@
 	handCraftedTargetCount int
 
 	moduleWithUnconvertedDepsMsgs []string
+
+	convertedModules []string
 }
 
 // Print the codegen metrics to stdout.
@@ -37,3 +39,9 @@
 		len(metrics.moduleWithUnconvertedDepsMsgs),
 		strings.Join(metrics.moduleWithUnconvertedDepsMsgs, "\n\t"))
 }
+
+func (metrics CodegenMetrics) AddConvertedModule(moduleName string) {
+	// Undo prebuilt_ module name prefix modifications
+	moduleName = android.RemoveOptionalPrebuiltPrefix(moduleName)
+	metrics.convertedModules = append(metrics.convertedModules, moduleName)
+}
diff --git a/cc/bp2build.go b/cc/bp2build.go
index 7a98fd0..67ea70e 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -111,24 +111,23 @@
 	return
 }
 
-// bp2buildParseSharedProps returns the attributes for the shared variant of a cc_library.
-func bp2BuildParseSharedProps(ctx android.TopDownMutatorContext, module *Module) staticOrSharedAttributes {
+// bp2BuildParseLibProps returns the attributes for a variant of a cc_library.
+func bp2BuildParseLibProps(ctx android.TopDownMutatorContext, module *Module, isStatic bool) staticOrSharedAttributes {
 	lib, ok := module.compiler.(*libraryDecorator)
 	if !ok {
 		return staticOrSharedAttributes{}
 	}
+	return bp2buildParseStaticOrSharedProps(ctx, module, lib, isStatic)
+}
 
-	return bp2buildParseStaticOrSharedProps(ctx, module, lib, false)
+// bp2buildParseSharedProps returns the attributes for the shared variant of a cc_library.
+func bp2BuildParseSharedProps(ctx android.TopDownMutatorContext, module *Module) staticOrSharedAttributes {
+	return bp2BuildParseLibProps(ctx, module, false)
 }
 
 // bp2buildParseStaticProps returns the attributes for the static variant of a cc_library.
 func bp2BuildParseStaticProps(ctx android.TopDownMutatorContext, module *Module) staticOrSharedAttributes {
-	lib, ok := module.compiler.(*libraryDecorator)
-	if !ok {
-		return staticOrSharedAttributes{}
-	}
-
-	return bp2buildParseStaticOrSharedProps(ctx, module, lib, true)
+	return bp2BuildParseLibProps(ctx, module, true)
 }
 
 func bp2buildParseStaticOrSharedProps(ctx android.TopDownMutatorContext, module *Module, lib *libraryDecorator, isStatic bool) staticOrSharedAttributes {
@@ -178,6 +177,7 @@
 	Src bazel.LabelAttribute
 }
 
+// NOTE: Used outside of Soong repo project, in the clangprebuilts.go bootstrap_go_package
 func Bp2BuildParsePrebuiltLibraryProps(ctx android.TopDownMutatorContext, module *Module) prebuiltAttributes {
 	var srcLabelAttribute bazel.LabelAttribute
 
diff --git a/cc/builder.go b/cc/builder.go
index b07acf1..4b0a4b6 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -199,8 +199,18 @@
 	// Rule for invoking clang-tidy (a clang-based linter).
 	clangTidy, clangTidyRE = pctx.RemoteStaticRules("clangTidy",
 		blueprint.RuleParams{
-			Command:     "rm -f $out && $tidyVars $reTemplate${config.ClangBin}/clang-tidy $tidyFlags $in -- $cFlags && touch $out",
-			CommandDeps: []string{"${config.ClangBin}/clang-tidy"},
+			Depfile: "${out}.d",
+			Deps:    blueprint.DepsGCC,
+			// Pick bash because some machines with old /bin/sh cannot handle arrays.
+			// All $cFlags and $tidyFlags should have single quotes escaped.
+			// Assume no single quotes in other parameters like $in, $out, $ccCmd.
+			Command: "/bin/bash -c 'SRCF=$in; TIDYF=$out; CLANGFLAGS=($cFlags); " +
+				"rm -f $$TIDYF $${TIDYF}.d && " +
+				"${config.CcWrapper}$ccCmd \"$${CLANGFLAGS[@]}\" -E -o /dev/null $$SRCF " +
+				"-MQ $$TIDYF -MD -MF $${TIDYF}.d && " +
+				"$tidyVars $reTemplate${config.ClangBin}/clang-tidy $tidyFlags $$SRCF " +
+				"-- \"$${CLANGFLAGS[@]}\" && touch $$TIDYF'",
+			CommandDeps: []string{"${config.ClangBin}/clang-tidy", "$ccCmd"},
 		},
 		&remoteexec.REParams{
 			Labels:               map[string]string{"type": "lint", "tool": "clang-tidy", "lang": "cpp"},
@@ -214,7 +224,7 @@
 			// (1) New timestamps trigger clang and clang-tidy compilations again.
 			// (2) Changing source files caused concurrent clang or clang-tidy jobs to crash.
 			Platform: map[string]string{remoteexec.PoolKey: "${config.REClangTidyPool}"},
-		}, []string{"cFlags", "tidyFlags", "tidyVars"}, []string{})
+		}, []string{"ccCmd", "cFlags", "tidyFlags", "tidyVars"}, []string{})
 
 	_ = pctx.SourcePathVariable("yasmCmd", "prebuilts/misc/${config.HostPrebuiltTag}/yasm/yasm")
 
@@ -437,6 +447,12 @@
 	}
 }
 
+func escapeSingleQuotes(s string) string {
+	// Replace single quotes to work when embedded in a single quoted string for bash.
+	// Relying on string concatenation of bash to get A'B from quoted 'A'\''B'.
+	return strings.Replace(s, `'`, `'\''`, -1)
+}
+
 // Generate rules for compiling multiple .c, .cpp, or .S files to individual .o files
 func transformSourceToObj(ctx android.ModuleContext, subdir string, srcFiles android.Paths,
 	flags builderFlags, pathDeps android.Paths, cFlagsDeps android.Paths) Objects {
@@ -672,14 +688,12 @@
 				Description: "clang-tidy " + srcFile.Rel(),
 				Output:      tidyFile,
 				Input:       srcFile,
-				// We must depend on objFile, since clang-tidy doesn't
-				// support exporting dependencies.
-				Implicit:  objFile,
-				Implicits: cFlagsDeps,
-				OrderOnly: pathDeps,
+				Implicits:   cFlagsDeps,
+				OrderOnly:   pathDeps,
 				Args: map[string]string{
-					"cFlags":    shareFlags("cFlags", moduleToolingFlags),
-					"tidyFlags": shareFlags("tidyFlags", config.TidyFlagsForSrcFile(srcFile, flags.tidyFlags)),
+					"ccCmd":     ccCmd,
+					"cFlags":    shareFlags("cFlags", escapeSingleQuotes(moduleToolingFlags)),
+					"tidyFlags": shareFlags("tidyFlags", escapeSingleQuotes(config.TidyFlagsForSrcFile(srcFile, flags.tidyFlags))),
 					"tidyVars":  tidyVars, // short and not shared
 				},
 			})
diff --git a/cc/library.go b/cc/library.go
index f568247..703d57f 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -205,6 +205,7 @@
 	RegisterLibraryBuildComponents(android.InitRegistrationContext)
 
 	android.RegisterBp2BuildMutator("cc_library_static", CcLibraryStaticBp2Build)
+	android.RegisterBp2BuildMutator("cc_library_shared", CcLibrarySharedBp2Build)
 	android.RegisterBp2BuildMutator("cc_library", CcLibraryBp2Build)
 }
 
@@ -216,6 +217,7 @@
 	ctx.RegisterModuleType("cc_library_host_shared", LibraryHostSharedFactory)
 }
 
+// TODO(b/199902614): Can this be factored to share with the other Attributes?
 // For bp2build conversion.
 type bazelCcLibraryAttributes struct {
 	// Attributes pertaining to both static and shared variants.
@@ -274,7 +276,7 @@
 	// converted, but not their shared variants. For these modules, delegate to
 	// the cc_library_static bp2build converter temporarily instead.
 	if android.GenerateCcLibraryStaticOnly(ctx) {
-		ccLibraryStaticBp2BuildInternal(ctx, m)
+		ccSharedOrStaticBp2BuildMutatorInternal(ctx, m, "cc_library_static")
 		return
 	}
 
@@ -368,6 +370,7 @@
 	module, library := NewLibrary(android.HostAndDeviceSupported)
 	library.BuildOnlyShared()
 	module.sdkMemberTypes = []android.SdkMemberType{sharedLibrarySdkMemberType}
+	module.bazelHandler = &ccLibraryBazelHandler{module: module}
 	return module.Init()
 }
 
@@ -601,7 +604,10 @@
 
 	handler.module.linker.(*libraryDecorator).unstrippedOutputFile = outputFilePath
 
-	tocFile := getTocFile(ctx, label, ccInfo.OutputFiles)
+	var tocFile android.OptionalPath
+	if len(ccInfo.TocFile) > 0 {
+		tocFile = android.OptionalPathForPath(android.PathForBazelOut(ctx, ccInfo.TocFile))
+	}
 	handler.module.linker.(*libraryDecorator).tocFile = tocFile
 
 	ctx.SetProvider(SharedLibraryInfoProvider, SharedLibraryInfo{
@@ -614,25 +620,6 @@
 	return true
 }
 
-// getTocFile looks for the .so.toc file in the target's output files, if any. The .so.toc file
-// contains the table of contents of all symbols of a shared object.
-func getTocFile(ctx android.ModuleContext, label string, outputFiles []string) android.OptionalPath {
-	var tocFile string
-	for _, file := range outputFiles {
-		if strings.HasSuffix(file, ".so.toc") {
-			if tocFile != "" {
-				ctx.ModuleErrorf("The %s target cannot produce more than 1 .toc file.", label)
-			}
-			tocFile = file
-			// Don't break to validate that there are no multiple .toc files per .so.
-		}
-	}
-	if tocFile == "" {
-		return android.OptionalPath{}
-	}
-	return android.OptionalPathForPath(android.PathForBazelOut(ctx, tocFile))
-}
-
 func (handler *ccLibraryBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
 	bazelCtx := ctx.Config().BazelContext
 	ccInfo, ok, err := bazelCtx.GetCcInfo(label, ctx.Arch().ArchType)
@@ -2323,6 +2310,123 @@
 	return outputFile
 }
 
+func ccSharedOrStaticBp2BuildMutator(ctx android.TopDownMutatorContext, modType string) {
+	module, ok := ctx.Module().(*Module)
+	if !ok {
+		// Not a cc module
+		return
+	}
+	if !module.ConvertWithBp2build(ctx) {
+		return
+	}
+	if ctx.ModuleType() != modType {
+		return
+	}
+
+	ccSharedOrStaticBp2BuildMutatorInternal(ctx, module, modType)
+}
+
+func ccSharedOrStaticBp2BuildMutatorInternal(ctx android.TopDownMutatorContext, module *Module, modType string) {
+	if modType != "cc_library_static" && modType != "cc_library_shared" {
+		panic("ccSharedOrStaticBp2BuildMutatorInternal only supports cc_library_{static,shared}")
+	}
+	isStatic := modType == "cc_library_static"
+
+	compilerAttrs := bp2BuildParseCompilerProps(ctx, module)
+	linkerAttrs := bp2BuildParseLinkerProps(ctx, module)
+	exportedIncludes := bp2BuildParseExportedIncludes(ctx, module)
+
+	// Append shared/static{} stanza properties. These won't be specified on
+	// cc_library_* itself, but may be specified in cc_defaults that this module
+	// depends on.
+	libSharedOrStaticAttrs := bp2BuildParseLibProps(ctx, module, isStatic)
+
+	compilerAttrs.srcs.Append(libSharedOrStaticAttrs.Srcs)
+	compilerAttrs.cSrcs.Append(libSharedOrStaticAttrs.Srcs_c)
+	compilerAttrs.asSrcs.Append(libSharedOrStaticAttrs.Srcs_as)
+	compilerAttrs.copts.Append(libSharedOrStaticAttrs.Copts)
+	linkerAttrs.exportedDeps.Append(libSharedOrStaticAttrs.Static_deps)
+	linkerAttrs.dynamicDeps.Append(libSharedOrStaticAttrs.Dynamic_deps)
+	linkerAttrs.wholeArchiveDeps.Append(libSharedOrStaticAttrs.Whole_archive_deps)
+	linkerAttrs.systemDynamicDeps.Append(libSharedOrStaticAttrs.System_dynamic_deps)
+
+	asFlags := compilerAttrs.asFlags
+	if compilerAttrs.asSrcs.IsEmpty() {
+		// Skip asflags for BUILD file simplicity if there are no assembly sources.
+		asFlags = bazel.MakeStringListAttribute(nil)
+	}
+
+	var attrs interface{}
+	if isStatic {
+		attrs = &bazelCcLibraryStaticAttributes{
+			Copts:               compilerAttrs.copts,
+			Srcs:                compilerAttrs.srcs,
+			Implementation_deps: linkerAttrs.deps,
+			Deps:                linkerAttrs.exportedDeps,
+			Whole_archive_deps:  linkerAttrs.wholeArchiveDeps,
+			Dynamic_deps:        linkerAttrs.dynamicDeps,
+			System_dynamic_deps: linkerAttrs.systemDynamicDeps,
+
+			Linkopts:               linkerAttrs.linkopts,
+			Use_libcrt:             linkerAttrs.useLibcrt,
+			Rtti:                   compilerAttrs.rtti,
+			Export_includes:        exportedIncludes.Includes,
+			Export_system_includes: exportedIncludes.SystemIncludes,
+			Local_includes:         compilerAttrs.localIncludes,
+			Absolute_includes:      compilerAttrs.absoluteIncludes,
+
+			Cppflags:   compilerAttrs.cppFlags,
+			Srcs_c:     compilerAttrs.cSrcs,
+			Conlyflags: compilerAttrs.conlyFlags,
+			Srcs_as:    compilerAttrs.asSrcs,
+			Asflags:    asFlags,
+		}
+	} else {
+		attrs = &bazelCcLibrarySharedAttributes{
+			Srcs:    compilerAttrs.srcs,
+			Srcs_c:  compilerAttrs.cSrcs,
+			Srcs_as: compilerAttrs.asSrcs,
+
+			Implementation_deps: linkerAttrs.deps,
+			Deps:                linkerAttrs.exportedDeps,
+			Whole_archive_deps:  linkerAttrs.wholeArchiveDeps,
+			Dynamic_deps:        linkerAttrs.dynamicDeps,
+			System_dynamic_deps: linkerAttrs.systemDynamicDeps,
+
+			Copts:      compilerAttrs.copts,
+			Cppflags:   compilerAttrs.cppFlags,
+			Conlyflags: compilerAttrs.conlyFlags,
+			Asflags:    asFlags,
+			Linkopts:   linkerAttrs.linkopts,
+
+			Use_libcrt: linkerAttrs.useLibcrt,
+			Rtti:       compilerAttrs.rtti,
+
+			Export_includes:        exportedIncludes.Includes,
+			Export_system_includes: exportedIncludes.SystemIncludes,
+			Local_includes:         compilerAttrs.localIncludes,
+			Absolute_includes:      compilerAttrs.absoluteIncludes,
+			Version_script:         linkerAttrs.versionScript,
+
+			Strip: stripAttributes{
+				Keep_symbols:                 linkerAttrs.stripKeepSymbols,
+				Keep_symbols_and_debug_frame: linkerAttrs.stripKeepSymbolsAndDebugFrame,
+				Keep_symbols_list:            linkerAttrs.stripKeepSymbolsList,
+				All:                          linkerAttrs.stripAll,
+				None:                         linkerAttrs.stripNone,
+			},
+		}
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class:        modType,
+		Bzl_load_location: fmt.Sprintf("//build/bazel/rules:%s.bzl", modType),
+	}
+
+	ctx.CreateBazelTargetModule(module.Name(), props, attrs)
+}
+
+// TODO(b/199902614): Can this be factored to share with the other Attributes?
 type bazelCcLibraryStaticAttributes struct {
 	Copts                  bazel.StringListAttribute
 	Srcs                   bazel.LabelListAttribute
@@ -2332,7 +2436,6 @@
 	Dynamic_deps           bazel.LabelListAttribute
 	System_dynamic_deps    bazel.LabelListAttribute
 	Linkopts               bazel.StringListAttribute
-	Linkstatic             bool
 	Use_libcrt             bazel.BoolAttribute
 	Rtti                   bazel.BoolAttribute
 	Export_includes        bazel.StringListAttribute
@@ -2346,80 +2449,42 @@
 	Conlyflags bazel.StringListAttribute
 	Srcs_as    bazel.LabelListAttribute
 	Asflags    bazel.StringListAttribute
-
-	Static staticOrSharedAttributes
-}
-
-func ccLibraryStaticBp2BuildInternal(ctx android.TopDownMutatorContext, module *Module) {
-	compilerAttrs := bp2BuildParseCompilerProps(ctx, module)
-	linkerAttrs := bp2BuildParseLinkerProps(ctx, module)
-	exportedIncludes := bp2BuildParseExportedIncludes(ctx, module)
-
-	asFlags := compilerAttrs.asFlags
-	if compilerAttrs.asSrcs.IsEmpty() {
-		// Skip asflags for BUILD file simplicity if there are no assembly sources.
-		asFlags = bazel.MakeStringListAttribute(nil)
-	}
-
-	// Append static{} stanza properties. These won't be specified on
-	// cc_library_static itself, but may be specified in cc_defaults that this module
-	// depends on.
-	staticAttrs := bp2BuildParseStaticProps(ctx, module)
-
-	compilerAttrs.srcs.Append(staticAttrs.Srcs)
-	compilerAttrs.cSrcs.Append(staticAttrs.Srcs_c)
-	compilerAttrs.asSrcs.Append(staticAttrs.Srcs_as)
-	compilerAttrs.copts.Append(staticAttrs.Copts)
-	linkerAttrs.exportedDeps.Append(staticAttrs.Static_deps)
-	linkerAttrs.dynamicDeps.Append(staticAttrs.Dynamic_deps)
-	linkerAttrs.wholeArchiveDeps.Append(staticAttrs.Whole_archive_deps)
-	linkerAttrs.systemDynamicDeps.Append(staticAttrs.System_dynamic_deps)
-
-	attrs := &bazelCcLibraryStaticAttributes{
-		Copts:               compilerAttrs.copts,
-		Srcs:                compilerAttrs.srcs,
-		Implementation_deps: linkerAttrs.deps,
-		Deps:                linkerAttrs.exportedDeps,
-		Whole_archive_deps:  linkerAttrs.wholeArchiveDeps,
-		Dynamic_deps:        linkerAttrs.dynamicDeps,
-		System_dynamic_deps: linkerAttrs.systemDynamicDeps,
-
-		Linkopts:               linkerAttrs.linkopts,
-		Linkstatic:             true,
-		Use_libcrt:             linkerAttrs.useLibcrt,
-		Rtti:                   compilerAttrs.rtti,
-		Export_includes:        exportedIncludes.Includes,
-		Export_system_includes: exportedIncludes.SystemIncludes,
-		Local_includes:         compilerAttrs.localIncludes,
-		Absolute_includes:      compilerAttrs.absoluteIncludes,
-
-		Cppflags:   compilerAttrs.cppFlags,
-		Srcs_c:     compilerAttrs.cSrcs,
-		Conlyflags: compilerAttrs.conlyFlags,
-		Srcs_as:    compilerAttrs.asSrcs,
-		Asflags:    asFlags,
-	}
-
-	props := bazel.BazelTargetModuleProperties{
-		Rule_class:        "cc_library_static",
-		Bzl_load_location: "//build/bazel/rules:cc_library_static.bzl",
-	}
-
-	ctx.CreateBazelTargetModule(module.Name(), props, attrs)
 }
 
 func CcLibraryStaticBp2Build(ctx android.TopDownMutatorContext) {
-	module, ok := ctx.Module().(*Module)
-	if !ok {
-		// Not a cc module
-		return
-	}
-	if !module.ConvertWithBp2build(ctx) {
-		return
-	}
-	if ctx.ModuleType() != "cc_library_static" {
-		return
-	}
+	ccSharedOrStaticBp2BuildMutator(ctx, "cc_library_static")
+}
 
-	ccLibraryStaticBp2BuildInternal(ctx, module)
+// TODO(b/199902614): Can this be factored to share with the other Attributes?
+type bazelCcLibrarySharedAttributes struct {
+	Srcs    bazel.LabelListAttribute
+	Srcs_c  bazel.LabelListAttribute
+	Srcs_as bazel.LabelListAttribute
+
+	Implementation_deps bazel.LabelListAttribute
+	Deps                bazel.LabelListAttribute
+	Whole_archive_deps  bazel.LabelListAttribute
+	Dynamic_deps        bazel.LabelListAttribute
+	System_dynamic_deps bazel.LabelListAttribute
+
+	Linkopts   bazel.StringListAttribute
+	Use_libcrt bazel.BoolAttribute
+	Rtti       bazel.BoolAttribute
+	Strip      stripAttributes
+
+	Export_includes        bazel.StringListAttribute
+	Export_system_includes bazel.StringListAttribute
+	Local_includes         bazel.StringListAttribute
+	Absolute_includes      bazel.StringListAttribute
+	Hdrs                   bazel.LabelListAttribute
+	Version_script         bazel.LabelAttribute
+
+	Copts      bazel.StringListAttribute
+	Cppflags   bazel.StringListAttribute
+	Conlyflags bazel.StringListAttribute
+	Asflags    bazel.StringListAttribute
+}
+
+func CcLibrarySharedBp2Build(ctx android.TopDownMutatorContext) {
+	ccSharedOrStaticBp2BuildMutator(ctx, "cc_library_shared")
 }
diff --git a/cc/library_test.go b/cc/library_test.go
index 6b349b6..7ddfaa7 100644
--- a/cc/library_test.go
+++ b/cc/library_test.go
@@ -320,3 +320,48 @@
 		libfoo.Args["ldFlags"], "-Wl,--dynamic-list,foo.dynamic.txt")
 
 }
+
+func TestCcLibrarySharedWithBazel(t *testing.T) {
+	bp := `
+cc_library_shared {
+	name: "foo",
+	srcs: ["foo.cc"],
+	bazel_module: { label: "//foo/bar:bar" },
+}`
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
+	config.BazelContext = android.MockBazelContext{
+		OutputBaseDir: "outputbase",
+		LabelToCcInfo: map[string]cquery.CcInfo{
+			"//foo/bar:bar": cquery.CcInfo{
+				CcObjectFiles:        []string{"foo.o"},
+				Includes:             []string{"include"},
+				SystemIncludes:       []string{"system_include"},
+				RootDynamicLibraries: []string{"foo.so"},
+				TocFile:              "foo.so.toc",
+			},
+		},
+	}
+	ctx := testCcWithConfig(t, config)
+
+	sharedFoo := ctx.ModuleForTests("foo", "android_arm_armv7-a-neon_shared").Module()
+	producer := sharedFoo.(android.OutputFileProducer)
+	outputFiles, err := producer.OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_object outputfiles %s", err)
+	}
+	expectedOutputFiles := []string{"outputbase/execroot/__main__/foo.so"}
+	android.AssertDeepEquals(t, "output files", expectedOutputFiles, outputFiles.Strings())
+
+	tocFilePath := sharedFoo.(*Module).Toc()
+	if !tocFilePath.Valid() {
+		t.Errorf("Invalid tocFilePath: %s", tocFilePath)
+	}
+	tocFile := tocFilePath.Path()
+	expectedToc := "outputbase/execroot/__main__/foo.so.toc"
+	android.AssertStringEquals(t, "toc file", expectedToc, tocFile.String())
+
+	entries := android.AndroidMkEntriesForTest(t, ctx, sharedFoo)[0]
+	expectedFlags := []string{"-Ioutputbase/execroot/__main__/include", "-isystem outputbase/execroot/__main__/system_include"}
+	gotFlags := entries.EntryMap["LOCAL_EXPORT_CFLAGS"]
+	android.AssertDeepEquals(t, "androidmk exported cflags", expectedFlags, gotFlags)
+}
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index 1401c75..7733c1b 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -110,17 +110,12 @@
 		return true
 	}
 
-	// Don't preopt system server jars that are updatable.
-	if global.ApexSystemServerJars.ContainsJar(module.Name) {
-		return true
-	}
-
 	// If OnlyPreoptBootImageAndSystemServer=true and module is not in boot class path skip
 	// Also preopt system server jars since selinux prevents system server from loading anything from
 	// /data. If we don't do this they will need to be extracted which is not favorable for RAM usage
 	// or performance. If PreoptExtractedApk is true, we ignore the only preopt boot image options.
 	if global.OnlyPreoptBootImageAndSystemServer && !global.BootJars.ContainsJar(module.Name) &&
-		!global.SystemServerJars.ContainsJar(module.Name) && !module.PreoptExtractedApk {
+		!AllSystemServerJars(ctx, global).ContainsJar(module.Name) && !module.PreoptExtractedApk {
 		return true
 	}
 
@@ -201,6 +196,14 @@
 	return profilePath
 }
 
+// Returns the dex location of a system server java library.
+func GetSystemServerDexLocation(global *GlobalConfig, lib string) string {
+	if apex := global.ApexSystemServerJars.ApexOfJar(lib); apex != "" {
+		return fmt.Sprintf("/apex/%s/javalib/%s.jar", apex, lib)
+	}
+	return fmt.Sprintf("/system/framework/%s.jar", lib)
+}
+
 func dexpreoptCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, global *GlobalConfig,
 	module *ModuleConfig, rule *android.RuleBuilder, archIdx int, profile android.WritablePath,
 	appImage bool, generateDM bool) {
@@ -216,6 +219,13 @@
 	}
 
 	toOdexPath := func(path string) string {
+		if global.ApexSystemServerJars.ContainsJar(module.Name) {
+			return filepath.Join(
+				"/system/framework/oat",
+				arch.String(),
+				strings.ReplaceAll(path[1:], "/", "@")+"@classes.odex")
+		}
+
 		return filepath.Join(
 			filepath.Dir(path),
 			"oat",
@@ -234,20 +244,21 @@
 
 	invocationPath := odexPath.ReplaceExtension(ctx, "invocation")
 
-	systemServerJars := NonApexSystemServerJars(ctx, global)
+	systemServerJars := AllSystemServerJars(ctx, global)
 
 	rule.Command().FlagWithArg("mkdir -p ", filepath.Dir(odexPath.String()))
 	rule.Command().FlagWithOutput("rm -f ", odexPath)
 
-	if jarIndex := android.IndexList(module.Name, systemServerJars); jarIndex >= 0 {
+	if jarIndex := systemServerJars.IndexOfJar(module.Name); jarIndex >= 0 {
 		// System server jars should be dexpreopted together: class loader context of each jar
 		// should include all preceding jars on the system server classpath.
 
 		var clcHost android.Paths
 		var clcTarget []string
-		for _, lib := range systemServerJars[:jarIndex] {
+		for i := 0; i < jarIndex; i++ {
+			lib := systemServerJars.Jar(i)
 			clcHost = append(clcHost, SystemServerDexJarHostPath(ctx, lib))
-			clcTarget = append(clcTarget, filepath.Join("/system/framework", lib+".jar"))
+			clcTarget = append(clcTarget, GetSystemServerDexLocation(global, lib))
 		}
 
 		// Copy the system server jar to a predefined location where dex2oat will find it.
@@ -362,7 +373,7 @@
 
 	if !android.PrefixInList(preoptFlags, "--compiler-filter=") {
 		var compilerFilter string
-		if global.SystemServerJars.ContainsJar(module.Name) {
+		if systemServerJars.ContainsJar(module.Name) {
 			// Jars of system server, use the product option if it is set, speed otherwise.
 			if global.SystemServerCompilerFilter != "" {
 				compilerFilter = global.SystemServerCompilerFilter
@@ -416,7 +427,7 @@
 
 	// PRODUCT_SYSTEM_SERVER_DEBUG_INFO overrides WITH_DEXPREOPT_DEBUG_INFO.
 	// PRODUCT_OTHER_JAVA_DEBUG_INFO overrides WITH_DEXPREOPT_DEBUG_INFO.
-	if global.SystemServerJars.ContainsJar(module.Name) {
+	if systemServerJars.ContainsJar(module.Name) {
 		if global.AlwaysSystemServerDebugInfo {
 			debugInfo = true
 		} else if global.NeverSystemServerDebugInfo {
@@ -518,14 +529,15 @@
 	}
 }
 
-var nonApexSystemServerJarsKey = android.NewOnceKey("nonApexSystemServerJars")
+var allSystemServerJarsKey = android.NewOnceKey("allSystemServerJars")
 
 // TODO: eliminate the superficial global config parameter by moving global config definition
 // from java subpackage to dexpreopt.
-func NonApexSystemServerJars(ctx android.PathContext, global *GlobalConfig) []string {
-	return ctx.Config().Once(nonApexSystemServerJarsKey, func() interface{} {
-		return android.RemoveListFromList(global.SystemServerJars.CopyOfJars(), global.ApexSystemServerJars.CopyOfJars())
-	}).([]string)
+func AllSystemServerJars(ctx android.PathContext, global *GlobalConfig) *android.ConfiguredJarList {
+	return ctx.Config().Once(allSystemServerJarsKey, func() interface{} {
+		allSystemServerJars := global.SystemServerJars.AppendList(global.ApexSystemServerJars)
+		return &allSystemServerJars
+	}).(*android.ConfiguredJarList)
 }
 
 // A predefined location for the system server dex jars. This is needed in order to generate
@@ -551,12 +563,12 @@
 	mctx, isModule := ctx.(android.ModuleContext)
 	if isModule {
 		config := GetGlobalConfig(ctx)
-		jars := NonApexSystemServerJars(ctx, config)
+		jars := AllSystemServerJars(ctx, config)
 		mctx.WalkDeps(func(dep android.Module, parent android.Module) bool {
-			depIndex := android.IndexList(dep.Name(), jars)
+			depIndex := jars.IndexOfJar(dep.Name())
 			if jarIndex < depIndex && !config.BrokenSuboptimalOrderOfSystemServerJars {
-				jar := jars[jarIndex]
-				dep := jars[depIndex]
+				jar := jars.Jar(jarIndex)
+				dep := jars.Jar(depIndex)
 				mctx.ModuleErrorf("non-optimal order of jars on the system server classpath:"+
 					" '%s' precedes its dependency '%s', so dexpreopt is unable to resolve any"+
 					" references from '%s' to '%s'.\n", jar, dep, jar, dep)
diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go
index 4ee61b6..798d776 100644
--- a/dexpreopt/dexpreopt_test.go
+++ b/dexpreopt/dexpreopt_test.go
@@ -33,17 +33,35 @@
 }
 
 func testModuleConfig(ctx android.PathContext, name, partition string) *ModuleConfig {
+	return createTestModuleConfig(
+		name,
+		fmt.Sprintf("/%s/app/test/%s.apk", partition, name),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/%s.apk", name, name)),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/dex/%s.jar", name, name)),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/enforce_uses_libraries.status", name)))
+}
+
+func testApexModuleConfig(ctx android.PathContext, name, apexName string) *ModuleConfig {
+	return createTestModuleConfig(
+		name,
+		fmt.Sprintf("/apex/%s/javalib/%s.jar", apexName, name),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/dexpreopt/%s.jar", name, name)),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/aligned/%s.jar", name, name)),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/enforce_uses_libraries.status", name)))
+}
+
+func createTestModuleConfig(name, dexLocation string, buildPath, dexPath, enforceUsesLibrariesStatusFile android.OutputPath) *ModuleConfig {
 	return &ModuleConfig{
 		Name:                            name,
-		DexLocation:                     fmt.Sprintf("/%s/app/test/%s.apk", partition, name),
-		BuildPath:                       android.PathForOutput(ctx, fmt.Sprintf("%s/%s.apk", name, name)),
-		DexPath:                         android.PathForOutput(ctx, fmt.Sprintf("%s/dex/%s.jar", name, name)),
+		DexLocation:                     dexLocation,
+		BuildPath:                       buildPath,
+		DexPath:                         dexPath,
 		UncompressedDex:                 false,
 		HasApkLibraries:                 false,
 		PreoptFlags:                     nil,
 		ProfileClassListing:             android.OptionalPath{},
 		ProfileIsTextListing:            false,
-		EnforceUsesLibrariesStatusFile:  android.PathForOutput(ctx, fmt.Sprintf("%s/enforce_uses_libraries.status", name)),
+		EnforceUsesLibrariesStatusFile:  enforceUsesLibrariesStatusFile,
 		EnforceUsesLibraries:            false,
 		ClassLoaderContexts:             nil,
 		Archs:                           []android.ArchType{android.Arm},
@@ -140,6 +158,29 @@
 
 }
 
+func TestDexPreoptApexSystemServerJars(t *testing.T) {
+	config := android.TestConfig("out", nil, "", nil)
+	ctx := android.BuilderContextForTesting(config)
+	globalSoong := globalSoongConfigForTests()
+	global := GlobalConfigForTests(ctx)
+	module := testApexModuleConfig(ctx, "service-A", "com.android.apex1")
+
+	global.ApexSystemServerJars = android.CreateTestConfiguredJarList(
+		[]string{"com.android.apex1:service-A"})
+
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	wantInstalls := android.RuleBuilderInstalls{
+		{android.PathForOutput(ctx, "service-A/dexpreopt/oat/arm/javalib.odex"), "/system/framework/oat/arm/apex@com.android.apex1@javalib@service-A.jar@classes.odex"},
+		{android.PathForOutput(ctx, "service-A/dexpreopt/oat/arm/javalib.vdex"), "/system/framework/oat/arm/apex@com.android.apex1@javalib@service-A.jar@classes.vdex"},
+	}
+
+	android.AssertStringEquals(t, "installs", wantInstalls.String(), rule.Installs().String())
+}
+
 func TestDexPreoptProfile(t *testing.T) {
 	config := android.TestConfig("out", nil, "", nil)
 	ctx := android.BuilderContextForTesting(config)
diff --git a/java/androidmk.go b/java/androidmk.go
index 68ccd82..71370c9 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -61,7 +61,13 @@
 	var entriesList []android.AndroidMkEntries
 
 	if library.hideApexVariantFromMake {
-		// For a java library built for an APEX we don't need Make module
+		// For a java library built for an APEX, we don't need a Make module for itself. Otherwise, it
+		// will conflict with the platform variant because they have the same module name in the
+		// makefile. However, we need to add its dexpreopt outputs as sub-modules, if it is preopted.
+		dexpreoptEntries := library.dexpreopter.AndroidMkEntriesForApex()
+		if len(dexpreoptEntries) > 0 {
+			entriesList = append(entriesList, dexpreoptEntries...)
+		}
 		entriesList = append(entriesList, android.AndroidMkEntries{Disabled: true})
 	} else if !library.ApexModuleBase.AvailableFor(android.AvailableToPlatform) {
 		// Platform variant.  If not available for the platform, we don't need Make module.
diff --git a/java/bootclasspath.go b/java/bootclasspath.go
index 4108770..52ce77d 100644
--- a/java/bootclasspath.go
+++ b/java/bootclasspath.go
@@ -95,15 +95,6 @@
 	if ctx.OtherModuleDependencyVariantExists(variations, prebuiltName) {
 		ctx.AddVariationDependencies(variations, tag, prebuiltName)
 		addedDep = true
-	} else if ctx.Config().AlwaysUsePrebuiltSdks() && len(variations) > 0 {
-		// TODO(b/179354495): Remove this code path once the Android build has been fully migrated to
-		//  use bootclasspath_fragment properly.
-		// Some prebuilt java_sdk_library modules do not yet have an APEX variations so try and add a
-		// dependency on the non-APEX variant.
-		if ctx.OtherModuleDependencyVariantExists(nil, prebuiltName) {
-			ctx.AddVariationDependencies(nil, tag, prebuiltName)
-			addedDep = true
-		}
 	}
 
 	// If no appropriate variant existing for this, so no dependency could be added, then it is an
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 0faae36..e9dc982 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -15,13 +15,46 @@
 package java
 
 import (
+	"path/filepath"
+	"strings"
+
 	"android/soong/android"
 	"android/soong/dexpreopt"
 )
 
-type dexpreopterInterface interface {
+type DexpreopterInterface interface {
 	IsInstallable() bool // Structs that embed dexpreopter must implement this.
 	dexpreoptDisabled(ctx android.BaseModuleContext) bool
+	DexpreoptBuiltInstalledForApex() []dexpreopterInstall
+	AndroidMkEntriesForApex() []android.AndroidMkEntries
+}
+
+type dexpreopterInstall struct {
+	// A unique name to distinguish an output from others for the same java library module. Usually in
+	// the form of `<arch>-<encoded-path>.odex/vdex/art`.
+	name string
+
+	// The name of the input java module.
+	moduleName string
+
+	// The path to the dexpreopt output on host.
+	outputPathOnHost android.Path
+
+	// The directory on the device for the output to install to.
+	installDirOnDevice android.InstallPath
+
+	// The basename (the last segment of the path) for the output to install as.
+	installFileOnDevice string
+}
+
+// The full module name of the output in the makefile.
+func (install *dexpreopterInstall) FullModuleName() string {
+	return install.moduleName + install.SubModuleName()
+}
+
+// The sub-module name of the output in the makefile (the name excluding the java module name).
+func (install *dexpreopterInstall) SubModuleName() string {
+	return "-dexpreopt-" + install.name
 }
 
 type dexpreopter struct {
@@ -39,7 +72,9 @@
 	enforceUsesLibs     bool
 	classLoaderContexts dexpreopt.ClassLoaderContextMap
 
-	builtInstalled string
+	// See the `dexpreopt` function for details.
+	builtInstalled        string
+	builtInstalledForApex []dexpreopterInstall
 
 	// The config is used for two purposes:
 	// - Passing dexpreopt information about libraries from Soong to Make. This is needed when
@@ -74,6 +109,17 @@
 	dexpreopt.DexpreoptRunningInSoong = true
 }
 
+func isApexVariant(ctx android.BaseModuleContext) bool {
+	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+	return !apexInfo.IsForPlatform()
+}
+
+func moduleName(ctx android.BaseModuleContext) string {
+	// Remove the "prebuilt_" prefix if the module is from a prebuilt because the prefix is not
+	// expected by dexpreopter.
+	return android.RemoveOptionalPrebuiltPrefix(ctx.ModuleName())
+}
+
 func (d *dexpreopter) dexpreoptDisabled(ctx android.BaseModuleContext) bool {
 	global := dexpreopt.GetGlobalConfig(ctx)
 
@@ -81,7 +127,7 @@
 		return true
 	}
 
-	if inList(ctx.ModuleName(), global.DisablePreoptModules) {
+	if inList(moduleName(ctx), global.DisablePreoptModules) {
 		return true
 	}
 
@@ -93,7 +139,7 @@
 		return true
 	}
 
-	if !ctx.Module().(dexpreopterInterface).IsInstallable() {
+	if !ctx.Module().(DexpreopterInterface).IsInstallable() {
 		return true
 	}
 
@@ -101,8 +147,20 @@
 		return true
 	}
 
-	// Don't preopt APEX variant module
-	if apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo); !apexInfo.IsForPlatform() {
+	if isApexVariant(ctx) {
+		// Don't preopt APEX variant module unless the module is an APEX system server jar and we are
+		// building the entire system image.
+		if !global.ApexSystemServerJars.ContainsJar(moduleName(ctx)) || ctx.Config().UnbundledBuild() {
+			return true
+		}
+	} else {
+		// Don't preopt the platform variant of an APEX system server jar to avoid conflicts.
+		if global.ApexSystemServerJars.ContainsJar(moduleName(ctx)) {
+			return true
+		}
+	}
+
+	if !android.IsModulePreferred(ctx.Module()) {
 		return true
 	}
 
@@ -112,17 +170,40 @@
 }
 
 func dexpreoptToolDepsMutator(ctx android.BottomUpMutatorContext) {
-	if d, ok := ctx.Module().(dexpreopterInterface); !ok || d.dexpreoptDisabled(ctx) {
+	if d, ok := ctx.Module().(DexpreopterInterface); !ok || d.dexpreoptDisabled(ctx) {
 		return
 	}
 	dexpreopt.RegisterToolDeps(ctx)
 }
 
-func odexOnSystemOther(ctx android.ModuleContext, installPath android.InstallPath) bool {
-	return dexpreopt.OdexOnSystemOtherByName(ctx.ModuleName(), android.InstallPathToOnDevicePath(ctx, installPath), dexpreopt.GetGlobalConfig(ctx))
+func (d *dexpreopter) odexOnSystemOther(ctx android.ModuleContext, installPath android.InstallPath) bool {
+	return dexpreopt.OdexOnSystemOtherByName(moduleName(ctx), android.InstallPathToOnDevicePath(ctx, installPath), dexpreopt.GetGlobalConfig(ctx))
+}
+
+// Returns the install path of the dex jar of a module.
+//
+// Do not rely on `ApexInfo.ApexVariationName` because it can be something like "apex1000", rather
+// than the `name` in the path `/apex/<name>` as suggested in its comment.
+//
+// This function is on a best-effort basis. It cannot handle the case where an APEX jar is not a
+// system server jar, which is fine because we currently only preopt system server jars for APEXes.
+func (d *dexpreopter) getInstallPath(
+	ctx android.ModuleContext, defaultInstallPath android.InstallPath) android.InstallPath {
+	global := dexpreopt.GetGlobalConfig(ctx)
+	if global.ApexSystemServerJars.ContainsJar(moduleName(ctx)) {
+		dexLocation := dexpreopt.GetSystemServerDexLocation(global, moduleName(ctx))
+		return android.PathForModuleInPartitionInstall(ctx, "", strings.TrimPrefix(dexLocation, "/"))
+	}
+	if !d.dexpreoptDisabled(ctx) && isApexVariant(ctx) &&
+		filepath.Base(defaultInstallPath.PartitionDir()) != "apex" {
+		ctx.ModuleErrorf("unable to get the install path of the dex jar for dexpreopt")
+	}
+	return defaultInstallPath
 }
 
 func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.WritablePath) {
+	global := dexpreopt.GetGlobalConfig(ctx)
+
 	// TODO(b/148690468): The check on d.installPath is to bail out in cases where
 	// the dexpreopter struct hasn't been fully initialized before we're called,
 	// e.g. in aar.go. This keeps the behaviour that dexpreopting is effectively
@@ -133,7 +214,7 @@
 
 	dexLocation := android.InstallPathToOnDevicePath(ctx, d.installPath)
 
-	providesUsesLib := ctx.ModuleName()
+	providesUsesLib := moduleName(ctx)
 	if ulib, ok := ctx.Module().(ProvidesUsesLib); ok {
 		name := ulib.ProvidesUsesLib()
 		if name != nil {
@@ -147,17 +228,15 @@
 		return
 	}
 
-	global := dexpreopt.GetGlobalConfig(ctx)
-
-	isSystemServerJar := global.SystemServerJars.ContainsJar(ctx.ModuleName())
+	isSystemServerJar := global.SystemServerJars.ContainsJar(moduleName(ctx)) ||
+		global.ApexSystemServerJars.ContainsJar(moduleName(ctx))
 
 	bootImage := defaultBootImageConfig(ctx)
 	if global.UseArtImage {
 		bootImage = artBootImageConfig(ctx)
 	}
 
-	// System server jars are an exception: they are dexpreopted without updatable bootclasspath.
-	dexFiles, dexLocations := bcpForDexpreopt(ctx, global.PreoptWithUpdatableBcp && !isSystemServerJar)
+	dexFiles, dexLocations := bcpForDexpreopt(ctx, global.PreoptWithUpdatableBcp)
 
 	targets := ctx.MultiTargets()
 	if len(targets) == 0 {
@@ -199,15 +278,15 @@
 			profileIsTextListing = true
 		} else if global.ProfileDir != "" {
 			profileClassListing = android.ExistentPathForSource(ctx,
-				global.ProfileDir, ctx.ModuleName()+".prof")
+				global.ProfileDir, moduleName(ctx)+".prof")
 		}
 	}
 
 	// Full dexpreopt config, used to create dexpreopt build rules.
 	dexpreoptConfig := &dexpreopt.ModuleConfig{
-		Name:            ctx.ModuleName(),
+		Name:            moduleName(ctx),
 		DexLocation:     dexLocation,
-		BuildPath:       android.PathForModuleOut(ctx, "dexpreopt", ctx.ModuleName()+".jar").OutputPath,
+		BuildPath:       android.PathForModuleOut(ctx, "dexpreopt", moduleName(ctx)+".jar").OutputPath,
 		DexPath:         dexJarFile,
 		ManifestPath:    android.OptionalPathForPath(d.manifestFile),
 		UncompressedDex: d.uncompressedDex,
@@ -256,5 +335,53 @@
 
 	dexpreoptRule.Build("dexpreopt", "dexpreopt")
 
-	d.builtInstalled = dexpreoptRule.Installs().String()
+	if global.ApexSystemServerJars.ContainsJar(moduleName(ctx)) {
+		// APEX variants of java libraries are hidden from Make, so their dexpreopt outputs need special
+		// handling. Currently, for APEX variants of java libraries, only those in the system server
+		// classpath are handled here. Preopting of boot classpath jars in the ART APEX are handled in
+		// java/dexpreopt_bootjars.go, and other APEX jars are not preopted.
+		for _, install := range dexpreoptRule.Installs() {
+			// Remove the "/" prefix because the path should be relative to $ANDROID_PRODUCT_OUT.
+			installDir := strings.TrimPrefix(filepath.Dir(install.To), "/")
+			installBase := filepath.Base(install.To)
+			arch := filepath.Base(installDir)
+			installPath := android.PathForModuleInPartitionInstall(ctx, "", installDir)
+			// The installs will be handled by Make as sub-modules of the java library.
+			d.builtInstalledForApex = append(d.builtInstalledForApex, dexpreopterInstall{
+				name:                arch + "-" + installBase,
+				moduleName:          moduleName(ctx),
+				outputPathOnHost:    install.From,
+				installDirOnDevice:  installPath,
+				installFileOnDevice: installBase,
+			})
+		}
+	} else {
+		// The installs will be handled by Make as LOCAL_SOONG_BUILT_INSTALLED of the java library
+		// module.
+		d.builtInstalled = dexpreoptRule.Installs().String()
+	}
+}
+
+func (d *dexpreopter) DexpreoptBuiltInstalledForApex() []dexpreopterInstall {
+	return d.builtInstalledForApex
+}
+
+func (d *dexpreopter) AndroidMkEntriesForApex() []android.AndroidMkEntries {
+	var entries []android.AndroidMkEntries
+	for _, install := range d.builtInstalledForApex {
+		install := install
+		entries = append(entries, android.AndroidMkEntries{
+			Class:      "ETC",
+			SubName:    install.SubModuleName(),
+			OutputFile: android.OptionalPathForPath(install.outputPathOnHost),
+			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+				func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+					entries.SetString("LOCAL_MODULE_PATH", install.installDirOnDevice.ToMakePath().String())
+					entries.SetString("LOCAL_INSTALLED_MODULE_STEM", install.installFileOnDevice)
+					entries.SetString("LOCAL_NOT_AVAILABLE_FOR_PLATFORM", "false")
+				},
+			},
+		})
+	}
+	return entries
 }
diff --git a/java/dexpreopt_test.go b/java/dexpreopt_test.go
index 8dc7b79..1c1070a 100644
--- a/java/dexpreopt_test.go
+++ b/java/dexpreopt_test.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"runtime"
+	"strings"
 	"testing"
 
 	"android/soong/android"
@@ -24,11 +25,17 @@
 	"android/soong/dexpreopt"
 )
 
+func init() {
+	RegisterFakeRuntimeApexMutator()
+}
+
 func TestDexpreoptEnabled(t *testing.T) {
 	tests := []struct {
-		name    string
-		bp      string
-		enabled bool
+		name        string
+		bp          string
+		moduleName  string
+		apexVariant bool
+		enabled     bool
 	}{
 		{
 			name: "app",
@@ -148,13 +155,81 @@
 				}`,
 			enabled: true,
 		},
+		{
+			name: "apex variant",
+			bp: `
+				java_library {
+					name: "foo",
+					installable: true,
+					srcs: ["a.java"],
+					apex_available: ["com.android.apex1"],
+				}`,
+			apexVariant: true,
+			enabled:     false,
+		},
+		{
+			name: "apex variant of apex system server jar",
+			bp: `
+				java_library {
+					name: "service-foo",
+					installable: true,
+					srcs: ["a.java"],
+					apex_available: ["com.android.apex1"],
+				}`,
+			moduleName:  "service-foo",
+			apexVariant: true,
+			enabled:     true,
+		},
+		{
+			name: "apex variant of prebuilt apex system server jar",
+			bp: `
+				java_library {
+					name: "prebuilt_service-foo",
+					installable: true,
+					srcs: ["a.java"],
+					apex_available: ["com.android.apex1"],
+				}`,
+			moduleName:  "prebuilt_service-foo",
+			apexVariant: true,
+			enabled:     true,
+		},
+		{
+			name: "platform variant of apex system server jar",
+			bp: `
+				java_library {
+					name: "service-foo",
+					installable: true,
+					srcs: ["a.java"],
+					apex_available: ["com.android.apex1"],
+				}`,
+			moduleName:  "service-foo",
+			apexVariant: false,
+			enabled:     false,
+		},
 	}
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			ctx, _ := testJava(t, test.bp)
+			preparers := android.GroupFixturePreparers(
+				PrepareForTestWithJavaDefaultModules,
+				PrepareForTestWithFakeApexMutator,
+				dexpreopt.FixtureSetApexSystemServerJars("com.android.apex1:service-foo"),
+			)
 
-			dexpreopt := ctx.ModuleForTests("foo", "android_common").MaybeRule("dexpreopt")
+			result := preparers.RunTestWithBp(t, test.bp)
+			ctx := result.TestContext
+
+			moduleName := "foo"
+			if test.moduleName != "" {
+				moduleName = test.moduleName
+			}
+
+			variant := "android_common"
+			if test.apexVariant {
+				variant += "_apex1000"
+			}
+
+			dexpreopt := ctx.ModuleForTests(moduleName, variant).MaybeRule("dexpreopt")
 			enabled := dexpreopt.Rule != nil
 
 			if enabled != test.enabled {
@@ -220,3 +295,145 @@
 	testDex2oatToolDep(true, true, true, prebuiltDex2oatPath)
 	testDex2oatToolDep(false, true, false, prebuiltDex2oatPath)
 }
+
+func TestDexpreoptBuiltInstalledForApex(t *testing.T) {
+	preparers := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		PrepareForTestWithFakeApexMutator,
+		dexpreopt.FixtureSetApexSystemServerJars("com.android.apex1:service-foo"),
+	)
+
+	// An APEX system server jar.
+	result := preparers.RunTestWithBp(t, `
+		java_library {
+			name: "service-foo",
+			installable: true,
+			srcs: ["a.java"],
+			apex_available: ["com.android.apex1"],
+		}`)
+	ctx := result.TestContext
+	module := ctx.ModuleForTests("service-foo", "android_common_apex1000")
+	library := module.Module().(*Library)
+
+	installs := library.dexpreopter.DexpreoptBuiltInstalledForApex()
+
+	android.AssertIntEquals(t, "install count", 2, len(installs))
+
+	android.AssertStringEquals(t, "installs[0] FullModuleName",
+		"service-foo-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.odex",
+		installs[0].FullModuleName())
+
+	android.AssertStringEquals(t, "installs[0] SubModuleName",
+		"-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.odex",
+		installs[0].SubModuleName())
+
+	android.AssertStringEquals(t, "installs[1] FullModuleName",
+		"service-foo-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.vdex",
+		installs[1].FullModuleName())
+
+	android.AssertStringEquals(t, "installs[1] SubModuleName",
+		"-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.vdex",
+		installs[1].SubModuleName())
+
+	// Not an APEX system server jar.
+	result = preparers.RunTestWithBp(t, `
+		java_library {
+			name: "foo",
+			installable: true,
+			srcs: ["a.java"],
+		}`)
+	ctx = result.TestContext
+	module = ctx.ModuleForTests("foo", "android_common")
+	library = module.Module().(*Library)
+
+	installs = library.dexpreopter.DexpreoptBuiltInstalledForApex()
+
+	android.AssertIntEquals(t, "install count", 0, len(installs))
+}
+
+func filterDexpreoptEntriesList(entriesList []android.AndroidMkEntries) []android.AndroidMkEntries {
+	var results []android.AndroidMkEntries
+	for _, entries := range entriesList {
+		if strings.Contains(entries.EntryMap["LOCAL_MODULE"][0], "-dexpreopt-") {
+			results = append(results, entries)
+		}
+	}
+	return results
+}
+
+func verifyEntries(t *testing.T, message string, expectedModule string,
+	expectedPrebuiltModuleFile string, expectedModulePath string, expectedInstalledModuleStem string,
+	entries android.AndroidMkEntries) {
+	android.AssertStringEquals(t, message+" LOCAL_MODULE", expectedModule,
+		entries.EntryMap["LOCAL_MODULE"][0])
+
+	android.AssertStringEquals(t, message+" LOCAL_MODULE_CLASS", "ETC",
+		entries.EntryMap["LOCAL_MODULE_CLASS"][0])
+
+	android.AssertStringDoesContain(t, message+" LOCAL_PREBUILT_MODULE_FILE",
+		entries.EntryMap["LOCAL_PREBUILT_MODULE_FILE"][0], expectedPrebuiltModuleFile)
+
+	android.AssertStringDoesContain(t, message+" LOCAL_MODULE_PATH",
+		entries.EntryMap["LOCAL_MODULE_PATH"][0], expectedModulePath)
+
+	android.AssertStringEquals(t, message+" LOCAL_INSTALLED_MODULE_STEM",
+		expectedInstalledModuleStem, entries.EntryMap["LOCAL_INSTALLED_MODULE_STEM"][0])
+
+	android.AssertStringEquals(t, message+" LOCAL_NOT_AVAILABLE_FOR_PLATFORM",
+		"false", entries.EntryMap["LOCAL_NOT_AVAILABLE_FOR_PLATFORM"][0])
+}
+
+func TestAndroidMkEntriesForApex(t *testing.T) {
+	preparers := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		PrepareForTestWithFakeApexMutator,
+		dexpreopt.FixtureSetApexSystemServerJars("com.android.apex1:service-foo"),
+	)
+
+	// An APEX system server jar.
+	result := preparers.RunTestWithBp(t, `
+		java_library {
+			name: "service-foo",
+			installable: true,
+			srcs: ["a.java"],
+			apex_available: ["com.android.apex1"],
+		}`)
+	ctx := result.TestContext
+	module := ctx.ModuleForTests("service-foo", "android_common_apex1000")
+
+	entriesList := android.AndroidMkEntriesForTest(t, ctx, module.Module())
+	entriesList = filterDexpreoptEntriesList(entriesList)
+
+	android.AssertIntEquals(t, "entries count", 2, len(entriesList))
+
+	verifyEntries(t,
+		"entriesList[0]",
+		"service-foo-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.odex",
+		"/dexpreopt/oat/arm64/javalib.odex",
+		"/system/framework/oat/arm64",
+		"apex@com.android.apex1@javalib@service-foo.jar@classes.odex",
+		entriesList[0])
+
+	verifyEntries(t,
+		"entriesList[1]",
+		"service-foo-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.vdex",
+		"/dexpreopt/oat/arm64/javalib.vdex",
+		"/system/framework/oat/arm64",
+		"apex@com.android.apex1@javalib@service-foo.jar@classes.vdex",
+		entriesList[1])
+
+	// Not an APEX system server jar.
+	result = preparers.RunTestWithBp(t, `
+		java_library {
+			name: "foo",
+			installable: true,
+			srcs: ["a.java"],
+		}`)
+	ctx = result.TestContext
+	module = ctx.ModuleForTests("foo", "android_common")
+
+	entriesList = android.AndroidMkEntriesForTest(t, ctx, module.Module())
+	entriesList = filterDexpreoptEntriesList(entriesList)
+
+	android.AssertIntEquals(t, "entries count", 0, len(entriesList))
+}
diff --git a/java/java.go b/java/java.go
index 1a052b4..e2665ef 100644
--- a/java/java.go
+++ b/java/java.go
@@ -487,7 +487,7 @@
 	}
 
 	// Store uncompressed dex files that are preopted on /system.
-	if !dexpreopter.dexpreoptDisabled(ctx) && (ctx.Host() || !odexOnSystemOther(ctx, dexpreopter.installPath)) {
+	if !dexpreopter.dexpreoptDisabled(ctx) && (ctx.Host() || !dexpreopter.odexOnSystemOther(ctx, dexpreopter.installPath)) {
 		return true
 	}
 	if ctx.Config().UncompressPrivAppDex() &&
@@ -508,7 +508,8 @@
 	}
 
 	j.checkSdkVersions(ctx)
-	j.dexpreopter.installPath = android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar")
+	j.dexpreopter.installPath = j.dexpreopter.getInstallPath(
+		ctx, android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar"))
 	j.dexpreopter.isSDKLibrary = j.deviceProperties.IsSDKLibrary
 	if j.dexProperties.Uncompress_dex == nil {
 		// If the value was not force-set by the user, use reasonable default based on the module.
@@ -1368,7 +1369,8 @@
 
 			// Dex compilation
 
-			j.dexpreopter.installPath = android.PathForModuleInstall(ctx, "framework", jarName)
+			j.dexpreopter.installPath = j.dexpreopter.getInstallPath(
+				ctx, android.PathForModuleInstall(ctx, "framework", jarName))
 			if j.dexProperties.Uncompress_dex == nil {
 				// If the value was not force-set by the user, use reasonable default based on the module.
 				j.dexProperties.Uncompress_dex = proptools.BoolPtr(shouldUncompressDex(ctx, &j.dexpreopter))
@@ -1509,7 +1511,7 @@
 	return Bool(j.properties.Installable)
 }
 
-var _ dexpreopterInterface = (*Import)(nil)
+var _ DexpreopterInterface = (*Import)(nil)
 
 // java_import imports one or more `.jar` files into the build graph as if they were built by a java_library module.
 //
@@ -1622,7 +1624,8 @@
 		j.hideApexVariantFromMake = true
 	}
 
-	j.dexpreopter.installPath = android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar")
+	j.dexpreopter.installPath = j.dexpreopter.getInstallPath(
+		ctx, android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar"))
 	j.dexpreopter.uncompressedDex = shouldUncompressDex(ctx, &j.dexpreopter)
 
 	inputJar := ctx.ExpandSource(j.properties.Jars[0], "jars")
diff --git a/java/sdk_library.go b/java/sdk_library.go
index ce8f179..1d39071 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -1907,6 +1907,7 @@
 	android.SdkBase
 
 	hiddenAPI
+	dexpreopter
 
 	properties sdkLibraryImportProperties
 
@@ -2111,6 +2112,14 @@
 	}
 }
 
+func (module *SdkLibraryImport) AndroidMkEntries() []android.AndroidMkEntries {
+	// For an SDK library imported from a prebuilt APEX, we don't need a Make module for itself, as we
+	// don't need to install it. However, we need to add its dexpreopt outputs as sub-modules, if it
+	// is preopted.
+	dexpreoptEntries := module.dexpreopter.AndroidMkEntriesForApex()
+	return append(dexpreoptEntries, android.AndroidMkEntries{Disabled: true})
+}
+
 var _ android.ApexModule = (*SdkLibraryImport)(nil)
 
 // Implements android.ApexModule
@@ -2208,8 +2217,16 @@
 			di := ctx.OtherModuleProvider(deapexerModule, android.DeapexerProvider).(android.DeapexerInfo)
 			if dexOutputPath := di.PrebuiltExportPath(apexRootRelativePathToJavaLib(module.BaseModuleName())); dexOutputPath != nil {
 				module.dexJarFile = dexOutputPath
-				module.installFile = android.PathForModuleInPartitionInstall(ctx, "apex", ai.ApexVariationName, apexRootRelativePathToJavaLib(module.BaseModuleName()))
+				installPath := android.PathForModuleInPartitionInstall(
+					ctx, "apex", ai.ApexVariationName, apexRootRelativePathToJavaLib(module.BaseModuleName()))
+				module.installFile = installPath
 				module.initHiddenAPI(ctx, dexOutputPath, module.findScopePaths(apiScopePublic).stubsImplPath[0], nil)
+
+				// Dexpreopting.
+				module.dexpreopter.installPath = module.dexpreopter.getInstallPath(ctx, installPath)
+				module.dexpreopter.isSDKLibrary = true
+				module.dexpreopter.uncompressedDex = shouldUncompressDex(ctx, &module.dexpreopter)
+				module.dexpreopt(ctx, dexOutputPath)
 			} else {
 				// This should never happen as a variant for a prebuilt_apex is only created if the
 				// prebuilt_apex has been configured to export the java library dex file.
@@ -2328,6 +2345,11 @@
 	}
 }
 
+// to satisfy java.DexpreopterInterface interface
+func (module *SdkLibraryImport) IsInstallable() bool {
+	return true
+}
+
 var _ android.RequiredFilesFromPrebuiltApex = (*SdkLibraryImport)(nil)
 
 func (module *SdkLibraryImport) RequiredFilesFromPrebuiltApex(ctx android.BaseModuleContext) []string {
diff --git a/java/sdk_library_test.go b/java/sdk_library_test.go
index 938bb28..d6c0946 100644
--- a/java/sdk_library_test.go
+++ b/java/sdk_library_test.go
@@ -598,6 +598,7 @@
 	}
 
 	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
+		`dex2oatd`,
 		`prebuilt_sdklib.stubs`,
 		`prebuilt_sdklib.stubs.source.test`,
 		`prebuilt_sdklib.stubs.system`,
@@ -674,7 +675,6 @@
 		`)
 
 	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
-		`dex2oatd`,
 		`prebuilt_sdklib`,
 		`sdklib.impl`,
 		`sdklib.stubs`,
@@ -683,6 +683,7 @@
 	})
 
 	CheckModuleDependencies(t, result.TestContext, "prebuilt_sdklib", "android_common", []string{
+		`dex2oatd`,
 		`prebuilt_sdklib.stubs`,
 		`sdklib.impl`,
 		`sdklib.xml`,
diff --git a/java/systemserver_classpath_fragment.go b/java/systemserver_classpath_fragment.go
index 5311f62..de2a978 100644
--- a/java/systemserver_classpath_fragment.go
+++ b/java/systemserver_classpath_fragment.go
@@ -123,10 +123,16 @@
 	blueprint.BaseDependencyTag
 }
 
+// The systemserverclasspath_fragment contents must never depend on prebuilts.
+func (systemServerClasspathFragmentContentDependencyTag) ReplaceSourceWithPrebuilt() bool {
+	return false
+}
+
 // Contents of system server fragments in an apex are considered to be directly in the apex, as if
 // they were listed in java_libs.
 func (systemServerClasspathFragmentContentDependencyTag) CopyDirectlyInAnyApex() {}
 
+var _ android.ReplaceSourceWithPrebuilt = systemServerClasspathFragmentContentDepTag
 var _ android.CopyDirectlyInAnyApexTag = systemServerClasspathFragmentContentDepTag
 
 // The tag used for the dependency between the systemserverclasspath_fragment module and its contents.
diff --git a/java/testing.go b/java/testing.go
index 138ef8d..a642753 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -451,3 +451,45 @@
 	output := sourceGlobalCompatConfig.Output(allOutputs[0])
 	android.AssertPathsRelativeToTopEquals(t, message+": inputs", expectedPaths, output.Implicits)
 }
+
+// Register the fake APEX mutator to `android.InitRegistrationContext` as if the real mutator exists
+// at runtime. This must be called in `init()` of a test if the test is going to use the fake APEX
+// mutator. Otherwise, we will be missing the runtime mutator because "soong-apex" is not a
+// dependency, which will cause an inconsistency between testing and runtime mutators.
+func RegisterFakeRuntimeApexMutator() {
+	registerFakeApexMutator(android.InitRegistrationContext)
+}
+
+var PrepareForTestWithFakeApexMutator = android.GroupFixturePreparers(
+	android.FixtureRegisterWithContext(registerFakeApexMutator),
+)
+
+func registerFakeApexMutator(ctx android.RegistrationContext) {
+	ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
+		ctx.BottomUp("apex", fakeApexMutator).Parallel()
+	})
+}
+
+type apexModuleBase interface {
+	ApexAvailable() []string
+}
+
+var _ apexModuleBase = (*Library)(nil)
+var _ apexModuleBase = (*SdkLibrary)(nil)
+
+// A fake APEX mutator that creates a platform variant and an APEX variant for modules with
+// `apex_available`. It helps us avoid a dependency on the real mutator defined in "soong-apex",
+// which will cause a cyclic dependency, and it provides an easy way to create an APEX variant for
+// testing without dealing with all the complexities in the real mutator.
+func fakeApexMutator(mctx android.BottomUpMutatorContext) {
+	switch mctx.Module().(type) {
+	case *Library, *SdkLibrary:
+		if len(mctx.Module().(apexModuleBase).ApexAvailable()) > 0 {
+			modules := mctx.CreateVariations("", "apex1000")
+			apexInfo := android.ApexInfo{
+				ApexVariationName: "apex1000",
+			}
+			mctx.SetVariationProvider(modules[1], android.ApexInfoProvider, apexInfo)
+		}
+	}
+}
diff --git a/rust/config/allowed_list.go b/rust/config/allowed_list.go
index 63a8f04..47ca3a7 100644
--- a/rust/config/allowed_list.go
+++ b/rust/config/allowed_list.go
@@ -28,6 +28,7 @@
 		"system/librustutils",
 		"system/logging/liblog",
 		"system/logging/rust",
+		"system/nfc",
 		"system/security",
 		"system/tools/aidl",
 		"tools/security/fuzzing/example_rust_fuzzer",
diff --git a/sdk/update.go b/sdk/update.go
index c1a94b6..89a5c92 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -765,6 +765,8 @@
 	name string
 }
 
+var _ android.BpPropertyTag = propertyTag{}
+
 // A BpPropertyTag to add to a property that contains references to other sdk members.
 //
 // This will cause the references to be rewritten to a versioned reference in the version