Merge changes I8f4eaed1,I358a62d3

* changes:
  Dexpreopt standalone system server jars from prebuilts.
  Dexpreopt standalone system server jars.
diff --git a/android/androidmk.go b/android/androidmk.go
index 4f6e24c..72b6584 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -518,7 +518,7 @@
 	a.AddStrings("LOCAL_TARGET_REQUIRED_MODULES", a.Target_required...)
 
 	// If the install rule was generated by Soong tell Make about it.
-	if amod.InstallBypassMake() && len(base.katiInstalls) > 0 {
+	if len(base.katiInstalls) > 0 {
 		// Assume the primary install file is last since it probably needs to depend on any other
 		// installed files.  If that is not the case we can add a method to specify the primary
 		// installed file.
diff --git a/android/arch.go b/android/arch.go
index 3cc5e82..e08fd5c 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -168,7 +168,7 @@
 	return archType
 }
 
-// ArchTypeList returns the a slice copy of the 4 supported ArchTypes for arm,
+// ArchTypeList returns a slice copy of the 4 supported ArchTypes for arm,
 // arm64, x86 and x86_64.
 func ArchTypeList() []ArchType {
 	return append([]ArchType(nil), archTypeList...)
@@ -408,7 +408,7 @@
 
 	// addPathDepsForProps does not descend into sub structs, so we need to descend into the
 	// arch-specific properties ourselves
-	properties := []interface{}{}
+	var properties []interface{}
 	for _, archProperties := range m.archProperties {
 		for _, archProps := range archProperties {
 			archPropValues := reflect.ValueOf(archProps).Elem()
@@ -995,8 +995,11 @@
 
 	// Store the original list of top level property structs
 	base.generalProperties = m.GetProperties()
+	if len(base.archProperties) != 0 {
+		panic(fmt.Errorf("module %s already has archProperties", m.Name()))
+	}
 
-	for _, properties := range base.generalProperties {
+	getStructType := func(properties interface{}) reflect.Type {
 		propertiesValue := reflect.ValueOf(properties)
 		t := propertiesValue.Type()
 		if propertiesValue.Kind() != reflect.Ptr {
@@ -1006,10 +1009,14 @@
 
 		propertiesValue = propertiesValue.Elem()
 		if propertiesValue.Kind() != reflect.Struct {
-			panic(fmt.Errorf("properties must be a pointer to a struct, got %T",
+			panic(fmt.Errorf("properties must be a pointer to a struct, got a pointer to %T",
 				propertiesValue.Interface()))
 		}
+		return t
+	}
 
+	for _, properties := range base.generalProperties {
+		t := getStructType(properties)
 		// Get or create the arch-specific property struct types for this property struct type.
 		archPropTypes := archPropTypeMap.Once(NewCustomOnceKey(t), func() interface{} {
 			return createArchPropTypeDesc(t)
diff --git a/android/bazel.go b/android/bazel.go
index 8c63204..9a966b6 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -245,6 +245,7 @@
 		"build/soong/cc/libbuildversion":                     Bp2BuildDefaultTrue, // Skip tests subdir
 		"build/soong/cc/ndkstubgen":                          Bp2BuildDefaultTrue,
 		"build/soong/cc/symbolfile":                          Bp2BuildDefaultTrue,
+		"build/soong/scripts":                                Bp2BuildDefaultTrueRecursively,
 		"cts/common/device-side/nativetesthelper/jni":        Bp2BuildDefaultTrueRecursively,
 		"development/apps/DevelopmentSettings":               Bp2BuildDefaultTrue,
 		"development/apps/Fallback":                          Bp2BuildDefaultTrue,
@@ -296,6 +297,7 @@
 		"external/libcxx":                                    Bp2BuildDefaultTrueRecursively,
 		"external/libcxxabi":                                 Bp2BuildDefaultTrueRecursively,
 		"external/libevent":                                  Bp2BuildDefaultTrueRecursively,
+		"external/libpng":                                    Bp2BuildDefaultTrueRecursively,
 		"external/lz4/lib":                                   Bp2BuildDefaultTrue,
 		"external/lzma/C":                                    Bp2BuildDefaultTrueRecursively,
 		"external/mdnsresponder":                             Bp2BuildDefaultTrueRecursively,
@@ -303,9 +305,9 @@
 		"external/pcre":                                      Bp2BuildDefaultTrueRecursively,
 		"external/protobuf":                                  Bp2BuildDefaultTrueRecursively,
 		"external/python/six":                                Bp2BuildDefaultTrueRecursively,
-		"external/selinux/libsepol":                          Bp2BuildDefaultTrueRecursively,
 		"external/scudo":                                     Bp2BuildDefaultTrueRecursively,
 		"external/selinux/libselinux":                        Bp2BuildDefaultTrueRecursively,
+		"external/selinux/libsepol":                          Bp2BuildDefaultTrueRecursively,
 		"external/zlib":                                      Bp2BuildDefaultTrueRecursively,
 		"external/zstd":                                      Bp2BuildDefaultTrueRecursively,
 		"frameworks/base/media/tests/MediaDump":              Bp2BuildDefaultTrue,
@@ -322,6 +324,7 @@
 		"packages/apps/HTMLViewer":                           Bp2BuildDefaultTrue,
 		"packages/apps/Protips":                              Bp2BuildDefaultTrue,
 		"packages/modules/adb":                               Bp2BuildDefaultTrue,
+		"packages/modules/adb/apex":                          Bp2BuildDefaultTrue,
 		"packages/modules/adb/crypto":                        Bp2BuildDefaultTrueRecursively,
 		"packages/modules/adb/libs":                          Bp2BuildDefaultTrueRecursively,
 		"packages/modules/adb/pairing_auth":                  Bp2BuildDefaultTrueRecursively,
@@ -333,7 +336,7 @@
 		"packages/services/Car/tests/SampleRearViewCamera":   Bp2BuildDefaultTrue,
 		"prebuilts/clang/host/linux-x86":                     Bp2BuildDefaultTrueRecursively,
 		"system/apex":                                        Bp2BuildDefaultFalse, // TODO(b/207466993): flaky failures
-		"system/core/debuggerd":                              Bp2BuildDefaultTrue,
+		"system/core/debuggerd":                              Bp2BuildDefaultTrueRecursively,
 		"system/core/diagnose_usb":                           Bp2BuildDefaultTrueRecursively,
 		"system/core/libasyncio":                             Bp2BuildDefaultTrue,
 		"system/core/libcrypto_utils":                        Bp2BuildDefaultTrueRecursively,
@@ -361,8 +364,7 @@
 
 	// Per-module denylist to always opt modules out of both bp2build and mixed builds.
 	bp2buildModuleDoNotConvertList = []string{
-		"libnativehelper_compat_libc++",              // Broken compile: implicit declaration of function 'strerror_r' is invalid in C99
-		"art_libdexfile_dex_instruction_list_header", // breaks libart_mterp.armng, header not found
+		"libnativehelper_compat_libc", // Broken compile: implicit declaration of function 'strerror_r' is invalid in C99
 
 		"libandroid_runtime_lazy", // depends on unconverted modules: libbinder_headers
 		"libcmd",                  // depends on unconverted modules: libbinder
@@ -371,14 +373,13 @@
 
 		"libsepol", // TODO(b/207408632): Unsupported case of .l sources in cc library rules
 
-		"get_clang_version_test", // depends on unconverted module: get_clang_version
+		"gen-kotlin-build-file.py", // module has same name as source
 
 		"libbinder",               // TODO(b/188503688): Disabled for some archs,
 		"libactivitymanager_aidl", // TODO(b/207426160): Depends on activity_manager_procstate_aidl, which is an aidl filegroup.
 
-		"libnativehelper_lazy_mts_jni", // depends on unconverted modules: libgmock_ndk
-		"libnativehelper_mts_jni",      // depends on unconverted modules: libgmock_ndk
-		"libnativetesthelper_jni",      // depends on unconverted modules: libgtest_ndk_c++
+		"libnativehelper_lazy_mts_jni", "libnativehelper_mts_jni", // depends on unconverted modules: libgmock_ndk
+		"libnativetesthelper_jni", "libgmock_main_ndk", "libgmock_ndk", // depends on unconverted module: libgtest_ndk_c++
 
 		"statslog-framework-java-gen", "statslog.cpp", "statslog.h", "statslog.rs", "statslog_header.rs", // depends on unconverted modules: stats-log-api-gen
 
@@ -386,31 +387,24 @@
 
 		"libstatslog", // depends on unconverted modules: statslog.cpp, statslog.h, ...
 
-		"libgmock_main_ndk", "libgmock_ndk", // depends on unconverted module: libgtest_ndk_c++
-
 		"cmd",                                                        // depends on unconverted module packagemanager_aidl-cpp, of unsupported type aidl_interface
 		"servicedispatcher",                                          // depends on unconverted module android.debug_aidl, of unsupported type aidl_interface
 		"libutilscallstack",                                          // depends on unconverted module libbacktrace
 		"libbacktrace",                                               // depends on unconverted module libunwindstack
 		"libdebuggerd_handler",                                       // depends on unconverted module libdebuggerd_handler_core
 		"libdebuggerd_handler_core", "libdebuggerd_handler_fallback", // depends on unconverted module libdebuggerd
-		"unwind_for_offline",        // depends on unconverted module libunwindstack_utils
-		"libdebuggerd",              // depends on unconverted modules libdexfile_support, libunwindstack, gwp_asan_crash_handler, libtombstone_proto, libprotobuf-cpp-lite
-		"libdexfile_static",         // depends on libartpalette, libartbase, libdexfile, which are of unsupported type: art_cc_library.
+		"unwind_for_offline", // depends on unconverted module libunwindstack_utils
+		"libdebuggerd",       // depends on unconverted modules libdexfile_support, libunwindstack, gwp_asan_crash_handler, libtombstone_proto, libprotobuf-cpp-lite
+		"libdexfile_static",  // depends on libartpalette, libartbase, libdexfile, which are of unsupported type: art_cc_library.
+
 		"host_bionic_linker_asm",    // depends on extract_linker, a go binary.
 		"host_bionic_linker_script", // depends on extract_linker, a go binary.
+		"static_crasher",            // depends on unconverted modules: libdebuggerd_handler
 
-		"pbtombstone", // depends on libprotobuf-cpp-lite, libtombstone_proto
-		"crash_dump",  // depends on unconverted module libprotobuf-cpp-lite
-
-		"libunwindstack_local", "libunwindstack_utils", // depends on unconverted module libunwindstack
-		"libunwindstack",    // depends on libdexfile_support, of unsupported module type art_cc_library_static
-		"libc_malloc_debug", // depends on unconverted module libunwindstack
+		"pbtombstone", "crash_dump", // depends on libdebuggerd, libunwindstack
 
 		"libbase_ndk", // http://b/186826477, fails to link libctscamera2_jni for device (required for CtsCameraTestCases)
 
-		"lib_linker_config_proto_lite", // contains .proto sources
-
 		"libprotobuf-python",               // contains .proto sources
 		"libprotobuf-internal-protos",      // b/210751803, we don't handle path property for filegroups
 		"libprotobuf-internal-python-srcs", // b/210751803, we don't handle path property for filegroups
@@ -418,10 +412,7 @@
 		"libprotobuf-java-util-full",       // b/210751803, we don't handle path property for filegroups
 		"conscrypt",                        // b/210751803, we don't handle path property for filegroups
 
-		"libseccomp_policy", // b/201094425: depends on func_to_syscall_nrs, which depends on py_binary, which is unsupported in mixed builds.
-		"libfdtrack",        // depends on unconverted module libunwindstack
-
-		"gwp_asan_crash_handler", // cc_library, ld.lld: error: undefined symbol: memset
+		"conv_linker_config", // depends on linker_config_proto, a python lib with proto sources
 
 		"brotli-fuzzer-corpus", // b/202015218: outputs are in location incompatible with bazel genrule handling.
 
@@ -430,14 +421,8 @@
 		"platform_tools_properties",
 		"build_tools_source_properties",
 
-		// Tests. Handle later.
-		"libbionic_tests_headers_posix", // http://b/186024507, cc_library_static, sched.h, time.h not found
-		"libjemalloc5_integrationtest",
-		"libjemalloc5_stresstestlib",
-		"libjemalloc5_unittest",
-
 		// APEX support
-		"com.android.runtime", // http://b/194746715, apex, depends on 'libc_malloc_debug'
+		"com.android.runtime", // depends on unconverted modules: bionic-linker-config, linkerconfig
 
 		"libgtest_ndk_c++",      // b/201816222: Requires sdk_version support.
 		"libgtest_main_ndk_c++", // b/201816222: Requires sdk_version support.
@@ -467,6 +452,8 @@
 	// Per-module denylist to opt modules out of mixed builds. Such modules will
 	// still be generated via bp2build.
 	mixedBuildsDisabledList = []string{
+		"art_libdexfile_dex_instruction_list_header", // breaks libart_mterp.armng, header not found
+
 		"libbrotli",               // http://b/198585397, ld.lld: error: bionic/libc/arch-arm64/generic/bionic/memmove.S:95:(.text+0x10): relocation R_AARCH64_CONDBR19 out of range: -1404176 is not in [-1048576, 1048575]; references __memcpy
 		"minijail_constants_json", // http://b/200899432, bazel-built cc_genrule does not work in mixed build when it is a dependency of another soong module.
 
diff --git a/android/config.go b/android/config.go
index 93b32d1..6573eac 100644
--- a/android/config.go
+++ b/android/config.go
@@ -569,9 +569,6 @@
 
 func (c *config) HostToolPath(ctx PathContext, tool string) Path {
 	path := pathForInstall(ctx, ctx.Config().BuildOS, ctx.Config().BuildArch, "bin", false, tool)
-	if ctx.Config().KatiEnabled() {
-		path = path.ToMakePath()
-	}
 	return path
 }
 
@@ -581,17 +578,11 @@
 		ext = ".dylib"
 	}
 	path := pathForInstall(ctx, ctx.Config().BuildOS, ctx.Config().BuildArch, "lib64", false, lib+ext)
-	if ctx.Config().KatiEnabled() {
-		path = path.ToMakePath()
-	}
 	return path
 }
 
 func (c *config) HostJavaToolPath(ctx PathContext, tool string) Path {
 	path := pathForInstall(ctx, ctx.Config().BuildOS, ctx.Config().BuildArch, "framework", false, tool)
-	if ctx.Config().KatiEnabled() {
-		path = path.ToMakePath()
-	}
 	return path
 }
 
diff --git a/android/defaults.go b/android/defaults.go
index 7b3d38c..5677638 100644
--- a/android/defaults.go
+++ b/android/defaults.go
@@ -92,6 +92,7 @@
 	if module.base().module == nil {
 		panic("InitAndroidModule must be called before InitDefaultableModule")
 	}
+
 	module.setProperties(module.GetProperties(), module.base().variableProperties)
 
 	module.AddProperties(module.defaults())
@@ -118,6 +119,11 @@
 
 type DefaultsModuleBase struct {
 	DefaultableModuleBase
+
+	// Included to support setting bazel_module.label for multiple Soong modules to the same Bazel
+	// target. This is primarily useful for modules that were architecture specific and instead are
+	// handled in Bazel as a select().
+	BazelModuleBase
 }
 
 // The common pattern for defaults modules is to register separate instances of
@@ -160,6 +166,7 @@
 type DefaultsModule interface {
 	Module
 	Defaults
+	Bazelable
 }
 
 func (d *DefaultsModuleBase) properties() []interface{} {
@@ -170,8 +177,7 @@
 	return d.defaultableVariableProperties
 }
 
-func (d *DefaultsModuleBase) GenerateAndroidBuildActions(ctx ModuleContext) {
-}
+func (d *DefaultsModuleBase) GenerateAndroidBuildActions(ctx ModuleContext) {}
 
 // ConvertWithBp2build to fulfill Bazelable interface; however, at this time defaults module are
 // *NOT* converted with bp2build
@@ -186,6 +192,8 @@
 		&ApexProperties{},
 		&distProperties{})
 
+	// Bazel module must be initialized _before_ Defaults to be included in cc_defaults module.
+	InitBazelModule(module)
 	initAndroidModuleBase(module)
 	initProductVariableModule(module)
 	initArchModule(module)
@@ -212,7 +220,6 @@
 	// The applicable licenses property for defaults is 'licenses'.
 	setPrimaryLicensesProperty(module, "licenses", &commonProperties.Licenses)
 
-	base.module = module
 }
 
 var _ Defaults = (*DefaultsModuleBase)(nil)
diff --git a/android/module.go b/android/module.go
index 189781a..6de4165 100644
--- a/android/module.go
+++ b/android/module.go
@@ -429,7 +429,6 @@
 	InstallInRecovery() bool
 	InstallInRoot() bool
 	InstallInVendor() bool
-	InstallBypassMake() bool
 	InstallForceOS() (*OsType, *ArchType)
 
 	RequiredModuleNames() []string
@@ -496,7 +495,6 @@
 	InstallInRecovery() bool
 	InstallInRoot() bool
 	InstallInVendor() bool
-	InstallBypassMake() bool
 	InstallForceOS() (*OsType, *ArchType)
 	HideFromMake()
 	IsHideFromMake() bool
@@ -1704,10 +1702,6 @@
 	return false
 }
 
-func (m *ModuleBase) InstallBypassMake() bool {
-	return true
-}
-
 func (m *ModuleBase) InstallForceOS() (*OsType, *ArchType) {
 	return nil, nil
 }
@@ -2096,18 +2090,18 @@
 	return GlobFiles(e, globPattern, excludes)
 }
 
-func (b *earlyModuleContext) IsSymlink(path Path) bool {
-	fileInfo, err := b.config.fs.Lstat(path.String())
+func (e *earlyModuleContext) IsSymlink(path Path) bool {
+	fileInfo, err := e.config.fs.Lstat(path.String())
 	if err != nil {
-		b.ModuleErrorf("os.Lstat(%q) failed: %s", path.String(), err)
+		e.ModuleErrorf("os.Lstat(%q) failed: %s", path.String(), err)
 	}
 	return fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink
 }
 
-func (b *earlyModuleContext) Readlink(path Path) string {
-	dest, err := b.config.fs.Readlink(path.String())
+func (e *earlyModuleContext) Readlink(path Path) string {
+	dest, err := e.config.fs.Readlink(path.String())
 	if err != nil {
-		b.ModuleErrorf("os.Readlink(%q) failed: %s", path.String(), err)
+		e.ModuleErrorf("os.Readlink(%q) failed: %s", path.String(), err)
 	}
 	return dest
 }
@@ -2829,10 +2823,6 @@
 	return m.module.InstallInRoot()
 }
 
-func (m *moduleContext) InstallBypassMake() bool {
-	return m.module.InstallBypassMake()
-}
-
 func (m *moduleContext) InstallForceOS() (*OsType, *ArchType) {
 	return m.module.InstallForceOS()
 }
@@ -2857,12 +2847,6 @@
 		return true
 	}
 
-	if m.Device() {
-		if m.Config().KatiEnabled() && !m.InstallBypassMake() {
-			return true
-		}
-	}
-
 	return false
 }
 
@@ -2921,7 +2905,7 @@
 			orderOnlyDeps = deps
 		}
 
-		if m.Config().KatiEnabled() && m.InstallBypassMake() {
+		if m.Config().KatiEnabled() {
 			// When creating the install rule in Soong but embedding in Make, write the rule to a
 			// makefile instead of directly to the ninja file so that main.mk can add the
 			// dependencies from the `required` property that are hard to resolve in Soong.
@@ -2980,7 +2964,7 @@
 	}
 	if !m.skipInstall() {
 
-		if m.Config().KatiEnabled() && m.InstallBypassMake() {
+		if m.Config().KatiEnabled() {
 			// When creating the symlink rule in Soong but embedding in Make, write the rule to a
 			// makefile instead of directly to the ninja file so that main.mk can add the
 			// dependencies from the `required` property that are hard to resolve in Soong.
@@ -3026,7 +3010,7 @@
 	m.module.base().hooks.runInstallHooks(m, nil, fullInstallPath, true)
 
 	if !m.skipInstall() {
-		if m.Config().KatiEnabled() && m.InstallBypassMake() {
+		if m.Config().KatiEnabled() {
 			// When creating the symlink rule in Soong but embedding in Make, write the rule to a
 			// makefile instead of directly to the ninja file so that main.mk can add the
 			// dependencies from the `required` property that are hard to resolve in Soong.
diff --git a/android/module_test.go b/android/module_test.go
index 8607a8d..d9e2c87 100644
--- a/android/module_test.go
+++ b/android/module_test.go
@@ -204,10 +204,6 @@
 	}
 }
 
-func (m *depsModule) InstallBypassMake() bool {
-	return true
-}
-
 func (m *depsModule) GenerateAndroidBuildActions(ctx ModuleContext) {
 	outputFile := PathForModuleOut(ctx, ctx.ModuleName())
 	ctx.Build(pctx, BuildParams{
@@ -450,7 +446,7 @@
 	assertOrderOnlys(symlinkRule("foo"))
 }
 
-func TestInstallBypassMake(t *testing.T) {
+func TestInstallKatiEnabled(t *testing.T) {
 	if runtime.GOOS != "linux" {
 		t.Skip("requires linux")
 	}
diff --git a/android/package_test.go b/android/package_test.go
index 7ea10a4..65c4240 100644
--- a/android/package_test.go
+++ b/android/package_test.go
@@ -11,7 +11,7 @@
 }{
 	// Package default_visibility handling is tested in visibility_test.go
 	{
-		name: "package must not accept visibility and name properties",
+		name: "package must not accept visibility, name or licenses properties",
 		fs: map[string][]byte{
 			"top/Android.bp": []byte(`
 				package {
@@ -48,8 +48,7 @@
 					default_visibility: ["//visibility:private"],
 					default_applicable_licenses: ["license"],
 				}
-
-			        package {
+				package {
 				}`),
 		},
 		expectedErrors: []string{
diff --git a/android/paths.go b/android/paths.go
index e68106c..70e427b 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -112,7 +112,6 @@
 	InstallInDebugRamdisk() bool
 	InstallInRecovery() bool
 	InstallInRoot() bool
-	InstallBypassMake() bool
 	InstallForceOS() (*OsType, *ArchType)
 }
 
@@ -465,9 +464,6 @@
 // PathForGoBinary returns the path to the installed location of a bootstrap_go_binary module.
 func PathForGoBinary(ctx PathContext, goBinary bootstrap.GoBinaryTool) Path {
 	goBinaryInstallDir := pathForInstall(ctx, ctx.Config().BuildOS, ctx.Config().BuildArch, "bin", false)
-	if ctx.Config().KatiEnabled() {
-		goBinaryInstallDir = goBinaryInstallDir.ToMakePath()
-	}
 	rel := Rel(ctx, goBinaryInstallDir.String(), goBinary.InstallPath())
 	return goBinaryInstallDir.Join(ctx, rel)
 }
@@ -1646,8 +1642,8 @@
 	return p
 }
 
-// ToMakePath returns a new InstallPath that points to Make's install directory instead of Soong's,
-// i.e. out/ instead of out/soong/.
+// Deprecated: ToMakePath is a noop, PathForModuleInstall always returns Make paths when building
+// embedded in Make.
 func (p InstallPath) ToMakePath() InstallPath {
 	p.makePath = true
 	return p
@@ -1688,9 +1684,6 @@
 
 func makePathForInstall(ctx ModuleInstallPathContext, os OsType, arch ArchType, partition string, debug bool, pathComponents ...string) InstallPath {
 	ret := pathForInstall(ctx, os, arch, partition, debug, pathComponents...)
-	if ctx.InstallBypassMake() && ctx.Config().KatiEnabled() {
-		ret = ret.ToMakePath()
-	}
 	return ret
 }
 
@@ -1732,7 +1725,10 @@
 		soongOutDir:  ctx.Config().soongOutDir,
 		partitionDir: partionPath,
 		partition:    partition,
-		makePath:     false,
+	}
+
+	if ctx.Config().KatiEnabled() {
+		base.makePath = true
 	}
 
 	return base.Join(ctx, pathComponents...)
@@ -2008,10 +2004,6 @@
 	return m.inRoot
 }
 
-func (m testModuleInstallPathContext) InstallBypassMake() bool {
-	return false
-}
-
 func (m testModuleInstallPathContext) InstallForceOS() (*OsType, *ArchType) {
 	return m.forceOS, m.forceArch
 }
diff --git a/android/paths_test.go b/android/paths_test.go
index 3cad852..2f87977 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -1486,7 +1486,8 @@
 		AssertPathRelativeToTopEquals(t, "install path for soong", "out/soong/target/product/test_device/system/install/path", p)
 	})
 	t.Run("install for make", func(t *testing.T) {
-		p := PathForModuleInstall(ctx, "install/path").ToMakePath()
+		p := PathForModuleInstall(ctx, "install/path")
+		p.makePath = true
 		AssertPathRelativeToTopEquals(t, "install path for make", "out/target/product/test_device/system/install/path", p)
 	})
 	t.Run("output", func(t *testing.T) {
@@ -1500,14 +1501,12 @@
 	t.Run("mixture", func(t *testing.T) {
 		paths := Paths{
 			PathForModuleInstall(ctx, "install/path"),
-			PathForModuleInstall(ctx, "install/path").ToMakePath(),
 			PathForOutput(ctx, "output/path"),
 			PathForSource(ctx, "source/path"),
 		}
 
 		expected := []string{
 			"out/soong/target/product/test_device/system/install/path",
-			"out/target/product/test_device/system/install/path",
 			"out/soong/output/path",
 			"source/path",
 		}
diff --git a/android/rule_builder.go b/android/rule_builder.go
index f8de5fb..1c6b1c0 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -839,14 +839,6 @@
 		// The tool is in the Soong output directory, it will be copied to __SBOX_OUT_DIR__/tools/out
 		return filepath.Join(sboxToolsSubDir, "out", relOutSoong)
 	}
-	if ctx.Config().KatiEnabled() {
-		toolDir = toolDir.ToMakePath()
-		relOut, isRelOut, _ := maybeRelErr(toolDir.String(), path.String())
-		if isRelOut {
-			// The tool is in the Make output directory, it will be copied to __SBOX_OUT_DIR__/tools/out
-			return filepath.Join(sboxToolsSubDir, "out", relOut)
-		}
-	}
 	// The tool is in the source directory, it will be copied to __SBOX_OUT_DIR__/tools/src
 	return filepath.Join(sboxToolsSubDir, "src", path.String())
 }
diff --git a/android/test_suites.go b/android/test_suites.go
index 22f6cf2..55e1da7 100644
--- a/android/test_suites.go
+++ b/android/test_suites.go
@@ -60,7 +60,7 @@
 	for _, module := range SortedStringKeys(files) {
 		installedPaths = append(installedPaths, files[module]...)
 	}
-	testCasesDir := pathForInstall(ctx, ctx.Config().BuildOS, X86, "testcases", false).ToMakePath()
+	testCasesDir := pathForInstall(ctx, ctx.Config().BuildOS, X86, "testcases", false)
 
 	outputFile := PathForOutput(ctx, "packaging", "robolectric-tests.zip")
 	rule := NewRuleBuilder(pctx, ctx)
diff --git a/android/util.go b/android/util.go
index a0394f6..0ee253e 100644
--- a/android/util.go
+++ b/android/util.go
@@ -65,22 +65,6 @@
 	return buf.String()
 }
 
-// SortedIntKeys returns the keys of the given integer-keyed map in the ascending order
-// TODO(asmundak): once Go has generics, combine this with SortedStringKeys below.
-func SortedIntKeys(m interface{}) []int {
-	v := reflect.ValueOf(m)
-	if v.Kind() != reflect.Map {
-		panic(fmt.Sprintf("%#v is not a map", m))
-	}
-	keys := v.MapKeys()
-	s := make([]int, 0, len(keys))
-	for _, key := range keys {
-		s = append(s, int(key.Int()))
-	}
-	sort.Ints(s)
-	return s
-}
-
 // SorterStringKeys returns the keys of the given string-keyed map in the ascending order
 func SortedStringKeys(m interface{}) []string {
 	v := reflect.ValueOf(m)
@@ -96,21 +80,6 @@
 	return s
 }
 
-// SortedStringMapValues returns the values of the string-values map in the ascending order
-func SortedStringMapValues(m interface{}) []string {
-	v := reflect.ValueOf(m)
-	if v.Kind() != reflect.Map {
-		panic(fmt.Sprintf("%#v is not a map", m))
-	}
-	keys := v.MapKeys()
-	s := make([]string, 0, len(keys))
-	for _, key := range keys {
-		s = append(s, v.MapIndex(key).String())
-	}
-	sort.Strings(s)
-	return s
-}
-
 // IndexList returns the index of the first occurrence of the given string in the list or -1
 func IndexList(s string, list []string) int {
 	for i, l := range list {
diff --git a/apex/androidmk.go b/apex/androidmk.go
index f001fa2..8cca137 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -149,7 +149,7 @@
 		var modulePath string
 		if apexType == flattenedApex {
 			// /system/apex/<name>/{lib|framework|...}
-			modulePath = filepath.Join(a.installDir.ToMakePath().String(), apexBundleName, fi.installDir)
+			modulePath = filepath.Join(a.installDir.String(), apexBundleName, fi.installDir)
 			fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", modulePath)
 			if a.primaryApexType && !symbolFilesNotNeeded {
 				fmt.Fprintln(w, "LOCAL_SOONG_SYMBOL_PATH :=", pathWhenActivated)
@@ -362,7 +362,7 @@
 				data.Entries.WriteLicenseVariables(w)
 				fmt.Fprintln(w, "LOCAL_MODULE_CLASS := ETC") // do we need a new class?
 				fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", a.outputFile.String())
-				fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", a.installDir.ToMakePath().String())
+				fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", a.installDir.String())
 				stemSuffix := apexType.suffix()
 				if a.isCompressed {
 					stemSuffix = imageCapexSuffix
diff --git a/apex/apex.go b/apex/apex.go
index b7faa5b..635ff30 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -458,10 +458,6 @@
 	modulePaths []string
 }
 
-func (*apexBundle) InstallBypassMake() bool {
-	return true
-}
-
 // apexFileClass represents a type of file that can be included in APEX.
 type apexFileClass int
 
@@ -2647,7 +2643,7 @@
 	//
 	// Module separator
 	//
-	m["com.android.bluetooth.updatable"] = []string{
+	m["com.android.bluetooth"] = []string{
 		"android.hardware.audio.common@5.0",
 		"android.hardware.bluetooth.a2dp@1.0",
 		"android.hardware.bluetooth.audio@2.0",
diff --git a/apex/apex_test.go b/apex/apex_test.go
index e2262f7..9ab8ca1 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -2657,7 +2657,10 @@
 }
 
 func TestVendorApex(t *testing.T) {
-	ctx := testApex(t, `
+	result := android.GroupFixturePreparers(
+		prepareForApexTest,
+		android.FixtureModifyConfig(android.SetKatiEnabledForTests),
+	).RunTestWithBp(t, `
 		apex {
 			name: "myapex",
 			key: "myapex.key",
@@ -2681,24 +2684,24 @@
 		}
 	`)
 
-	ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{
+	ensureExactContents(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
 		"bin/mybin",
 		"lib64/libfoo.so",
 		// TODO(b/159195575): Add an option to use VNDK libs from VNDK APEX
 		"lib64/libc++.so",
 	})
 
-	apexBundle := ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
-	data := android.AndroidMkDataForTest(t, ctx, apexBundle)
+	apexBundle := result.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
+	data := android.AndroidMkDataForTest(t, result.TestContext, apexBundle)
 	name := apexBundle.BaseModuleName()
 	prefix := "TARGET_"
 	var builder strings.Builder
 	data.Custom(&builder, name, prefix, "", data)
-	androidMk := android.StringRelativeToTop(ctx.Config(), builder.String())
+	androidMk := android.StringRelativeToTop(result.Config, builder.String())
 	installPath := "out/target/product/test_device/vendor/apex"
 	ensureContains(t, androidMk, "LOCAL_MODULE_PATH := "+installPath)
 
-	apexManifestRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("apexManifestRule")
+	apexManifestRule := result.ModuleForTests("myapex", "android_common_myapex_image").Rule("apexManifestRule")
 	requireNativeLibs := names(apexManifestRule.Args["requireNativeLibs"])
 	ensureListNotContains(t, requireNativeLibs, ":vndk")
 }
diff --git a/apex/bootclasspath_fragment_test.go b/apex/bootclasspath_fragment_test.go
index 7ecf9b2..ce828e1 100644
--- a/apex/bootclasspath_fragment_test.go
+++ b/apex/bootclasspath_fragment_test.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"path"
 	"sort"
 	"strings"
 	"testing"
@@ -442,6 +443,24 @@
 		checkCopiesToPredefinedLocationForArt(t, result.Config, module, "bar", "foo")
 	})
 
+	t.Run("boot image disable generate profile", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			commonPreparer,
+
+			// Configure some libraries in the art bootclasspath_fragment that match the source
+			// bootclasspath_fragment's contents property.
+			java.FixtureConfigureBootJars("com.android.art:foo", "com.android.art:bar"),
+			addSource("foo", "bar"),
+			dexpreopt.FixtureDisableGenerateProfile(true),
+		).RunTest(t)
+
+		files := getFiles(t, result.TestContext, "com.android.art", "android_common_com.android.art_image")
+		for _, file := range files {
+			matched, _ := path.Match("etc/boot-image.prof", file.path)
+			android.AssertBoolEquals(t, "\"etc/boot-image.prof\" should not be in the APEX", matched, false)
+		}
+	})
+
 	t.Run("boot image files with preferred prebuilt", func(t *testing.T) {
 		result := android.GroupFixturePreparers(
 			commonPreparer,
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index 254c90e..02d8075 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -222,7 +222,7 @@
 			Host_required: p.hostRequired,
 			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 				func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-					entries.SetString("LOCAL_MODULE_PATH", p.installDir.ToMakePath().String())
+					entries.SetString("LOCAL_MODULE_PATH", p.installDir.String())
 					entries.SetString("LOCAL_MODULE_STEM", p.installFilename)
 					entries.SetPath("LOCAL_SOONG_INSTALLED_MODULE", p.installedFile)
 					entries.SetString("LOCAL_SOONG_INSTALL_PAIRS", p.outputApex.String()+":"+p.installedFile.String())
@@ -256,7 +256,7 @@
 		Include:      "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", p.installDir.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", p.installDir.String())
 				entries.SetString("LOCAL_SOONG_INSTALLED_MODULE", filepath.Join(p.installDir.String(), fi.stem()))
 				entries.SetString("LOCAL_SOONG_INSTALL_PAIRS",
 					fi.builtFile.String()+":"+filepath.Join(p.installDir.String(), fi.stem()))
@@ -472,10 +472,6 @@
 	inputApex android.Path
 }
 
-func (p *Prebuilt) InstallBypassMake() bool {
-	return true
-}
-
 type ApexFileProperties struct {
 	// the path to the prebuilt .apex file to import.
 	//
diff --git a/bp2build/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go
index b4eb28f..eaceea9 100644
--- a/bp2build/cc_library_conversion_test.go
+++ b/bp2build/cc_library_conversion_test.go
@@ -2041,3 +2041,121 @@
 		},
 	})
 }
+
+func TestCcLibraryProtoFilegroups(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcProtoPreamble +
+			simpleModuleDoNotConvertBp2build("filegroup", "a_fg_proto") +
+			simpleModuleDoNotConvertBp2build("filegroup", "b_protos") +
+			simpleModuleDoNotConvertBp2build("filegroup", "c-proto-srcs") +
+			simpleModuleDoNotConvertBp2build("filegroup", "proto-srcs-d") + `
+cc_library {
+	name: "a",
+	srcs: [":a_fg_proto"],
+	proto: {
+		canonical_path_from_root: false,
+		export_proto_headers: true,
+	},
+	include_build_directory: false,
+}
+
+cc_library {
+	name: "b",
+	srcs: [":b_protos"],
+	proto: {
+		canonical_path_from_root: false,
+		export_proto_headers: true,
+	},
+	include_build_directory: false,
+}
+
+cc_library {
+	name: "c",
+	srcs: [":c-proto-srcs"],
+	proto: {
+		canonical_path_from_root: false,
+		export_proto_headers: true,
+	},
+	include_build_directory: false,
+}
+
+cc_library {
+	name: "d",
+	srcs: [":proto-srcs-d"],
+	proto: {
+		canonical_path_from_root: false,
+		export_proto_headers: true,
+	},
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("proto_library", "a_proto", attrNameToString{
+				"srcs": `[":a_fg_proto"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "a_cc_proto_lite", attrNameToString{
+				"deps": `[":a_proto"]`,
+			}), makeBazelTarget("cc_library_static", "a_bp2build_cc_library_static", attrNameToString{
+				"deps":               `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":a_cc_proto_lite"]`,
+				"srcs":               `[":a_fg_proto_cpp_srcs"]`,
+				"srcs_as":            `[":a_fg_proto_as_srcs"]`,
+				"srcs_c":             `[":a_fg_proto_c_srcs"]`,
+			}), makeBazelTarget("cc_library_shared", "a", attrNameToString{
+				"dynamic_deps":       `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":a_cc_proto_lite"]`,
+				"srcs":               `[":a_fg_proto_cpp_srcs"]`,
+				"srcs_as":            `[":a_fg_proto_as_srcs"]`,
+				"srcs_c":             `[":a_fg_proto_c_srcs"]`,
+			}), makeBazelTarget("proto_library", "b_proto", attrNameToString{
+				"srcs": `[":b_protos"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "b_cc_proto_lite", attrNameToString{
+				"deps": `[":b_proto"]`,
+			}), makeBazelTarget("cc_library_static", "b_bp2build_cc_library_static", attrNameToString{
+				"deps":               `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":b_cc_proto_lite"]`,
+				"srcs":               `[":b_protos_cpp_srcs"]`,
+				"srcs_as":            `[":b_protos_as_srcs"]`,
+				"srcs_c":             `[":b_protos_c_srcs"]`,
+			}), makeBazelTarget("cc_library_shared", "b", attrNameToString{
+				"dynamic_deps":       `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":b_cc_proto_lite"]`,
+				"srcs":               `[":b_protos_cpp_srcs"]`,
+				"srcs_as":            `[":b_protos_as_srcs"]`,
+				"srcs_c":             `[":b_protos_c_srcs"]`,
+			}), makeBazelTarget("proto_library", "c_proto", attrNameToString{
+				"srcs": `[":c-proto-srcs"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "c_cc_proto_lite", attrNameToString{
+				"deps": `[":c_proto"]`,
+			}), makeBazelTarget("cc_library_static", "c_bp2build_cc_library_static", attrNameToString{
+				"deps":               `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":c_cc_proto_lite"]`,
+				"srcs":               `[":c-proto-srcs_cpp_srcs"]`,
+				"srcs_as":            `[":c-proto-srcs_as_srcs"]`,
+				"srcs_c":             `[":c-proto-srcs_c_srcs"]`,
+			}), makeBazelTarget("cc_library_shared", "c", attrNameToString{
+				"dynamic_deps":       `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":c_cc_proto_lite"]`,
+				"srcs":               `[":c-proto-srcs_cpp_srcs"]`,
+				"srcs_as":            `[":c-proto-srcs_as_srcs"]`,
+				"srcs_c":             `[":c-proto-srcs_c_srcs"]`,
+			}), makeBazelTarget("proto_library", "d_proto", attrNameToString{
+				"srcs": `[":proto-srcs-d"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "d_cc_proto_lite", attrNameToString{
+				"deps": `[":d_proto"]`,
+			}), makeBazelTarget("cc_library_static", "d_bp2build_cc_library_static", attrNameToString{
+				"deps":               `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":d_cc_proto_lite"]`,
+				"srcs":               `[":proto-srcs-d_cpp_srcs"]`,
+				"srcs_as":            `[":proto-srcs-d_as_srcs"]`,
+				"srcs_c":             `[":proto-srcs-d_c_srcs"]`,
+			}), makeBazelTarget("cc_library_shared", "d", attrNameToString{
+				"dynamic_deps":       `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":d_cc_proto_lite"]`,
+				"srcs":               `[":proto-srcs-d_cpp_srcs"]`,
+				"srcs_as":            `[":proto-srcs-d_as_srcs"]`,
+				"srcs_c":             `[":proto-srcs-d_c_srcs"]`,
+			}),
+		},
+	})
+}
diff --git a/bp2build/testing.go b/bp2build/testing.go
index 15cf486..8ae1a38 100644
--- a/bp2build/testing.go
+++ b/bp2build/testing.go
@@ -133,8 +133,8 @@
 		android.FailIfErrored(t, errs)
 	}
 	if actualCount, expectedCount := len(bazelTargets), len(tc.expectedBazelTargets); actualCount != expectedCount {
-		t.Errorf("%s: Expected %d bazel target, got `%d``",
-			tc.description, expectedCount, actualCount)
+		t.Errorf("%s: Expected %d bazel target (%s), got `%d`` (%s)",
+			tc.description, expectedCount, tc.expectedBazelTargets, actualCount, bazelTargets)
 	} else {
 		for i, target := range bazelTargets {
 			if w, g := tc.expectedBazelTargets[i], target.content; w != g {
diff --git a/cc/androidmk.go b/cc/androidmk.go
index 636bab8..800b58f 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -453,7 +453,7 @@
 	}
 
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		path, file := filepath.Split(installer.path.ToMakePath().String())
+		path, file := filepath.Split(installer.path.String())
 		stem, suffix, _ := android.SplitFileExt(file)
 		entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
 		entries.SetString("LOCAL_MODULE_PATH", path)
@@ -532,7 +532,7 @@
 		c.libraryDecorator.androidMkWriteExportedFlags(entries)
 
 		if c.shared() || c.static() {
-			path, file := filepath.Split(c.path.ToMakePath().String())
+			path, file := filepath.Split(c.path.String())
 			stem, suffix, ext := android.SplitFileExt(file)
 			entries.SetString("LOCAL_BUILT_MODULE_STEM", "$(LOCAL_MODULE)"+ext)
 			entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
diff --git a/cc/bp2build.go b/cc/bp2build.go
index 6c1646c..fad40be 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -16,6 +16,7 @@
 import (
 	"fmt"
 	"path/filepath"
+	"regexp"
 	"strings"
 
 	"android/soong/android"
@@ -33,6 +34,12 @@
 	protoSrcPartition = "proto"
 )
 
+var (
+	// ignoring case, checks for proto or protos as an independent word in the name, whether at the
+	// beginning, end, or middle. e.g. "proto.foo", "bar-protos", "baz_proto_srcs" would all match
+	filegroupLikelyProtoPattern = regexp.MustCompile("(?i)(^|[^a-z])proto(s)?([^a-z]|$)")
+)
+
 // staticOrSharedAttributes are the Bazel-ified versions of StaticOrSharedProperties --
 // properties which apply to either the shared or static version of a cc_library module.
 type staticOrSharedAttributes struct {
@@ -77,18 +84,18 @@
 		if !exists || !isFilegroup(m) {
 			return labelStr, false
 		}
-		likelyProtos := strings.HasSuffix(labelStr, "proto") || strings.HasSuffix(labelStr, "protos")
+		likelyProtos := filegroupLikelyProtoPattern.MatchString(label.OriginalModuleName)
 		return labelStr, likelyProtos
 	}
 
 	// TODO(b/190006308): Handle language detection of sources in a Bazel rule.
 	partitioned := bazel.PartitionLabelListAttribute(ctx, &srcs, bazel.LabelPartitions{
-		cSrcPartition:  bazel.LabelPartition{Extensions: []string{".c"}, LabelMapper: addSuffixForFilegroup("_c_srcs")},
-		asSrcPartition: bazel.LabelPartition{Extensions: []string{".s", ".S"}, LabelMapper: addSuffixForFilegroup("_as_srcs")},
+		protoSrcPartition: bazel.LabelPartition{Extensions: []string{".proto"}, LabelMapper: isProtoFilegroup},
+		cSrcPartition:     bazel.LabelPartition{Extensions: []string{".c"}, LabelMapper: addSuffixForFilegroup("_c_srcs")},
+		asSrcPartition:    bazel.LabelPartition{Extensions: []string{".s", ".S"}, LabelMapper: addSuffixForFilegroup("_as_srcs")},
 		// C++ is the "catch-all" group, and comprises generated sources because we don't
 		// know the language of these sources until the genrule is executed.
-		cppSrcPartition:   bazel.LabelPartition{Extensions: []string{".cpp", ".cc", ".cxx", ".mm"}, LabelMapper: addSuffixForFilegroup("_cpp_srcs"), Keep_remainder: true},
-		protoSrcPartition: bazel.LabelPartition{Extensions: []string{".proto"}, LabelMapper: isProtoFilegroup},
+		cppSrcPartition: bazel.LabelPartition{Extensions: []string{".cpp", ".cc", ".cxx", ".mm"}, LabelMapper: addSuffixForFilegroup("_cpp_srcs"), Keep_remainder: true},
 	})
 
 	return partitioned
diff --git a/cc/builder.go b/cc/builder.go
index 8af2255..fa7f7a3 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -529,6 +529,14 @@
 	cppflags += " ${config.NoOverrideGlobalCflags}"
 	toolingCppflags += " ${config.NoOverrideGlobalCflags}"
 
+	modulePath := android.PathForModuleSrc(ctx).String()
+	if android.IsThirdPartyPath(modulePath) {
+		cflags += " ${config.NoOverrideExternalGlobalCflags}"
+		toolingCflags += " ${config.NoOverrideExternalGlobalCflags}"
+		cppflags += " ${config.NoOverrideExternalGlobalCflags}"
+		toolingCppflags += " ${config.NoOverrideExternalGlobalCflags}"
+	}
+
 	// Multiple source files have build rules usually share the same cFlags or tidyFlags.
 	// Define only one version in this module and share it in multiple build rules.
 	// To simplify the code, the shared variables are all named as $flags<nnn>.
diff --git a/cc/cc.go b/cc/cc.go
index 5c4edb9..22baf30 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -1385,8 +1385,6 @@
 	return c.installer != nil && c.installer.installInRoot()
 }
 
-func (c *Module) InstallBypassMake() bool { return true }
-
 type baseModuleContext struct {
 	android.BaseModuleContext
 	moduleContextImpl
@@ -3496,10 +3494,6 @@
 type Defaults struct {
 	android.ModuleBase
 	android.DefaultsModuleBase
-	// Included to support setting bazel_module.label for multiple Soong modules to the same Bazel
-	// target. This is primarily useful for modules that were architecture specific and instead are
-	// handled in Bazel as a select().
-	android.BazelModuleBase
 	android.ApexModuleBase
 }
 
@@ -3547,8 +3541,6 @@
 		&prebuiltLinkerProperties{},
 	)
 
-	// Bazel module must be initialized _before_ Defaults to be included in cc_defaults module.
-	android.InitBazelModule(module)
 	android.InitDefaultsModule(module)
 
 	return module
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 9ffe48d..bcc6fcd 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -4005,12 +4005,12 @@
 		{
 			name:     "c",
 			src:      "foo.c",
-			expected: combineSlices(baseExpectedFlags, conly, expectedIncludes, cflags, cstd, lastIncludes, []string{"${config.NoOverrideGlobalCflags}"}),
+			expected: combineSlices(baseExpectedFlags, conly, expectedIncludes, cflags, cstd, lastIncludes, []string{"${config.NoOverrideGlobalCflags}", "${config.NoOverrideExternalGlobalCflags}"}),
 		},
 		{
 			name:     "cc",
 			src:      "foo.cc",
-			expected: combineSlices(baseExpectedFlags, cppOnly, expectedIncludes, cflags, cppstd, lastIncludes, []string{"${config.NoOverrideGlobalCflags}"}),
+			expected: combineSlices(baseExpectedFlags, cppOnly, expectedIncludes, cflags, cppstd, lastIncludes, []string{"${config.NoOverrideGlobalCflags}", "${config.NoOverrideExternalGlobalCflags}"}),
 		},
 		{
 			name:     "assemble",
diff --git a/cc/config/global.go b/cc/config/global.go
index a340e46..7f2c23e 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -224,8 +224,6 @@
 		"-Wno-pessimizing-move",                     // http://b/154270751
 		// New warnings to be fixed after clang-r399163
 		"-Wno-non-c-typedef-for-linkage", // http://b/161304145
-		// New warnings to be fixed after clang-r407598
-		"-Wno-string-concatenation", // http://b/175068488
 		// New warnings to be fixed after clang-r428724
 		"-Wno-align-mismatch", // http://b/193679946
 		// New warnings to be fixed after clang-r433403
@@ -233,6 +231,12 @@
 		"-Wno-error=unused-but-set-parameter", // http://b/197240255
 	}
 
+	noOverrideExternalGlobalCflags = []string{
+		// http://b/197240255
+		"-Wno-unused-but-set-variable",
+		"-Wno-unused-but-set-parameter",
+	}
+
 	// Extra cflags for external third-party projects to disable warnings that
 	// are infeasible to fix in all the external projects and their upstream repos.
 	extraExternalCflags = []string{
@@ -259,6 +263,9 @@
 
 		// http://b/199369603
 		"-Wno-null-pointer-subtraction",
+
+		// http://b/175068488
+		"-Wno-string-concatenation",
 	}
 
 	IllegalFlags = []string{
@@ -346,6 +353,7 @@
 
 	exportStringListStaticVariable("HostGlobalCflags", hostGlobalCflags)
 	exportStringListStaticVariable("NoOverrideGlobalCflags", noOverrideGlobalCflags)
+	exportStringListStaticVariable("NoOverrideExternalGlobalCflags", noOverrideExternalGlobalCflags)
 	exportStringListStaticVariable("CommonGlobalCppflags", commonGlobalCppflags)
 	exportStringListStaticVariable("ExternalCflags", extraExternalCflags)
 
diff --git a/cc/makevars.go b/cc/makevars.go
index 8d7a163..b7aaaad 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -95,6 +95,7 @@
 	ctx.Strict("CLANG_EXTERNAL_CFLAGS", "${config.ExternalCflags}")
 	ctx.Strict("GLOBAL_CLANG_CFLAGS_NO_OVERRIDE", "${config.NoOverrideGlobalCflags}")
 	ctx.Strict("GLOBAL_CLANG_CPPFLAGS_NO_OVERRIDE", "")
+	ctx.Strict("GLOBAL_CLANG_EXTERNAL_CFLAGS_NO_OVERRIDE", "${config.NoOverrideExternalGlobalCflags}")
 
 	ctx.Strict("BOARD_VNDK_VERSION", ctx.DeviceConfig().VndkVersion())
 	ctx.Strict("RECOVERY_SNAPSHOT_VERSION", ctx.DeviceConfig().RecoverySnapshotVersion())
diff --git a/dexpreopt/testing.go b/dexpreopt/testing.go
index 5131cd3..47ae494 100644
--- a/dexpreopt/testing.go
+++ b/dexpreopt/testing.go
@@ -167,3 +167,10 @@
 		dexpreoptConfig.BootImageProfiles = android.PathsForSource(ctx, profiles)
 	})
 }
+
+// FixtureDisableGenerateProfile sets the DisableGenerateProfile property in the global config.
+func FixtureDisableGenerateProfile(disable bool) android.FixturePreparer {
+	return FixtureModifyGlobalConfig(func(_ android.PathContext, dexpreoptConfig *GlobalConfig) {
+		dexpreoptConfig.DisableGenerateProfile = disable
+	})
+}
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index 80ab41b..c2866ab 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -376,7 +376,7 @@
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.SetString("LOCAL_MODULE_TAGS", "optional")
-				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", p.outputFilePath.Base())
 				if len(p.properties.Symlinks) > 0 {
 					entries.AddStrings("LOCAL_MODULE_SYMLINKS", p.properties.Symlinks...)
diff --git a/etc/snapshot_etc.go b/etc/snapshot_etc.go
index 9a25d5a..b54a8a6 100644
--- a/etc/snapshot_etc.go
+++ b/etc/snapshot_etc.go
@@ -128,7 +128,7 @@
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.SetString("LOCAL_MODULE_TAGS", "optional")
-				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", p.outputFilePath.Base())
 			},
 		},
diff --git a/filesystem/bootimg.go b/filesystem/bootimg.go
index 73d807d..fc973a4 100644
--- a/filesystem/bootimg.go
+++ b/filesystem/bootimg.go
@@ -267,7 +267,7 @@
 		OutputFile: android.OptionalPathForPath(b.output),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", b.installDir.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", b.installDir.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", b.installFileName())
 			},
 		},
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index b2caa51..0796258 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -394,7 +394,7 @@
 		OutputFile: android.OptionalPathForPath(f.output),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", f.installDir.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", f.installDir.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", f.installFileName())
 			},
 		},
diff --git a/filesystem/logical_partition.go b/filesystem/logical_partition.go
index 739e609..e2f7d7b 100644
--- a/filesystem/logical_partition.go
+++ b/filesystem/logical_partition.go
@@ -215,7 +215,7 @@
 		OutputFile: android.OptionalPathForPath(l.output),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", l.installDir.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", l.installDir.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", l.installFileName())
 			},
 		},
diff --git a/filesystem/vbmeta.go b/filesystem/vbmeta.go
index 3f16c0d..63e0aba 100644
--- a/filesystem/vbmeta.go
+++ b/filesystem/vbmeta.go
@@ -247,7 +247,7 @@
 		OutputFile: android.OptionalPathForPath(v.output),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", v.installDir.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", v.installDir.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", v.installFileName())
 			},
 		},
diff --git a/java/androidmk.go b/java/androidmk.go
index 55242fe..19fe7e2 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -692,7 +692,7 @@
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.SetString("LOCAL_CERTIFICATE", r.certificate.AndroidMkString())
-				entries.SetPath("LOCAL_MODULE_PATH", r.installDir.ToMakePath())
+				entries.SetPath("LOCAL_MODULE_PATH", r.installDir)
 				entries.AddStrings("LOCAL_OVERRIDES_PACKAGES", r.properties.Overrides...)
 			},
 		},
diff --git a/java/app.go b/java/app.go
index b753c0c..1c69aeb 100755
--- a/java/app.go
+++ b/java/app.go
@@ -485,7 +485,7 @@
 		a.jniLibs = jniLibs
 		if a.shouldEmbedJnis(ctx) {
 			jniJarFile = android.PathForModuleOut(ctx, "jnilibs.zip")
-			a.installPathForJNISymbols = a.installPath(ctx).ToMakePath()
+			a.installPathForJNISymbols = a.installPath(ctx)
 			TransformJniLibsToJar(ctx, jniJarFile, jniLibs, a.useEmbeddedNativeLibs(ctx))
 			for _, jni := range jniLibs {
 				if jni.coverageFile.Valid() {
diff --git a/java/app_import.go b/java/app_import.go
index 4d1969e..3e5f972 100644
--- a/java/app_import.go
+++ b/java/app_import.go
@@ -108,8 +108,6 @@
 	return true
 }
 
-func (a *AndroidAppImport) InstallBypassMake() bool { return true }
-
 // Updates properties with variant-specific values.
 func (a *AndroidAppImport) processVariants(ctx android.LoadHookContext) {
 	config := ctx.Config()
diff --git a/java/classpath_fragment.go b/java/classpath_fragment.go
index e77971b..ca27528 100644
--- a/java/classpath_fragment.go
+++ b/java/classpath_fragment.go
@@ -204,7 +204,7 @@
 		OutputFile: android.OptionalPathForPath(c.outputFilepath),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", c.installDirPath.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", c.installDirPath.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", c.outputFilepath.Base())
 			},
 		},
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index edb6984..e9bc518 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -390,7 +390,7 @@
 			OutputFile: android.OptionalPathForPath(install.outputPathOnHost),
 			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 				func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-					entries.SetString("LOCAL_MODULE_PATH", install.installDirOnDevice.ToMakePath().String())
+					entries.SetString("LOCAL_MODULE_PATH", install.installDirOnDevice.String())
 					entries.SetString("LOCAL_INSTALLED_MODULE_STEM", install.installFileOnDevice)
 					entries.SetString("LOCAL_NOT_AVAILABLE_FOR_PLATFORM", "false")
 				},
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 75083e8..c599c4d 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -506,8 +506,18 @@
 		dst := dstBootJarsByModule[name]
 
 		if src == nil {
+			// A dex boot jar should be provided by the source java module. It needs to be installable or
+			// have compile_dex=true - cf. assignments to java.Module.dexJarFile.
+			//
+			// However, the source java module may be either replaced or overridden (using prefer:true) by
+			// a prebuilt java module with the same name. In that case the dex boot jar needs to be
+			// provided by the corresponding prebuilt APEX module. That APEX is the one that refers
+			// through a exported_(boot|systemserver)classpath_fragments property to a
+			// prebuilt_(boot|systemserver)classpath_fragment module, which in turn lists the prebuilt
+			// java module in the contents property. If that chain is broken then this dependency will
+			// fail.
 			if !ctx.Config().AllowMissingDependencies() {
-				ctx.ModuleErrorf("module %s does not provide a dex boot jar", name)
+				ctx.ModuleErrorf("module %s does not provide a dex boot jar (see comment next to this message in Soong for details)", name)
 			} else {
 				ctx.AddMissingDependencies([]string{name})
 			}
diff --git a/java/dexpreopt_check.go b/java/dexpreopt_check.go
index fade066..149cbb7 100644
--- a/java/dexpreopt_check.go
+++ b/java/dexpreopt_check.go
@@ -59,7 +59,7 @@
 
 func getInstallPath(ctx android.ModuleContext, location string) android.InstallPath {
 	return android.PathForModuleInPartitionInstall(
-		ctx, "", strings.TrimPrefix(location, "/")).ToMakePath()
+		ctx, "", strings.TrimPrefix(location, "/"))
 }
 
 func (m *dexpreoptSystemserverCheck) GenerateAndroidBuildActions(ctx android.ModuleContext) {
diff --git a/java/java.go b/java/java.go
index f77c694..35b5ec3 100644
--- a/java/java.go
+++ b/java/java.go
@@ -267,8 +267,6 @@
 	return j.kytheFiles
 }
 
-func (j *Module) InstallBypassMake() bool { return true }
-
 type dependencyTag struct {
 	blueprint.BaseDependencyTag
 	name string
diff --git a/java/platform_compat_config.go b/java/platform_compat_config.go
index 0d8ebac..f442ddf 100644
--- a/java/platform_compat_config.go
+++ b/java/platform_compat_config.go
@@ -115,7 +115,7 @@
 		Include:    "$(BUILD_PREBUILT)",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", p.configFile.Base())
 			},
 		},
diff --git a/java/robolectric.go b/java/robolectric.go
index 16af546..f719521 100644
--- a/java/robolectric.go
+++ b/java/robolectric.go
@@ -352,7 +352,6 @@
 	return module
 }
 
-func (r *robolectricTest) InstallBypassMake() bool  { return true }
 func (r *robolectricTest) InstallInTestcases() bool { return true }
 func (r *robolectricTest) InstallForceOS() (*android.OsType, *android.ArchType) {
 	return &r.forceOSType, &r.forceArchType
@@ -430,7 +429,6 @@
 	}
 }
 
-func (r *robolectricRuntimes) InstallBypassMake() bool  { return true }
 func (r *robolectricRuntimes) InstallInTestcases() bool { return true }
 func (r *robolectricRuntimes) InstallForceOS() (*android.OsType, *android.ArchType) {
 	return &r.forceOSType, &r.forceArchType
diff --git a/java/rro_test.go b/java/rro_test.go
index 27abbe4..be0d7ba 100644
--- a/java/rro_test.go
+++ b/java/rro_test.go
@@ -62,6 +62,7 @@
 	result := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 		PrepareForTestWithOverlayBuildComponents,
+		android.FixtureModifyConfig(android.SetKatiEnabledForTests),
 		fs.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
@@ -127,7 +128,10 @@
 }
 
 func TestRuntimeResourceOverlay_JavaDefaults(t *testing.T) {
-	ctx, config := testJava(t, `
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		android.FixtureModifyConfig(android.SetKatiEnabledForTests),
+	).RunTestWithBp(t, `
 		java_defaults {
 			name: "rro_defaults",
 			theme: "default_theme",
@@ -148,7 +152,7 @@
 	//
 	// RRO module with defaults
 	//
-	m := ctx.ModuleForTests("foo_with_defaults", "android_common")
+	m := result.ModuleForTests("foo_with_defaults", "android_common")
 
 	// Check AAPT2 link flags.
 	aapt2Flags := strings.Split(m.Output("package-res.apk").Args["flags"], " ")
@@ -159,14 +163,14 @@
 	}
 
 	// Check device location.
-	path := android.AndroidMkEntriesForTest(t, ctx, m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
+	path := android.AndroidMkEntriesForTest(t, result.TestContext, m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
 	expectedPath := []string{shared.JoinPath("out/target/product/test_device/product/overlay/default_theme")}
-	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_MODULE_PATH", config, expectedPath, path)
+	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_MODULE_PATH", result.Config, expectedPath, path)
 
 	//
 	// RRO module without defaults
 	//
-	m = ctx.ModuleForTests("foo_barebones", "android_common")
+	m = result.ModuleForTests("foo_barebones", "android_common")
 
 	// Check AAPT2 link flags.
 	aapt2Flags = strings.Split(m.Output("package-res.apk").Args["flags"], " ")
@@ -176,9 +180,9 @@
 	}
 
 	// Check device location.
-	path = android.AndroidMkEntriesForTest(t, ctx, m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
+	path = android.AndroidMkEntriesForTest(t, result.TestContext, m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
 	expectedPath = []string{shared.JoinPath("out/target/product/test_device/product/overlay")}
-	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_MODULE_PATH", config, expectedPath, path)
+	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_MODULE_PATH", result.Config, expectedPath, path)
 }
 
 func TestOverrideRuntimeResourceOverlay(t *testing.T) {
diff --git a/java/sdk_library.go b/java/sdk_library.go
index de0d796..b8ab69a 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -2641,7 +2641,7 @@
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.SetString("LOCAL_MODULE_TAGS", "optional")
-				entries.SetString("LOCAL_MODULE_PATH", module.installDirPath.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", module.installDirPath.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", module.outputFilePath.Base())
 			},
 		},
diff --git a/linkerconfig/linkerconfig.go b/linkerconfig/linkerconfig.go
index 8d0ad7c..dbc112e 100644
--- a/linkerconfig/linkerconfig.go
+++ b/linkerconfig/linkerconfig.go
@@ -155,7 +155,7 @@
 		OutputFile: android.OptionalPathForPath(l.outputFilePath),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", l.installDirPath.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", l.installDirPath.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", l.outputFilePath.Base())
 				entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", !installable)
 				entries.SetString("LINKER_CONFIG_PATH_"+l.Name(), l.OutputFile().String())
diff --git a/mk2rbc/expr.go b/mk2rbc/expr.go
index 81b31c7..7cd4899 100644
--- a/mk2rbc/expr.go
+++ b/mk2rbc/expr.go
@@ -16,21 +16,22 @@
 
 import (
 	"fmt"
-	"strconv"
 	"strings"
 )
 
-// Represents an expression in the Starlark code. An expression has
-// a type, and it can be evaluated.
+// Represents an expression in the Starlark code. An expression has a type.
 type starlarkExpr interface {
 	starlarkNode
 	typ() starlarkType
-	// Try to substitute variable values. Return substitution result
-	// and whether it is the same as the original expression.
-	eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool)
 	// Emit the code to copy the expression, otherwise we will end up
 	// with source and target pointing to the same list.
 	emitListVarCopy(gctx *generationContext)
+	// Return the expression, calling the transformer func for
+	// every expression in the tree. If the transformer func returns non-nil,
+	// its result is used in place of the expression it was called with in the
+	// resulting expression. The resulting starlarkExpr will contain as many
+	// of the same objects from the original expression as possible.
+	transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr
 }
 
 func maybeString(expr starlarkExpr) (string, bool) {
@@ -44,12 +45,6 @@
 	literal string
 }
 
-func (s *stringLiteralExpr) eval(_ map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	res = s
-	same = true
-	return
-}
-
 func (s *stringLiteralExpr) emit(gctx *generationContext) {
 	gctx.writef("%q", s.literal)
 }
@@ -62,17 +57,19 @@
 	s.emit(gctx)
 }
 
+func (s *stringLiteralExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if replacement := transformer(s); replacement != nil {
+		return replacement
+	} else {
+		return s
+	}
+}
+
 // Integer literal
 type intLiteralExpr struct {
 	literal int
 }
 
-func (s *intLiteralExpr) eval(_ map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	res = s
-	same = true
-	return
-}
-
 func (s *intLiteralExpr) emit(gctx *generationContext) {
 	gctx.writef("%d", s.literal)
 }
@@ -85,15 +82,19 @@
 	s.emit(gctx)
 }
 
+func (s *intLiteralExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if replacement := transformer(s); replacement != nil {
+		return replacement
+	} else {
+		return s
+	}
+}
+
 // Boolean literal
 type boolLiteralExpr struct {
 	literal bool
 }
 
-func (b *boolLiteralExpr) eval(_ map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	return b, true
-}
-
 func (b *boolLiteralExpr) emit(gctx *generationContext) {
 	if b.literal {
 		gctx.write("True")
@@ -110,6 +111,14 @@
 	b.emit(gctx)
 }
 
+func (b *boolLiteralExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if replacement := transformer(b); replacement != nil {
+		return replacement
+	} else {
+		return b
+	}
+}
+
 // interpolateExpr represents Starlark's interpolation operator <string> % list
 // we break <string> into a list of chunks, i.e., "first%second%third" % (X, Y)
 // will have chunks = ["first", "second", "third"] and args = [X, Y]
@@ -118,6 +127,35 @@
 	args   []starlarkExpr
 }
 
+func NewInterpolateExpr(parts []starlarkExpr) starlarkExpr {
+	result := &interpolateExpr{}
+	needString := true
+	for _, part := range parts {
+		if needString {
+			if strLit, ok := part.(*stringLiteralExpr); ok {
+				result.chunks = append(result.chunks, strLit.literal)
+			} else {
+				result.chunks = append(result.chunks, "")
+			}
+			needString = false
+		} else {
+			if strLit, ok := part.(*stringLiteralExpr); ok {
+				result.chunks[len(result.chunks)-1] += strLit.literal
+			} else {
+				result.args = append(result.args, part)
+				needString = true
+			}
+		}
+	}
+	if len(result.chunks) == len(result.args) {
+		result.chunks = append(result.chunks, "")
+	}
+	if len(result.args) == 0 {
+		return &stringLiteralExpr{literal: strings.Join(result.chunks, "")}
+	}
+	return result
+}
+
 func (xi *interpolateExpr) emit(gctx *generationContext) {
 	if len(xi.chunks) != len(xi.args)+1 {
 		panic(fmt.Errorf("malformed interpolateExpr: #chunks(%d) != #args(%d)+1",
@@ -151,37 +189,6 @@
 	}
 }
 
-func (xi *interpolateExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	same = true
-	newChunks := []string{xi.chunks[0]}
-	var newArgs []starlarkExpr
-	for i, arg := range xi.args {
-		newArg, sameArg := arg.eval(valueMap)
-		same = same && sameArg
-		switch x := newArg.(type) {
-		case *stringLiteralExpr:
-			newChunks[len(newChunks)-1] += x.literal + xi.chunks[i+1]
-			same = false
-			continue
-		case *intLiteralExpr:
-			newChunks[len(newChunks)-1] += strconv.Itoa(x.literal) + xi.chunks[i+1]
-			same = false
-			continue
-		default:
-			newChunks = append(newChunks, xi.chunks[i+1])
-			newArgs = append(newArgs, newArg)
-		}
-	}
-	if same {
-		res = xi
-	} else if len(newChunks) == 1 {
-		res = &stringLiteralExpr{newChunks[0]}
-	} else {
-		res = &interpolateExpr{chunks: newChunks, args: newArgs}
-	}
-	return
-}
-
 func (_ *interpolateExpr) typ() starlarkType {
 	return starlarkTypeString
 }
@@ -190,19 +197,29 @@
 	xi.emit(gctx)
 }
 
+func (xi *interpolateExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	argsCopy := make([]starlarkExpr, len(xi.args))
+	for i, arg := range xi.args {
+		argsCopy[i] = arg.transform(transformer)
+	}
+	xi.args = argsCopy
+	if replacement := transformer(xi); replacement != nil {
+		return replacement
+	} else {
+		return xi
+	}
+}
+
 type variableRefExpr struct {
 	ref       variable
 	isDefined bool
 }
 
-func (v *variableRefExpr) eval(map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	predefined, ok := v.ref.(*predefinedVariable)
-	if same = !ok; same {
-		res = v
-	} else {
-		res = predefined.value
+func NewVariableRefExpr(ref variable, isDefined bool) starlarkExpr {
+	if predefined, ok := ref.(*predefinedVariable); ok {
+		return predefined.value
 	}
-	return
+	return &variableRefExpr{ref, isDefined}
 }
 
 func (v *variableRefExpr) emit(gctx *generationContext) {
@@ -220,17 +237,16 @@
 	}
 }
 
-type toStringExpr struct {
-	expr starlarkExpr
+func (v *variableRefExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if replacement := transformer(v); replacement != nil {
+		return replacement
+	} else {
+		return v
+	}
 }
 
-func (s *toStringExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	if x, same := s.expr.eval(valueMap); same {
-		res = s
-	} else {
-		res = &toStringExpr{expr: x}
-	}
-	return
+type toStringExpr struct {
+	expr starlarkExpr
 }
 
 func (s *toStringExpr) emit(ctx *generationContext) {
@@ -265,17 +281,17 @@
 	s.emit(gctx)
 }
 
-type notExpr struct {
-	expr starlarkExpr
+func (s *toStringExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	s.expr = s.expr.transform(transformer)
+	if replacement := transformer(s); replacement != nil {
+		return replacement
+	} else {
+		return s
+	}
 }
 
-func (n *notExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	if x, same := n.expr.eval(valueMap); same {
-		res = n
-	} else {
-		res = &notExpr{expr: x}
-	}
-	return
+type notExpr struct {
+	expr starlarkExpr
 }
 
 func (n *notExpr) emit(ctx *generationContext) {
@@ -291,22 +307,20 @@
 	n.emit(gctx)
 }
 
+func (n *notExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	n.expr = n.expr.transform(transformer)
+	if replacement := transformer(n); replacement != nil {
+		return replacement
+	} else {
+		return n
+	}
+}
+
 type eqExpr struct {
 	left, right starlarkExpr
 	isEq        bool // if false, it's !=
 }
 
-func (eq *eqExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	xLeft, sameLeft := eq.left.eval(valueMap)
-	xRight, sameRight := eq.right.eval(valueMap)
-	if same = sameLeft && sameRight; same {
-		res = eq
-	} else {
-		res = &eqExpr{left: xLeft, right: xRight, isEq: eq.isEq}
-	}
-	return
-}
-
 func (eq *eqExpr) emit(gctx *generationContext) {
 	var stringOperand string
 	var otherOperand starlarkExpr
@@ -360,18 +374,21 @@
 	eq.emit(gctx)
 }
 
+func (eq *eqExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	eq.left = eq.left.transform(transformer)
+	eq.right = eq.right.transform(transformer)
+	if replacement := transformer(eq); replacement != nil {
+		return replacement
+	} else {
+		return eq
+	}
+}
+
 // variableDefinedExpr corresponds to Make's ifdef VAR
 type variableDefinedExpr struct {
 	v variable
 }
 
-func (v *variableDefinedExpr) eval(_ map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	res = v
-	same = true
-	return
-
-}
-
 func (v *variableDefinedExpr) emit(gctx *generationContext) {
 	if v.v != nil {
 		v.v.emitDefined(gctx)
@@ -388,24 +405,13 @@
 	v.emit(gctx)
 }
 
-type listExpr struct {
-	items []starlarkExpr
+func (v *variableDefinedExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	// TODO: VariableDefinedExpr isn't really an expression?
+	return v
 }
 
-func (l *listExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	newItems := make([]starlarkExpr, len(l.items))
-	same = true
-	for i, item := range l.items {
-		var sameItem bool
-		newItems[i], sameItem = item.eval(valueMap)
-		same = same && sameItem
-	}
-	if same {
-		res = l
-	} else {
-		res = &listExpr{newItems}
-	}
-	return
+type listExpr struct {
+	items []starlarkExpr
 }
 
 func (l *listExpr) emit(gctx *generationContext) {
@@ -442,6 +448,19 @@
 	l.emit(gctx)
 }
 
+func (l *listExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	itemsCopy := make([]starlarkExpr, len(l.items))
+	for i, item := range l.items {
+		itemsCopy[i] = item.transform(transformer)
+	}
+	l.items = itemsCopy
+	if replacement := transformer(l); replacement != nil {
+		return replacement
+	} else {
+		return l
+	}
+}
+
 func newStringListExpr(items []string) *listExpr {
 	v := listExpr{}
 	for _, item := range items {
@@ -481,22 +500,6 @@
 	gctx.indentLevel -= 2
 }
 
-func (c *concatExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	same = true
-	xConcat := &concatExpr{items: make([]starlarkExpr, len(c.items))}
-	for i, item := range c.items {
-		var sameItem bool
-		xConcat.items[i], sameItem = item.eval(valueMap)
-		same = same && sameItem
-	}
-	if same {
-		res = c
-	} else {
-		res = xConcat
-	}
-	return
-}
-
 func (_ *concatExpr) typ() starlarkType {
 	return starlarkTypeList
 }
@@ -505,6 +508,19 @@
 	c.emit(gctx)
 }
 
+func (c *concatExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	itemsCopy := make([]starlarkExpr, len(c.items))
+	for i, item := range c.items {
+		itemsCopy[i] = item.transform(transformer)
+	}
+	c.items = itemsCopy
+	if replacement := transformer(c); replacement != nil {
+		return replacement
+	} else {
+		return c
+	}
+}
+
 // inExpr generates <expr> [not] in <list>
 type inExpr struct {
 	expr  starlarkExpr
@@ -512,19 +528,6 @@
 	isNot bool
 }
 
-func (i *inExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	x := &inExpr{isNot: i.isNot}
-	var sameExpr, sameList bool
-	x.expr, sameExpr = i.expr.eval(valueMap)
-	x.list, sameList = i.list.eval(valueMap)
-	if same = sameExpr && sameList; same {
-		res = i
-	} else {
-		res = x
-	}
-	return
-}
-
 func (i *inExpr) emit(gctx *generationContext) {
 	i.expr.emit(gctx)
 	if i.isNot {
@@ -543,35 +546,44 @@
 	i.emit(gctx)
 }
 
+func (i *inExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	i.expr = i.expr.transform(transformer)
+	i.list = i.list.transform(transformer)
+	if replacement := transformer(i); replacement != nil {
+		return replacement
+	} else {
+		return i
+	}
+}
+
 type indexExpr struct {
 	array starlarkExpr
 	index starlarkExpr
 }
 
-func (ix indexExpr) emit(gctx *generationContext) {
+func (ix *indexExpr) emit(gctx *generationContext) {
 	ix.array.emit(gctx)
 	gctx.write("[")
 	ix.index.emit(gctx)
 	gctx.write("]")
 }
 
-func (ix indexExpr) typ() starlarkType {
+func (ix *indexExpr) typ() starlarkType {
 	return starlarkTypeString
 }
 
-func (ix indexExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	newArray, isSameArray := ix.array.eval(valueMap)
-	newIndex, isSameIndex := ix.index.eval(valueMap)
-	if same = isSameArray && isSameIndex; same {
-		res = ix
-	} else {
-		res = &indexExpr{newArray, newIndex}
-	}
-	return
+func (ix *indexExpr) emitListVarCopy(gctx *generationContext) {
+	ix.emit(gctx)
 }
 
-func (ix indexExpr) emitListVarCopy(gctx *generationContext) {
-	ix.emit(gctx)
+func (ix *indexExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	ix.array = ix.array.transform(transformer)
+	ix.index = ix.index.transform(transformer)
+	if replacement := transformer(ix); replacement != nil {
+		return replacement
+	} else {
+		return ix
+	}
 }
 
 type callExpr struct {
@@ -581,27 +593,6 @@
 	returnType starlarkType
 }
 
-func (cx *callExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	newCallExpr := &callExpr{name: cx.name, args: make([]starlarkExpr, len(cx.args)),
-		returnType: cx.returnType}
-	if cx.object != nil {
-		newCallExpr.object, same = cx.object.eval(valueMap)
-	} else {
-		same = true
-	}
-	for i, args := range cx.args {
-		var s bool
-		newCallExpr.args[i], s = args.eval(valueMap)
-		same = same && s
-	}
-	if same {
-		res = cx
-	} else {
-		res = newCallExpr
-	}
-	return
-}
-
 func (cx *callExpr) emit(gctx *generationContext) {
 	sep := ""
 	if cx.object != nil {
@@ -642,28 +633,27 @@
 	cx.emit(gctx)
 }
 
+func (cx *callExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if cx.object != nil {
+		cx.object = cx.object.transform(transformer)
+	}
+	argsCopy := make([]starlarkExpr, len(cx.args))
+	for i, arg := range cx.args {
+		argsCopy[i] = arg.transform(transformer)
+	}
+	if replacement := transformer(cx); replacement != nil {
+		return replacement
+	} else {
+		return cx
+	}
+}
+
 type ifExpr struct {
 	condition starlarkExpr
 	ifTrue    starlarkExpr
 	ifFalse   starlarkExpr
 }
 
-func (i *ifExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	cond, condSame := i.condition.eval(valueMap)
-	t, tSame := i.ifTrue.eval(valueMap)
-	f, fSame := i.ifFalse.eval(valueMap)
-	same = condSame && tSame && fSame
-	if same {
-		return i, same
-	} else {
-		return &ifExpr{
-			condition: cond,
-			ifTrue:    t,
-			ifFalse:   f,
-		}, same
-	}
-}
-
 func (i *ifExpr) emit(gctx *generationContext) {
 	gctx.write("(")
 	i.ifTrue.emit(gctx)
@@ -691,17 +681,78 @@
 	i.emit(gctx)
 }
 
+func (i *ifExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	i.condition = i.condition.transform(transformer)
+	i.ifTrue = i.ifTrue.transform(transformer)
+	i.ifFalse = i.ifFalse.transform(transformer)
+	if replacement := transformer(i); replacement != nil {
+		return replacement
+	} else {
+		return i
+	}
+}
+
+type identifierExpr struct {
+	name string
+}
+
+func (i *identifierExpr) emit(gctx *generationContext) {
+	gctx.write(i.name)
+}
+
+func (i *identifierExpr) typ() starlarkType {
+	return starlarkTypeUnknown
+}
+
+func (i *identifierExpr) emitListVarCopy(gctx *generationContext) {
+	i.emit(gctx)
+}
+
+func (i *identifierExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if replacement := transformer(i); replacement != nil {
+		return replacement
+	} else {
+		return i
+	}
+}
+
+type foreachExpr struct {
+	varName string
+	list    starlarkExpr
+	action  starlarkExpr
+}
+
+func (f *foreachExpr) emit(gctx *generationContext) {
+	gctx.write("[")
+	f.action.emit(gctx)
+	gctx.write(" for " + f.varName + " in ")
+	f.list.emit(gctx)
+	gctx.write("]")
+}
+
+func (f *foreachExpr) typ() starlarkType {
+	return starlarkTypeList
+}
+
+func (f *foreachExpr) emitListVarCopy(gctx *generationContext) {
+	f.emit(gctx)
+}
+
+func (f *foreachExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	f.list = f.list.transform(transformer)
+	f.action = f.action.transform(transformer)
+	if replacement := transformer(f); replacement != nil {
+		return replacement
+	} else {
+		return f
+	}
+}
+
 type badExpr struct {
 	errorLocation ErrorLocation
 	message       string
 }
 
-func (b *badExpr) eval(_ map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	res = b
-	same = true
-	return
-}
-
 func (b *badExpr) emit(gctx *generationContext) {
 	gctx.emitConversionError(b.errorLocation, b.message)
 }
@@ -714,6 +765,14 @@
 	panic("implement me")
 }
 
+func (b *badExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if replacement := transformer(b); replacement != nil {
+		return replacement
+	} else {
+		return b
+	}
+}
+
 func maybeConvertToStringList(expr starlarkExpr) starlarkExpr {
 	if xString, ok := expr.(*stringLiteralExpr); ok {
 		return newStringListExpr(strings.Fields(xString.literal))
diff --git a/mk2rbc/mk2rbc.go b/mk2rbc/mk2rbc.go
index d5ff181..024311e 100644
--- a/mk2rbc/mk2rbc.go
+++ b/mk2rbc/mk2rbc.go
@@ -105,12 +105,13 @@
 	"dist-for-goals":                      {baseName + ".mkdist_for_goals", starlarkTypeVoid, hiddenArgGlobal},
 	"enforce-product-packages-exist":      {baseName + ".enforce_product_packages_exist", starlarkTypeVoid, hiddenArgNone},
 	"error":                               {baseName + ".mkerror", starlarkTypeVoid, hiddenArgNone},
-	"findstring":                          {"!findstring", starlarkTypeInt, hiddenArgNone},
+	"findstring":                          {baseName + ".findstring", starlarkTypeString, hiddenArgNone},
 	"find-copy-subdir-files":              {baseName + ".find_and_copy", starlarkTypeList, hiddenArgNone},
 	"find-word-in-list":                   {"!find-word-in-list", starlarkTypeUnknown, hiddenArgNone}, // internal macro
 	"filter":                              {baseName + ".filter", starlarkTypeList, hiddenArgNone},
 	"filter-out":                          {baseName + ".filter_out", starlarkTypeList, hiddenArgNone},
 	"firstword":                           {"!firstword", starlarkTypeString, hiddenArgNone},
+	"foreach":                             {"!foreach", starlarkTypeList, hiddenArgNone},
 	"get-vendor-board-platforms":          {"!get-vendor-board-platforms", starlarkTypeList, hiddenArgNone}, // internal macro, used by is-board-platform, etc.
 	"if":                                  {"!if", starlarkTypeUnknown, hiddenArgNone},
 	"info":                                {baseName + ".mkinfo", starlarkTypeVoid, hiddenArgNone},
@@ -147,14 +148,10 @@
 	"warning":    {baseName + ".mkwarning", starlarkTypeVoid, hiddenArgNone},
 	"word":       {baseName + "!word", starlarkTypeString, hiddenArgNone},
 	"wildcard":   {baseName + ".expand_wildcard", starlarkTypeList, hiddenArgNone},
+	"words":      {baseName + ".words", starlarkTypeList, hiddenArgNone},
 }
 
-var builtinFuncRex = regexp.MustCompile(
-	"^(addprefix|addsuffix|abspath|and|basename|call|dir|error|eval" +
-		"|flavor|foreach|file|filter|filter-out|findstring|firstword|guile" +
-		"|if|info|join|lastword|notdir|or|origin|patsubst|realpath" +
-		"|shell|sort|strip|subst|suffix|value|warning|word|wordlist|words" +
-		"|wildcard)")
+var identifierFullMatchRegex = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")
 
 // Conversion request parameters
 type Request struct {
@@ -414,7 +411,6 @@
 	ifNestLevel      int
 	moduleNameCount  map[string]int // count of imported modules with given basename
 	fatalError       error
-	builtinMakeVars  map[string]starlarkExpr
 	outputSuffix     string
 	errorLogger      ErrorLogger
 	tracedVariables  map[string]bool // variables to be traced in the generated script
@@ -467,7 +463,6 @@
 		currentNodeIndex: 0,
 		ifNestLevel:      0,
 		moduleNameCount:  make(map[string]int),
-		builtinMakeVars:  map[string]starlarkExpr{},
 		variables:        make(map[string]variable),
 		dependentModules: make(map[string]*moduleInfo),
 		soongNamespaces:  make(map[string]map[string]bool),
@@ -603,9 +598,6 @@
 		}
 	}
 
-	// TODO(asmundak): move evaluation to a separate pass
-	asgn.value, _ = asgn.value.eval(ctx.builtinMakeVars)
-
 	asgn.previous = ctx.lastAssignment(name)
 	ctx.setLastAssignment(name, asgn)
 	switch a.Type {
@@ -632,7 +624,6 @@
 		ctx.wrapBadExpr(xBad)
 		return
 	}
-	val, _ = val.eval(ctx.builtinMakeVars)
 
 	// Unfortunately, Soong namespaces can be set up by directly setting corresponding Make
 	// variables instead of via add_soong_config_namespace + add_soong_config_var_value.
@@ -788,7 +779,6 @@
 
 func (ctx *parseContext) handleSubConfig(
 	v mkparser.Node, pathExpr starlarkExpr, loadAlways bool, processModule func(inheritedModule)) {
-	pathExpr, _ = pathExpr.eval(ctx.builtinMakeVars)
 
 	// In a simple case, the name of a module to inherit/include is known statically.
 	if path, ok := maybeString(pathExpr); ok {
@@ -1125,7 +1115,7 @@
 			return xBad, true
 		}
 		return &eqExpr{
-			left:  &variableRefExpr{ctx.addVariable("TARGET_BOARD_PLATFORM"), false},
+			left:  NewVariableRefExpr(ctx.addVariable("TARGET_BOARD_PLATFORM"), false),
 			right: call.args[0],
 			isEq:  isEq,
 		}, true
@@ -1134,7 +1124,7 @@
 			return xBad, true
 		}
 		return &inExpr{
-			expr:  &variableRefExpr{ctx.addVariable("TARGET_BOARD_PLATFORM"), false},
+			expr:  NewVariableRefExpr(ctx.addVariable("TARGET_BOARD_PLATFORM"), false),
 			list:  maybeConvertToStringList(call.args[0]),
 			isNot: !isEq,
 		}, true
@@ -1143,7 +1133,7 @@
 			return xBad, true
 		}
 		return &inExpr{
-			expr:  &variableRefExpr{ctx.addVariable("TARGET_PRODUCT"), true},
+			expr:  NewVariableRefExpr(ctx.addVariable("TARGET_PRODUCT"), true),
 			list:  maybeConvertToStringList(call.args[0]),
 			isNot: !isEq,
 		}, true
@@ -1156,8 +1146,8 @@
 			return ctx.newBadExpr(directive, "cannot handle non-constant argument to is-vendor-board-platform"), true
 		}
 		return &inExpr{
-			expr:  &variableRefExpr{ctx.addVariable("TARGET_BOARD_PLATFORM"), false},
-			list:  &variableRefExpr{ctx.addVariable(s + "_BOARD_PLATFORMS"), true},
+			expr:  NewVariableRefExpr(ctx.addVariable("TARGET_BOARD_PLATFORM"), false),
+			list:  NewVariableRefExpr(ctx.addVariable(s+"_BOARD_PLATFORMS"), true),
 			isNot: !isEq,
 		}, true
 
@@ -1186,8 +1176,8 @@
 		// if the expression is ifneq (,$(call is-vendor-board-platform,...)), negate==true,
 		// so we should set inExpr.isNot to false
 		return &inExpr{
-			expr:  &variableRefExpr{ctx.addVariable("TARGET_BOARD_PLATFORM"), false},
-			list:  &variableRefExpr{ctx.addVariable("QCOM_BOARD_PLATFORMS"), true},
+			expr:  NewVariableRefExpr(ctx.addVariable("TARGET_BOARD_PLATFORM"), false),
+			list:  NewVariableRefExpr(ctx.addVariable("QCOM_BOARD_PLATFORMS"), true),
 			isNot: isEq,
 		}, true
 	}
@@ -1288,8 +1278,21 @@
 			right: &intLiteralExpr{-1},
 			isEq:  !negate,
 		}
+	} else if s, ok := maybeString(xValue); ok {
+		if s2, ok := maybeString(xCall.args[0]); ok && s == s2 {
+			return &eqExpr{
+				left: &callExpr{
+					object:     xCall.args[1],
+					name:       "find",
+					args:       []starlarkExpr{xCall.args[0]},
+					returnType: starlarkTypeInt,
+				},
+				right: &intLiteralExpr{-1},
+				isEq:  negate,
+			}
+		}
 	}
-	return ctx.newBadExpr(directive, "findstring result can be compared only to empty: %s", xValue)
+	return ctx.newBadExpr(directive, "$(findstring) can only be compared to nothing or its first argument")
 }
 
 func (ctx *parseContext) parseCompareStripFuncResult(directive *mkparser.Directive,
@@ -1356,12 +1359,12 @@
 				args: []starlarkExpr{
 					&stringLiteralExpr{literal: substParts[0]},
 					&stringLiteralExpr{literal: substParts[1]},
-					&variableRefExpr{v, ctx.lastAssignment(v.name()) != nil},
+					NewVariableRefExpr(v, ctx.lastAssignment(v.name()) != nil),
 				},
 			}
 		}
 		if v := ctx.addVariable(refDump); v != nil {
-			return &variableRefExpr{v, ctx.lastAssignment(v.name()) != nil}
+			return NewVariableRefExpr(v, ctx.lastAssignment(v.name()) != nil)
 		}
 		return ctx.newBadExpr(node, "unknown variable %s", refDump)
 	}
@@ -1399,12 +1402,14 @@
 	switch expr.name {
 	case "if":
 		return ctx.parseIfFunc(node, args)
+	case "foreach":
+		return ctx.parseForeachFunc(node, args)
 	case "word":
 		return ctx.parseWordFunc(node, args)
 	case "firstword", "lastword":
 		return ctx.parseFirstOrLastwordFunc(node, expr.name, args)
 	case "my-dir":
-		return &variableRefExpr{ctx.addVariable("LOCAL_PATH"), true}
+		return NewVariableRefExpr(ctx.addVariable("LOCAL_PATH"), true)
 	case "subst", "patsubst":
 		return ctx.parseSubstFunc(node, expr.name, args)
 	default:
@@ -1483,6 +1488,38 @@
 	}
 }
 
+func (ctx *parseContext) parseForeachFunc(node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+	words := args.Split(",")
+	if len(words) != 3 {
+		return ctx.newBadExpr(node, "foreach function should have 3 arguments, found "+strconv.Itoa(len(words)))
+	}
+	if !words[0].Const() || words[0].Empty() || !identifierFullMatchRegex.MatchString(words[0].Strings[0]) {
+		return ctx.newBadExpr(node, "first argument to foreach function must be a simple string identifier")
+	}
+	loopVarName := words[0].Strings[0]
+	list := ctx.parseMakeString(node, words[1])
+	action := ctx.parseMakeString(node, words[2]).transform(func(expr starlarkExpr) starlarkExpr {
+		if varRefExpr, ok := expr.(*variableRefExpr); ok && varRefExpr.ref.name() == loopVarName {
+			return &identifierExpr{loopVarName}
+		}
+		return nil
+	})
+
+	if list.typ() != starlarkTypeList {
+		list = &callExpr{
+			name:       "words",
+			returnType: knownFunctions["words"].returnType,
+			args:       []starlarkExpr{list},
+		}
+	}
+
+	return &foreachExpr{
+		varName: loopVarName,
+		list:    list,
+		action:  action,
+	}
+}
+
 func (ctx *parseContext) parseWordFunc(node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
 	words := args.Split(",")
 	if len(words) != 2 {
@@ -1504,7 +1541,7 @@
 	if array.typ() != starlarkTypeList {
 		array = &callExpr{object: array, name: "split", returnType: starlarkTypeList}
 	}
-	return indexExpr{array, &intLiteralExpr{int(index - 1)}}
+	return &indexExpr{array, &intLiteralExpr{int(index - 1)}}
 }
 
 func (ctx *parseContext) parseFirstOrLastwordFunc(node mkparser.Node, name string, args *mkparser.MakeString) starlarkExpr {
@@ -1535,16 +1572,18 @@
 	// If we reached here, it's neither string literal nor a simple variable,
 	// we need a full-blown interpolation node that will generate
 	// "a%b%c" % (X, Y) for a$(X)b$(Y)c
-	xInterp := &interpolateExpr{args: make([]starlarkExpr, len(mk.Variables))}
-	for i, ref := range mk.Variables {
-		arg := ctx.parseReference(node, ref.Name)
-		if x, ok := arg.(*badExpr); ok {
-			return x
+	parts := make([]starlarkExpr, len(mk.Variables)+len(mk.Strings))
+	for i := 0; i < len(parts); i++ {
+		if i%2 == 0 {
+			parts[i] = &stringLiteralExpr{literal: mk.Strings[i/2]}
+		} else {
+			parts[i] = ctx.parseReference(node, mk.Variables[i/2].Name)
+			if x, ok := parts[i].(*badExpr); ok {
+				return x
+			}
 		}
-		xInterp.args[i] = arg
 	}
-	xInterp.chunks = append(xInterp.chunks, mk.Strings...)
-	return xInterp
+	return NewInterpolateExpr(parts)
 }
 
 // Handles the statements whose treatment is the same in all contexts: comment,
diff --git a/mk2rbc/mk2rbc_test.go b/mk2rbc/mk2rbc_test.go
index 78444c9..94c4fe6 100644
--- a/mk2rbc/mk2rbc_test.go
+++ b/mk2rbc/mk2rbc_test.go
@@ -633,15 +633,42 @@
 		desc:   "findstring call",
 		mkname: "product.mk",
 		in: `
+result := $(findstring a,a b c)
+result := $(findstring b,x y z)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  _result = rblf.findstring("a", "a b c")
+  _result = rblf.findstring("b", "x y z")
+`,
+	},
+	{
+		desc:   "findstring in if statement",
+		mkname: "product.mk",
+		in: `
+ifeq ($(findstring foo,$(PRODUCT_PACKAGES)),)
+endif
 ifneq ($(findstring foo,$(PRODUCT_PACKAGES)),)
 endif
+ifeq ($(findstring foo,$(PRODUCT_PACKAGES)),foo)
+endif
+ifneq ($(findstring foo,$(PRODUCT_PACKAGES)),foo)
+endif
 `,
 		expected: `load("//build/make/core:product_config.rbc", "rblf")
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
+  if (cfg.get("PRODUCT_PACKAGES", [])).find("foo") == -1:
+    pass
   if (cfg.get("PRODUCT_PACKAGES", [])).find("foo") != -1:
     pass
+  if (cfg.get("PRODUCT_PACKAGES", [])).find("foo") != -1:
+    pass
+  if (cfg.get("PRODUCT_PACKAGES", [])).find("foo") == -1:
+    pass
 `,
 	},
 	{
@@ -1131,6 +1158,28 @@
   g["OBJECTS2"] = rblf.mkpatsubst("%.c", "%.o", g["SOURCES"])
 `,
 	},
+	{
+		desc:   "foreach expressions",
+		mkname: "product.mk",
+		in: `
+BOOT_KERNEL_MODULES := foo.ko bar.ko
+BOOT_KERNEL_MODULES_FILTER := $(foreach m,$(BOOT_KERNEL_MODULES),%/$(m))
+BOOT_KERNEL_MODULES_LIST := foo.ko
+BOOT_KERNEL_MODULES_LIST += bar.ko
+BOOT_KERNEL_MODULES_FILTER_2 := $(foreach m,$(BOOT_KERNEL_MODULES_LIST),%/$(m))
+
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["BOOT_KERNEL_MODULES"] = "foo.ko bar.ko"
+  g["BOOT_KERNEL_MODULES_FILTER"] = ["%%/%s" % m for m in rblf.words(g["BOOT_KERNEL_MODULES"])]
+  g["BOOT_KERNEL_MODULES_LIST"] = ["foo.ko"]
+  g["BOOT_KERNEL_MODULES_LIST"] += ["bar.ko"]
+  g["BOOT_KERNEL_MODULES_FILTER_2"] = ["%%/%s" % m for m in g["BOOT_KERNEL_MODULES_LIST"]]
+`,
+	},
 }
 
 var known_variables = []struct {
diff --git a/python/androidmk.go b/python/androidmk.go
index ccc85ec..233d867 100644
--- a/python/androidmk.go
+++ b/python/androidmk.go
@@ -78,7 +78,7 @@
 	entries.Required = append(entries.Required, "libc++")
 	entries.ExtraEntries = append(entries.ExtraEntries,
 		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-			path, file := filepath.Split(installer.path.ToMakePath().String())
+			path, file := filepath.Split(installer.path.String())
 			stem := strings.TrimSuffix(file, filepath.Ext(file))
 
 			entries.SetString("LOCAL_MODULE_SUFFIX", filepath.Ext(file))
diff --git a/rust/androidmk.go b/rust/androidmk.go
index 1f18b4a..4e58632 100644
--- a/rust/androidmk.go
+++ b/rust/androidmk.go
@@ -193,7 +193,7 @@
 	ret.ExtraEntries = append(ret.ExtraEntries,
 		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 			entries.SetPath("LOCAL_SOONG_UNSTRIPPED_BINARY", compiler.unstrippedOutputFile)
-			path, file := filepath.Split(compiler.path.ToMakePath().String())
+			path, file := filepath.Split(compiler.path.String())
 			stem, suffix, _ := android.SplitFileExt(file)
 			entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
 			entries.SetString("LOCAL_MODULE_PATH", path)
diff --git a/rust/rust.go b/rust/rust.go
index 300c0f5..c2585f2 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -1312,10 +1312,6 @@
 	return mod.InRecovery()
 }
 
-func (mod *Module) InstallBypassMake() bool {
-	return true
-}
-
 func linkPathFromFilePath(filepath android.Path) string {
 	return strings.Split(filepath.String(), filepath.Base())[0]
 }
diff --git a/scripts/OWNERS b/scripts/OWNERS
index 88787cd..3f4f9c0 100644
--- a/scripts/OWNERS
+++ b/scripts/OWNERS
@@ -1,5 +1,4 @@
 per-file system-clang-format,system-clang-format-2 = enh@google.com,smoreland@google.com
-per-file build-mainline-modules.sh = ngeoffray@google.com,paulduffin@google.com,mast@google.com
 per-file build-aml-prebuilts.sh = ngeoffray@google.com,paulduffin@google.com,mast@google.com
 per-file construct_context.py = ngeoffray@google.com,calin@google.com,skvadrik@google.com
 per-file conv_linker_config.py = kiyoungkim@google.com, jiyong@google.com, jooyung@google.com
diff --git a/scripts/build-mainline-modules.sh b/scripts/build-mainline-modules.sh
deleted file mode 100755
index 1e3f6ce..0000000
--- a/scripts/build-mainline-modules.sh
+++ /dev/null
@@ -1,108 +0,0 @@
-#!/bin/bash -e
-
-# Non exhaustive list of modules where we want prebuilts. More can be added as
-# needed.
-MAINLINE_MODULES=(
-  com.android.art
-  com.android.art.debug
-  com.android.art.testing
-  com.android.conscrypt
-  com.android.i18n
-  com.android.os.statsd
-  com.android.runtime
-  com.android.tzdata
-)
-
-# List of SDKs and module exports we know of.
-MODULES_SDK_AND_EXPORTS=(
-  art-module-sdk
-  art-module-test-exports
-  conscrypt-module-host-exports
-  conscrypt-module-sdk
-  conscrypt-module-test-exports
-  i18n-module-host-exports
-  i18n-module-sdk
-  i18n-module-test-exports
-  platform-mainline-sdk
-  platform-mainline-test-exports
-  runtime-module-host-exports
-  runtime-module-sdk
-  statsd-module-sdk
-  tzdata-module-test-exports
-)
-
-# List of libraries installed on the platform that are needed for ART chroot
-# testing.
-PLATFORM_LIBRARIES=(
-  heapprofd_client_api
-  libartpalette-system
-  liblog
-)
-
-# We want to create apex modules for all supported architectures.
-PRODUCTS=(
-  aosp_arm
-  aosp_arm64
-  aosp_x86
-  aosp_x86_64
-)
-
-if [ ! -e "build/make/core/Makefile" ]; then
-  echo "$0 must be run from the top of the tree"
-  exit 1
-fi
-
-echo_and_run() {
-  echo "$*"
-  "$@"
-}
-
-lib_dir() {
-  case $1 in
-    (aosp_arm|aosp_x86) echo "lib";;
-    (aosp_arm64|aosp_x86_64) echo "lib64";;
-  esac
-}
-
-# Make sure this build builds from source, regardless of the default.
-export SOONG_CONFIG_art_module_source_build=true
-
-# This script does not intend to handle compressed APEX
-export OVERRIDE_PRODUCT_COMPRESSED_APEX=false
-
-OUT_DIR=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT= get_build_var OUT_DIR)
-DIST_DIR=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT= get_build_var DIST_DIR)
-
-for product in "${PRODUCTS[@]}"; do
-  echo_and_run build/soong/soong_ui.bash --make-mode $@ \
-    TARGET_PRODUCT=${product} \
-    ${MAINLINE_MODULES[@]} \
-    ${PLATFORM_LIBRARIES[@]}
-
-  PRODUCT_OUT=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT=${product} get_build_var PRODUCT_OUT)
-  TARGET_ARCH=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT=${product} get_build_var TARGET_ARCH)
-  rm -rf ${DIST_DIR}/${TARGET_ARCH}/
-  mkdir -p ${DIST_DIR}/${TARGET_ARCH}/
-  for module in "${MAINLINE_MODULES[@]}"; do
-    echo_and_run cp ${PWD}/${PRODUCT_OUT}/system/apex/${module}.apex ${DIST_DIR}/${TARGET_ARCH}/
-  done
-  for library in "${PLATFORM_LIBRARIES[@]}"; do
-    libdir=$(lib_dir $product)
-    echo_and_run cp ${PWD}/${PRODUCT_OUT}/system/${libdir}/${library}.so ${DIST_DIR}/${TARGET_ARCH}/
-  done
-done
-
-# We use force building LLVM components flag (even though we actually don't
-# compile them) because we don't have bionic host prebuilts
-# for them.
-export FORCE_BUILD_LLVM_COMPONENTS=true
-
-# Create multi-archs SDKs in a different out directory. The multi-arch script
-# uses Soong in --soong-only mode which cannot use the same directory as normal
-# mode with make.
-export OUT_DIR=${OUT_DIR}/aml
-echo_and_run build/soong/scripts/build-aml-prebuilts.sh \
-  TARGET_PRODUCT=mainline_sdk ${MODULES_SDK_AND_EXPORTS[@]}
-
-rm -rf ${DIST_DIR}/mainline-sdks
-echo_and_run cp -R ${OUT_DIR}/soong/mainline-sdks ${DIST_DIR}
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index 2d98e8b..e1df8ac 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -166,10 +166,6 @@
 
 var _ android.HostToolProvider = (*ShBinary)(nil)
 
-func (s *ShBinary) InstallBypassMake() bool {
-	return true
-}
-
 type ShTest struct {
 	ShBinary
 
@@ -435,7 +431,7 @@
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				s.customAndroidMkEntries(entries)
-				entries.SetPath("LOCAL_MODULE_PATH", s.installDir.ToMakePath())
+				entries.SetPath("LOCAL_MODULE_PATH", s.installDir)
 				entries.AddCompatibilityTestSuites(s.testProperties.Test_suites...)
 				if s.testConfig != nil {
 					entries.SetPath("LOCAL_FULL_TEST_CONFIG", s.testConfig)
diff --git a/sh/sh_binary_test.go b/sh/sh_binary_test.go
index 865d5f3..28b6fb9 100644
--- a/sh/sh_binary_test.go
+++ b/sh/sh_binary_test.go
@@ -42,7 +42,10 @@
 }
 
 func TestShTestSubDir(t *testing.T) {
-	ctx, config := testShBinary(t, `
+	result := android.GroupFixturePreparers(
+		prepareForShTest,
+		android.FixtureModifyConfig(android.SetKatiEnabledForTests),
+	).RunTestWithBp(t, `
 		sh_test {
 			name: "foo",
 			src: "test.sh",
@@ -50,17 +53,20 @@
 		}
 	`)
 
-	mod := ctx.ModuleForTests("foo", "android_arm64_armv8-a").Module().(*ShTest)
+	mod := result.ModuleForTests("foo", "android_arm64_armv8-a").Module().(*ShTest)
 
-	entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
+	entries := android.AndroidMkEntriesForTest(t, result.TestContext, mod)[0]
 
 	expectedPath := "out/target/product/test_device/data/nativetest64/foo_test"
 	actualPath := entries.EntryMap["LOCAL_MODULE_PATH"][0]
-	android.AssertStringPathRelativeToTopEquals(t, "LOCAL_MODULE_PATH[0]", config, expectedPath, actualPath)
+	android.AssertStringPathRelativeToTopEquals(t, "LOCAL_MODULE_PATH[0]", result.Config, expectedPath, actualPath)
 }
 
 func TestShTest(t *testing.T) {
-	ctx, config := testShBinary(t, `
+	result := android.GroupFixturePreparers(
+		prepareForShTest,
+		android.FixtureModifyConfig(android.SetKatiEnabledForTests),
+	).RunTestWithBp(t, `
 		sh_test {
 			name: "foo",
 			src: "test.sh",
@@ -72,13 +78,13 @@
 		}
 	`)
 
-	mod := ctx.ModuleForTests("foo", "android_arm64_armv8-a").Module().(*ShTest)
+	mod := result.ModuleForTests("foo", "android_arm64_armv8-a").Module().(*ShTest)
 
-	entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
+	entries := android.AndroidMkEntriesForTest(t, result.TestContext, mod)[0]
 
 	expectedPath := "out/target/product/test_device/data/nativetest64/foo"
 	actualPath := entries.EntryMap["LOCAL_MODULE_PATH"][0]
-	android.AssertStringPathRelativeToTopEquals(t, "LOCAL_MODULE_PATH[0]", config, expectedPath, actualPath)
+	android.AssertStringPathRelativeToTopEquals(t, "LOCAL_MODULE_PATH[0]", result.Config, expectedPath, actualPath)
 
 	expectedData := []string{":testdata/data1", ":testdata/sub/data2"}
 	actualData := entries.EntryMap["LOCAL_TEST_DATA"]
diff --git a/snapshot/host_snapshot.go b/snapshot/host_snapshot.go
index 2a25a00..252cef8 100644
--- a/snapshot/host_snapshot.go
+++ b/snapshot/host_snapshot.go
@@ -180,7 +180,7 @@
 		DistFiles:  android.MakeDefaultDistFiles(f.zipFile.Path()),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", f.installDir.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", f.installDir.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", f.installFileName())
 			},
 		},
diff --git a/ui/build/config.go b/ui/build/config.go
index 4c26d3e..b6d0d27 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -721,10 +721,6 @@
 }
 
 func (c *configImpl) SoongBuildInvocationNeeded() bool {
-	if c.Dist() {
-		return true
-	}
-
 	if len(c.Arguments()) > 0 {
 		// Explicit targets requested that are not special targets like b2pbuild
 		// or the JSON module graph
@@ -736,6 +732,11 @@
 		return true
 	}
 
+	// bp2build + dist may be used to dist bp2build logs but does not require SoongBuildInvocation
+	if c.Dist() && !c.Bp2Build() {
+		return true
+	}
+
 	// build.ninja doesn't need to be generated
 	return false
 }