Merge "Respect `provides_uses_lib` for modules added via `[optional_]uses_libs`"
diff --git a/android/Android.bp b/android/Android.bp
index a8fa53a..f17a8a0 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -12,7 +12,6 @@
         "soong",
         "soong-android-soongconfig",
         "soong-bazel",
-        "soong-env",
         "soong-shared",
         "soong-ui-metrics_proto",
     ],
@@ -34,6 +33,7 @@
         "deptag.go",
         "expand.go",
         "filegroup.go",
+        "fixture.go",
         "hooks.go",
         "image.go",
         "license.go",
@@ -87,6 +87,7 @@
         "depset_test.go",
         "deptag_test.go",
         "expand_test.go",
+        "fixture_test.go",
         "license_kind_test.go",
         "license_test.go",
         "licenses_test.go",
diff --git a/android/android_test.go b/android/android_test.go
index 46b7054..68cb705 100644
--- a/android/android_test.go
+++ b/android/android_test.go
@@ -44,3 +44,5 @@
 
 	os.Exit(run())
 }
+
+var emptyTestFixtureFactory = NewFixtureFactory(&buildDir)
diff --git a/android/arch.go b/android/arch.go
index f719ddc..20b4ab0 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -1615,3 +1615,97 @@
 
 	return buildTargets, nil
 }
+
+// GetArchProperties returns a map of architectures to the values of the
+// properties of the 'dst' struct that are specific to that architecture.
+//
+// For example, passing a struct { Foo bool, Bar string } will return an
+// interface{} that can be type asserted back into the same struct, containing
+// the arch specific property value specified by the module if defined.
+func (m *ModuleBase) GetArchProperties(dst interface{}) map[ArchType]interface{} {
+	// Return value of the arch types to the prop values for that arch.
+	archToProp := map[ArchType]interface{}{}
+
+	// Nothing to do for non-arch-specific modules.
+	if !m.ArchSpecific() {
+		return archToProp
+	}
+
+	// archProperties has the type of [][]interface{}. Looks complicated, so let's
+	// explain this step by step.
+	//
+	// Loop over the outer index, which determines the property struct that
+	// contains a matching set of properties in dst that we're interested in.
+	// For example, BaseCompilerProperties or BaseLinkerProperties.
+	for i := range m.archProperties {
+		if m.archProperties[i] == nil {
+			// Skip over nil arch props
+			continue
+		}
+
+		// Non-nil arch prop, let's see if the props match up.
+		for _, arch := range ArchTypeList() {
+			// e.g X86, Arm
+			field := arch.Field
+
+			// If it's not nil, loop over the inner index, which determines the arch variant
+			// of the prop type. In an Android.bp file, this is like looping over:
+			//
+			// arch: { arm: { key: value, ... }, x86: { key: value, ... } }
+			for _, archProperties := range m.archProperties[i] {
+				archPropValues := reflect.ValueOf(archProperties).Elem()
+
+				// This is the archPropRoot struct. Traverse into the Arch nested struct.
+				src := archPropValues.FieldByName("Arch").Elem()
+
+				// Step into non-nil pointers to structs in the src value.
+				if src.Kind() == reflect.Ptr {
+					if src.IsNil() {
+						// Ignore nil pointers.
+						continue
+					}
+					src = src.Elem()
+				}
+
+				// Find the requested field (e.g. x86, x86_64) in the src struct.
+				src = src.FieldByName(field)
+				if !src.IsValid() {
+					continue
+				}
+
+				// We only care about structs. These are not the droids you are looking for.
+				if src.Kind() != reflect.Struct {
+					continue
+				}
+
+				// If the value of the field is a struct  then step into the
+				// BlueprintEmbed field. The special "BlueprintEmbed" name is
+				// used by createArchPropTypeDesc to embed the arch properties
+				// in the parent struct, so the src arch prop should be in this
+				// field.
+				//
+				// See createArchPropTypeDesc for more details on how Arch-specific
+				// module properties are processed from the nested props and written
+				// into the module's archProperties.
+				src = src.FieldByName("BlueprintEmbed")
+
+				// Clone the destination prop, since we want a unique prop struct per arch.
+				dstClone := reflect.New(reflect.ValueOf(dst).Elem().Type()).Interface()
+
+				// Copy the located property struct into the cloned destination property struct.
+				err := proptools.ExtendMatchingProperties([]interface{}{dstClone}, src.Interface(), nil, proptools.OrderReplace)
+				if err != nil {
+					// This is fine, it just means the src struct doesn't match.
+					continue
+				}
+
+				// Found the prop for the arch, you have.
+				archToProp[arch] = dstClone
+
+				// Go to the next prop.
+				break
+			}
+		}
+	}
+	return archToProp
+}
diff --git a/android/config.go b/android/config.go
index f0bba81..ef5eadf 100644
--- a/android/config.go
+++ b/android/config.go
@@ -305,10 +305,7 @@
 	return testConfig
 }
 
-// TestArchConfig returns a Config object suitable for using for tests that
-// need to run the arch mutator.
-func TestArchConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
-	testConfig := TestConfig(buildDir, env, bp, fs)
+func modifyTestConfigToSupportArchMutator(testConfig Config) {
 	config := testConfig.config
 
 	config.Targets = map[OsType][]Target{
@@ -334,7 +331,13 @@
 	config.TestProductVariables.DeviceArchVariant = proptools.StringPtr("armv8-a")
 	config.TestProductVariables.DeviceSecondaryArch = proptools.StringPtr("arm")
 	config.TestProductVariables.DeviceSecondaryArchVariant = proptools.StringPtr("armv7-a-neon")
+}
 
+// TestArchConfig returns a Config object suitable for using for tests that
+// need to run the arch mutator.
+func TestArchConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
+	testConfig := TestConfig(buildDir, env, bp, fs)
+	modifyTestConfigToSupportArchMutator(testConfig)
 	return testConfig
 }
 
diff --git a/android/env.go b/android/env.go
index c2a09aa..a8c7777 100644
--- a/android/env.go
+++ b/android/env.go
@@ -21,7 +21,7 @@
 	"strings"
 	"syscall"
 
-	"android/soong/env"
+	"android/soong/shared"
 )
 
 // This file supports dependencies on environment variables.  During build manifest generation,
@@ -113,7 +113,7 @@
 		return
 	}
 
-	data, err := env.EnvFileContents(envDeps)
+	data, err := shared.EnvFileContents(envDeps)
 	if err != nil {
 		ctx.Errorf(err.Error())
 	}
diff --git a/android/filegroup.go b/android/filegroup.go
index 593e470..2eb4741 100644
--- a/android/filegroup.go
+++ b/android/filegroup.go
@@ -24,6 +24,10 @@
 	RegisterBp2BuildMutator("filegroup", FilegroupBp2Build)
 }
 
+var PrepareForTestWithFilegroup = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+	ctx.RegisterModuleType("filegroup", FileGroupFactory)
+})
+
 // https://docs.bazel.build/versions/master/be/general.html#filegroup
 type bazelFilegroupAttributes struct {
 	Srcs bazel.LabelList
diff --git a/android/fixture.go b/android/fixture.go
new file mode 100644
index 0000000..0efe329
--- /dev/null
+++ b/android/fixture.go
@@ -0,0 +1,604 @@
+// 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 android
+
+import (
+	"reflect"
+	"strings"
+	"testing"
+)
+
+// Provides support for creating test fixtures on which tests can be run. Reduces duplication
+// of test setup by allow tests to easily reuse setup code.
+//
+// Fixture
+// =======
+// These determine the environment within which a test can be run. Fixtures are mutable and are
+// created by FixtureFactory instances and mutated by FixturePreparer instances. They are created by
+// first creating a base Fixture (which is essentially empty) and then applying FixturePreparer
+// instances to it to modify the environment.
+//
+// FixtureFactory
+// ==============
+// These are responsible for creating fixtures. Factories are immutable and are intended to be
+// initialized once and reused to create multiple fixtures. Each factory has a list of fixture
+// preparers that prepare a fixture for running a test. Factories can also be used to create other
+// factories by extending them with additional fixture preparers.
+//
+// FixturePreparer
+// ===============
+// These are responsible for modifying a Fixture in preparation for it to run a test. Preparers are
+// intended to be immutable and able to prepare multiple Fixture objects simultaneously without
+// them sharing any data.
+//
+// FixturePreparers are only ever invoked once per test fixture. Prior to invocation the list of
+// FixturePreparers are flattened and deduped while preserving the order they first appear in the
+// list. This makes it easy to reuse, group and combine FixturePreparers together.
+//
+// Each small self contained piece of test setup should be their own FixturePreparer. e.g.
+// * A group of related modules.
+// * A group of related mutators.
+// * A combination of both.
+// * Configuration.
+//
+// They should not overlap, e.g. the same module type should not be registered by different
+// FixturePreparers as using them both would cause a build error. In that case the preparer should
+// be split into separate parts and combined together using FixturePreparers(...).
+//
+// e.g. attempting to use AllPreparers in preparing a Fixture would break as it would attempt to
+// register module bar twice:
+//   var Preparer1 = FixtureRegisterWithContext(RegisterModuleFooAndBar)
+//   var Preparer2 = FixtureRegisterWithContext(RegisterModuleBarAndBaz)
+//   var AllPreparers = FixturePreparers(Preparer1, Preparer2)
+//
+// However, when restructured like this it would work fine:
+//   var PreparerFoo = FixtureRegisterWithContext(RegisterModuleFoo)
+//   var PreparerBar = FixtureRegisterWithContext(RegisterModuleBar)
+//   var PreparerBaz = FixtureRegisterWithContext(RegisterModuleBaz)
+//   var Preparer1 = FixturePreparers(RegisterModuleFoo, RegisterModuleBar)
+//   var Preparer2 = FixturePreparers(RegisterModuleBar, RegisterModuleBaz)
+//   var AllPreparers = FixturePreparers(Preparer1, Preparer2)
+//
+// As after deduping and flattening AllPreparers would result in the following preparers being
+// applied:
+// 1. PreparerFoo
+// 2. PreparerBar
+// 3. PreparerBaz
+//
+// Preparers can be used for both integration and unit tests.
+//
+// Integration tests typically use all the module types, mutators and singletons that are available
+// for that package to try and replicate the behavior of the runtime build as closely as possible.
+// However, that realism comes at a cost of increased fragility (as they can be broken by changes in
+// many different parts of the build) and also increased runtime, especially if they use lots of
+// singletons and mutators.
+//
+// Unit tests on the other hand try and minimize the amount of code being tested which makes them
+// less susceptible to changes elsewhere in the build and quick to run but at a cost of potentially
+// not testing realistic scenarios.
+//
+// Supporting unit tests effectively require that preparers are available at the lowest granularity
+// possible. Supporting integration tests effectively require that the preparers are organized into
+// groups that provide all the functionality available.
+//
+// At least in terms of tests that check the behavior of build components via processing
+// `Android.bp` there is no clear separation between a unit test and an integration test. Instead
+// they vary from one end that tests a single module (e.g. filegroup) to the other end that tests a
+// whole system of modules, mutators and singletons (e.g. apex + hiddenapi).
+//
+// TestResult
+// ==========
+// These are created by running tests in a Fixture and provide access to the Config and TestContext
+// in which the tests were run.
+//
+// Example
+// =======
+//
+// An exported preparer for use by other packages that need to use java modules.
+//
+// package java
+// var PrepareForIntegrationTestWithJava = FixturePreparers(
+//    android.PrepareForIntegrationTestWithAndroid,
+//    FixtureRegisterWithContext(RegisterAGroupOfRelatedModulesMutatorsAndSingletons),
+//    FixtureRegisterWithContext(RegisterAnotherGroupOfRelatedModulesMutatorsAndSingletons),
+//    ...
+// )
+//
+// Some files to use in tests in the java package.
+//
+// var javaMockFS = android.MockFS{
+//		"api/current.txt":        nil,
+//		"api/removed.txt":        nil,
+//    ...
+// }
+//
+// A package private factory for use for testing java within the java package.
+//
+// var javaFixtureFactory = NewFixtureFactory(
+//    PrepareForIntegrationTestWithJava,
+//    FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+//      ctx.RegisterModuleType("test_module", testModule)
+//    }),
+//    javaMockFS.AddToFixture(),
+//    ...
+// }
+//
+// func TestJavaStuff(t *testing.T) {
+//   result := javaFixtureFactory.RunTest(t,
+//       android.FixtureWithRootAndroidBp(`java_library {....}`),
+//       android.MockFS{...}.AddToFixture(),
+//   )
+//   ... test result ...
+// }
+//
+// package cc
+// var PrepareForTestWithCC = FixturePreparers(
+//    android.PrepareForArchMutator,
+//	  android.prepareForPrebuilts,
+//    FixtureRegisterWithContext(RegisterRequiredBuildComponentsForTest),
+//    ...
+// )
+//
+// package apex
+//
+// var PrepareForApex = FixturePreparers(
+//    ...
+// )
+//
+// Use modules and mutators from java, cc and apex. Any duplicate preparers (like
+// android.PrepareForArchMutator) will be automatically deduped.
+//
+// var apexFixtureFactory = android.NewFixtureFactory(
+//    PrepareForJava,
+//    PrepareForCC,
+//    PrepareForApex,
+// )
+
+// Factory for Fixture objects.
+//
+// This is configured with a set of FixturePreparer objects that are used to
+// initialize each Fixture instance this creates.
+type FixtureFactory interface {
+
+	// Creates a copy of this instance and adds some additional preparers.
+	//
+	// Before the preparers are used they are combined with the preparers provided when the factory
+	// was created, any groups of preparers are flattened, and the list is deduped so that each
+	// preparer is only used once. See the file documentation in android/fixture.go for more details.
+	Extend(preparers ...FixturePreparer) FixtureFactory
+
+	// Create a Fixture.
+	Fixture(t *testing.T, preparers ...FixturePreparer) Fixture
+
+	// Run the test, expecting no errors, returning a TestResult instance.
+	//
+	// Shorthand for Fixture(t, preparers...).RunTest()
+	RunTest(t *testing.T, preparers ...FixturePreparer) *TestResult
+
+	// Run the test with the supplied Android.bp file.
+	//
+	// Shorthand for RunTest(t, android.FixtureWithRootAndroidBp(bp))
+	RunTestWithBp(t *testing.T, bp string) *TestResult
+}
+
+// Create a new FixtureFactory that will apply the supplied preparers.
+//
+// The buildDirSupplier is a pointer to the package level buildDir variable that is initialized by
+// the package level setUp method. It has to be a pointer to the variable as the variable will not
+// have been initialized at the time the factory is created.
+func NewFixtureFactory(buildDirSupplier *string, preparers ...FixturePreparer) FixtureFactory {
+	return &fixtureFactory{
+		buildDirSupplier: buildDirSupplier,
+		preparers:        dedupAndFlattenPreparers(nil, preparers),
+	}
+}
+
+// A set of mock files to add to the mock file system.
+type MockFS map[string][]byte
+
+func (fs MockFS) Merge(extra map[string][]byte) {
+	for p, c := range extra {
+		fs[p] = c
+	}
+}
+
+func (fs MockFS) AddToFixture() FixturePreparer {
+	return FixtureMergeMockFs(fs)
+}
+
+// Modify the config
+func FixtureModifyConfig(mutator func(config Config)) FixturePreparer {
+	return newSimpleFixturePreparer(func(f *fixture) {
+		mutator(f.config)
+	})
+}
+
+// Modify the config and context
+func FixtureModifyConfigAndContext(mutator func(config Config, ctx *TestContext)) FixturePreparer {
+	return newSimpleFixturePreparer(func(f *fixture) {
+		mutator(f.config, f.ctx)
+	})
+}
+
+// Modify the context
+func FixtureModifyContext(mutator func(ctx *TestContext)) FixturePreparer {
+	return newSimpleFixturePreparer(func(f *fixture) {
+		mutator(f.ctx)
+	})
+}
+
+func FixtureRegisterWithContext(registeringFunc func(ctx RegistrationContext)) FixturePreparer {
+	return FixtureModifyContext(func(ctx *TestContext) { registeringFunc(ctx) })
+}
+
+// Modify the mock filesystem
+func FixtureModifyMockFS(mutator func(fs MockFS)) FixturePreparer {
+	return newSimpleFixturePreparer(func(f *fixture) {
+		mutator(f.mockFS)
+	})
+}
+
+// Merge the supplied file system into the mock filesystem.
+//
+// Paths that already exist in the mock file system are overridden.
+func FixtureMergeMockFs(mockFS MockFS) FixturePreparer {
+	return FixtureModifyMockFS(func(fs MockFS) {
+		fs.Merge(mockFS)
+	})
+}
+
+// Add a file to the mock filesystem
+func FixtureAddFile(path string, contents []byte) FixturePreparer {
+	return FixtureModifyMockFS(func(fs MockFS) {
+		fs[path] = contents
+	})
+}
+
+// Add a text file to the mock filesystem
+func FixtureAddTextFile(path string, contents string) FixturePreparer {
+	return FixtureAddFile(path, []byte(contents))
+}
+
+// Add the root Android.bp file with the supplied contents.
+func FixtureWithRootAndroidBp(contents string) FixturePreparer {
+	return FixtureAddTextFile("Android.bp", contents)
+}
+
+// Create a composite FixturePreparer that is equivalent to applying each of the supplied
+// FixturePreparer instances in order.
+func FixturePreparers(preparers ...FixturePreparer) FixturePreparer {
+	return &compositeFixturePreparer{dedupAndFlattenPreparers(nil, preparers)}
+}
+
+type simpleFixturePreparerVisitor func(preparer *simpleFixturePreparer)
+
+// FixturePreparer is an opaque interface that can change a fixture.
+type FixturePreparer interface {
+	// visit calls the supplied visitor with each *simpleFixturePreparer instances in this preparer,
+	visit(simpleFixturePreparerVisitor)
+}
+
+type fixturePreparers []FixturePreparer
+
+func (f fixturePreparers) visit(visitor simpleFixturePreparerVisitor) {
+	for _, p := range f {
+		p.visit(visitor)
+	}
+}
+
+// dedupAndFlattenPreparers removes any duplicates and flattens any composite FixturePreparer
+// instances.
+//
+// base      - a list of already flattened and deduped preparers that will be applied first before
+//             the list of additional preparers. Any duplicates of these in the additional preparers
+//             will be ignored.
+//
+// preparers - a list of additional unflattened, undeduped preparers that will be applied after the
+//             base preparers.
+//
+// Returns a deduped and flattened list of the preparers minus any that exist in the base preparers.
+func dedupAndFlattenPreparers(base []*simpleFixturePreparer, preparers fixturePreparers) []*simpleFixturePreparer {
+	var list []*simpleFixturePreparer
+	visited := make(map[*simpleFixturePreparer]struct{})
+
+	// Mark the already flattened and deduped preparers, if any, as having been seen so that
+	// duplicates of these in the additional preparers will be discarded.
+	for _, s := range base {
+		visited[s] = struct{}{}
+	}
+
+	preparers.visit(func(preparer *simpleFixturePreparer) {
+		if _, seen := visited[preparer]; !seen {
+			visited[preparer] = struct{}{}
+			list = append(list, preparer)
+		}
+	})
+	return list
+}
+
+// compositeFixturePreparer is a FixturePreparer created from a list of fixture preparers.
+type compositeFixturePreparer struct {
+	preparers []*simpleFixturePreparer
+}
+
+func (c *compositeFixturePreparer) visit(visitor simpleFixturePreparerVisitor) {
+	for _, p := range c.preparers {
+		p.visit(visitor)
+	}
+}
+
+// simpleFixturePreparer is a FixturePreparer that applies a function to a fixture.
+type simpleFixturePreparer struct {
+	function func(fixture *fixture)
+}
+
+func (s *simpleFixturePreparer) visit(visitor simpleFixturePreparerVisitor) {
+	visitor(s)
+}
+
+func newSimpleFixturePreparer(preparer func(fixture *fixture)) FixturePreparer {
+	return &simpleFixturePreparer{function: preparer}
+}
+
+// Fixture defines the test environment.
+type Fixture interface {
+	// Run the test, expecting no errors, returning a TestResult instance.
+	RunTest() *TestResult
+}
+
+// Provides general test support.
+type TestHelper struct {
+	*testing.T
+}
+
+// AssertBoolEquals checks if the expected and actual values are equal and if they are not then it
+// reports an error prefixed with the supplied message and including a reason for why it failed.
+func (h *TestHelper) AssertBoolEquals(message string, expected bool, actual bool) {
+	h.Helper()
+	if actual != expected {
+		h.Errorf("%s: expected %t, actual %t", message, expected, actual)
+	}
+}
+
+// AssertStringEquals checks if the expected and actual values are equal and if they are not then
+// it reports an error prefixed with the supplied message and including a reason for why it failed.
+func (h *TestHelper) AssertStringEquals(message string, expected string, actual string) {
+	h.Helper()
+	if actual != expected {
+		h.Errorf("%s: expected %s, actual %s", message, expected, actual)
+	}
+}
+
+// AssertTrimmedStringEquals checks if the expected and actual values are the same after trimming
+// leading and trailing spaces from them both. If they are not then it reports an error prefixed
+// with the supplied message and including a reason for why it failed.
+func (h *TestHelper) AssertTrimmedStringEquals(message string, expected string, actual string) {
+	h.Helper()
+	h.AssertStringEquals(message, strings.TrimSpace(expected), strings.TrimSpace(actual))
+}
+
+// AssertStringDoesContain checks if the string contains the expected substring. If it does not
+// then it reports an error prefixed with the supplied message and including a reason for why it
+// failed.
+func (h *TestHelper) AssertStringDoesContain(message string, s string, expectedSubstring string) {
+	h.Helper()
+	if !strings.Contains(s, expectedSubstring) {
+		h.Errorf("%s: could not find %q within %q", message, expectedSubstring, s)
+	}
+}
+
+// AssertStringDoesNotContain checks if the string contains the expected substring. If it does then
+// it reports an error prefixed with the supplied message and including a reason for why it failed.
+func (h *TestHelper) AssertStringDoesNotContain(message string, s string, unexpectedSubstring string) {
+	h.Helper()
+	if strings.Contains(s, unexpectedSubstring) {
+		h.Errorf("%s: unexpectedly found %q within %q", message, unexpectedSubstring, s)
+	}
+}
+
+// AssertArrayString checks if the expected and actual values are equal and if they are not then it
+// reports an error prefixed with the supplied message and including a reason for why it failed.
+func (h *TestHelper) AssertArrayString(message string, expected, actual []string) {
+	h.Helper()
+	if len(actual) != len(expected) {
+		h.Errorf("%s: expected %d (%q), actual (%d) %q", message, len(expected), expected, len(actual), actual)
+		return
+	}
+	for i := range actual {
+		if actual[i] != expected[i] {
+			h.Errorf("%s: expected %d-th, %q (%q), actual %q (%q)",
+				message, i, expected[i], expected, actual[i], actual)
+			return
+		}
+	}
+}
+
+// AssertArrayString checks if the expected and actual values are equal using reflect.DeepEqual and
+// if they are not then it reports an error prefixed with the supplied message and including a
+// reason for why it failed.
+func (h *TestHelper) AssertDeepEquals(message string, expected interface{}, actual interface{}) {
+	h.Helper()
+	if !reflect.DeepEqual(actual, expected) {
+		h.Errorf("%s: expected:\n  %#v\n got:\n  %#v", message, expected, actual)
+	}
+}
+
+// Struct to allow TestResult to embed a *TestContext and allow call forwarding to its methods.
+type testContext struct {
+	*TestContext
+}
+
+// The result of running a test.
+type TestResult struct {
+	TestHelper
+	testContext
+
+	fixture *fixture
+	Config  Config
+}
+
+var _ FixtureFactory = (*fixtureFactory)(nil)
+
+type fixtureFactory struct {
+	buildDirSupplier *string
+	preparers        []*simpleFixturePreparer
+}
+
+func (f *fixtureFactory) Extend(preparers ...FixturePreparer) FixtureFactory {
+	all := append(f.preparers, dedupAndFlattenPreparers(f.preparers, preparers)...)
+	return &fixtureFactory{
+		buildDirSupplier: f.buildDirSupplier,
+		preparers:        all,
+	}
+}
+
+func (f *fixtureFactory) Fixture(t *testing.T, preparers ...FixturePreparer) Fixture {
+	config := TestConfig(*f.buildDirSupplier, nil, "", nil)
+	ctx := NewTestContext(config)
+	fixture := &fixture{
+		factory: f,
+		t:       t,
+		config:  config,
+		ctx:     ctx,
+		mockFS:  make(MockFS),
+	}
+
+	for _, preparer := range f.preparers {
+		preparer.function(fixture)
+	}
+
+	for _, preparer := range dedupAndFlattenPreparers(f.preparers, preparers) {
+		preparer.function(fixture)
+	}
+
+	return fixture
+}
+
+func (f *fixtureFactory) RunTest(t *testing.T, preparers ...FixturePreparer) *TestResult {
+	t.Helper()
+	fixture := f.Fixture(t, preparers...)
+	return fixture.RunTest()
+}
+
+func (f *fixtureFactory) RunTestWithBp(t *testing.T, bp string) *TestResult {
+	t.Helper()
+	return f.RunTest(t, FixtureWithRootAndroidBp(bp))
+}
+
+type fixture struct {
+	factory *fixtureFactory
+	t       *testing.T
+	config  Config
+	ctx     *TestContext
+	mockFS  MockFS
+}
+
+func (f *fixture) RunTest() *TestResult {
+	f.t.Helper()
+
+	ctx := f.ctx
+
+	// The TestConfig() method assumes that the mock filesystem is available when creating so creates
+	// the mock file system immediately. Similarly, the NewTestContext(Config) method assumes that the
+	// supplied Config's FileSystem has been properly initialized before it is called and so it takes
+	// its own reference to the filesystem. However, fixtures create the Config and TestContext early
+	// so they can be modified by preparers at which time the mockFS has not been populated (because
+	// it too is modified by preparers). So, this reinitializes the Config and TestContext's
+	// FileSystem using the now populated mockFS.
+	f.config.mockFileSystem("", f.mockFS)
+	ctx.SetFs(ctx.config.fs)
+	if ctx.config.mockBpList != "" {
+		ctx.SetModuleListFile(ctx.config.mockBpList)
+	}
+
+	ctx.Register()
+	_, errs := ctx.ParseBlueprintsFiles("ignored")
+	FailIfErrored(f.t, errs)
+	_, errs = ctx.PrepareBuildActions(f.config)
+	FailIfErrored(f.t, errs)
+
+	result := &TestResult{
+		TestHelper:  TestHelper{T: f.t},
+		testContext: testContext{ctx},
+		fixture:     f,
+		Config:      f.config,
+	}
+	return result
+}
+
+// NormalizePathForTesting removes the test invocation specific build directory from the supplied
+// path.
+//
+// If the path is within the build directory (e.g. an OutputPath) then this returns the relative
+// path to avoid tests having to deal with the dynamically generated build directory.
+//
+// Otherwise, this returns the supplied path as it is almost certainly a source path that is
+// relative to the root of the source tree.
+//
+// Even though some information is removed from some paths and not others it should be possible to
+// differentiate between them by the paths themselves, e.g. output paths will likely include
+// ".intermediates" but source paths won't.
+func (r *TestResult) NormalizePathForTesting(path Path) string {
+	pathContext := PathContextForTesting(r.Config)
+	pathAsString := path.String()
+	if rel, isRel := MaybeRel(pathContext, r.Config.BuildDir(), pathAsString); isRel {
+		return rel
+	}
+	return pathAsString
+}
+
+// NormalizePathsForTesting normalizes each path in the supplied list and returns their normalized
+// forms.
+func (r *TestResult) NormalizePathsForTesting(paths Paths) []string {
+	var result []string
+	for _, path := range paths {
+		result = append(result, r.NormalizePathForTesting(path))
+	}
+	return result
+}
+
+// NewFixture creates a new test fixture that is based on the one that created this result. It is
+// intended to test the output of module types that generate content to be processed by the build,
+// e.g. sdk snapshots.
+func (r *TestResult) NewFixture(preparers ...FixturePreparer) Fixture {
+	return r.fixture.factory.Fixture(r.T, preparers...)
+}
+
+// RunTest is shorthand for NewFixture(preparers...).RunTest().
+func (r *TestResult) RunTest(preparers ...FixturePreparer) *TestResult {
+	r.Helper()
+	return r.fixture.factory.Fixture(r.T, preparers...).RunTest()
+}
+
+// Module returns the module with the specific name and of the specified variant.
+func (r *TestResult) Module(name string, variant string) Module {
+	return r.ModuleForTests(name, variant).Module()
+}
+
+// Create a *TestResult object suitable for use within a subtest.
+//
+// This ensures that any errors reported by the TestResult, e.g. from within one of its
+// Assert... methods, will be associated with the sub test and not the main test.
+//
+// result := ....RunTest()
+// t.Run("subtest", func(t *testing.T) {
+//    subResult := result.ResultForSubTest(t)
+//    subResult.AssertStringEquals("something", ....)
+// })
+func (r *TestResult) ResultForSubTest(t *testing.T) *TestResult {
+	subTestResult := *r
+	r.T = t
+	return &subTestResult
+}
diff --git a/android/fixture_test.go b/android/fixture_test.go
new file mode 100644
index 0000000..7bc033b
--- /dev/null
+++ b/android/fixture_test.go
@@ -0,0 +1,49 @@
+// 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 android
+
+import "testing"
+
+// Make sure that FixturePreparer instances are only called once per fixture and in the order in
+// which they were added.
+func TestFixtureDedup(t *testing.T) {
+	list := []string{}
+
+	appendToList := func(s string) FixturePreparer {
+		return FixtureModifyConfig(func(_ Config) {
+			list = append(list, s)
+		})
+	}
+
+	preparer1 := appendToList("preparer1")
+	preparer2 := appendToList("preparer2")
+	preparer3 := appendToList("preparer3")
+	preparer4 := appendToList("preparer4")
+
+	preparer1Then2 := FixturePreparers(preparer1, preparer2)
+
+	preparer2Then1 := FixturePreparers(preparer2, preparer1)
+
+	buildDir := "build"
+	factory := NewFixtureFactory(&buildDir, preparer1, preparer2, preparer1, preparer1Then2)
+
+	extension := factory.Extend(preparer4, preparer2)
+
+	extension.Fixture(t, preparer1, preparer2, preparer2Then1, preparer3)
+
+	h := TestHelper{t}
+	h.AssertDeepEquals("preparers called in wrong order",
+		[]string{"preparer1", "preparer2", "preparer4", "preparer3"}, list)
+}
diff --git a/android/module.go b/android/module.go
index 5342246..f0e17ba 100644
--- a/android/module.go
+++ b/android/module.go
@@ -443,6 +443,7 @@
 	Disable()
 	Enabled() bool
 	Target() Target
+	MultiTargets() []Target
 	Owner() string
 	InstallInData() bool
 	InstallInTestcases() bool
@@ -1123,8 +1124,15 @@
 	variableProperties      interface{}
 	hostAndDeviceProperties hostAndDeviceProperties
 	generalProperties       []interface{}
-	archProperties          [][]interface{}
-	customizableProperties  []interface{}
+
+	// Arch specific versions of structs in generalProperties. The outer index
+	// has the same order as generalProperties as initialized in
+	// InitAndroidArchModule, and the inner index chooses the props specific to
+	// the architecture. The interface{} value is an archPropRoot that is
+	// filled with arch specific values by the arch mutator.
+	archProperties [][]interface{}
+
+	customizableProperties []interface{}
 
 	// Properties specific to the Blueprint to BUILD migration.
 	bazelTargetModuleProperties bazel.BazelTargetModuleProperties
diff --git a/android/prebuilt.go b/android/prebuilt.go
index 39d30c5..04864a1 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -100,7 +100,7 @@
 // more modules like this.
 func (p *Prebuilt) SingleSourcePath(ctx ModuleContext) Path {
 	if p.srcsSupplier != nil {
-		srcs := p.srcsSupplier(ctx)
+		srcs := p.srcsSupplier(ctx, ctx.Module())
 
 		if len(srcs) == 0 {
 			ctx.PropertyErrorf(p.srcsPropertyName, "missing prebuilt source file")
@@ -128,8 +128,11 @@
 
 // Called to provide the srcs value for the prebuilt module.
 //
+// This can be called with a context for any module not just the prebuilt one itself. It can also be
+// called concurrently.
+//
 // Return the src value or nil if it is not available.
-type PrebuiltSrcsSupplier func(ctx BaseModuleContext) []string
+type PrebuiltSrcsSupplier func(ctx BaseModuleContext, prebuilt Module) []string
 
 // Initialize the module as a prebuilt module that uses the provided supplier to access the
 // prebuilt sources of the module.
@@ -163,7 +166,7 @@
 		panic(fmt.Errorf("srcs must not be nil"))
 	}
 
-	srcsSupplier := func(ctx BaseModuleContext) []string {
+	srcsSupplier := func(ctx BaseModuleContext, _ Module) []string {
 		return *srcs
 	}
 
@@ -184,7 +187,7 @@
 	srcFieldIndex := srcStructField.Index
 	srcPropertyName := proptools.PropertyNameForField(srcField)
 
-	srcsSupplier := func(ctx BaseModuleContext) []string {
+	srcsSupplier := func(ctx BaseModuleContext, _ Module) []string {
 		if !module.Enabled() {
 			return nil
 		}
@@ -256,12 +259,12 @@
 			panic(fmt.Errorf("prebuilt module did not have InitPrebuiltModule called on it"))
 		}
 		if !p.properties.SourceExists {
-			p.properties.UsePrebuilt = p.usePrebuilt(ctx, nil)
+			p.properties.UsePrebuilt = p.usePrebuilt(ctx, nil, m)
 		}
 	} else if s, ok := ctx.Module().(Module); ok {
 		ctx.VisitDirectDepsWithTag(PrebuiltDepTag, func(m Module) {
 			p := m.(PrebuiltInterface).Prebuilt()
-			if p.usePrebuilt(ctx, s) {
+			if p.usePrebuilt(ctx, s, m) {
 				p.properties.UsePrebuilt = true
 				s.ReplacedByPrebuilt()
 			}
@@ -296,8 +299,8 @@
 
 // usePrebuilt returns true if a prebuilt should be used instead of the source module.  The prebuilt
 // will be used if it is marked "prefer" or if the source module is disabled.
-func (p *Prebuilt) usePrebuilt(ctx TopDownMutatorContext, source Module) bool {
-	if p.srcsSupplier != nil && len(p.srcsSupplier(ctx)) == 0 {
+func (p *Prebuilt) usePrebuilt(ctx TopDownMutatorContext, source Module, prebuilt Module) bool {
+	if p.srcsSupplier != nil && len(p.srcsSupplier(ctx, prebuilt)) == 0 {
 		return false
 	}
 
diff --git a/android/prebuilt_test.go b/android/prebuilt_test.go
index 9ac3875..164b1bc 100644
--- a/android/prebuilt_test.go
+++ b/android/prebuilt_test.go
@@ -262,7 +262,7 @@
 }
 
 func TestPrebuilts(t *testing.T) {
-	fs := map[string][]byte{
+	fs := MockFS{
 		"prebuilt_file": nil,
 		"source_file":   nil,
 	}
@@ -277,32 +277,33 @@
 						deps: [":bar"],
 					}`
 			}
-			config := TestArchConfig(buildDir, nil, bp, fs)
 
 			// Add windows to the target list to test the logic when a variant is
 			// disabled by default.
 			if !Windows.DefaultDisabled {
 				t.Errorf("windows is assumed to be disabled by default")
 			}
-			config.config.Targets[Windows] = []Target{
-				{Windows, Arch{ArchType: X86_64}, NativeBridgeDisabled, "", "", true},
-			}
 
-			ctx := NewTestArchContext(config)
-			registerTestPrebuiltBuildComponents(ctx)
-			ctx.RegisterModuleType("filegroup", FileGroupFactory)
-			ctx.Register()
+			result := emptyTestFixtureFactory.Extend(
+				PrepareForTestWithArchMutator,
+				PrepareForTestWithPrebuilts,
+				PrepareForTestWithOverrides,
+				PrepareForTestWithFilegroup,
+				// Add a Windows target to the configuration.
+				FixtureModifyConfig(func(config Config) {
+					config.Targets[Windows] = []Target{
+						{Windows, Arch{ArchType: X86_64}, NativeBridgeDisabled, "", "", true},
+					}
+				}),
+				fs.AddToFixture(),
+				FixtureRegisterWithContext(registerTestPrebuiltModules),
+			).RunTestWithBp(t, bp)
 
-			_, errs := ctx.ParseBlueprintsFiles("Android.bp")
-			FailIfErrored(t, errs)
-			_, errs = ctx.PrepareBuildActions(config)
-			FailIfErrored(t, errs)
-
-			for _, variant := range ctx.ModuleVariantsForTests("foo") {
-				foo := ctx.ModuleForTests("foo", variant)
+			for _, variant := range result.ModuleVariantsForTests("foo") {
+				foo := result.ModuleForTests("foo", variant)
 				t.Run(foo.Module().Target().Os.String(), func(t *testing.T) {
 					var dependsOnSourceModule, dependsOnPrebuiltModule bool
-					ctx.VisitDirectDeps(foo.Module(), func(m blueprint.Module) {
+					result.VisitDirectDeps(foo.Module(), func(m blueprint.Module) {
 						if _, ok := m.(*sourceModule); ok {
 							dependsOnSourceModule = true
 						}
@@ -381,14 +382,18 @@
 }
 
 func registerTestPrebuiltBuildComponents(ctx RegistrationContext) {
-	ctx.RegisterModuleType("prebuilt", newPrebuiltModule)
-	ctx.RegisterModuleType("source", newSourceModule)
-	ctx.RegisterModuleType("override_source", newOverrideSourceModule)
+	registerTestPrebuiltModules(ctx)
 
 	RegisterPrebuiltMutators(ctx)
 	ctx.PostDepsMutators(RegisterOverridePostDepsMutators)
 }
 
+func registerTestPrebuiltModules(ctx RegistrationContext) {
+	ctx.RegisterModuleType("prebuilt", newPrebuiltModule)
+	ctx.RegisterModuleType("source", newSourceModule)
+	ctx.RegisterModuleType("override_source", newOverrideSourceModule)
+}
+
 type prebuiltModule struct {
 	ModuleBase
 	prebuilt   Prebuilt
diff --git a/android/testing.go b/android/testing.go
index 1b1feb7..5832796 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -48,6 +48,43 @@
 	return ctx
 }
 
+var PrepareForTestWithArchMutator = FixturePreparers(
+	// Configure architecture targets in the fixture config.
+	FixtureModifyConfig(modifyTestConfigToSupportArchMutator),
+
+	// Add the arch mutator to the context.
+	FixtureRegisterWithContext(func(ctx RegistrationContext) {
+		ctx.PreDepsMutators(registerArchMutator)
+	}),
+)
+
+var PrepareForTestWithDefaults = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+	ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
+})
+
+var PrepareForTestWithComponentsMutator = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+	ctx.PreArchMutators(RegisterComponentsMutator)
+})
+
+var PrepareForTestWithPrebuilts = FixtureRegisterWithContext(RegisterPrebuiltMutators)
+
+var PrepareForTestWithOverrides = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+	ctx.PostDepsMutators(RegisterOverridePostDepsMutators)
+})
+
+// Prepares an integration test with build components from the android package.
+var PrepareForIntegrationTestWithAndroid = FixturePreparers(
+	// Mutators. Must match order in mutator.go.
+	PrepareForTestWithArchMutator,
+	PrepareForTestWithDefaults,
+	PrepareForTestWithComponentsMutator,
+	PrepareForTestWithPrebuilts,
+	PrepareForTestWithOverrides,
+
+	// Modules
+	PrepareForTestWithFilegroup,
+)
+
 func NewTestArchContext(config Config) *TestContext {
 	ctx := NewTestContext(config)
 	ctx.preDeps = append(ctx.preDeps, registerArchMutator)
diff --git a/androidmk/androidmk/androidmk.go b/androidmk/androidmk/androidmk.go
index 2d1bbb4..b8316a3 100644
--- a/androidmk/androidmk/androidmk.go
+++ b/androidmk/androidmk/androidmk.go
@@ -48,6 +48,22 @@
 	"-": "_dash_",
 }
 
+// Fix steps that should only run in the androidmk tool, i.e. should only be applied to
+// newly-converted Android.bp files.
+var fixSteps = bpfix.FixStepsExtension{
+	Name: "androidmk",
+	Steps: []bpfix.FixStep{
+		{
+			Name: "RewriteRuntimeResourceOverlay",
+			Fix:  bpfix.RewriteRuntimeResourceOverlay,
+		},
+	},
+}
+
+func init() {
+	bpfix.RegisterFixStepExtension(&fixSteps)
+}
+
 func (f *bpFile) insertComment(s string) {
 	f.comments = append(f.comments, &bpparser.CommentGroup{
 		Comments: []*bpparser.Comment{
diff --git a/apex/apex.go b/apex/apex.go
index 662bbbd..a4bdc63 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -54,8 +54,6 @@
 func RegisterPreDepsMutators(ctx android.RegisterMutatorsContext) {
 	ctx.TopDown("apex_vndk", apexVndkMutator).Parallel()
 	ctx.BottomUp("apex_vndk_deps", apexVndkDepsMutator).Parallel()
-	ctx.BottomUp("prebuilt_apex_select_source", prebuiltSelectSourceMutator).Parallel()
-	ctx.BottomUp("deapexer_select_source", deapexerSelectSourceMutator).Parallel()
 }
 
 func RegisterPostDepsMutators(ctx android.RegisterMutatorsContext) {
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 3f56047..a2992df 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -19,6 +19,7 @@
 	"io/ioutil"
 	"os"
 	"path"
+	"path/filepath"
 	"reflect"
 	"regexp"
 	"sort"
@@ -191,6 +192,7 @@
 		"AppSet.apks":                                nil,
 		"foo.rs":                                     nil,
 		"libfoo.jar":                                 nil,
+		"libbar.jar":                                 nil,
 	}
 
 	cc.GatherRequiredFilesForTest(fs)
@@ -970,8 +972,8 @@
 
 	mylibLdFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_apex29").Rule("ld").Args["libFlags"]
 
-	// Ensure that mylib is linking with the version 29 stubs for mylib2
-	ensureContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared_29/mylib2.so")
+	// Ensure that mylib is linking with the latest version of stub for mylib2
+	ensureContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared_current/mylib2.so")
 	// ... and not linking to the non-stub (impl) variant of mylib2
 	ensureNotContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared/mylib2.so")
 
@@ -1055,11 +1057,11 @@
 		config.TestProductVariables.Platform_version_active_codenames = []string{"Z"}
 	})
 
-	// Ensure that mylib from myapex is built against "min_sdk_version" stub ("Z"), which is non-final
+	// Ensure that mylib from myapex is built against the latest stub (current)
 	mylibCflags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static_apex10000").Rule("cc").Args["cFlags"]
-	ensureContains(t, mylibCflags, "-D__LIBSTUB_API__=9000 ")
+	ensureContains(t, mylibCflags, "-D__LIBSTUB_API__=10000 ")
 	mylibLdflags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
-	ensureContains(t, mylibLdflags, "libstub/android_arm64_armv8-a_shared_Z/libstub.so ")
+	ensureContains(t, mylibLdflags, "libstub/android_arm64_armv8-a_shared_current/libstub.so ")
 
 	// Ensure that libplatform is built against latest stub ("current") of mylib3 from the apex
 	libplatformCflags := ctx.ModuleForTests("libplatform", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
@@ -1357,18 +1359,18 @@
 		shouldNotLink []string
 	}{
 		{
-			name:          "should link to the latest",
+			name:          "unspecified version links to the latest",
 			minSdkVersion: "",
 			apexVariant:   "apex10000",
 			shouldLink:    "30",
 			shouldNotLink: []string{"29"},
 		},
 		{
-			name:          "should link to llndk#29",
+			name:          "always use the latest",
 			minSdkVersion: "min_sdk_version: \"29\",",
 			apexVariant:   "apex29",
-			shouldLink:    "29",
-			shouldNotLink: []string{"30"},
+			shouldLink:    "30",
+			shouldNotLink: []string{"29"},
 		},
 	}
 	for _, tc := range testcases {
@@ -1530,8 +1532,8 @@
 }
 
 func TestApexMinSdkVersion_NativeModulesShouldBeBuiltAgainstStubs(t *testing.T) {
-	// there are three links between liba --> libz
-	// 1) myapex -> libx -> liba -> libz    : this should be #29 link, but fallback to #28
+	// there are three links between liba --> libz.
+	// 1) myapex -> libx -> liba -> libz    : this should be #30 link
 	// 2) otherapex -> liby -> liba -> libz : this should be #30 link
 	// 3) (platform) -> liba -> libz        : this should be non-stub link
 	ctx, _ := testApex(t, `
@@ -1605,9 +1607,9 @@
 	}
 	// platform liba is linked to non-stub version
 	expectLink("liba", "shared", "libz", "shared")
-	// liba in myapex is linked to #28
-	expectLink("liba", "shared_apex29", "libz", "shared_28")
-	expectNoLink("liba", "shared_apex29", "libz", "shared_30")
+	// liba in myapex is linked to #30
+	expectLink("liba", "shared_apex29", "libz", "shared_30")
+	expectNoLink("liba", "shared_apex29", "libz", "shared_28")
 	expectNoLink("liba", "shared_apex29", "libz", "shared")
 	// liba in otherapex is linked to #30
 	expectLink("liba", "shared_apex30", "libz", "shared_30")
@@ -1825,41 +1827,6 @@
 	ensureListNotContains(t, cm.Properties.AndroidMkStaticLibs, "libunwind")
 }
 
-func TestApexMinSdkVersion_ErrorIfIncompatibleStubs(t *testing.T) {
-	testApexError(t, `"libz" .*: not found a version\(<=29\)`, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			native_shared_libs: ["libx"],
-			min_sdk_version: "29",
-		}
-
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-
-		cc_library {
-			name: "libx",
-			shared_libs: ["libz"],
-			system_shared_libs: [],
-			stl: "none",
-			apex_available: [ "myapex" ],
-			min_sdk_version: "29",
-		}
-
-		cc_library {
-			name: "libz",
-			system_shared_libs: [],
-			stl: "none",
-			stubs: {
-				versions: ["30"],
-			},
-		}
-	`)
-}
-
 func TestApexMinSdkVersion_ErrorIfIncompatibleVersion(t *testing.T) {
 	testApexError(t, `module "mylib".*: should support min_sdk_version\(29\)`, `
 		apex {
@@ -2171,7 +2138,7 @@
 			private_key: "testkey.pem",
 		}
 
-		// mylib in myapex will link to mylib2#29
+		// mylib in myapex will link to mylib2#30
 		// mylib in otherapex will link to mylib2(non-stub) in otherapex as well
 		cc_library {
 			name: "mylib",
@@ -2205,7 +2172,7 @@
 		libFlags := ld.Args["libFlags"]
 		ensureContains(t, libFlags, "android_arm64_armv8-a_"+to_variant+"/"+to+".so")
 	}
-	expectLink("mylib", "shared_apex29", "mylib2", "shared_29")
+	expectLink("mylib", "shared_apex29", "mylib2", "shared_30")
 	expectLink("mylib", "shared_apex30", "mylib2", "shared_apex30")
 }
 
@@ -2274,7 +2241,7 @@
 	// ensure libfoo is linked with "S" version of libbar stub
 	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared_apex10000")
 	libFlags := libfoo.Rule("ld").Args["libFlags"]
-	ensureContains(t, libFlags, "android_arm64_armv8-a_shared_S/libbar.so")
+	ensureContains(t, libFlags, "android_arm64_armv8-a_shared_T/libbar.so")
 }
 
 func TestFilesInSubDir(t *testing.T) {
@@ -4366,14 +4333,15 @@
 		// Make sure the import has been given the correct path to the dex jar.
 		p := ctx.ModuleForTests(name, "android_common_myapex").Module().(java.UsesLibraryDependency)
 		dexJarBuildPath := p.DexJarBuildPath()
-		if expected, actual := ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar", android.NormalizePathForTesting(dexJarBuildPath); actual != expected {
+		stem := android.RemoveOptionalPrebuiltPrefix(name)
+		if expected, actual := ".intermediates/myapex.deapexer/android_common/deapexer/javalib/"+stem+".jar", android.NormalizePathForTesting(dexJarBuildPath); actual != expected {
 			t.Errorf("Incorrect DexJarBuildPath value '%s', expected '%s'", actual, expected)
 		}
 	}
 
-	ensureNoSourceVariant := func(t *testing.T, ctx *android.TestContext) {
+	ensureNoSourceVariant := func(t *testing.T, ctx *android.TestContext, name string) {
 		// Make sure that an apex variant is not created for the source module.
-		if expected, actual := []string{"android_common"}, ctx.ModuleVariantsForTests("libfoo"); !reflect.DeepEqual(expected, actual) {
+		if expected, actual := []string{"android_common"}, ctx.ModuleVariantsForTests(name); !reflect.DeepEqual(expected, actual) {
 			t.Errorf("invalid set of variants for %q: expected %q, found %q", "libfoo", expected, actual)
 		}
 	}
@@ -4390,19 +4358,42 @@
 					src: "myapex-arm.apex",
 				},
 			},
-			exported_java_libs: ["libfoo"],
+			exported_java_libs: ["libfoo", "libbar"],
 		}
 
 		java_import {
 			name: "libfoo",
 			jars: ["libfoo.jar"],
 		}
+
+		java_sdk_library_import {
+			name: "libbar",
+			public: {
+				jars: ["libbar.jar"],
+			},
+		}
 	`
 
 		// Make sure that dexpreopt can access dex implementation files from the prebuilt.
 		ctx := testDexpreoptWithApexes(t, bp, "", transform)
 
+		// Make sure that the deapexer has the correct input APEX.
+		deapexer := ctx.ModuleForTests("myapex.deapexer", "android_common")
+		rule := deapexer.Rule("deapexer")
+		if expected, actual := []string{"myapex-arm64.apex"}, android.NormalizePathsForTesting(rule.Implicits); !reflect.DeepEqual(expected, actual) {
+			t.Errorf("expected: %q, found: %q", expected, actual)
+		}
+
+		// Make sure that the prebuilt_apex has the correct input APEX.
+		prebuiltApex := ctx.ModuleForTests("myapex", "android_common")
+		rule = prebuiltApex.Rule("android/soong/android.Cp")
+		if expected, actual := "myapex-arm64.apex", android.NormalizePathForTesting(rule.Input); !reflect.DeepEqual(expected, actual) {
+			t.Errorf("expected: %q, found: %q", expected, actual)
+		}
+
 		checkDexJarBuildPath(t, ctx, "libfoo")
+
+		checkDexJarBuildPath(t, ctx, "libbar")
 	})
 
 	t.Run("prebuilt with source preferred", func(t *testing.T) {
@@ -4418,7 +4409,7 @@
 					src: "myapex-arm.apex",
 				},
 			},
-			exported_java_libs: ["libfoo"],
+			exported_java_libs: ["libfoo", "libbar"],
 		}
 
 		java_import {
@@ -4429,13 +4420,29 @@
 		java_library {
 			name: "libfoo",
 		}
+
+		java_sdk_library_import {
+			name: "libbar",
+			public: {
+				jars: ["libbar.jar"],
+			},
+		}
+
+		java_sdk_library {
+			name: "libbar",
+			srcs: ["foo/bar/MyClass.java"],
+			unsafe_ignore_missing_latest_api: true,
+		}
 	`
 
 		// Make sure that dexpreopt can access dex implementation files from the prebuilt.
 		ctx := testDexpreoptWithApexes(t, bp, "", transform)
 
 		checkDexJarBuildPath(t, ctx, "prebuilt_libfoo")
-		ensureNoSourceVariant(t, ctx)
+		ensureNoSourceVariant(t, ctx, "libfoo")
+
+		checkDexJarBuildPath(t, ctx, "prebuilt_libbar")
+		ensureNoSourceVariant(t, ctx, "libbar")
 	})
 
 	t.Run("prebuilt preferred with source", func(t *testing.T) {
@@ -4450,7 +4457,7 @@
 					src: "myapex-arm.apex",
 				},
 			},
-			exported_java_libs: ["libfoo"],
+			exported_java_libs: ["libfoo", "libbar"],
 		}
 
 		java_import {
@@ -4462,26 +4469,45 @@
 		java_library {
 			name: "libfoo",
 		}
+
+		java_sdk_library_import {
+			name: "libbar",
+			prefer: true,
+			public: {
+				jars: ["libbar.jar"],
+			},
+		}
+
+		java_sdk_library {
+			name: "libbar",
+			srcs: ["foo/bar/MyClass.java"],
+			unsafe_ignore_missing_latest_api: true,
+		}
 	`
 
 		// Make sure that dexpreopt can access dex implementation files from the prebuilt.
 		ctx := testDexpreoptWithApexes(t, bp, "", transform)
 
 		checkDexJarBuildPath(t, ctx, "prebuilt_libfoo")
-		ensureNoSourceVariant(t, ctx)
+		ensureNoSourceVariant(t, ctx, "libfoo")
+
+		checkDexJarBuildPath(t, ctx, "prebuilt_libbar")
+		ensureNoSourceVariant(t, ctx, "libbar")
 	})
 }
 
 func TestBootDexJarsFromSourcesAndPrebuilts(t *testing.T) {
 	transform := func(config *dexpreopt.GlobalConfig) {
-		config.BootJars = android.CreateTestConfiguredJarList([]string{"myapex:libfoo"})
+		config.BootJars = android.CreateTestConfiguredJarList([]string{"myapex:libfoo", "myapex:libbar"})
 	}
 
-	checkBootDexJarPath := func(t *testing.T, ctx *android.TestContext, bootDexJarPath string) {
+	checkBootDexJarPath := func(t *testing.T, ctx *android.TestContext, stem string, bootDexJarPath string) {
+		t.Helper()
 		s := ctx.SingletonForTests("dex_bootjars")
 		foundLibfooJar := false
+		base := stem + ".jar"
 		for _, output := range s.AllOutputs() {
-			if strings.HasSuffix(output, "/libfoo.jar") {
+			if filepath.Base(output) == base {
 				foundLibfooJar = true
 				buildRule := s.Output(output)
 				actual := android.NormalizePathForTesting(buildRule.Input)
@@ -4496,6 +4522,7 @@
 	}
 
 	checkHiddenAPIIndexInputs := func(t *testing.T, ctx *android.TestContext, expectedInputs string) {
+		t.Helper()
 		hiddenAPIIndex := ctx.SingletonForTests("hiddenapi_index")
 		indexRule := hiddenAPIIndex.Rule("singleton-merged-hiddenapi-index")
 		java.CheckHiddenAPIRuleInputs(t, expectedInputs, indexRule)
@@ -4513,7 +4540,7 @@
 					src: "myapex-arm.apex",
 				},
 			},
-			exported_java_libs: ["libfoo"],
+			exported_java_libs: ["libfoo", "libbar"],
 		}
 
 		java_import {
@@ -4521,13 +4548,23 @@
 			jars: ["libfoo.jar"],
 			apex_available: ["myapex"],
 		}
+
+		java_sdk_library_import {
+			name: "libbar",
+			public: {
+				jars: ["libbar.jar"],
+			},
+			apex_available: ["myapex"],
+		}
 	`
 
 		ctx := testDexpreoptWithApexes(t, bp, "", transform)
-		checkBootDexJarPath(t, ctx, ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar")
+		checkBootDexJarPath(t, ctx, "libfoo", ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar")
+		checkBootDexJarPath(t, ctx, "libbar", ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
 
 		// Make sure that the dex file from the prebuilt_apex contributes to the hiddenapi index file.
 		checkHiddenAPIIndexInputs(t, ctx, `
+.intermediates/libbar/android_common_myapex/hiddenapi/index.csv
 .intermediates/libfoo/android_common_myapex/hiddenapi/index.csv
 `)
 	})
@@ -4544,7 +4581,7 @@
 					src: "myapex-arm.apex",
 				},
 			},
-			exported_java_libs: ["libfoo"],
+			exported_java_libs: ["libfoo", "libbar"],
 		}
 
 		java_import {
@@ -4558,6 +4595,21 @@
 			srcs: ["foo/bar/MyClass.java"],
 			apex_available: ["myapex"],
 		}
+
+		java_sdk_library_import {
+			name: "libbar",
+			public: {
+				jars: ["libbar.jar"],
+			},
+			apex_available: ["myapex"],
+		}
+
+		java_sdk_library {
+			name: "libbar",
+			srcs: ["foo/bar/MyClass.java"],
+			unsafe_ignore_missing_latest_api: true,
+			apex_available: ["myapex"],
+		}
 	`
 
 		// In this test the source (java_library) libfoo is active since the
@@ -4580,7 +4632,7 @@
 					src: "myapex-arm.apex",
 				},
 			},
-			exported_java_libs: ["libfoo"],
+			exported_java_libs: ["libfoo", "libbar"],
 		}
 
 		java_import {
@@ -4595,13 +4647,31 @@
 			srcs: ["foo/bar/MyClass.java"],
 			apex_available: ["myapex"],
 		}
+
+		java_sdk_library_import {
+			name: "libbar",
+			prefer: true,
+			public: {
+				jars: ["libbar.jar"],
+			},
+			apex_available: ["myapex"],
+		}
+
+		java_sdk_library {
+			name: "libbar",
+			srcs: ["foo/bar/MyClass.java"],
+			unsafe_ignore_missing_latest_api: true,
+			apex_available: ["myapex"],
+		}
 	`
 
 		ctx := testDexpreoptWithApexes(t, bp, "", transform)
-		checkBootDexJarPath(t, ctx, ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar")
+		checkBootDexJarPath(t, ctx, "libfoo", ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar")
+		checkBootDexJarPath(t, ctx, "libbar", ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
 
 		// Make sure that the dex file from the prebuilt_apex contributes to the hiddenapi index file.
 		checkHiddenAPIIndexInputs(t, ctx, `
+.intermediates/prebuilt_libbar/android_common_myapex/hiddenapi/index.csv
 .intermediates/prebuilt_libfoo/android_common_myapex/hiddenapi/index.csv
 `)
 	})
@@ -4611,7 +4681,7 @@
 		apex {
 			name: "myapex",
 			key: "myapex.key",
-			java_libs: ["libfoo"],
+			java_libs: ["libfoo", "libbar"],
 		}
 
 		apex_key {
@@ -4630,7 +4700,7 @@
 					src: "myapex-arm.apex",
 				},
 			},
-			exported_java_libs: ["libfoo"],
+			exported_java_libs: ["libfoo", "libbar"],
 		}
 
 		java_import {
@@ -4644,13 +4714,30 @@
 			srcs: ["foo/bar/MyClass.java"],
 			apex_available: ["myapex"],
 		}
+
+		java_sdk_library_import {
+			name: "libbar",
+			public: {
+				jars: ["libbar.jar"],
+			},
+			apex_available: ["myapex"],
+		}
+
+		java_sdk_library {
+			name: "libbar",
+			srcs: ["foo/bar/MyClass.java"],
+			unsafe_ignore_missing_latest_api: true,
+			apex_available: ["myapex"],
+		}
 	`
 
 		ctx := testDexpreoptWithApexes(t, bp, "", transform)
-		checkBootDexJarPath(t, ctx, ".intermediates/libfoo/android_common_apex10000/hiddenapi/libfoo.jar")
+		checkBootDexJarPath(t, ctx, "libfoo", ".intermediates/libfoo/android_common_apex10000/hiddenapi/libfoo.jar")
+		checkBootDexJarPath(t, ctx, "libbar", ".intermediates/libbar/android_common_myapex/hiddenapi/libbar.jar")
 
 		// Make sure that the dex file from the prebuilt_apex contributes to the hiddenapi index file.
 		checkHiddenAPIIndexInputs(t, ctx, `
+.intermediates/libbar/android_common_myapex/hiddenapi/index.csv
 .intermediates/libfoo/android_common_apex10000/hiddenapi/index.csv
 `)
 	})
@@ -4680,7 +4767,7 @@
 					src: "myapex-arm.apex",
 				},
 			},
-			exported_java_libs: ["libfoo"],
+			exported_java_libs: ["libfoo", "libbar"],
 		}
 
 		java_import {
@@ -4695,13 +4782,31 @@
 			srcs: ["foo/bar/MyClass.java"],
 			apex_available: ["myapex"],
 		}
+
+		java_sdk_library_import {
+			name: "libbar",
+			prefer: true,
+			public: {
+				jars: ["libbar.jar"],
+			},
+			apex_available: ["myapex"],
+		}
+
+		java_sdk_library {
+			name: "libbar",
+			srcs: ["foo/bar/MyClass.java"],
+			unsafe_ignore_missing_latest_api: true,
+			apex_available: ["myapex"],
+		}
 	`
 
 		ctx := testDexpreoptWithApexes(t, bp, "", transform)
-		checkBootDexJarPath(t, ctx, ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar")
+		checkBootDexJarPath(t, ctx, "libfoo", ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar")
+		checkBootDexJarPath(t, ctx, "libbar", ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
 
 		// Make sure that the dex file from the prebuilt_apex contributes to the hiddenapi index file.
 		checkHiddenAPIIndexInputs(t, ctx, `
+.intermediates/prebuilt_libbar/android_common_prebuilt_myapex/hiddenapi/index.csv
 .intermediates/prebuilt_libfoo/android_common_prebuilt_myapex/hiddenapi/index.csv
 `)
 	})
@@ -6319,6 +6424,9 @@
 	}
 	cc.GatherRequiredFilesForTest(fs)
 
+	for k, v := range filesForSdkLibrary {
+		fs[k] = v
+	}
 	config := android.TestArchConfig(buildDir, nil, bp, fs)
 
 	ctx := android.NewTestArchContext(config)
@@ -6327,6 +6435,7 @@
 	ctx.RegisterModuleType("prebuilt_apex", PrebuiltFactory)
 	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
 	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
+	ctx.PreArchMutators(android.RegisterComponentsMutator)
 	android.RegisterPrebuiltMutators(ctx)
 	cc.RegisterRequiredBuildComponentsForTest(ctx)
 	java.RegisterRequiredBuildComponentsForTest(ctx)
diff --git a/apex/deapexer.go b/apex/deapexer.go
index 8f4a285..46ce41f 100644
--- a/apex/deapexer.go
+++ b/apex/deapexer.go
@@ -65,7 +65,7 @@
 		&module.properties,
 		&module.apexFileProperties,
 	)
-	android.InitSingleSourcePrebuiltModule(module, &module.apexFileProperties, "Source")
+	android.InitPrebuiltModuleWithSrcSupplier(module, module.apexFileProperties.prebuiltApexSelector, "src")
 	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	return module
 }
@@ -78,16 +78,6 @@
 	return p.prebuilt.Name(p.ModuleBase.Name())
 }
 
-func deapexerSelectSourceMutator(ctx android.BottomUpMutatorContext) {
-	p, ok := ctx.Module().(*Deapexer)
-	if !ok {
-		return
-	}
-	if err := p.apexFileProperties.selectSource(ctx); err != nil {
-		ctx.ModuleErrorf("%s", err)
-	}
-}
-
 func (p *Deapexer) DepsMutator(ctx android.BottomUpMutatorContext) {
 	// Add dependencies from the java modules to which this exports files from the `.apex` file onto
 	// this module so that they can access the `DeapexerInfo` object that this provides.
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index ec7f253..3280cd8 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -109,8 +109,10 @@
 
 type ApexFileProperties struct {
 	// the path to the prebuilt .apex file to import.
-	Source string `blueprint:"mutated"`
-
+	//
+	// This cannot be marked as `android:"arch_variant"` because the `prebuilt_apex` is only mutated
+	// for android_common. That is so that it will have the same arch variant as, and so be compatible
+	// with, the source `apex` module type that it replaces.
 	Src  *string
 	Arch struct {
 		Arm struct {
@@ -128,15 +130,20 @@
 	}
 }
 
-func (p *ApexFileProperties) selectSource(ctx android.BottomUpMutatorContext) error {
-	// This is called before prebuilt_select and prebuilt_postdeps mutators
-	// The mutators requires that src to be set correctly for each arch so that
-	// arch variants are disabled when src is not provided for the arch.
-	if len(ctx.MultiTargets()) != 1 {
-		return fmt.Errorf("compile_multilib shouldn't be \"both\" for prebuilt_apex")
+// prebuiltApexSelector selects the correct prebuilt APEX file for the build target.
+//
+// The ctx parameter can be for any module not just the prebuilt module so care must be taken not
+// to use methods on it that are specific to the current module.
+//
+// See the ApexFileProperties.Src property.
+func (p *ApexFileProperties) prebuiltApexSelector(ctx android.BaseModuleContext, prebuilt android.Module) []string {
+	multiTargets := prebuilt.MultiTargets()
+	if len(multiTargets) != 1 {
+		ctx.OtherModuleErrorf(prebuilt, "compile_multilib shouldn't be \"both\" for prebuilt_apex")
+		return nil
 	}
 	var src string
-	switch ctx.MultiTargets()[0].Arch.ArchType {
+	switch multiTargets[0].Arch.ArchType {
 	case android.Arm:
 		src = String(p.Arch.Arm.Src)
 	case android.Arm64:
@@ -146,14 +153,14 @@
 	case android.X86_64:
 		src = String(p.Arch.X86_64.Src)
 	default:
-		return fmt.Errorf("prebuilt_apex does not support %q", ctx.MultiTargets()[0].Arch.String())
+		ctx.OtherModuleErrorf(prebuilt, "prebuilt_apex does not support %q", multiTargets[0].Arch.String())
+		return nil
 	}
 	if src == "" {
 		src = String(p.Src)
 	}
-	p.Source = src
 
-	return nil
+	return []string{src}
 }
 
 type PrebuiltProperties struct {
@@ -217,7 +224,7 @@
 func PrebuiltFactory() android.Module {
 	module := &Prebuilt{}
 	module.AddProperties(&module.properties)
-	android.InitSingleSourcePrebuiltModule(module, &module.properties, "Source")
+	android.InitPrebuiltModuleWithSrcSupplier(module, module.properties.prebuiltApexSelector, "src")
 	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
 
 	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
@@ -250,16 +257,6 @@
 	return name
 }
 
-func prebuiltSelectSourceMutator(ctx android.BottomUpMutatorContext) {
-	p, ok := ctx.Module().(*Prebuilt)
-	if !ok {
-		return
-	}
-	if err := p.properties.selectSource(ctx); err != nil {
-		ctx.ModuleErrorf("%s", err)
-	}
-}
-
 type exportedDependencyTag struct {
 	blueprint.BaseDependencyTag
 	name string
@@ -535,7 +532,7 @@
 	module := &ApexSet{}
 	module.AddProperties(&module.properties)
 
-	srcsSupplier := func(ctx android.BaseModuleContext) []string {
+	srcsSupplier := func(ctx android.BaseModuleContext, _ android.Module) []string {
 		return module.prebuiltSrcs(ctx)
 	}
 
diff --git a/bazel/properties.go b/bazel/properties.go
index a4df4bc..a5ffa55 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -14,6 +14,8 @@
 
 package bazel
 
+import "fmt"
+
 type bazelModuleProperties struct {
 	// The label of the Bazel target replacing this Soong module.
 	Label string
@@ -63,3 +65,72 @@
 		ll.Excludes = append(other.Excludes, other.Excludes...)
 	}
 }
+
+// StringListAttribute corresponds to the string_list Bazel attribute type with
+// support for additional metadata, like configurations.
+type StringListAttribute struct {
+	// The base value of the string list attribute.
+	Value []string
+
+	// Optional additive set of list values to the base value.
+	ArchValues stringListArchValues
+}
+
+// Arch-specific string_list typed Bazel attribute values. This should correspond
+// to the types of architectures supported for compilation in arch.go.
+type stringListArchValues struct {
+	X86     []string
+	X86_64  []string
+	Arm     []string
+	Arm64   []string
+	Default []string
+	// TODO(b/181299724): this is currently missing the "common" arch, which
+	// doesn't have an equivalent platform() definition yet.
+}
+
+// HasArchSpecificValues returns true if the attribute contains
+// architecture-specific string_list values.
+func (attrs *StringListAttribute) HasArchSpecificValues() bool {
+	for _, arch := range []string{"x86", "x86_64", "arm", "arm64", "default"} {
+		if len(attrs.GetValueForArch(arch)) > 0 {
+			return true
+		}
+	}
+	return false
+}
+
+// GetValueForArch returns the string_list attribute value for an architecture.
+func (attrs *StringListAttribute) GetValueForArch(arch string) []string {
+	switch arch {
+	case "x86":
+		return attrs.ArchValues.X86
+	case "x86_64":
+		return attrs.ArchValues.X86_64
+	case "arm":
+		return attrs.ArchValues.Arm
+	case "arm64":
+		return attrs.ArchValues.Arm64
+	case "default":
+		return attrs.ArchValues.Default
+	default:
+		panic(fmt.Errorf("Unknown arch: %s", arch))
+	}
+}
+
+// SetValueForArch sets the string_list attribute value for an architecture.
+func (attrs *StringListAttribute) SetValueForArch(arch string, value []string) {
+	switch arch {
+	case "x86":
+		attrs.ArchValues.X86 = value
+	case "x86_64":
+		attrs.ArchValues.X86_64 = value
+	case "arm":
+		attrs.ArchValues.Arm = value
+	case "arm64":
+		attrs.ArchValues.Arm64 = value
+	case "default":
+		attrs.ArchValues.Default = value
+	default:
+		panic(fmt.Errorf("Unknown arch: %s", arch))
+	}
+}
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
index 7d748ec..8deb5a2 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -10,6 +10,7 @@
         "bp2build.go",
         "build_conversion.go",
         "bzl_conversion.go",
+        "configurability.go",
         "conversion.go",
         "metrics.go",
     ],
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index a4c4a0d..7fa4996 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -354,11 +354,42 @@
 		ret += makeIndent(indent)
 		ret += "]"
 	case reflect.Struct:
+		// Special cases where the bp2build sends additional information to the codegenerator
+		// by wrapping the attributes in a custom struct type.
 		if labels, ok := propertyValue.Interface().(bazel.LabelList); ok {
 			// TODO(b/165114590): convert glob syntax
 			return prettyPrint(reflect.ValueOf(labels.Includes), indent)
 		} else if label, ok := propertyValue.Interface().(bazel.Label); ok {
 			return fmt.Sprintf("%q", label.Label), nil
+		} else if stringList, ok := propertyValue.Interface().(bazel.StringListAttribute); ok {
+			// A Bazel string_list attribute that may contain a select statement.
+			ret, err := prettyPrint(reflect.ValueOf(stringList.Value), indent)
+			if err != nil {
+				return ret, err
+			}
+
+			if !stringList.HasArchSpecificValues() {
+				// Select statement not needed.
+				return ret, nil
+			}
+
+			ret += " + " + "select({\n"
+			for _, arch := range android.ArchTypeList() {
+				value := stringList.GetValueForArch(arch.Name)
+				if len(value) > 0 {
+					ret += makeIndent(indent + 1)
+					list, _ := prettyPrint(reflect.ValueOf(value), indent+1)
+					ret += fmt.Sprintf("\"%s\": %s,\n", platformArchMap[arch], list)
+				}
+			}
+
+			ret += makeIndent(indent + 1)
+			list, _ := prettyPrint(reflect.ValueOf(stringList.GetValueForArch("default")), indent+1)
+			ret += fmt.Sprintf("\"%s\": %s,\n", "//conditions:default", list)
+
+			ret += makeIndent(indent)
+			ret += "})"
+			return ret, err
 		}
 
 		ret = "{\n"
diff --git a/bp2build/cc_object_conversion_test.go b/bp2build/cc_object_conversion_test.go
index f655842..1d4e322 100644
--- a/bp2build/cc_object_conversion_test.go
+++ b/bp2build/cc_object_conversion_test.go
@@ -99,15 +99,6 @@
 cc_defaults {
     name: "foo_defaults",
     defaults: ["foo_bar_defaults"],
-	// TODO(b/178130668): handle configurable attributes that depend on the platform
-    arch: {
-        x86: {
-            cflags: ["-fPIC"],
-        },
-        x86_64: {
-            cflags: ["-fPIC"],
-        },
-    },
 }
 
 cc_defaults {
@@ -233,3 +224,137 @@
 		}
 	}
 }
+
+func TestCcObjectConfigurableAttributesBp2Build(t *testing.T) {
+	testCases := []struct {
+		description                        string
+		moduleTypeUnderTest                string
+		moduleTypeUnderTestFactory         android.ModuleFactory
+		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
+		blueprint                          string
+		expectedBazelTargets               []string
+		filesystem                         map[string]string
+	}{
+		{
+			description:                        "cc_object setting cflags for one arch",
+			moduleTypeUnderTest:                "cc_object",
+			moduleTypeUnderTestFactory:         cc.ObjectFactory,
+			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+			blueprint: `cc_object {
+    name: "foo",
+    arch: {
+        x86: {
+            cflags: ["-fPIC"],
+        },
+    },
+    bazel_module: { bp2build_available: true },
+}
+`,
+			expectedBazelTargets: []string{
+				`cc_object(
+    name = "foo",
+    copts = [
+        "-fno-addrsig",
+    ] + select({
+        "@bazel_tools//platforms:x86_32": [
+            "-fPIC",
+        ],
+        "//conditions:default": [
+        ],
+    }),
+)`,
+			},
+		},
+		{
+			description:                        "cc_object setting cflags for 4 architectures",
+			moduleTypeUnderTest:                "cc_object",
+			moduleTypeUnderTestFactory:         cc.ObjectFactory,
+			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+			blueprint: `cc_object {
+    name: "foo",
+    arch: {
+        x86: {
+            cflags: ["-fPIC"],
+        },
+        x86_64: {
+            cflags: ["-fPIC"],
+        },
+        arm: {
+            cflags: ["-Wall"],
+        },
+        arm64: {
+            cflags: ["-Wall"],
+        },
+    },
+    bazel_module: { bp2build_available: true },
+}
+`,
+			expectedBazelTargets: []string{
+				`cc_object(
+    name = "foo",
+    copts = [
+        "-fno-addrsig",
+    ] + select({
+        "@bazel_tools//platforms:arm": [
+            "-Wall",
+        ],
+        "@bazel_tools//platforms:aarch64": [
+            "-Wall",
+        ],
+        "@bazel_tools//platforms:x86_32": [
+            "-fPIC",
+        ],
+        "@bazel_tools//platforms:x86_64": [
+            "-fPIC",
+        ],
+        "//conditions:default": [
+        ],
+    }),
+)`,
+			},
+		},
+	}
+
+	dir := "."
+	for _, testCase := range testCases {
+		filesystem := make(map[string][]byte)
+		toParse := []string{
+			"Android.bp",
+		}
+		config := android.TestConfig(buildDir, nil, testCase.blueprint, filesystem)
+		ctx := android.NewTestContext(config)
+		// Always register cc_defaults module factory
+		ctx.RegisterModuleType("cc_defaults", func() android.Module { return cc.DefaultsFactory() })
+
+		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
+		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
+		ctx.RegisterForBazelConversion()
+
+		_, errs := ctx.ParseFileList(dir, toParse)
+		if Errored(t, testCase.description, errs) {
+			continue
+		}
+		_, errs = ctx.ResolveDependencies(config)
+		if Errored(t, testCase.description, errs) {
+			continue
+		}
+
+		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+		bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
+		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
+			fmt.Println(bazelTargets)
+			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
+		} else {
+			for i, target := range bazelTargets {
+				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
+					t.Errorf(
+						"%s: Expected generated Bazel target to be '%s', got '%s'",
+						testCase.description,
+						w,
+						g,
+					)
+				}
+			}
+		}
+	}
+}
diff --git a/bp2build/configurability.go b/bp2build/configurability.go
new file mode 100644
index 0000000..47cf3c6
--- /dev/null
+++ b/bp2build/configurability.go
@@ -0,0 +1,15 @@
+package bp2build
+
+import "android/soong/android"
+
+// Configurability support for bp2build.
+
+var (
+	// A map of architectures to the Bazel label of the constraint_value.
+	platformArchMap = map[android.ArchType]string{
+		android.Arm:    "@bazel_tools//platforms:arm",
+		android.Arm64:  "@bazel_tools//platforms:aarch64",
+		android.X86:    "@bazel_tools//platforms:x86_32",
+		android.X86_64: "@bazel_tools//platforms:x86_64",
+	}
+)
diff --git a/bpfix/bpfix/bpfix.go b/bpfix/bpfix/bpfix.go
index 94b8252..fae6101 100644
--- a/bpfix/bpfix/bpfix.go
+++ b/bpfix/bpfix/bpfix.go
@@ -670,6 +670,26 @@
 	return nil
 }
 
+func RewriteRuntimeResourceOverlay(f *Fixer) error {
+	for _, def := range f.tree.Defs {
+		mod, ok := def.(*parser.Module)
+		if !(ok && mod.Type == "runtime_resource_overlay") {
+			continue
+		}
+		// runtime_resource_overlays are always product specific in Make.
+		if _, ok := mod.GetProperty("product_specific"); !ok {
+			prop := &parser.Property{
+				Name: "product_specific",
+				Value: &parser.Bool{
+					Value: true,
+				},
+			}
+			mod.Properties = append(mod.Properties, prop)
+		}
+	}
+	return nil
+}
+
 // Removes library dependencies which are empty (and restricted from usage in Soong)
 func removeEmptyLibDependencies(f *Fixer) error {
 	emptyLibraries := []string{
diff --git a/bpfix/bpfix/bpfix_test.go b/bpfix/bpfix/bpfix_test.go
index ef9814f..61dfe1a 100644
--- a/bpfix/bpfix/bpfix_test.go
+++ b/bpfix/bpfix/bpfix_test.go
@@ -1056,3 +1056,71 @@
 		})
 	}
 }
+
+func TestRewriteRuntimeResourceOverlay(t *testing.T) {
+	tests := []struct {
+		name string
+		in   string
+		out  string
+	}{
+		{
+			name: "product_specific runtime_resource_overlay",
+			in: `
+				runtime_resource_overlay {
+					name: "foo",
+					resource_dirs: ["res"],
+					product_specific: true,
+				}
+			`,
+			out: `
+				runtime_resource_overlay {
+					name: "foo",
+					resource_dirs: ["res"],
+					product_specific: true,
+				}
+			`,
+		},
+		{
+			// It's probably wrong for runtime_resource_overlay not to be product specific, but let's not
+			// debate it here.
+			name: "non-product_specific runtime_resource_overlay",
+			in: `
+				runtime_resource_overlay {
+					name: "foo",
+					resource_dirs: ["res"],
+					product_specific: false,
+				}
+			`,
+			out: `
+				runtime_resource_overlay {
+					name: "foo",
+					resource_dirs: ["res"],
+					product_specific: false,
+				}
+			`,
+		},
+		{
+			name: "runtime_resource_overlay without product_specific value",
+			in: `
+				runtime_resource_overlay {
+					name: "foo",
+					resource_dirs: ["res"],
+				}
+			`,
+			out: `
+				runtime_resource_overlay {
+					name: "foo",
+					resource_dirs: ["res"],
+					product_specific: true,
+				}
+			`,
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			runPass(t, test.in, test.out, func(fixer *Fixer) error {
+				return RewriteRuntimeResourceOverlay(fixer)
+			})
+		})
+	}
+}
diff --git a/cc/cc.go b/cc/cc.go
index 11d8718..47a1f8a 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -2420,36 +2420,6 @@
 	}
 }
 
-// Returns the highest version which is <= maxSdkVersion.
-// For example, with maxSdkVersion is 10 and versionList is [9,11]
-// it returns 9 as string.  The list of stubs must be in order from
-// oldest to newest.
-func (c *Module) chooseSdkVersion(ctx android.PathContext, stubsInfo []SharedStubLibrary,
-	maxSdkVersion android.ApiLevel) (SharedStubLibrary, error) {
-
-	for i := range stubsInfo {
-		stubInfo := stubsInfo[len(stubsInfo)-i-1]
-		var ver android.ApiLevel
-		if stubInfo.Version == "" {
-			ver = android.FutureApiLevel
-		} else {
-			var err error
-			ver, err = android.ApiLevelFromUser(ctx, stubInfo.Version)
-			if err != nil {
-				return SharedStubLibrary{}, err
-			}
-		}
-		if ver.LessThanOrEqualTo(maxSdkVersion) {
-			return stubInfo, nil
-		}
-	}
-	var versionList []string
-	for _, stubInfo := range stubsInfo {
-		versionList = append(versionList, stubInfo.Version)
-	}
-	return SharedStubLibrary{}, fmt.Errorf("not found a version(<=%s) in versionList: %v", maxSdkVersion.String(), versionList)
-}
-
 // Convert dependencies to paths.  Returns a PathDeps containing paths
 func (c *Module) depsToPaths(ctx android.ModuleContext) PathDeps {
 	var depPaths PathDeps
@@ -2633,16 +2603,12 @@
 						useStubs = !android.DirectlyInAllApexes(apexInfo, depName)
 					}
 
-					// when to use (unspecified) stubs, check min_sdk_version and choose the right one
+					// when to use (unspecified) stubs, use the latest one.
 					if useStubs {
-						sharedLibraryStubsInfo, err :=
-							c.chooseSdkVersion(ctx, sharedLibraryStubsInfo.SharedStubLibraries, c.apexSdkVersion)
-						if err != nil {
-							ctx.OtherModuleErrorf(dep, err.Error())
-							return
-						}
-						sharedLibraryInfo = sharedLibraryStubsInfo.SharedLibraryInfo
-						depExporterInfo = sharedLibraryStubsInfo.FlagExporterInfo
+						stubs := sharedLibraryStubsInfo.SharedStubLibraries
+						toUse := stubs[len(stubs)-1]
+						sharedLibraryInfo = toUse.SharedLibraryInfo
+						depExporterInfo = toUse.FlagExporterInfo
 					}
 				}
 
diff --git a/cc/object.go b/cc/object.go
index 140a066..32347b8 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -93,7 +93,7 @@
 type bazelObjectAttributes struct {
 	Srcs               bazel.LabelList
 	Deps               bazel.LabelList
-	Copts              []string
+	Copts              bazel.StringListAttribute
 	Local_include_dirs []string
 }
 
@@ -133,13 +133,14 @@
 		ctx.ModuleErrorf("compiler must not be nil for a cc_object module")
 	}
 
-	var copts []string
+	// Set arch-specific configurable attributes
+	var copts bazel.StringListAttribute
 	var srcs []string
 	var excludeSrcs []string
 	var localIncludeDirs []string
 	for _, props := range m.compiler.compilerProps() {
 		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
-			copts = baseCompilerProps.Cflags
+			copts.Value = baseCompilerProps.Cflags
 			srcs = baseCompilerProps.Srcs
 			excludeSrcs = baseCompilerProps.Exclude_srcs
 			localIncludeDirs = baseCompilerProps.Local_include_dirs
@@ -154,6 +155,13 @@
 		}
 	}
 
+	for arch, p := range m.GetArchProperties(&BaseCompilerProperties{}) {
+		if cProps, ok := p.(*BaseCompilerProperties); ok {
+			copts.SetValueForArch(arch.Name, cProps.Cflags)
+		}
+	}
+	copts.SetValueForArch("default", []string{})
+
 	attrs := &bazelObjectAttributes{
 		Srcs:               android.BazelLabelForModuleSrcExcludes(ctx, srcs, excludeSrcs),
 		Deps:               deps,
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index 2cd18cb..6b9a3d5 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -246,7 +246,7 @@
 
 	module.AddProperties(&prebuilt.properties)
 
-	srcsSupplier := func(ctx android.BaseModuleContext) []string {
+	srcsSupplier := func(ctx android.BaseModuleContext, _ android.Module) []string {
 		return prebuilt.prebuiltSrcs(ctx)
 	}
 
diff --git a/cc/tidy.go b/cc/tidy.go
index 251c67b..616cf8a 100644
--- a/cc/tidy.go
+++ b/cc/tidy.go
@@ -42,6 +42,21 @@
 	Properties TidyProperties
 }
 
+var quotedFlagRegexp, _ = regexp.Compile(`^-?-[^=]+=('|").*('|")$`)
+
+// When passing flag -name=value, if user add quotes around 'value',
+// the quotation marks will be preserved by NinjaAndShellEscapeList
+// and the 'value' string with quotes won't work like the intended value.
+// So here we report an error if -*='*' is found.
+func checkNinjaAndShellEscapeList(ctx ModuleContext, prop string, slice []string) []string {
+	for _, s := range slice {
+		if quotedFlagRegexp.MatchString(s) {
+			ctx.PropertyErrorf(prop, "Extra quotes in: %s", s)
+		}
+	}
+	return proptools.NinjaAndShellEscapeList(slice)
+}
+
 func (tidy *tidyFeature) props() []interface{} {
 	return []interface{}{&tidy.Properties}
 }
@@ -74,8 +89,8 @@
 	if len(withTidyFlags) > 0 {
 		flags.TidyFlags = append(flags.TidyFlags, withTidyFlags)
 	}
-	esc := proptools.NinjaAndShellEscapeList
-	flags.TidyFlags = append(flags.TidyFlags, esc(tidy.Properties.Tidy_flags)...)
+	esc := checkNinjaAndShellEscapeList
+	flags.TidyFlags = append(flags.TidyFlags, esc(ctx, "tidy_flags", tidy.Properties.Tidy_flags)...)
 	// If TidyFlags does not contain -header-filter, add default header filter.
 	// Find the substring because the flag could also appear as --header-filter=...
 	// and with or without single or double quotes.
@@ -119,7 +134,7 @@
 		tidyChecks += config.TidyChecksForDir(ctx.ModuleDir())
 	}
 	if len(tidy.Properties.Tidy_checks) > 0 {
-		tidyChecks = tidyChecks + "," + strings.Join(esc(
+		tidyChecks = tidyChecks + "," + strings.Join(esc(ctx, "tidy_checks",
 			config.ClangRewriteTidyChecks(tidy.Properties.Tidy_checks)), ",")
 	}
 	if ctx.Windows() {
@@ -165,7 +180,7 @@
 			flags.TidyFlags = append(flags.TidyFlags, "-warnings-as-errors=-*")
 		}
 	} else if len(tidy.Properties.Tidy_checks_as_errors) > 0 {
-		tidyChecksAsErrors := "-warnings-as-errors=" + strings.Join(esc(tidy.Properties.Tidy_checks_as_errors), ",")
+		tidyChecksAsErrors := "-warnings-as-errors=" + strings.Join(esc(ctx, "tidy_checks_as_errors", tidy.Properties.Tidy_checks_as_errors), ",")
 		flags.TidyFlags = append(flags.TidyFlags, tidyChecksAsErrors)
 	}
 	return flags
diff --git a/cmd/soong_build/Android.bp b/cmd/soong_build/Android.bp
index 6a0a87b..9f09224 100644
--- a/cmd/soong_build/Android.bp
+++ b/cmd/soong_build/Android.bp
@@ -25,7 +25,6 @@
         "soong",
         "soong-android",
         "soong-bp2build",
-        "soong-env",
         "soong-ui-metrics_proto",
     ],
     srcs: [
diff --git a/cmd/soong_env/Android.bp b/cmd/soong_env/Android.bp
deleted file mode 100644
index ad717d0..0000000
--- a/cmd/soong_env/Android.bp
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2015 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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-bootstrap_go_binary {
-    name: "soong_env",
-    deps: [
-        "soong-env",
-    ],
-    srcs: [
-        "soong_env.go",
-    ],
-    default: true,
-}
diff --git a/cmd/soong_env/soong_env.go b/cmd/soong_env/soong_env.go
deleted file mode 100644
index 8020b17..0000000
--- a/cmd/soong_env/soong_env.go
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2015 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.
-
-// soong_env determines if the given soong environment file (usually ".soong.environment") is stale
-// by comparing its contents to the current corresponding environment variable values.
-// It fails if the file cannot be opened or corrupted, or its contents differ from the current
-// values.
-
-package main
-
-import (
-	"flag"
-	"fmt"
-	"os"
-
-	"android/soong/env"
-)
-
-func usage() {
-	fmt.Fprintf(os.Stderr, "usage: soong_env env_file\n")
-	fmt.Fprintf(os.Stderr, "exits with success if the environment varibles in env_file match\n")
-	fmt.Fprintf(os.Stderr, "the current environment\n")
-	flag.PrintDefaults()
-	os.Exit(2)
-}
-
-// This is a simple executable packaging, and the real work happens in env.StaleEnvFile.
-func main() {
-	flag.Parse()
-
-	if flag.NArg() != 1 {
-		usage()
-	}
-
-	stale, err := env.StaleEnvFile(flag.Arg(0))
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "error: %s\n", err.Error())
-		os.Exit(1)
-	}
-
-	if stale {
-		os.Exit(1)
-	}
-
-	os.Exit(0)
-}
diff --git a/env/Android.bp b/env/Android.bp
deleted file mode 100644
index c6a97b1..0000000
--- a/env/Android.bp
+++ /dev/null
@@ -1,11 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-bootstrap_go_package {
-    name: "soong-env",
-    pkgPath: "android/soong/env",
-    srcs: [
-        "env.go",
-    ],
-}
diff --git a/java/Android.bp b/java/Android.bp
index 9bfd009..461b16d 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -58,7 +58,6 @@
         "sdk_library.go",
         "sdk_library_external.go",
         "support_libraries.go",
-        "sysprop.go",
         "system_modules.go",
         "testing.go",
         "tradefed.go",
diff --git a/java/androidmk.go b/java/androidmk.go
index 9bdb70c..3d3eae5 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -220,7 +220,7 @@
 	}
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "JAVA_LIBRARIES",
-		OutputFile: android.OptionalPathForPath(prebuilt.maybeStrippedDexJarFile),
+		OutputFile: android.OptionalPathForPath(prebuilt.dexJarFile),
 		Include:    "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
diff --git a/java/app.go b/java/app.go
index e81b25d..2d918e9 100755
--- a/java/app.go
+++ b/java/app.go
@@ -469,7 +469,7 @@
 		a.Module.compile(ctx, a.aaptSrcJar)
 	}
 
-	return a.maybeStrippedDexJarFile
+	return a.dexJarFile
 }
 
 func (a *AndroidApp) jniBuildActions(jniLibs []jniLib, ctx android.ModuleContext) android.WritablePath {
diff --git a/java/app_test.go b/java/app_test.go
index de06c29..c5a618c 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -685,6 +685,51 @@
 	}
 }
 
+func TestAppJavaResources(t *testing.T) {
+	bp := `
+			android_app {
+				name: "foo",
+				sdk_version: "current",
+				java_resources: ["resources/a"],
+				srcs: ["a.java"],
+			}
+
+			android_app {
+				name: "bar",
+				sdk_version: "current",
+				java_resources: ["resources/a"],
+			}
+		`
+
+	ctx := testApp(t, bp)
+
+	foo := ctx.ModuleForTests("foo", "android_common")
+	fooResources := foo.Output("res/foo.jar")
+	fooDexJar := foo.Output("dex-withres/foo.jar")
+	fooDexJarAligned := foo.Output("dex-withres-aligned/foo.jar")
+	fooApk := foo.Rule("combineApk")
+
+	if g, w := fooDexJar.Inputs.Strings(), fooResources.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected resource jar %q in foo dex jar inputs %q", w, g)
+	}
+
+	if g, w := fooDexJarAligned.Input.String(), fooDexJar.Output.String(); g != w {
+		t.Errorf("expected dex jar %q in foo aligned dex jar inputs %q", w, g)
+	}
+
+	if g, w := fooApk.Inputs.Strings(), fooDexJarAligned.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected aligned dex jar %q in foo apk inputs %q", w, g)
+	}
+
+	bar := ctx.ModuleForTests("bar", "android_common")
+	barResources := bar.Output("res/bar.jar")
+	barApk := bar.Rule("combineApk")
+
+	if g, w := barApk.Inputs.Strings(), barResources.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected resources jar %q in bar apk inputs %q", w, g)
+	}
+}
+
 func TestAndroidResources(t *testing.T) {
 	testCases := []struct {
 		name                       string
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 86b1895..e94b20c 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -210,6 +210,15 @@
 // apps instead of the Framework boot image extension (see DEXPREOPT_USE_ART_IMAGE and UseArtImage).
 //
 
+var artApexNames = []string{
+	"com.android.art",
+	"com.android.art.debug",
+	"com.android.art,testing",
+	"com.google.android.art",
+	"com.google.android.art.debug",
+	"com.google.android.art.testing",
+}
+
 func init() {
 	RegisterDexpreoptBootJarsComponents(android.InitRegistrationContext)
 }
@@ -485,7 +494,14 @@
 
 	switch image.name {
 	case artBootImageName:
-		if apexInfo.InApexByBaseName("com.android.art") || apexInfo.InApexByBaseName("com.android.art.debug") || apexInfo.InApexByBaseName("com.android.art,testing") {
+		inArtApex := false
+		for _, n := range artApexNames {
+			if apexInfo.InApexByBaseName(n) {
+				inArtApex = true
+				break
+			}
+		}
+		if inArtApex {
 			// ok: found the jar in the ART apex
 		} else if name == "jacocoagent" && ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") {
 			// exception (skip and continue): Jacoco platform variant for a coverage build
diff --git a/java/hiddenapi.go b/java/hiddenapi.go
index da2c48f..208ced7 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -148,7 +148,18 @@
 		primary = configurationName == ctx.ModuleName()
 
 		// A source module that has been replaced by a prebuilt can never be the primary module.
-		primary = primary && !module.IsReplacedByPrebuilt()
+		if module.IsReplacedByPrebuilt() {
+			ctx.VisitDirectDepsWithTag(android.PrebuiltDepTag, func(prebuilt android.Module) {
+				if h, ok := prebuilt.(hiddenAPIIntf); ok && h.bootDexJar() != nil {
+					primary = false
+				} else {
+					ctx.ModuleErrorf(
+						"hiddenapi has determined that the source module %q should be ignored as it has been"+
+							" replaced by the prebuilt module %q but unfortunately it does not provide a"+
+							" suitable boot dex jar", ctx.ModuleName(), ctx.OtherModuleName(prebuilt))
+				}
+			})
+		}
 	}
 	h.primary = primary
 }
diff --git a/java/hiddenapi_singleton_test.go b/java/hiddenapi_singleton_test.go
index c0f0e38..fb63820 100644
--- a/java/hiddenapi_singleton_test.go
+++ b/java/hiddenapi_singleton_test.go
@@ -126,6 +126,29 @@
 `, indexParams)
 }
 
+func TestHiddenAPISingletonWithSourceAndPrebuiltPreferredButNoDex(t *testing.T) {
+	config := testConfigWithBootJars(`
+		java_library {
+			name: "foo",
+			srcs: ["a.java"],
+			compile_dex: true,
+		}
+
+		java_import {
+			name: "foo",
+			jars: ["a.jar"],
+			prefer: true,
+		}
+	`, []string{"platform:foo"}, nil)
+
+	ctx := testContextWithHiddenAPI(config)
+
+	runWithErrors(t, ctx, config,
+		"hiddenapi has determined that the source module \"foo\" should be ignored as it has been"+
+			" replaced by the prebuilt module \"prebuilt_foo\" but unfortunately it does not provide a"+
+			" suitable boot dex jar")
+}
+
 func TestHiddenAPISingletonWithPrebuilt(t *testing.T) {
 	ctx, _ := testHiddenAPIBootJars(t, `
 		java_import {
diff --git a/java/java.go b/java/java.go
index 78cd362..e471f0d 100644
--- a/java/java.go
+++ b/java/java.go
@@ -370,6 +370,10 @@
 	// If true, generate the signature file of APK Signing Scheme V4, along side the signed APK file.
 	// Defaults to false.
 	V4_signature *bool
+
+	// Only for libraries created by a sysprop_library module, SyspropPublicStub is the name of the
+	// public stubs library.
+	SyspropPublicStub string `blueprint:"mutated"`
 }
 
 // Functionality common to Module and Import
@@ -434,9 +438,6 @@
 	// output file containing classes.dex and resources
 	dexJarFile android.Path
 
-	// output file that contains classes.dex if it should be in the output file
-	maybeStrippedDexJarFile android.Path
-
 	// output file containing uninstrumented classes that will be instrumented by jacoco
 	jacocoReportClassesFile android.Path
 
@@ -583,6 +584,16 @@
 
 var JavaInfoProvider = blueprint.NewProvider(JavaInfo{})
 
+// SyspropPublicStubInfo contains info about the sysprop public stub library that corresponds to
+// the sysprop implementation library.
+type SyspropPublicStubInfo struct {
+	// JavaInfo is the JavaInfoProvider of the sysprop public stub library that corresponds to
+	// the sysprop implementation library.
+	JavaInfo JavaInfo
+}
+
+var SyspropPublicStubInfoProvider = blueprint.NewProvider(SyspropPublicStubInfo{})
+
 // Methods that need to be implemented for a module that is added to apex java_libs property.
 type ApexDependency interface {
 	HeaderJars() android.Paths
@@ -652,29 +663,30 @@
 }
 
 var (
-	dataNativeBinsTag     = dependencyTag{name: "dataNativeBins"}
-	staticLibTag          = dependencyTag{name: "staticlib"}
-	libTag                = dependencyTag{name: "javalib"}
-	java9LibTag           = dependencyTag{name: "java9lib"}
-	pluginTag             = dependencyTag{name: "plugin"}
-	errorpronePluginTag   = dependencyTag{name: "errorprone-plugin"}
-	exportedPluginTag     = dependencyTag{name: "exported-plugin"}
-	bootClasspathTag      = dependencyTag{name: "bootclasspath"}
-	systemModulesTag      = dependencyTag{name: "system modules"}
-	frameworkResTag       = dependencyTag{name: "framework-res"}
-	kotlinStdlibTag       = dependencyTag{name: "kotlin-stdlib"}
-	kotlinAnnotationsTag  = dependencyTag{name: "kotlin-annotations"}
-	proguardRaiseTag      = dependencyTag{name: "proguard-raise"}
-	certificateTag        = dependencyTag{name: "certificate"}
-	instrumentationForTag = dependencyTag{name: "instrumentation_for"}
-	extraLintCheckTag     = dependencyTag{name: "extra-lint-check"}
-	jniLibTag             = dependencyTag{name: "jnilib"}
-	jniInstallTag         = installDependencyTag{name: "jni install"}
-	binaryInstallTag      = installDependencyTag{name: "binary install"}
-	usesLibTag            = makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion)
-	usesLibCompat28Tag    = makeUsesLibraryDependencyTag(28)
-	usesLibCompat29Tag    = makeUsesLibraryDependencyTag(29)
-	usesLibCompat30Tag    = makeUsesLibraryDependencyTag(30)
+	dataNativeBinsTag       = dependencyTag{name: "dataNativeBins"}
+	staticLibTag            = dependencyTag{name: "staticlib"}
+	libTag                  = dependencyTag{name: "javalib"}
+	java9LibTag             = dependencyTag{name: "java9lib"}
+	pluginTag               = dependencyTag{name: "plugin"}
+	errorpronePluginTag     = dependencyTag{name: "errorprone-plugin"}
+	exportedPluginTag       = dependencyTag{name: "exported-plugin"}
+	bootClasspathTag        = dependencyTag{name: "bootclasspath"}
+	systemModulesTag        = dependencyTag{name: "system modules"}
+	frameworkResTag         = dependencyTag{name: "framework-res"}
+	kotlinStdlibTag         = dependencyTag{name: "kotlin-stdlib"}
+	kotlinAnnotationsTag    = dependencyTag{name: "kotlin-annotations"}
+	proguardRaiseTag        = dependencyTag{name: "proguard-raise"}
+	certificateTag          = dependencyTag{name: "certificate"}
+	instrumentationForTag   = dependencyTag{name: "instrumentation_for"}
+	extraLintCheckTag       = dependencyTag{name: "extra-lint-check"}
+	jniLibTag               = dependencyTag{name: "jnilib"}
+	syspropPublicStubDepTag = dependencyTag{name: "sysprop public stub"}
+	jniInstallTag           = installDependencyTag{name: "jni install"}
+	binaryInstallTag        = installDependencyTag{name: "binary install"}
+	usesLibTag              = makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion)
+	usesLibCompat28Tag      = makeUsesLibraryDependencyTag(28)
+	usesLibCompat29Tag      = makeUsesLibraryDependencyTag(29)
+	usesLibCompat30Tag      = makeUsesLibraryDependencyTag(30)
 )
 
 func IsLibDepTag(depTag blueprint.DependencyTag) bool {
@@ -813,35 +825,17 @@
 		j.linter.deps(ctx)
 
 		sdkDeps(ctx, sdkContext(j), j.dexer)
-	}
 
-	syspropPublicStubs := syspropPublicStubs(ctx.Config())
-
-	// rewriteSyspropLibs validates if a java module can link against platform's sysprop_library,
-	// and redirects dependency to public stub depending on the link type.
-	rewriteSyspropLibs := func(libs []string, prop string) []string {
-		// make a copy
-		ret := android.CopyOf(libs)
-
-		for idx, lib := range libs {
-			stub, ok := syspropPublicStubs[lib]
-
-			if !ok {
-				continue
-			}
-
-			linkType, _ := j.getLinkType(ctx.ModuleName())
-			// only platform modules can use internal props
-			if linkType != javaPlatform {
-				ret[idx] = stub
-			}
+		if j.deviceProperties.SyspropPublicStub != "" {
+			// This is a sysprop implementation library that has a corresponding sysprop public
+			// stubs library, and a dependency on it so that dependencies on the implementation can
+			// be forwarded to the public stubs library when necessary.
+			ctx.AddVariationDependencies(nil, syspropPublicStubDepTag, j.deviceProperties.SyspropPublicStub)
 		}
-
-		return ret
 	}
 
-	libDeps := ctx.AddVariationDependencies(nil, libTag, rewriteSyspropLibs(j.properties.Libs, "libs")...)
-	ctx.AddVariationDependencies(nil, staticLibTag, rewriteSyspropLibs(j.properties.Static_libs, "static_libs")...)
+	libDeps := ctx.AddVariationDependencies(nil, libTag, j.properties.Libs...)
+	ctx.AddVariationDependencies(nil, staticLibTag, j.properties.Static_libs...)
 
 	// Add dependency on libraries that provide additional hidden api annotations.
 	ctx.AddVariationDependencies(nil, hiddenApiAnnotationsTag, j.properties.Hiddenapi_additional_annotations...)
@@ -856,15 +850,11 @@
 		//      if true, enable enforcement
 		//    PRODUCT_INTER_PARTITION_JAVA_LIBRARY_ALLOWLIST
 		//      exception list of java_library names to allow inter-partition dependency
-		for idx, lib := range j.properties.Libs {
+		for idx := range j.properties.Libs {
 			if libDeps[idx] == nil {
 				continue
 			}
 
-			if _, ok := syspropPublicStubs[lib]; ok {
-				continue
-			}
-
 			if javaDep, ok := libDeps[idx].(javaSdkLibraryEnforceContext); ok {
 				// java_sdk_library is always allowed at inter-partition dependency.
 				// So, skip check.
@@ -1134,6 +1124,8 @@
 		}
 	}
 
+	linkType, _ := j.getLinkType(ctx.ModuleName())
+
 	ctx.VisitDirectDeps(func(module android.Module) {
 		otherName := ctx.OtherModuleName(module)
 		tag := ctx.OtherModuleDependencyTag(module)
@@ -1156,6 +1148,14 @@
 			}
 		} else if ctx.OtherModuleHasProvider(module, JavaInfoProvider) {
 			dep := ctx.OtherModuleProvider(module, JavaInfoProvider).(JavaInfo)
+			if linkType != javaPlatform &&
+				ctx.OtherModuleHasProvider(module, SyspropPublicStubInfoProvider) {
+				// dep is a sysprop implementation library, but this module is not linking against
+				// the platform, so it gets the sysprop public stubs library instead.  Replace
+				// dep with the JavaInfo from the SyspropPublicStubInfoProvider.
+				syspropDep := ctx.OtherModuleProvider(module, SyspropPublicStubInfoProvider).(SyspropPublicStubInfo)
+				dep = syspropDep.JavaInfo
+			}
 			switch tag {
 			case bootClasspathTag:
 				deps.bootClasspath = append(deps.bootClasspath, dep.HeaderJars...)
@@ -1214,6 +1214,12 @@
 				deps.kotlinStdlib = append(deps.kotlinStdlib, dep.HeaderJars...)
 			case kotlinAnnotationsTag:
 				deps.kotlinAnnotations = dep.HeaderJars
+			case syspropPublicStubDepTag:
+				// This is a sysprop implementation library, forward the JavaInfoProvider from
+				// the corresponding sysprop public stub library as SyspropPublicStubInfoProvider.
+				ctx.SetProvider(SyspropPublicStubInfoProvider, SyspropPublicStubInfo{
+					JavaInfo: dep,
+				})
 			}
 		} else if dep, ok := module.(android.SourceFileProducer); ok {
 			switch tag {
@@ -1818,47 +1824,51 @@
 		}
 	}
 
-	if ctx.Device() && j.hasCode(ctx) &&
-		(Bool(j.properties.Installable) || Bool(j.dexProperties.Compile_dex)) {
-		if j.shouldInstrumentStatic(ctx) {
-			j.dexer.extraProguardFlagFiles = append(j.dexer.extraProguardFlagFiles,
-				android.PathForSource(ctx, "build/make/core/proguard.jacoco.flags"))
-		}
-		// Dex compilation
-		var dexOutputFile android.OutputPath
-		dexOutputFile = j.dexer.compileDex(ctx, flags, j.minSdkVersion(), outputFile, jarName)
-		if ctx.Failed() {
-			return
-		}
-
-		// Hidden API CSV generation and dex encoding
-		dexOutputFile = j.hiddenAPIExtractAndEncode(ctx, dexOutputFile, j.implementationJarFile,
-			proptools.Bool(j.dexProperties.Uncompress_dex))
-
-		// merge dex jar with resources if necessary
-		if j.resourceJar != nil {
-			jars := android.Paths{dexOutputFile, j.resourceJar}
-			combinedJar := android.PathForModuleOut(ctx, "dex-withres", jarName).OutputPath
-			TransformJarsToJar(ctx, combinedJar, "for dex resources", jars, android.OptionalPath{},
-				false, nil, nil)
-			if *j.dexProperties.Uncompress_dex {
-				combinedAlignedJar := android.PathForModuleOut(ctx, "dex-withres-aligned", jarName).OutputPath
-				TransformZipAlign(ctx, combinedAlignedJar, combinedJar)
-				dexOutputFile = combinedAlignedJar
-			} else {
-				dexOutputFile = combinedJar
+	if ctx.Device() && (Bool(j.properties.Installable) || Bool(j.dexProperties.Compile_dex)) {
+		if j.hasCode(ctx) {
+			if j.shouldInstrumentStatic(ctx) {
+				j.dexer.extraProguardFlagFiles = append(j.dexer.extraProguardFlagFiles,
+					android.PathForSource(ctx, "build/make/core/proguard.jacoco.flags"))
 			}
+			// Dex compilation
+			var dexOutputFile android.OutputPath
+			dexOutputFile = j.dexer.compileDex(ctx, flags, j.minSdkVersion(), outputFile, jarName)
+			if ctx.Failed() {
+				return
+			}
+
+			// Hidden API CSV generation and dex encoding
+			dexOutputFile = j.hiddenAPIExtractAndEncode(ctx, dexOutputFile, j.implementationJarFile,
+				proptools.Bool(j.dexProperties.Uncompress_dex))
+
+			// merge dex jar with resources if necessary
+			if j.resourceJar != nil {
+				jars := android.Paths{dexOutputFile, j.resourceJar}
+				combinedJar := android.PathForModuleOut(ctx, "dex-withres", jarName).OutputPath
+				TransformJarsToJar(ctx, combinedJar, "for dex resources", jars, android.OptionalPath{},
+					false, nil, nil)
+				if *j.dexProperties.Uncompress_dex {
+					combinedAlignedJar := android.PathForModuleOut(ctx, "dex-withres-aligned", jarName).OutputPath
+					TransformZipAlign(ctx, combinedAlignedJar, combinedJar)
+					dexOutputFile = combinedAlignedJar
+				} else {
+					dexOutputFile = combinedJar
+				}
+			}
+
+			j.dexJarFile = dexOutputFile
+
+			// Dexpreopting
+			j.dexpreopt(ctx, dexOutputFile)
+
+			outputFile = dexOutputFile
+		} else {
+			// There is no code to compile into a dex jar, make sure the resources are propagated
+			// to the APK if this is an app.
+			outputFile = implementationAndResourcesJar
+			j.dexJarFile = j.resourceJar
 		}
 
-		j.dexJarFile = dexOutputFile
-
-		// Dexpreopting
-		j.dexpreopt(ctx, dexOutputFile)
-
-		j.maybeStrippedDexJarFile = dexOutputFile
-
-		outputFile = dexOutputFile
-
 		if ctx.Failed() {
 			return
 		}
@@ -3183,8 +3193,7 @@
 
 	properties DexImportProperties
 
-	dexJarFile              android.Path
-	maybeStrippedDexJarFile android.Path
+	dexJarFile android.Path
 
 	dexpreopter
 
@@ -3271,8 +3280,6 @@
 
 	j.dexpreopt(ctx, dexOutputFile)
 
-	j.maybeStrippedDexJarFile = dexOutputFile
-
 	if apexInfo.IsForPlatform() {
 		ctx.InstallFile(android.PathForModuleInstall(ctx, "framework"),
 			j.Stem()+".jar", dexOutputFile)
diff --git a/java/java_test.go b/java/java_test.go
index 8407f24..bb51ebc 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -114,20 +114,26 @@
 	pathCtx := android.PathContextForTesting(config)
 	dexpreopt.SetTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx))
 
+	runWithErrors(t, ctx, config, pattern)
+
+	return ctx, config
+}
+
+func runWithErrors(t *testing.T, ctx *android.TestContext, config android.Config, pattern string) {
 	ctx.Register()
 	_, errs := ctx.ParseBlueprintsFiles("Android.bp")
 	if len(errs) > 0 {
 		android.FailIfNoMatchingErrors(t, pattern, errs)
-		return ctx, config
+		return
 	}
 	_, errs = ctx.PrepareBuildActions(config)
 	if len(errs) > 0 {
 		android.FailIfNoMatchingErrors(t, pattern, errs)
-		return ctx, config
+		return
 	}
 
 	t.Fatalf("missing expected error %q (0 errors are returned)", pattern)
-	return ctx, config
+	return
 }
 
 func testJavaWithFS(t *testing.T, bp string, fs map[string][]byte) (*android.TestContext, android.Config) {
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 90823a0..30d120d 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -1772,6 +1772,8 @@
 	android.ApexModuleBase
 	android.SdkBase
 
+	hiddenAPI
+
 	properties sdkLibraryImportProperties
 
 	// Map from api scope to the scope specific property structure.
@@ -1786,6 +1788,9 @@
 	// The reference to the xml permissions module created by the source module.
 	// Is nil if the source module does not exist.
 	xmlPermissionsFileModule *sdkLibraryXml
+
+	// Path to the dex implementation jar obtained from the prebuilt_apex, if any.
+	dexJarFile android.Path
 }
 
 var _ SdkLibraryDependency = (*SdkLibraryImport)(nil)
@@ -1982,6 +1987,8 @@
 func (module *SdkLibraryImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	module.generateCommonBuildActions(ctx)
 
+	var deapexerModule android.Module
+
 	// Record the paths to the prebuilt stubs library and stubs source.
 	ctx.VisitDirectDeps(func(to android.Module) {
 		tag := ctx.OtherModuleDependencyTag(to)
@@ -2007,6 +2014,11 @@
 				ctx.ModuleErrorf("xml permissions file module must be of type *sdkLibraryXml but was %T", to)
 			}
 		}
+
+		// Save away the `deapexer` module on which this depends, if any.
+		if tag == android.DeapexerTag {
+			deapexerModule = to
+		}
 	})
 
 	// Populate the scope paths with information from the properties.
@@ -2019,6 +2031,32 @@
 		paths.currentApiFilePath = android.OptionalPathForModuleSrc(ctx, scopeProperties.Current_api)
 		paths.removedApiFilePath = android.OptionalPathForModuleSrc(ctx, scopeProperties.Removed_api)
 	}
+
+	if ctx.Device() {
+		// If this is a variant created for a prebuilt_apex then use the dex implementation jar
+		// obtained from the associated deapexer module.
+		ai := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+		if ai.ForPrebuiltApex {
+			if deapexerModule == nil {
+				// This should never happen as a variant for a prebuilt_apex is only created if the
+				// deapxer module has been configured to export the dex implementation jar for this module.
+				ctx.ModuleErrorf("internal error: module %q does not depend on a `deapexer` module for prebuilt_apex %q",
+					module.Name(), ai.ApexVariationName)
+			}
+
+			// Get the path of the dex implementation jar from the `deapexer` module.
+			di := ctx.OtherModuleProvider(deapexerModule, android.DeapexerProvider).(android.DeapexerInfo)
+			if dexOutputPath := di.PrebuiltExportPath(module.BaseModuleName(), ".dexjar"); dexOutputPath != nil {
+				module.dexJarFile = dexOutputPath
+				module.initHiddenAPI(ctx, module.configurationName)
+				module.hiddenAPIExtractInformation(ctx, dexOutputPath, module.findScopePaths(apiScopePublic).stubsImplPath[0])
+			} 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.
+				ctx.ModuleErrorf("internal error: no dex implementation jar available from prebuilt_apex %q", deapexerModule.Name())
+			}
+		}
+	}
 }
 
 func (module *SdkLibraryImport) sdkJars(ctx android.BaseModuleContext, sdkVersion sdkSpec, headerJars bool) android.Paths {
@@ -2051,6 +2089,11 @@
 
 // to satisfy UsesLibraryDependency interface
 func (module *SdkLibraryImport) DexJarBuildPath() android.Path {
+	// The dex implementation jar extracted from the .apex file should be used in preference to the
+	// source.
+	if module.dexJarFile != nil {
+		return module.dexJarFile
+	}
 	if module.implLibraryModule == nil {
 		return nil
 	} else {
diff --git a/java/sdk_library_external.go b/java/sdk_library_external.go
index 2934936..0acaa13 100644
--- a/java/sdk_library_external.go
+++ b/java/sdk_library_external.go
@@ -75,10 +75,15 @@
 	return inList(j.Name(), ctx.Config().InterPartitionJavaLibraryAllowList())
 }
 
+func (j *Module) syspropWithPublicStubs() bool {
+	return j.deviceProperties.SyspropPublicStub != ""
+}
+
 type javaSdkLibraryEnforceContext interface {
 	Name() string
 	allowListedInterPartitionJavaLibrary(ctx android.EarlyModuleContext) bool
 	partitionGroup(ctx android.EarlyModuleContext) partitionGroup
+	syspropWithPublicStubs() bool
 }
 
 var _ javaSdkLibraryEnforceContext = (*Module)(nil)
@@ -88,6 +93,10 @@
 		return
 	}
 
+	if dep.syspropWithPublicStubs() {
+		return
+	}
+
 	// If product interface is not enforced, skip check between system and product partition.
 	// But still need to check between product and vendor partition because product interface flag
 	// just represents enforcement between product and system, and vendor interface enforcement
diff --git a/java/sysprop.go b/java/sysprop.go
deleted file mode 100644
index e41aef6..0000000
--- a/java/sysprop.go
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// 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 java
-
-// This file contains a map to redirect dependencies towards sysprop_library. If a sysprop_library
-// is owned by Platform, and the client module links against system API, the public stub of the
-// sysprop_library should be used. The map will contain public stub names of sysprop_libraries.
-
-import (
-	"sync"
-
-	"android/soong/android"
-)
-
-type syspropLibraryInterface interface {
-	BaseModuleName() string
-	Owner() string
-	HasPublicStub() bool
-	JavaPublicStubName() string
-}
-
-var (
-	syspropPublicStubsKey  = android.NewOnceKey("syspropPublicStubsJava")
-	syspropPublicStubsLock sync.Mutex
-)
-
-func init() {
-	android.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.BottomUp("sysprop_java", SyspropMutator).Parallel()
-	})
-}
-
-func syspropPublicStubs(config android.Config) map[string]string {
-	return config.Once(syspropPublicStubsKey, func() interface{} {
-		return make(map[string]string)
-	}).(map[string]string)
-}
-
-// gather list of sysprop libraries owned by platform.
-func SyspropMutator(mctx android.BottomUpMutatorContext) {
-	if m, ok := mctx.Module().(syspropLibraryInterface); ok {
-		if m.Owner() != "Platform" || !m.HasPublicStub() {
-			return
-		}
-
-		syspropPublicStubs := syspropPublicStubs(mctx.Config())
-		syspropPublicStubsLock.Lock()
-		defer syspropPublicStubsLock.Unlock()
-
-		syspropPublicStubs[m.BaseModuleName()] = m.JavaPublicStubName()
-	}
-}
diff --git a/java/testing.go b/java/testing.go
index 781106f..bfa1e6b 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -240,6 +240,7 @@
 }
 
 func CheckHiddenAPIRuleInputs(t *testing.T, expected string, hiddenAPIRule android.TestingBuildParams) {
+	t.Helper()
 	actual := strings.TrimSpace(strings.Join(android.NormalizePathsForTesting(hiddenAPIRule.Implicits), "\n"))
 	expected = strings.TrimSpace(expected)
 	if actual != expected {
diff --git a/shared/Android.bp b/shared/Android.bp
index 5aa9d54..c79bc2b 100644
--- a/shared/Android.bp
+++ b/shared/Android.bp
@@ -6,6 +6,7 @@
     name: "soong-shared",
     pkgPath: "android/soong/shared",
     srcs: [
+        "env.go",
         "paths.go",
     ],
     deps: [
diff --git a/env/env.go b/shared/env.go
similarity index 89%
rename from env/env.go
rename to shared/env.go
index 735a38a..7900daa 100644
--- a/env/env.go
+++ b/shared/env.go
@@ -12,15 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// env implements the environment JSON file handling for the soong_env command line tool run before
-// the builder and for the env writer in the builder.
-package env
+// Implements the environment JSON file handling for serializing the
+// environment variables that were used in soong_build so that soong_ui can
+// check whether they have changed
+package shared
 
 import (
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
-	"os"
 	"sort"
 )
 
@@ -57,7 +57,7 @@
 // Reads and deserializes a Soong environment file located at the given file path to determine its
 // staleness. If any environment variable values have changed, it prints them out and returns true.
 // Failing to read or parse the file also causes it to return true.
-func StaleEnvFile(filepath string) (bool, error) {
+func StaleEnvFile(filepath string, getenv func(string) string) (bool, error) {
 	data, err := ioutil.ReadFile(filepath)
 	if err != nil {
 		return true, err
@@ -74,7 +74,7 @@
 	for _, entry := range contents {
 		key := entry.Key
 		old := entry.Value
-		cur := os.Getenv(key)
+		cur := getenv(key)
 		if old != cur {
 			changed = append(changed, fmt.Sprintf("%s (%q -> %q)", key, old, cur))
 		}
diff --git a/sysprop/sysprop_library.go b/sysprop/sysprop_library.go
index 2ccce15..4ad68ea 100644
--- a/sysprop/sysprop_library.go
+++ b/sysprop/sysprop_library.go
@@ -37,9 +37,10 @@
 }
 
 type syspropGenProperties struct {
-	Srcs  []string `android:"path"`
-	Scope string
-	Name  *string
+	Srcs      []string `android:"path"`
+	Scope     string
+	Name      *string
+	Check_api *string
 }
 
 type syspropJavaGenRule struct {
@@ -68,10 +69,6 @@
 func init() {
 	pctx.HostBinToolVariable("soongZipCmd", "soong_zip")
 	pctx.HostBinToolVariable("syspropJavaCmd", "sysprop_java")
-
-	android.PreArchMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.BottomUp("sysprop_deps", syspropDepsMutator).Parallel()
-	})
 }
 
 // syspropJavaGenRule module generates srcjar containing generated java APIs.
@@ -103,6 +100,12 @@
 	}
 }
 
+func (g *syspropJavaGenRule) DepsMutator(ctx android.BottomUpMutatorContext) {
+	// Add a dependency from the stubs to sysprop library so that the generator rule can depend on
+	// the check API rule of the sysprop library.
+	ctx.AddFarVariationDependencies(nil, nil, proptools.String(g.properties.Check_api))
+}
+
 func (g *syspropJavaGenRule) OutputFiles(tag string) (android.Paths, error) {
 	switch tag {
 	case "":
@@ -157,9 +160,6 @@
 	// If set to true, build a variant of the module for the host.  Defaults to false.
 	Host_supported *bool
 
-	// Whether public stub exists or not.
-	Public_stub *bool `blueprint:"mutated"`
-
 	Cpp struct {
 		// Minimum sdk version that the artifact should support when it runs as part of mainline modules(APEX).
 		// Forwarded to cc_library.min_sdk_version
@@ -202,11 +202,8 @@
 	return "lib" + m.BaseModuleName()
 }
 
-func (m *syspropLibrary) JavaPublicStubName() string {
-	if proptools.Bool(m.properties.Public_stub) {
-		return m.BaseModuleName() + "_public"
-	}
-	return ""
+func (m *syspropLibrary) javaPublicStubName() string {
+	return m.BaseModuleName() + "_public"
 }
 
 func (m *syspropLibrary) javaGenModuleName() string {
@@ -221,10 +218,6 @@
 	return m.ModuleBase.Name()
 }
 
-func (m *syspropLibrary) HasPublicStub() bool {
-	return proptools.Bool(m.properties.Public_stub)
-}
-
 func (m *syspropLibrary) CurrentSyspropApiFile() android.OptionalPath {
 	return m.currentApiFile
 }
@@ -399,16 +392,17 @@
 }
 
 type javaLibraryProperties struct {
-	Name             *string
-	Srcs             []string
-	Soc_specific     *bool
-	Device_specific  *bool
-	Product_specific *bool
-	Required         []string
-	Sdk_version      *string
-	Installable      *bool
-	Libs             []string
-	Stem             *string
+	Name              *string
+	Srcs              []string
+	Soc_specific      *bool
+	Device_specific   *bool
+	Product_specific  *bool
+	Required          []string
+	Sdk_version       *string
+	Installable       *bool
+	Libs              []string
+	Stem              *string
+	SyspropPublicStub string
 }
 
 func syspropLibraryHook(ctx android.LoadHookContext, m *syspropLibrary) {
@@ -490,35 +484,42 @@
 	// Contrast to C++, syspropJavaGenRule module will generate srcjar and the srcjar will be fed
 	// to Java implementation library.
 	ctx.CreateModule(syspropJavaGenFactory, &syspropGenProperties{
-		Srcs:  m.properties.Srcs,
-		Scope: scope,
-		Name:  proptools.StringPtr(m.javaGenModuleName()),
-	})
-
-	ctx.CreateModule(java.LibraryFactory, &javaLibraryProperties{
-		Name:             proptools.StringPtr(m.BaseModuleName()),
-		Srcs:             []string{":" + m.javaGenModuleName()},
-		Soc_specific:     proptools.BoolPtr(ctx.SocSpecific()),
-		Device_specific:  proptools.BoolPtr(ctx.DeviceSpecific()),
-		Product_specific: proptools.BoolPtr(ctx.ProductSpecific()),
-		Installable:      m.properties.Installable,
-		Sdk_version:      proptools.StringPtr("core_current"),
-		Libs:             []string{javaSyspropStub},
+		Srcs:      m.properties.Srcs,
+		Scope:     scope,
+		Name:      proptools.StringPtr(m.javaGenModuleName()),
+		Check_api: proptools.StringPtr(ctx.ModuleName()),
 	})
 
 	// if platform sysprop_library is installed in /system or /system-ext, we regard it as an API
 	// and allow any modules (even from different partition) to link against the sysprop_library.
 	// To do that, we create a public stub and expose it to modules with sdk_version: system_*.
+	var publicStub string
 	if isOwnerPlatform && installedInSystem {
-		m.properties.Public_stub = proptools.BoolPtr(true)
+		publicStub = m.javaPublicStubName()
+	}
+
+	ctx.CreateModule(java.LibraryFactory, &javaLibraryProperties{
+		Name:              proptools.StringPtr(m.BaseModuleName()),
+		Srcs:              []string{":" + m.javaGenModuleName()},
+		Soc_specific:      proptools.BoolPtr(ctx.SocSpecific()),
+		Device_specific:   proptools.BoolPtr(ctx.DeviceSpecific()),
+		Product_specific:  proptools.BoolPtr(ctx.ProductSpecific()),
+		Installable:       m.properties.Installable,
+		Sdk_version:       proptools.StringPtr("core_current"),
+		Libs:              []string{javaSyspropStub},
+		SyspropPublicStub: publicStub,
+	})
+
+	if publicStub != "" {
 		ctx.CreateModule(syspropJavaGenFactory, &syspropGenProperties{
-			Srcs:  m.properties.Srcs,
-			Scope: "public",
-			Name:  proptools.StringPtr(m.javaGenPublicStubName()),
+			Srcs:      m.properties.Srcs,
+			Scope:     "public",
+			Name:      proptools.StringPtr(m.javaGenPublicStubName()),
+			Check_api: proptools.StringPtr(ctx.ModuleName()),
 		})
 
 		ctx.CreateModule(java.LibraryFactory, &javaLibraryProperties{
-			Name:        proptools.StringPtr(m.JavaPublicStubName()),
+			Name:        proptools.StringPtr(publicStub),
 			Srcs:        []string{":" + m.javaGenPublicStubName()},
 			Installable: proptools.BoolPtr(false),
 			Sdk_version: proptools.StringPtr("core_current"),
@@ -537,15 +538,3 @@
 		*libraries = append(*libraries, "//"+ctx.ModuleDir()+":"+ctx.ModuleName())
 	}
 }
-
-// syspropDepsMutator adds dependencies from java implementation library to sysprop library.
-// java implementation library then depends on check API rule of sysprop library.
-func syspropDepsMutator(ctx android.BottomUpMutatorContext) {
-	if m, ok := ctx.Module().(*syspropLibrary); ok {
-		ctx.AddReverseDependency(m, nil, m.javaGenModuleName())
-
-		if proptools.Bool(m.properties.Public_stub) {
-			ctx.AddReverseDependency(m, nil, m.javaGenPublicStubName())
-		}
-	}
-}
diff --git a/sysprop/sysprop_test.go b/sysprop/sysprop_test.go
index 5cb9e64..9d914e3 100644
--- a/sysprop/sysprop_test.go
+++ b/sysprop/sysprop_test.go
@@ -26,7 +26,6 @@
 	"strings"
 	"testing"
 
-	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
 
@@ -61,16 +60,10 @@
 	java.RegisterRequiredBuildComponentsForTest(ctx)
 
 	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
-	ctx.PreArchMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.BottomUp("sysprop_deps", syspropDepsMutator).Parallel()
-	})
 
 	android.RegisterPrebuiltMutators(ctx)
 
 	cc.RegisterRequiredBuildComponentsForTest(ctx)
-	ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.BottomUp("sysprop_java", java.SyspropMutator).Parallel()
-	})
 
 	ctx.RegisterModuleType("sysprop_library", syspropLibraryFactory)
 
@@ -392,15 +385,9 @@
 	}
 
 	// Java modules linking against system API should use public stub
-	javaSystemApiClient := ctx.ModuleForTests("java-platform", "android_common")
-	publicStubFound := false
-	ctx.VisitDirectDeps(javaSystemApiClient.Module(), func(dep blueprint.Module) {
-		if dep.Name() == "sysprop-platform_public" {
-			publicStubFound = true
-		}
-	})
-	if !publicStubFound {
-		t.Errorf("system api client should use public stub")
+	javaSystemApiClient := ctx.ModuleForTests("java-platform", "android_common").Rule("javac")
+	syspropPlatformPublic := ctx.ModuleForTests("sysprop-platform_public", "android_common").Description("for turbine")
+	if g, w := javaSystemApiClient.Implicits.Strings(), syspropPlatformPublic.Output.String(); !android.InList(w, g) {
+		t.Errorf("system api client should use public stub %q, got %q", w, g)
 	}
-
 }
diff --git a/ui/build/soong.go b/ui/build/soong.go
index 899ab5d..125dbcc 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -19,8 +19,8 @@
 	"os"
 	"path/filepath"
 	"strconv"
-	"strings"
 
+	"android/soong/shared"
 	soong_metrics_proto "android/soong/ui/metrics/metrics_proto"
 
 	"github.com/golang/protobuf/proto"
@@ -79,29 +79,12 @@
 		defer ctx.EndTrace()
 
 		envFile := filepath.Join(config.SoongOutDir(), ".soong.environment")
-		envTool := filepath.Join(config.SoongOutDir(), ".bootstrap/bin/soong_env")
-		if _, err := os.Stat(envFile); err == nil {
-			if _, err := os.Stat(envTool); err == nil {
-				cmd := Command(ctx, config, "soong_env", envTool, envFile)
-				cmd.Sandbox = soongSandbox
-
-				var buf strings.Builder
-				cmd.Stdout = &buf
-				cmd.Stderr = &buf
-				if err := cmd.Run(); err != nil {
-					ctx.Verboseln("soong_env failed, forcing manifest regeneration")
-					os.Remove(envFile)
-				}
-
-				if buf.Len() > 0 {
-					ctx.Verboseln(buf.String())
-				}
-			} else {
-				ctx.Verboseln("Missing soong_env tool, forcing manifest regeneration")
-				os.Remove(envFile)
-			}
-		} else if !os.IsNotExist(err) {
-			ctx.Fatalf("Failed to stat %f: %v", envFile, err)
+		getenv := func(k string) string {
+			v, _ := config.Environment().Get(k)
+			return v
+		}
+		if stale, _ := shared.StaleEnvFile(envFile, getenv); stale {
+			os.Remove(envFile)
 		}
 	}()