Merge "Revert "Revert "Allow codename.fingerprint format for minSdkVersion"""
diff --git a/Android.bp b/Android.bp
index 0382ee2..0f7ef9e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -473,6 +473,7 @@
     ],
     testSrcs: [
         "apex/apex_test.go",
+        "apex/vndk_test.go",
     ],
     pluginFor: ["soong_build"],
 }
@@ -548,6 +549,7 @@
     name: "libatomic",
     defaults: ["linux_bionic_supported"],
     vendor_available: true,
+    ramdisk_available: true,
     recovery_available: true,
     native_bridge_supported: true,
 
@@ -594,8 +596,10 @@
     name: "libgcc_stripped",
     defaults: ["linux_bionic_supported"],
     vendor_available: true,
+    ramdisk_available: true,
     recovery_available: true,
     native_bridge_supported: true,
+    sdk_version: "current",
 
     arch: {
         arm: {
diff --git a/android/apex.go b/android/apex.go
index 0b901ae..a4b6956 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -82,7 +82,10 @@
 }
 
 type ApexProperties struct {
-	// Availability of this module in APEXes. Only the listed APEXes can include this module.
+	// Availability of this module in APEXes. Only the listed APEXes can contain
+	// this module. If the module has stubs then other APEXes and the platform may
+	// access it through them (subject to visibility).
+	//
 	// "//apex_available:anyapex" is a pseudo APEX name that matches to any APEX.
 	// "//apex_available:platform" refers to non-APEX partitions like "system.img".
 	// Default is ["//apex_available:platform", "//apex_available:anyapex"].
diff --git a/android/arch.go b/android/arch.go
index b5b8a8f..65833a8 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -838,8 +838,8 @@
 		osTargets = targets
 	}
 
-	// only the primary arch in the recovery partition
-	if os == Android && module.InstallInRecovery() {
+	// only the primary arch in the ramdisk / recovery partition
+	if os == Android && (module.InstallInRecovery() || module.InstallInRamdisk()) {
 		osTargets = []Target{osTargets[0]}
 	}
 
@@ -926,16 +926,31 @@
 	return targets
 }
 
-// createArchType takes a reflect.Type that is either a struct or a pointer to a struct, and returns a list of
-// reflect.Type that contains the arch-variant properties inside structs for each architecture, os, target, multilib,
-// etc.
-func createArchType(props reflect.Type) []reflect.Type {
-	propShards, _ := proptools.FilterPropertyStructSharded(props, filterArchStruct)
+type archPropTypeDesc struct {
+	arch, multilib, target reflect.Type
+}
+
+type archPropRoot struct {
+	Arch, Multilib, Target interface{}
+}
+
+// createArchPropTypeDesc takes a reflect.Type that is either a struct or a pointer to a struct, and
+// returns lists of reflect.Types that contains the arch-variant properties inside structs for each
+// arch, multilib and target property.
+func createArchPropTypeDesc(props reflect.Type) []archPropTypeDesc {
+	// Each property struct shard will be nested many times under the runtime generated arch struct,
+	// which can hit the limit of 64kB for the name of runtime generated structs.  They are nested
+	// 97 times now, which may grow in the future, plus there is some overhead for the containing
+	// type.  This number may need to be reduced if too many are added, but reducing it too far
+	// could cause problems if a single deeply nested property no longer fits in the name.
+	const maxArchTypeNameSize = 500
+
+	propShards, _ := proptools.FilterPropertyStructSharded(props, maxArchTypeNameSize, filterArchStruct)
 	if len(propShards) == 0 {
 		return nil
 	}
 
-	var ret []reflect.Type
+	var ret []archPropTypeDesc
 	for _, props := range propShards {
 
 		variantFields := func(names []string) []reflect.StructField {
@@ -1011,20 +1026,12 @@
 		}
 
 		targetType := reflect.StructOf(variantFields(targets))
-		ret = append(ret, reflect.StructOf([]reflect.StructField{
-			{
-				Name: "Arch",
-				Type: archType,
-			},
-			{
-				Name: "Multilib",
-				Type: multilibType,
-			},
-			{
-				Name: "Target",
-				Type: targetType,
-			},
-		}))
+
+		ret = append(ret, archPropTypeDesc{
+			arch:     reflect.PtrTo(archType),
+			multilib: reflect.PtrTo(multilibType),
+			target:   reflect.PtrTo(targetType),
+		})
 	}
 	return ret
 }
@@ -1036,11 +1043,20 @@
 		// 16-bit limit on structure name length. The name is constructed
 		// based on the Go source representation of the structure, so
 		// the tag names count towards that length.
-		//
-		// TODO: handle the uncommon case of other tags being involved
-		if field.Tag == `android:"arch_variant"` {
-			field.Tag = ""
+
+		androidTag := field.Tag.Get("android")
+		values := strings.Split(androidTag, ",")
+
+		if string(field.Tag) != `android:"`+strings.Join(values, ",")+`"` {
+			panic(fmt.Errorf("unexpected tag format %q", field.Tag))
 		}
+		// these tags don't need to be present in the runtime generated struct type.
+		values = RemoveListFromList(values, []string{"arch_variant", "variant_prepend", "path"})
+		if len(values) > 0 {
+			panic(fmt.Errorf("unknown tags %q in field %q", values, prefix+field.Name))
+		}
+
+		field.Tag = ""
 		return true, field
 	}
 	return false, field
@@ -1069,12 +1085,16 @@
 		}
 
 		archPropTypes := archPropTypeMap.Once(NewCustomOnceKey(t), func() interface{} {
-			return createArchType(t)
-		}).([]reflect.Type)
+			return createArchPropTypeDesc(t)
+		}).([]archPropTypeDesc)
 
 		var archProperties []interface{}
 		for _, t := range archPropTypes {
-			archProperties = append(archProperties, reflect.New(t).Interface())
+			archProperties = append(archProperties, &archPropRoot{
+				Arch:     reflect.Zero(t.arch).Interface(),
+				Multilib: reflect.Zero(t.multilib).Interface(),
+				Target:   reflect.Zero(t.target).Interface(),
+			})
 		}
 		base.archProperties = append(base.archProperties, archProperties)
 		m.AddProperties(archProperties...)
@@ -1088,6 +1108,13 @@
 func (m *ModuleBase) appendProperties(ctx BottomUpMutatorContext,
 	dst interface{}, src reflect.Value, field, srcPrefix string) reflect.Value {
 
+	if src.Kind() == reflect.Ptr {
+		if src.IsNil() {
+			return src
+		}
+		src = src.Elem()
+	}
+
 	src = src.FieldByName(field)
 	if !src.IsValid() {
 		ctx.ModuleErrorf("field %q does not exist", srcPrefix)
@@ -1134,7 +1161,7 @@
 		for _, archProperties := range m.archProperties[i] {
 			archPropValues := reflect.ValueOf(archProperties).Elem()
 
-			targetProp := archPropValues.FieldByName("Target")
+			targetProp := archPropValues.FieldByName("Target").Elem()
 
 			// Handle host-specific properties in the form:
 			// target: {
@@ -1229,9 +1256,9 @@
 		for _, archProperties := range m.archProperties[i] {
 			archPropValues := reflect.ValueOf(archProperties).Elem()
 
-			archProp := archPropValues.FieldByName("Arch")
-			multilibProp := archPropValues.FieldByName("Multilib")
-			targetProp := archPropValues.FieldByName("Target")
+			archProp := archPropValues.FieldByName("Arch").Elem()
+			multilibProp := archPropValues.FieldByName("Multilib").Elem()
+			targetProp := archPropValues.FieldByName("Target").Elem()
 
 			// Handle arch-specific properties in the form:
 			// arch: {
diff --git a/android/arch_test.go b/android/arch_test.go
index 98b0534..b987d56 100644
--- a/android/arch_test.go
+++ b/android/arch_test.go
@@ -55,6 +55,24 @@
 			filtered: true,
 		},
 		{
+			name: "tags",
+			in: &struct {
+				A *string `android:"arch_variant"`
+				B *string `android:"arch_variant,path"`
+				C *string `android:"arch_variant,path,variant_prepend"`
+				D *string `android:"path,variant_prepend,arch_variant"`
+				E *string `android:"path"`
+				F *string
+			}{},
+			out: &struct {
+				A *string
+				B *string
+				C *string
+				D *string
+			}{},
+			filtered: true,
+		},
+		{
 			name: "all filtered",
 			in: &struct {
 				A *string
diff --git a/android/config.go b/android/config.go
index 1cb543d..f907de6 100644
--- a/android/config.go
+++ b/android/config.go
@@ -827,6 +827,10 @@
 	return Bool(c.productVariables.UseRBE)
 }
 
+func (c *config) UseRemoteBuild() bool {
+	return c.UseGoma() || c.UseRBE()
+}
+
 func (c *config) RunErrorProne() bool {
 	return c.IsEnvTrue("RUN_ERROR_PRONE")
 }
@@ -1027,6 +1031,10 @@
 	return Bool(c.config.productVariables.NativeCoverage)
 }
 
+func (c *deviceConfig) ClangCoverageEnabled() bool {
+	return Bool(c.config.productVariables.ClangCoverage)
+}
+
 func (c *deviceConfig) CoverageEnabledForPath(path string) bool {
 	coverage := false
 	if c.config.productVariables.CoveragePaths != nil {
@@ -1233,3 +1241,7 @@
 func (c *deviceConfig) DeviceSecondaryArchVariant() string {
 	return String(c.config.productVariables.DeviceSecondaryArchVariant)
 }
+
+func (c *deviceConfig) BoardUsesRecoveryAsBoot() bool {
+	return Bool(c.config.productVariables.BoardUsesRecoveryAsBoot)
+}
diff --git a/android/defs.go b/android/defs.go
index 4890c66..5c815e6 100644
--- a/android/defs.go
+++ b/android/defs.go
@@ -99,6 +99,9 @@
 
 	// Used only when USE_GOMA=true is set, to restrict non-goma jobs to the local parallelism value
 	localPool = blueprint.NewBuiltinPool("local_pool")
+
+	// Used for processes that need significant RAM to ensure there are not too many running in parallel.
+	highmemPool = blueprint.NewBuiltinPool("highmem_pool")
 )
 
 func init() {
diff --git a/android/image.go b/android/image.go
index 5291ce3..061bfa5 100644
--- a/android/image.go
+++ b/android/image.go
@@ -22,6 +22,10 @@
 	// CoreVariantNeeded should return true if the module needs a core variant (installed on the system image).
 	CoreVariantNeeded(ctx BaseModuleContext) bool
 
+	// RamdiskVariantNeeded should return true if the module needs a ramdisk variant (installed on the
+	// ramdisk partition).
+	RamdiskVariantNeeded(ctx BaseModuleContext) bool
+
 	// RecoveryVariantNeeded should return true if the module needs a recovery variant (installed on the
 	// recovery partition).
 	RecoveryVariantNeeded(ctx BaseModuleContext) bool
@@ -46,6 +50,9 @@
 
 	// RecoveryVariation means a module to be installed to recovery image.
 	RecoveryVariation string = "recovery"
+
+	// RamdiskVariation means a module to be installed to ramdisk image.
+	RamdiskVariation string = "ramdisk"
 )
 
 // imageMutator creates variants for modules that implement the ImageInterface that
@@ -63,6 +70,9 @@
 		if m.CoreVariantNeeded(ctx) {
 			variations = append(variations, CoreVariation)
 		}
+		if m.RamdiskVariantNeeded(ctx) {
+			variations = append(variations, RamdiskVariation)
+		}
 		if m.RecoveryVariantNeeded(ctx) {
 			variations = append(variations, RecoveryVariation)
 		}
diff --git a/android/module.go b/android/module.go
index 05115d6..4a72889 100644
--- a/android/module.go
+++ b/android/module.go
@@ -167,6 +167,7 @@
 	InstallInData() bool
 	InstallInTestcases() bool
 	InstallInSanitizerDir() bool
+	InstallInRamdisk() bool
 	InstallInRecovery() bool
 	InstallInRoot() bool
 	InstallBypassMake() bool
@@ -207,6 +208,7 @@
 	InstallInData() bool
 	InstallInTestcases() bool
 	InstallInSanitizerDir() bool
+	InstallInRamdisk() bool
 	InstallInRecovery() bool
 	InstallInRoot() bool
 	InstallBypassMake() bool
@@ -384,6 +386,9 @@
 	// Whether this module is installed to recovery partition
 	Recovery *bool
 
+	// Whether this module is installed to ramdisk
+	Ramdisk *bool
+
 	// Whether this module is built for non-native architecures (also known as native bridge binary)
 	Native_bridge_supported *bool `android:"arch_variant"`
 
@@ -867,6 +872,10 @@
 	return false
 }
 
+func (m *ModuleBase) InstallInRamdisk() bool {
+	return Bool(m.commonProperties.Ramdisk)
+}
+
 func (m *ModuleBase) InstallInRecovery() bool {
 	return Bool(m.commonProperties.Recovery)
 }
@@ -898,6 +907,10 @@
 	}
 }
 
+func (m *ModuleBase) InRamdisk() bool {
+	return m.base().commonProperties.ImageVariation == RamdiskVariation
+}
+
 func (m *ModuleBase) InRecovery() bool {
 	return m.base().commonProperties.ImageVariation == RecoveryVariation
 }
@@ -1338,7 +1351,7 @@
 func (m *moduleContext) Rule(pctx PackageContext, name string, params blueprint.RuleParams,
 	argNames ...string) blueprint.Rule {
 
-	if (m.config.UseGoma() || m.config.UseRBE()) && params.Pool == nil {
+	if m.config.UseRemoteBuild() && params.Pool == nil {
 		// When USE_GOMA=true or USE_RBE=true are set and the rule is not supported by goma/RBE, restrict
 		// jobs to the local parallelism value
 		params.Pool = localPool
@@ -1649,6 +1662,10 @@
 	return m.module.InstallInSanitizerDir()
 }
 
+func (m *moduleContext) InstallInRamdisk() bool {
+	return m.module.InstallInRamdisk()
+}
+
 func (m *moduleContext) InstallInRecovery() bool {
 	return m.module.InstallInRecovery()
 }
diff --git a/android/mutator.go b/android/mutator.go
index f2f9663..3d1bb4f 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -27,6 +27,7 @@
 //   run Pre-deps mutators
 //   run depsMutator
 //   run PostDeps mutators
+//   run FinalDeps mutators (CreateVariations disallowed in this phase)
 //   continue on to GenerateAndroidBuildActions
 
 func registerMutatorsToContext(ctx *blueprint.Context, mutators []*mutator) {
@@ -43,7 +44,7 @@
 	}
 }
 
-func registerMutators(ctx *blueprint.Context, preArch, preDeps, postDeps []RegisterMutatorFunc) {
+func registerMutators(ctx *blueprint.Context, preArch, preDeps, postDeps, finalDeps []RegisterMutatorFunc) {
 	mctx := &registerMutatorsContext{}
 
 	register := func(funcs []RegisterMutatorFunc) {
@@ -60,11 +61,15 @@
 
 	register(postDeps)
 
+	mctx.finalPhase = true
+	register(finalDeps)
+
 	registerMutatorsToContext(ctx, mctx.mutators)
 }
 
 type registerMutatorsContext struct {
-	mutators []*mutator
+	mutators   []*mutator
+	finalPhase bool
 }
 
 type RegisterMutatorsContext interface {
@@ -102,6 +107,8 @@
 	RegisterOverridePostDepsMutators,
 }
 
+var finalDeps = []RegisterMutatorFunc{}
+
 func PreArchMutators(f RegisterMutatorFunc) {
 	preArch = append(preArch, f)
 }
@@ -114,6 +121,10 @@
 	postDeps = append(postDeps, f)
 }
 
+func FinalDepsMutators(f RegisterMutatorFunc) {
+	finalDeps = append(finalDeps, f)
+}
+
 type TopDownMutator func(TopDownMutatorContext)
 
 type TopDownMutatorContext interface {
@@ -156,14 +167,17 @@
 type bottomUpMutatorContext struct {
 	bp blueprint.BottomUpMutatorContext
 	baseModuleContext
+	finalPhase bool
 }
 
 func (x *registerMutatorsContext) BottomUp(name string, m BottomUpMutator) MutatorHandle {
+	finalPhase := x.finalPhase
 	f := func(ctx blueprint.BottomUpMutatorContext) {
 		if a, ok := ctx.Module().(Module); ok {
 			actx := &bottomUpMutatorContext{
 				bp:                ctx,
 				baseModuleContext: a.base().baseModuleContextFactory(ctx),
+				finalPhase:        finalPhase,
 			}
 			m(actx)
 		}
@@ -285,6 +299,10 @@
 }
 
 func (b *bottomUpMutatorContext) CreateVariations(variations ...string) []Module {
+	if b.finalPhase {
+		panic("CreateVariations not allowed in FinalDepsMutators")
+	}
+
 	modules := b.bp.CreateVariations(variations...)
 
 	aModules := make([]Module, len(modules))
@@ -299,6 +317,10 @@
 }
 
 func (b *bottomUpMutatorContext) CreateLocalVariations(variations ...string) []Module {
+	if b.finalPhase {
+		panic("CreateLocalVariations not allowed in FinalDepsMutators")
+	}
+
 	modules := b.bp.CreateLocalVariations(variations...)
 
 	aModules := make([]Module, len(modules))
diff --git a/android/mutator_test.go b/android/mutator_test.go
index d179f9d..191b535 100644
--- a/android/mutator_test.go
+++ b/android/mutator_test.go
@@ -15,9 +15,12 @@
 package android
 
 import (
+	"fmt"
 	"reflect"
+	"strings"
 	"testing"
 
+	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
 
@@ -185,3 +188,110 @@
 		t.Errorf("want module String() values:\n%q\ngot:\n%q", want, moduleStrings)
 	}
 }
+
+func TestFinalDepsPhase(t *testing.T) {
+	ctx := NewTestContext()
+
+	finalGot := map[string]int{}
+
+	dep1Tag := struct {
+		blueprint.BaseDependencyTag
+	}{}
+	dep2Tag := struct {
+		blueprint.BaseDependencyTag
+	}{}
+
+	ctx.PostDepsMutators(func(ctx RegisterMutatorsContext) {
+		ctx.BottomUp("far_deps_1", func(ctx BottomUpMutatorContext) {
+			if !strings.HasPrefix(ctx.ModuleName(), "common_dep") {
+				ctx.AddFarVariationDependencies([]blueprint.Variation{}, dep1Tag, "common_dep_1")
+			}
+		})
+		ctx.BottomUp("variant", func(ctx BottomUpMutatorContext) {
+			ctx.CreateLocalVariations("a", "b")
+		})
+	})
+
+	ctx.FinalDepsMutators(func(ctx RegisterMutatorsContext) {
+		ctx.BottomUp("far_deps_2", func(ctx BottomUpMutatorContext) {
+			if !strings.HasPrefix(ctx.ModuleName(), "common_dep") {
+				ctx.AddFarVariationDependencies([]blueprint.Variation{}, dep2Tag, "common_dep_2")
+			}
+		})
+		ctx.BottomUp("final", func(ctx BottomUpMutatorContext) {
+			finalGot[ctx.Module().String()] += 1
+			ctx.VisitDirectDeps(func(mod Module) {
+				finalGot[fmt.Sprintf("%s -> %s", ctx.Module().String(), mod)] += 1
+			})
+		})
+	})
+
+	ctx.RegisterModuleType("test", mutatorTestModuleFactory)
+
+	bp := `
+		test {
+			name: "common_dep_1",
+		}
+		test {
+			name: "common_dep_2",
+		}
+		test {
+			name: "foo",
+		}
+	`
+
+	config := TestConfig(buildDir, nil, bp, nil)
+	ctx.Register(config)
+
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	FailIfErrored(t, errs)
+	_, errs = ctx.PrepareBuildActions(config)
+	FailIfErrored(t, errs)
+
+	finalWant := map[string]int{
+		"common_dep_1{variant:a}":                   1,
+		"common_dep_1{variant:b}":                   1,
+		"common_dep_2{variant:a}":                   1,
+		"common_dep_2{variant:b}":                   1,
+		"foo{variant:a}":                            1,
+		"foo{variant:a} -> common_dep_1{variant:a}": 1,
+		"foo{variant:a} -> common_dep_2{variant:a}": 1,
+		"foo{variant:b}":                            1,
+		"foo{variant:b} -> common_dep_1{variant:b}": 1,
+		"foo{variant:b} -> common_dep_2{variant:a}": 1,
+	}
+
+	if !reflect.DeepEqual(finalWant, finalGot) {
+		t.Errorf("want:\n%q\ngot:\n%q", finalWant, finalGot)
+	}
+}
+
+func TestNoCreateVariationsInFinalDeps(t *testing.T) {
+	ctx := NewTestContext()
+
+	checkErr := func() {
+		if err := recover(); err == nil || !strings.Contains(fmt.Sprintf("%s", err), "not allowed in FinalDepsMutators") {
+			panic("Expected FinalDepsMutators consistency check to fail")
+		}
+	}
+
+	ctx.FinalDepsMutators(func(ctx RegisterMutatorsContext) {
+		ctx.BottomUp("vars", func(ctx BottomUpMutatorContext) {
+			defer checkErr()
+			ctx.CreateVariations("a", "b")
+		})
+		ctx.BottomUp("local_vars", func(ctx BottomUpMutatorContext) {
+			defer checkErr()
+			ctx.CreateLocalVariations("a", "b")
+		})
+	})
+
+	ctx.RegisterModuleType("test", mutatorTestModuleFactory)
+	config := TestConfig(buildDir, nil, `test {name: "foo"}`, nil)
+	ctx.Register(config)
+
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	FailIfErrored(t, errs)
+	_, errs = ctx.PrepareBuildActions(config)
+	FailIfErrored(t, errs)
+}
diff --git a/android/package_ctx.go b/android/package_ctx.go
index a228910..6350667 100644
--- a/android/package_ctx.go
+++ b/android/package_ctx.go
@@ -109,7 +109,7 @@
 		if len(ctx.errors) > 0 {
 			return params, ctx.errors[0]
 		}
-		if (ctx.Config().UseGoma() || ctx.Config().UseRBE()) && params.Pool == nil {
+		if ctx.Config().UseRemoteBuild() && params.Pool == nil {
 			// When USE_GOMA=true or USE_RBE=true are set and the rule is not supported by
 			// goma/RBE, restrict jobs to the local parallelism value
 			params.Pool = localPool
diff --git a/android/paths.go b/android/paths.go
index 02f56d0..da579d5 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -49,6 +49,7 @@
 	InstallInData() bool
 	InstallInTestcases() bool
 	InstallInSanitizerDir() bool
+	InstallInRamdisk() bool
 	InstallInRecovery() bool
 	InstallInRoot() bool
 	InstallBypassMake() bool
@@ -1254,6 +1255,15 @@
 		partition = "data"
 	} else if ctx.InstallInTestcases() {
 		partition = "testcases"
+	} else if ctx.InstallInRamdisk() {
+		if ctx.DeviceConfig().BoardUsesRecoveryAsBoot() {
+			partition = "recovery/root/first_stage_ramdisk"
+		} else {
+			partition = "ramdisk"
+		}
+		if !ctx.InstallInRoot() {
+			partition += "/system"
+		}
 	} else if ctx.InstallInRecovery() {
 		if ctx.InstallInRoot() {
 			partition = "recovery/root"
diff --git a/android/paths_test.go b/android/paths_test.go
index 46e3e1f..7a32026 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -202,6 +202,7 @@
 	inData         bool
 	inTestcases    bool
 	inSanitizerDir bool
+	inRamdisk      bool
 	inRecovery     bool
 	inRoot         bool
 }
@@ -224,6 +225,10 @@
 	return m.inSanitizerDir
 }
 
+func (m moduleInstallPathContextImpl) InstallInRamdisk() bool {
+	return m.inRamdisk
+}
+
 func (m moduleInstallPathContextImpl) InstallInRecovery() bool {
 	return m.inRecovery
 }
diff --git a/android/prebuilt_etc.go b/android/prebuilt_etc.go
index 388d17f..3dea6d8 100644
--- a/android/prebuilt_etc.go
+++ b/android/prebuilt_etc.go
@@ -41,6 +41,9 @@
 	// is the same as the file name of the source file.
 	Filename_from_src *bool `android:"arch_variant"`
 
+	// Make this module available when building for ramdisk.
+	Ramdisk_available *bool
+
 	// Make this module available when building for recovery.
 	Recovery_available *bool
 
@@ -69,6 +72,18 @@
 	additionalDependencies *Paths
 }
 
+func (p *PrebuiltEtc) inRamdisk() bool {
+	return p.ModuleBase.InRamdisk() || p.ModuleBase.InstallInRamdisk()
+}
+
+func (p *PrebuiltEtc) onlyInRamdisk() bool {
+	return p.ModuleBase.InstallInRamdisk()
+}
+
+func (p *PrebuiltEtc) InstallInRamdisk() bool {
+	return p.inRamdisk()
+}
+
 func (p *PrebuiltEtc) inRecovery() bool {
 	return p.ModuleBase.InRecovery() || p.ModuleBase.InstallInRecovery()
 }
@@ -86,7 +101,11 @@
 func (p *PrebuiltEtc) ImageMutatorBegin(ctx BaseModuleContext) {}
 
 func (p *PrebuiltEtc) CoreVariantNeeded(ctx BaseModuleContext) bool {
-	return !p.ModuleBase.InstallInRecovery()
+	return !p.ModuleBase.InstallInRecovery() && !p.ModuleBase.InstallInRamdisk()
+}
+
+func (p *PrebuiltEtc) RamdiskVariantNeeded(ctx BaseModuleContext) bool {
+	return Bool(p.properties.Ramdisk_available) || p.ModuleBase.InstallInRamdisk()
 }
 
 func (p *PrebuiltEtc) RecoveryVariantNeeded(ctx BaseModuleContext) bool {
@@ -167,6 +186,9 @@
 
 func (p *PrebuiltEtc) AndroidMkEntries() []AndroidMkEntries {
 	nameSuffix := ""
+	if p.inRamdisk() && !p.onlyInRamdisk() {
+		nameSuffix = ".ramdisk"
+	}
 	if p.inRecovery() && !p.onlyInRecovery() {
 		nameSuffix = ".recovery"
 	}
diff --git a/android/register.go b/android/register.go
index d5aa1a5..ccfe01e 100644
--- a/android/register.go
+++ b/android/register.go
@@ -102,7 +102,7 @@
 		ctx.RegisterSingletonType(t.name, t.factory)
 	}
 
-	registerMutators(ctx.Context, preArch, preDeps, postDeps)
+	registerMutators(ctx.Context, preArch, preDeps, postDeps, finalDeps)
 
 	// Register makevars after other singletons so they can export values through makevars
 	ctx.RegisterSingletonType("makevars", SingletonFactoryAdaptor(makeVarsSingletonFunc))
@@ -135,6 +135,7 @@
 
 	PreDepsMutators(f RegisterMutatorFunc)
 	PostDepsMutators(f RegisterMutatorFunc)
+	FinalDepsMutators(f RegisterMutatorFunc)
 }
 
 // Used to register build components from an init() method, e.g.
@@ -197,3 +198,7 @@
 func (ctx *initRegistrationContext) PostDepsMutators(f RegisterMutatorFunc) {
 	PostDepsMutators(f)
 }
+
+func (ctx *initRegistrationContext) FinalDepsMutators(f RegisterMutatorFunc) {
+	FinalDepsMutators(f)
+}
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 6f04672..928ba53 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -33,6 +33,8 @@
 	temporariesSet map[WritablePath]bool
 	restat         bool
 	sbox           bool
+	highmem        bool
+	remoteable     RemoteRuleSupports
 	sboxOutDir     WritablePath
 	missingDeps    []string
 }
@@ -87,6 +89,19 @@
 	return r
 }
 
+// HighMem marks the rule as a high memory rule, which will limit how many run in parallel with other high memory
+// rules.
+func (r *RuleBuilder) HighMem() *RuleBuilder {
+	r.highmem = true
+	return r
+}
+
+// Remoteable marks the rule as supporting remote execution.
+func (r *RuleBuilder) Remoteable(supports RemoteRuleSupports) *RuleBuilder {
+	r.remoteable = supports
+	return r
+}
+
 // Sbox marks the rule as needing to be wrapped by sbox. The WritablePath should point to the output
 // directory that sbox will wipe. It should not be written to by any other rule. sbox will ensure
 // that all outputs have been written, and will discard any output files that were not specified.
@@ -401,6 +416,17 @@
 		rspFileContent = "$in"
 	}
 
+	var pool blueprint.Pool
+	if ctx.Config().UseGoma() && r.remoteable&SUPPORTS_GOMA != 0 {
+		// When USE_GOMA=true is set and the rule is supported by goma, allow jobs to run outside the local pool.
+	} else if ctx.Config().UseRBE() && r.remoteable&SUPPORTS_RBE != 0 {
+		// When USE_GOMA=true is set and the rule is supported by RBE, allow jobs to run outside the local pool.
+	} else if r.highmem {
+		pool = highmemPool
+	} else if ctx.Config().UseRemoteBuild() {
+		pool = localPool
+	}
+
 	ctx.Build(pctx, BuildParams{
 		Rule: ctx.Rule(pctx, name, blueprint.RuleParams{
 			Command:        commandString,
@@ -408,6 +434,7 @@
 			Restat:         r.restat,
 			Rspfile:        rspFile,
 			RspfileContent: rspFileContent,
+			Pool:           pool,
 		}),
 		Inputs:          rspFileInputs,
 		Implicits:       r.Inputs(),
diff --git a/android/singleton.go b/android/singleton.go
index 91268ad..45a9b82 100644
--- a/android/singleton.go
+++ b/android/singleton.go
@@ -128,7 +128,7 @@
 }
 
 func (s *singletonContextAdaptor) Rule(pctx PackageContext, name string, params blueprint.RuleParams, argNames ...string) blueprint.Rule {
-	if (s.Config().UseGoma() || s.Config().UseRBE()) && params.Pool == nil {
+	if s.Config().UseRemoteBuild() && params.Pool == nil {
 		// When USE_GOMA=true or USE_RBE=true are set and the rule is not supported by goma/RBE, restrict
 		// jobs to the local parallelism value
 		params.Pool = localPool
diff --git a/android/testing.go b/android/testing.go
index c07af7f..9aff039 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -50,9 +50,9 @@
 
 type TestContext struct {
 	*Context
-	preArch, preDeps, postDeps []RegisterMutatorFunc
-	NameResolver               *NameResolver
-	config                     Config
+	preArch, preDeps, postDeps, finalDeps []RegisterMutatorFunc
+	NameResolver                          *NameResolver
+	config                                Config
 }
 
 func (ctx *TestContext) PreArchMutators(f RegisterMutatorFunc) {
@@ -72,12 +72,16 @@
 	ctx.postDeps = append(ctx.postDeps, f)
 }
 
+func (ctx *TestContext) FinalDepsMutators(f RegisterMutatorFunc) {
+	ctx.finalDeps = append(ctx.finalDeps, f)
+}
+
 func (ctx *TestContext) Register(config Config) {
 	ctx.SetFs(config.fs)
 	if config.mockBpList != "" {
 		ctx.SetModuleListFile(config.mockBpList)
 	}
-	registerMutators(ctx.Context.Context, ctx.preArch, ctx.preDeps, ctx.postDeps)
+	registerMutators(ctx.Context.Context, ctx.preArch, ctx.preDeps, ctx.postDeps, ctx.finalDeps)
 
 	ctx.RegisterSingletonType("env", EnvSingleton)
 
diff --git a/android/variable.go b/android/variable.go
index 2bf84dd..7473491 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -243,6 +243,7 @@
 	TidyChecks *string `json:",omitempty"`
 
 	NativeCoverage       *bool    `json:",omitempty"`
+	ClangCoverage        *bool    `json:",omitempty"`
 	CoveragePaths        []string `json:",omitempty"`
 	CoverageExcludePaths []string `json:",omitempty"`
 
@@ -316,6 +317,8 @@
 	EnforceProductPartitionInterface *bool `json:",omitempty"`
 
 	InstallExtraFlattenedApexes *bool `json:",omitempty"`
+
+	BoardUsesRecoveryAsBoot *bool `json:",omitempty"`
 }
 
 func boolPtr(v bool) *bool {
diff --git a/apex/androidmk.go b/apex/androidmk.go
index ad7d2f1..5fa5bf0 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -52,13 +52,39 @@
 		return moduleNames
 	}
 
+	var postInstallCommands []string
+	for _, fi := range a.filesInfo {
+		if a.linkToSystemLib && fi.transitiveDep && fi.AvailableToPlatform() {
+			// TODO(jiyong): pathOnDevice should come from fi.module, not being calculated here
+			linkTarget := filepath.Join("/system", fi.Path())
+			linkPath := filepath.Join(a.installDir.ToMakePath().String(), apexName, fi.Path())
+			mkdirCmd := "mkdir -p " + filepath.Dir(linkPath)
+			linkCmd := "ln -sfn " + linkTarget + " " + linkPath
+			postInstallCommands = append(postInstallCommands, mkdirCmd, linkCmd)
+		}
+	}
+
 	for _, fi := range a.filesInfo {
 		if cc, ok := fi.module.(*cc.Module); ok && cc.Properties.HideFromMake {
 			continue
 		}
 
-		if !android.InList(fi.moduleName, moduleNames) {
-			moduleNames = append(moduleNames, fi.moduleName)
+		linkToSystemLib := a.linkToSystemLib && fi.transitiveDep && fi.AvailableToPlatform()
+
+		var moduleName string
+		if linkToSystemLib {
+			moduleName = fi.moduleName
+		} else {
+			moduleName = fi.moduleName + "." + apexName + a.suffix
+		}
+
+		if !android.InList(moduleName, moduleNames) {
+			moduleNames = append(moduleNames, moduleName)
+		}
+
+		if linkToSystemLib {
+			// No need to copy the file since it's linked to the system file
+			continue
 		}
 
 		fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)")
@@ -67,7 +93,7 @@
 		} else {
 			fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
 		}
-		fmt.Fprintln(w, "LOCAL_MODULE :=", fi.moduleName)
+		fmt.Fprintln(w, "LOCAL_MODULE :=", moduleName)
 		// /apex/<apex_name>/{lib|framework|...}
 		pathWhenActivated := filepath.Join("$(PRODUCT_OUT)", "apex", apexName, fi.installDir)
 		if apexType == flattenedApex {
@@ -151,18 +177,23 @@
 			fmt.Fprintln(w, "include $(BUILD_SYSTEM)/soong_cc_prebuilt.mk")
 		} else {
 			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", fi.builtFile.Base())
-			if a.primaryApexType && fi.builtFile == a.manifestPbOut {
-				// Make apex_manifest.pb module for this APEX to override all other
-				// modules in the APEXes being overridden by this APEX
-				var patterns []string
-				for _, o := range a.overridableProperties.Overrides {
-					patterns = append(patterns, "%."+o+a.suffix)
-				}
-				fmt.Fprintln(w, "LOCAL_OVERRIDES_MODULES :=", strings.Join(patterns, " "))
+			if fi.builtFile == a.manifestPbOut {
+				if a.primaryApexType {
+					// Make apex_manifest.pb module for this APEX to override all other
+					// modules in the APEXes being overridden by this APEX
+					var patterns []string
+					for _, o := range a.overridableProperties.Overrides {
+						patterns = append(patterns, "%."+o+a.suffix)
+					}
+					fmt.Fprintln(w, "LOCAL_OVERRIDES_MODULES :=", strings.Join(patterns, " "))
 
-				if len(a.compatSymlinks) > 0 {
-					// For flattened apexes, compat symlinks are attached to apex_manifest.json which is guaranteed for every apex
-					fmt.Fprintln(w, "LOCAL_POST_INSTALL_CMD :=", strings.Join(a.compatSymlinks, " && "))
+					if apexType == flattenedApex && len(a.compatSymlinks) > 0 {
+						// For flattened apexes, compat symlinks are attached to apex_manifest.json which is guaranteed for every apex
+						postInstallCommands = append(postInstallCommands, a.compatSymlinks...)
+					}
+				}
+				if len(postInstallCommands) > 0 {
+					fmt.Fprintln(w, "LOCAL_POST_INSTALL_CMD :=", strings.Join(postInstallCommands, " && "))
 				}
 			}
 			fmt.Fprintln(w, "include $(BUILD_PREBUILT)")
diff --git a/apex/apex.go b/apex/apex.go
index 33b1be3..f925066 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -333,7 +333,7 @@
 // Mark the direct and transitive dependencies of apex bundles so that they
 // can be built for the apex bundles.
 func apexDepsMutator(mctx android.BottomUpMutatorContext) {
-	if a, ok := mctx.Module().(*apexBundle); ok {
+	if a, ok := mctx.Module().(*apexBundle); ok && !a.vndkApex {
 		apexBundleName := mctx.ModuleName()
 		mctx.WalkDeps(func(child, parent android.Module) bool {
 			depName := mctx.OtherModuleName(child)
@@ -361,7 +361,7 @@
 func apexMutator(mctx android.BottomUpMutatorContext) {
 	if am, ok := mctx.Module().(android.ApexModule); ok && am.CanHaveApexVariants() {
 		am.CreateApexVariations(mctx)
-	} else if _, ok := mctx.Module().(*apexBundle); ok {
+	} else if a, ok := mctx.Module().(*apexBundle); ok && !a.vndkApex {
 		// apex bundle itself is mutated so that it and its modules have same
 		// apex variant.
 		apexBundleName := mctx.ModuleName()
@@ -733,6 +733,30 @@
 	return af.builtFile != nil && af.builtFile.String() != ""
 }
 
+// Path() returns path of this apex file relative to the APEX root
+func (af *apexFile) Path() string {
+	return filepath.Join(af.installDir, af.builtFile.Base())
+}
+
+// SymlinkPaths() returns paths of the symlinks (if any) relative to the APEX root
+func (af *apexFile) SymlinkPaths() []string {
+	var ret []string
+	for _, symlink := range af.symlinks {
+		ret = append(ret, filepath.Join(af.installDir, symlink))
+	}
+	return ret
+}
+
+func (af *apexFile) AvailableToPlatform() bool {
+	if af.module == nil {
+		return false
+	}
+	if am, ok := af.module.(android.ApexModule); ok {
+		return am.AvailableFor(android.AvailableToPlatform)
+	}
+	return false
+}
+
 type apexBundle struct {
 	android.ModuleBase
 	android.DefaultableModuleBase
@@ -790,6 +814,10 @@
 	suffix string
 
 	installedFilesFile android.WritablePath
+
+	// Whether to create symlink to the system file instead of having a file
+	// inside the apex or not
+	linkToSystemLib bool
 }
 
 func addDependenciesForNativeModules(ctx android.BottomUpMutatorContext,
@@ -1056,7 +1084,7 @@
 }
 
 func (a *apexBundle) IsNativeCoverageNeeded(ctx android.BaseModuleContext) bool {
-	return ctx.Device() && ctx.DeviceConfig().NativeCoverageEnabled()
+	return ctx.Device() && (ctx.DeviceConfig().NativeCoverageEnabled() || ctx.DeviceConfig().ClangCoverageEnabled())
 }
 
 func (a *apexBundle) PreventInstall() {
@@ -1414,7 +1442,8 @@
 						// of the original test module (`depName`, shared by all `test_per_src`
 						// variations of that module).
 						af.moduleName = filepath.Base(af.builtFile.String())
-						af.transitiveDep = true
+						// these are not considered transitive dep
+						af.transitiveDep = false
 						filesInfo = append(filesInfo, af)
 						return true // track transitive dependencies
 					}
@@ -1452,15 +1481,22 @@
 
 	// remove duplicates in filesInfo
 	removeDup := func(filesInfo []apexFile) []apexFile {
-		encountered := make(map[string]bool)
-		result := []apexFile{}
+		encountered := make(map[string]apexFile)
 		for _, f := range filesInfo {
 			dest := filepath.Join(f.installDir, f.builtFile.Base())
-			if !encountered[dest] {
-				encountered[dest] = true
-				result = append(result, f)
+			if e, ok := encountered[dest]; !ok {
+				encountered[dest] = f
+			} else {
+				// If a module is directly included and also transitively depended on
+				// consider it as directly included.
+				e.transitiveDep = e.transitiveDep && f.transitiveDep
+				encountered[dest] = e
 			}
 		}
+		var result []apexFile
+		for _, v := range encountered {
+			result = append(result, v)
+		}
 		return result
 	}
 	filesInfo = removeDup(filesInfo)
@@ -1487,12 +1523,6 @@
 		}
 	}
 
-	// prepend the name of this APEX to the module names. These names will be the names of
-	// modules that will be defined if the APEX is flattened.
-	for i := range filesInfo {
-		filesInfo[i].moduleName = filesInfo[i].moduleName + "." + a.Name() + a.suffix
-	}
-
 	a.installDir = android.PathForModuleInstall(ctx, "apex")
 	a.filesInfo = filesInfo
 
@@ -1512,6 +1542,14 @@
 			return
 		}
 	}
+	// Optimization. If we are building bundled APEX, for the files that are gathered due to the
+	// transitive dependencies, don't place them inside the APEX, but place a symlink pointing
+	// the same library in the system partition, thus effectively sharing the same libraries
+	// across the APEX boundary. For unbundled APEX, all the gathered files are actually placed
+	// in the APEX.
+	a.linkToSystemLib = !ctx.Config().UnbundledBuild() &&
+		a.installable() &&
+		!proptools.Bool(a.properties.Use_vendor)
 
 	// prepare apex_manifest.json
 	a.buildManifest(ctx, provideNativeLibs, requireNativeLibs)
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 2103b6e..b7dd7fb 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -91,6 +91,10 @@
 	config.TestProductVariables.Binder32bit = proptools.BoolPtr(true)
 }
 
+func withUnbundledBuild(fs map[string][]byte, config android.Config) {
+	config.TestProductVariables.Unbundled_build = proptools.BoolPtr(true)
+}
+
 func testApexContext(t *testing.T, bp string, handlers ...testCustomizer) (*android.TestContext, android.Config) {
 	android.ClearApexDependency()
 
@@ -287,6 +291,9 @@
 	ctx.RegisterModuleType("prebuilt_apex", PrebuiltFactory)
 	ctx.RegisterModuleType("override_apex", overrideApexFactory)
 
+	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
+	ctx.PostDepsMutators(android.RegisterOverridePostDepsMutators)
+
 	cc.RegisterRequiredBuildComponentsForTest(ctx)
 	ctx.RegisterModuleType("cc_test", cc.TestFactory)
 	ctx.RegisterModuleType("vndk_prebuilt_shared", cc.VndkPrebuiltSharedFactory)
@@ -299,9 +306,7 @@
 	java.RegisterAppBuildComponents(ctx)
 	ctx.RegisterModuleType("java_sdk_library", java.SdkLibraryFactory)
 
-	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
 	ctx.PreDepsMutators(RegisterPreDepsMutators)
-	ctx.PostDepsMutators(android.RegisterOverridePostDepsMutators)
 	ctx.PostDepsMutators(RegisterPostDepsMutators)
 
 	ctx.Register(config)
@@ -516,7 +521,7 @@
 	found_foo_link_64 := false
 	found_foo := false
 	for _, cmd := range strings.Split(copyCmds, " && ") {
-		if strings.HasPrefix(cmd, "ln -s foo64") {
+		if strings.HasPrefix(cmd, "ln -sfn foo64") {
 			if strings.HasSuffix(cmd, "bin/foo") {
 				found_foo = true
 			} else if strings.HasSuffix(cmd, "bin/foo_link_64") {
@@ -595,7 +600,7 @@
 			apex_available: [ "myapex" ],
 		}
 	`)
-	ensureExactContents(t, ctx, "myapex", []string{
+	ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{
 		"etc/myetc",
 		"javalib/myjar.jar",
 		"lib64/mylib.so",
@@ -760,7 +765,7 @@
 	// Ensure that genstub is invoked with --apex
 	ensureContains(t, "--apex", ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_static_3").Rule("genStubSrc").Args["flags"])
 
-	ensureExactContents(t, ctx, "myapex", []string{
+	ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{
 		"lib64/mylib.so",
 		"lib64/mylib3.so",
 		"lib64/mylib4.so",
@@ -1597,46 +1602,72 @@
 	ensureContains(t, cFlags, "-Imy_include")
 }
 
-func ensureExactContents(t *testing.T, ctx *android.TestContext, moduleName string, files []string) {
+type fileInApex struct {
+	path   string // path in apex
+	src    string // src path
+	isLink bool
+}
+
+func getFiles(t *testing.T, ctx *android.TestContext, moduleName, variant string) []fileInApex {
 	t.Helper()
-	apexRule := ctx.ModuleForTests(moduleName, "android_common_"+moduleName+"_image").Rule("apexRule")
+	apexRule := ctx.ModuleForTests(moduleName, variant).Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 	imageApexDir := "/image.apex/"
-	var failed bool
-	var surplus []string
-	filesMatched := make(map[string]bool)
-	addContent := func(content string) {
-		for _, expected := range files {
-			if matched, _ := path.Match(expected, content); matched {
-				filesMatched[expected] = true
-				return
-			}
-		}
-		surplus = append(surplus, content)
-	}
+	var ret []fileInApex
 	for _, cmd := range strings.Split(copyCmds, "&&") {
 		cmd = strings.TrimSpace(cmd)
 		if cmd == "" {
 			continue
 		}
 		terms := strings.Split(cmd, " ")
+		var dst, src string
+		var isLink bool
 		switch terms[0] {
 		case "mkdir":
 		case "cp":
-			if len(terms) != 3 {
+			if len(terms) != 3 && len(terms) != 4 {
 				t.Fatal("copyCmds contains invalid cp command", cmd)
 			}
-			dst := terms[2]
+			dst = terms[len(terms)-1]
+			src = terms[len(terms)-2]
+			isLink = false
+		case "ln":
+			if len(terms) != 3 && len(terms) != 4 {
+				// ln LINK TARGET or ln -s LINK TARGET
+				t.Fatal("copyCmds contains invalid ln command", cmd)
+			}
+			dst = terms[len(terms)-1]
+			src = terms[len(terms)-2]
+			isLink = true
+		default:
+			t.Fatalf("copyCmds should contain mkdir/cp commands only: %q", cmd)
+		}
+		if dst != "" {
 			index := strings.Index(dst, imageApexDir)
 			if index == -1 {
 				t.Fatal("copyCmds should copy a file to image.apex/", cmd)
 			}
 			dstFile := dst[index+len(imageApexDir):]
-			addContent(dstFile)
-		default:
-			t.Fatalf("copyCmds should contain mkdir/cp commands only: %q", cmd)
+			ret = append(ret, fileInApex{path: dstFile, src: src, isLink: isLink})
 		}
 	}
+	return ret
+}
+
+func ensureExactContents(t *testing.T, ctx *android.TestContext, moduleName, variant string, files []string) {
+	t.Helper()
+	var failed bool
+	var surplus []string
+	filesMatched := make(map[string]bool)
+	for _, file := range getFiles(t, ctx, moduleName, variant) {
+		for _, expected := range files {
+			if matched, _ := path.Match(expected, file.path); matched {
+				filesMatched[expected] = true
+				return
+			}
+		}
+		surplus = append(surplus, file.path)
+	}
 
 	if len(surplus) > 0 {
 		sort.Strings(surplus)
@@ -1699,7 +1730,7 @@
 		}
 	`+vndkLibrariesTxtFiles("current"))
 
-	ensureExactContents(t, ctx, "myapex", []string{
+	ensureExactContents(t, ctx, "myapex", "android_common_image", []string{
 		"lib/libvndk.so",
 		"lib/libvndksp.so",
 		"lib64/libvndk.so",
@@ -1759,7 +1790,7 @@
 			"libvndk.arm.so": nil,
 		}))
 
-	ensureExactContents(t, ctx, "myapex", []string{
+	ensureExactContents(t, ctx, "myapex", "android_common_image", []string{
 		"lib/libvndk.so",
 		"lib/libvndk.arm.so",
 		"lib64/libvndk.so",
@@ -1850,7 +1881,7 @@
 			"libvndk27_x86_64.so": nil,
 		}))
 
-	ensureExactContents(t, ctx, "myapex_v27", []string{
+	ensureExactContents(t, ctx, "myapex_v27", "android_common_image", []string{
 		"lib/libvndk27_arm.so",
 		"lib64/libvndk27_arm64.so",
 		"etc/*",
@@ -1923,7 +1954,7 @@
 		}`+vndkLibrariesTxtFiles("28", "current"))
 
 	assertApexName := func(expected, moduleName string) {
-		bundle := ctx.ModuleForTests(moduleName, "android_common_"+moduleName+"_image").Module().(*apexBundle)
+		bundle := ctx.ModuleForTests(moduleName, "android_common_image").Module().(*apexBundle)
 		actual := proptools.String(bundle.properties.Apex_name)
 		if !reflect.DeepEqual(actual, expected) {
 			t.Errorf("Got '%v', expected '%v'", actual, expected)
@@ -1971,7 +2002,7 @@
 			},
 		}))
 
-	ensureExactContents(t, ctx, "myapex", []string{
+	ensureExactContents(t, ctx, "myapex", "android_common_image", []string{
 		"lib/libvndk.so",
 		"lib64/libvndk.so",
 		"etc/*",
@@ -2067,7 +2098,7 @@
 		}),
 	)
 
-	ensureExactContents(t, ctx, "myapex_v27", []string{
+	ensureExactContents(t, ctx, "myapex_v27", "android_common_image", []string{
 		"lib/libvndk27binder32.so",
 		"etc/*",
 	})
@@ -3411,7 +3442,7 @@
 	}))
 
 	// java_sdk_library installs both impl jar and permission XML
-	ensureExactContents(t, ctx, "myapex", []string{
+	ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{
 		"javalib/foo.jar",
 		"etc/permissions/foo.xml",
 	})
@@ -3481,6 +3512,106 @@
 	ensureContains(t, androidMk, "LOCAL_TARGET_REQUIRED_MODULES += e f\n")
 }
 
+func TestSymlinksFromApexToSystem(t *testing.T) {
+	bp := `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			native_shared_libs: ["mylib"],
+			java_libs: ["myjar"],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			shared_libs: ["myotherlib"],
+			system_shared_libs: [],
+			stl: "none",
+			apex_available: [
+				"myapex",
+				"//apex_available:platform",
+			],
+		}
+
+		cc_library {
+			name: "myotherlib",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+			apex_available: [
+				"myapex",
+				"//apex_available:platform",
+			],
+		}
+
+		java_library {
+			name: "myjar",
+			srcs: ["foo/bar/MyClass.java"],
+			sdk_version: "none",
+			system_modules: "none",
+			libs: ["myotherjar"],
+			compile_dex: true,
+			apex_available: [
+				"myapex",
+				"//apex_available:platform",
+			],
+		}
+
+		java_library {
+			name: "myotherjar",
+			srcs: ["foo/bar/MyClass.java"],
+			sdk_version: "none",
+			system_modules: "none",
+			apex_available: [
+				"myapex",
+				"//apex_available:platform",
+			],
+		}
+	`
+
+	ensureRealfileExists := func(t *testing.T, files []fileInApex, file string) {
+		for _, f := range files {
+			if f.path == file {
+				if f.isLink {
+					t.Errorf("%q is not a real file", file)
+				}
+				return
+			}
+		}
+		t.Errorf("%q is not found", file)
+	}
+
+	ensureSymlinkExists := func(t *testing.T, files []fileInApex, file string) {
+		for _, f := range files {
+			if f.path == file {
+				if !f.isLink {
+					t.Errorf("%q is not a symlink", file)
+				}
+				return
+			}
+		}
+		t.Errorf("%q is not found", file)
+	}
+
+	ctx, _ := testApex(t, bp, withUnbundledBuild)
+	files := getFiles(t, ctx, "myapex", "android_common_myapex_image")
+	ensureRealfileExists(t, files, "javalib/myjar.jar")
+	ensureRealfileExists(t, files, "lib64/mylib.so")
+	ensureRealfileExists(t, files, "lib64/myotherlib.so")
+
+	ctx, _ = testApex(t, bp)
+	files = getFiles(t, ctx, "myapex", "android_common_myapex_image")
+	ensureRealfileExists(t, files, "javalib/myjar.jar")
+	ensureRealfileExists(t, files, "lib64/mylib.so")
+	ensureSymlinkExists(t, files, "lib64/myotherlib.so") // this is symlink
+}
+
 func TestMain(m *testing.M) {
 	run := func() int {
 		setUp()
diff --git a/apex/builder.go b/apex/builder.go
index 290c1ea..8ae2b5c 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -258,34 +258,40 @@
 
 	apexType := a.properties.ApexType
 	suffix := apexType.suffix()
+	var implicitInputs []android.Path
 	unsignedOutputFile := android.PathForModuleOut(ctx, a.Name()+suffix+".unsigned")
 
-	filesToCopy := []android.Path{}
-	for _, f := range a.filesInfo {
-		filesToCopy = append(filesToCopy, f.builtFile)
+	// TODO(jiyong): construct the copy rules using RuleBuilder
+	var copyCommands []string
+	for _, fi := range a.filesInfo {
+		destPath := android.PathForModuleOut(ctx, "image"+suffix, fi.Path()).String()
+		copyCommands = append(copyCommands, "mkdir -p "+filepath.Dir(destPath))
+		if a.linkToSystemLib && fi.transitiveDep && fi.AvailableToPlatform() {
+			// TODO(jiyong): pathOnDevice should come from fi.module, not being calculated here
+			pathOnDevice := filepath.Join("/system", fi.Path())
+			copyCommands = append(copyCommands, "ln -sfn "+pathOnDevice+" "+destPath)
+		} else {
+			copyCommands = append(copyCommands, "cp -f "+fi.builtFile.String()+" "+destPath)
+			implicitInputs = append(implicitInputs, fi.builtFile)
+		}
+		// create additional symlinks pointing the file inside the APEX
+		for _, symlinkPath := range fi.SymlinkPaths() {
+			symlinkDest := android.PathForModuleOut(ctx, "image"+suffix, symlinkPath).String()
+			copyCommands = append(copyCommands, "ln -sfn "+filepath.Base(destPath)+" "+symlinkDest)
+		}
 	}
 
-	copyCommands := []string{}
-	emitCommands := []string{}
-	imageContentFile := android.PathForModuleOut(ctx, a.Name()+"-content.txt")
+	// TODO(jiyong): use RuleBuilder
+	var emitCommands []string
+	imageContentFile := android.PathForModuleOut(ctx, "content.txt")
 	emitCommands = append(emitCommands, "echo ./apex_manifest.pb >> "+imageContentFile.String())
 	if proptools.Bool(a.properties.Legacy_android10_support) {
 		emitCommands = append(emitCommands, "echo ./apex_manifest.json >> "+imageContentFile.String())
 	}
-	for i, src := range filesToCopy {
-		dest := filepath.Join(a.filesInfo[i].installDir, src.Base())
-		emitCommands = append(emitCommands, "echo './"+dest+"' >> "+imageContentFile.String())
-		dest_path := filepath.Join(android.PathForModuleOut(ctx, "image"+suffix).String(), dest)
-		copyCommands = append(copyCommands, "mkdir -p "+filepath.Dir(dest_path))
-		copyCommands = append(copyCommands, "cp "+src.String()+" "+dest_path)
-		for _, sym := range a.filesInfo[i].symlinks {
-			symlinkDest := filepath.Join(filepath.Dir(dest_path), sym)
-			copyCommands = append(copyCommands, "ln -s "+filepath.Base(dest)+" "+symlinkDest)
-		}
+	for _, fi := range a.filesInfo {
+		emitCommands = append(emitCommands, "echo './"+fi.Path()+"' >> "+imageContentFile.String())
 	}
 	emitCommands = append(emitCommands, "sort -o "+imageContentFile.String()+" "+imageContentFile.String())
-
-	implicitInputs := append(android.Paths(nil), filesToCopy...)
 	implicitInputs = append(implicitInputs, a.manifestPbOut)
 
 	if a.properties.Whitelisted_files != nil {
@@ -532,7 +538,7 @@
 	if a.installable() {
 		// For flattened APEX, do nothing but make sure that APEX manifest and apex_pubkey are also copied along
 		// with other ordinary files.
-		a.filesInfo = append(a.filesInfo, newApexFile(ctx, a.manifestPbOut, "apex_manifest.pb."+a.Name()+a.suffix, ".", etc, nil))
+		a.filesInfo = append(a.filesInfo, newApexFile(ctx, a.manifestPbOut, "apex_manifest.pb", ".", etc, nil))
 
 		// rename to apex_pubkey
 		copiedPubkey := android.PathForModuleOut(ctx, "apex_pubkey")
@@ -541,7 +547,7 @@
 			Input:  a.public_key_file,
 			Output: copiedPubkey,
 		})
-		a.filesInfo = append(a.filesInfo, newApexFile(ctx, copiedPubkey, "apex_pubkey."+a.Name()+a.suffix, ".", etc, nil))
+		a.filesInfo = append(a.filesInfo, newApexFile(ctx, copiedPubkey, "apex_pubkey", ".", etc, nil))
 
 		if a.properties.ApexType == flattenedApex {
 			apexName := proptools.StringDefault(a.properties.Apex_name, a.Name())
diff --git a/apex/vndk_test.go b/apex/vndk_test.go
new file mode 100644
index 0000000..391072e
--- /dev/null
+++ b/apex/vndk_test.go
@@ -0,0 +1,94 @@
+package apex
+
+import (
+	"testing"
+
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android"
+)
+
+func TestVndkApexUsesVendorVariant(t *testing.T) {
+	bp := `
+		apex_vndk {
+			name: "myapex",
+			key: "mykey",
+		}
+		apex_key {
+			name: "mykey",
+		}
+		cc_library {
+			name: "libfoo",
+			vendor_available: true,
+			vndk: {
+				enabled: true,
+			},
+			system_shared_libs: [],
+			stl: "none",
+			notice: "custom_notice",
+		}
+		` + vndkLibrariesTxtFiles("current")
+
+	ensureFileSrc := func(t *testing.T, files []fileInApex, path, src string) {
+		t.Helper()
+		for _, f := range files {
+			if f.path == path {
+				ensureContains(t, f.src, src)
+				return
+			}
+		}
+		t.Fail()
+	}
+
+	t.Run("VNDK lib doesn't have an apex variant", func(t *testing.T) {
+		ctx, _ := testApex(t, bp)
+
+		// libfoo doesn't have apex variants
+		for _, variant := range ctx.ModuleVariantsForTests("libfoo") {
+			ensureNotContains(t, variant, "_myapex")
+		}
+
+		// VNDK APEX doesn't create apex variant
+		files := getFiles(t, ctx, "myapex", "android_common_image")
+		ensureFileSrc(t, files, "lib/libfoo.so", "libfoo/android_vendor.VER_arm_armv7-a-neon_shared/libfoo.so")
+	})
+
+	t.Run("VNDK APEX gathers only vendor variants even if product variants are available", func(t *testing.T) {
+		ctx, _ := testApex(t, bp, func(fs map[string][]byte, config android.Config) {
+			// Now product variant is available
+			config.TestProductVariables.ProductVndkVersion = proptools.StringPtr("current")
+		})
+
+		files := getFiles(t, ctx, "myapex", "android_common_image")
+		ensureFileSrc(t, files, "lib/libfoo.so", "libfoo/android_vendor.VER_arm_armv7-a-neon_shared/libfoo.so")
+	})
+
+	t.Run("VNDK APEX supports coverage variants", func(t *testing.T) {
+		ctx, _ := testApex(t, bp+`
+			cc_library {
+				name: "libprofile-extras",
+				vendor_available: true,
+				native_coverage: false,
+				system_shared_libs: [],
+				stl: "none",
+				notice: "custom_notice",
+			}
+			cc_library {
+				name: "libprofile-clang-extras",
+				vendor_available: true,
+				native_coverage: false,
+				system_shared_libs: [],
+				stl: "none",
+				notice: "custom_notice",
+			}
+		`, func(fs map[string][]byte, config android.Config) {
+			config.TestProductVariables.NativeCoverage = proptools.BoolPtr(true)
+		})
+
+		files := getFiles(t, ctx, "myapex", "android_common_image")
+		ensureFileSrc(t, files, "lib/libfoo.so", "libfoo/android_vendor.VER_arm_armv7-a-neon_shared/libfoo.so")
+
+		files = getFiles(t, ctx, "myapex", "android_common_cov_image")
+		ensureFileSrc(t, files, "lib/libfoo.so", "libfoo/android_vendor.VER_arm_armv7-a-neon_shared_cov/libfoo.so")
+	})
+}
diff --git a/build_kzip.bash b/build_kzip.bash
index 329825a..008030f 100755
--- a/build_kzip.bash
+++ b/build_kzip.bash
@@ -14,6 +14,7 @@
 
 : ${BUILD_NUMBER:=$(uuidgen)}
 : ${KYTHE_KZIP_ENCODING:=proto}
+export KYTHE_KZIP_ENCODING
 
 # The extraction might fail for some source files, so run with -k and then check that
 # sufficiently many files were generated.
diff --git a/cc/androidmk.go b/cc/androidmk.go
index c9cd01c..137cb63 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -27,6 +27,7 @@
 	nativeBridgeSuffix = ".native_bridge"
 	productSuffix      = ".product"
 	vendorSuffix       = ".vendor"
+	ramdiskSuffix      = ".ramdisk"
 	recoverySuffix     = ".recovery"
 )
 
@@ -40,6 +41,7 @@
 	UseVndk() bool
 	VndkVersion() string
 	static() bool
+	InRamdisk() bool
 	InRecovery() bool
 }
 
@@ -233,7 +235,7 @@
 		})
 	}
 	if len(library.Properties.Stubs.Versions) > 0 &&
-		android.DirectlyInAnyApex(ctx, ctx.Name()) && !ctx.InRecovery() && !ctx.UseVndk() &&
+		android.DirectlyInAnyApex(ctx, ctx.Name()) && !ctx.InRamdisk() && !ctx.InRecovery() && !ctx.UseVndk() &&
 		!ctx.static() {
 		if !library.buildStubs() {
 			ret.SubName = ".bootstrap"
diff --git a/cc/binary.go b/cc/binary.go
index ba6ed5f..280d17b 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -264,7 +264,7 @@
 				} else {
 					switch ctx.Os() {
 					case android.Android:
-						if ctx.bootstrap() && !ctx.inRecovery() {
+						if ctx.bootstrap() && !ctx.inRecovery() && !ctx.inRamdisk() {
 							flags.DynamicLinker = "/system/bin/bootstrap/linker"
 						} else {
 							flags.DynamicLinker = "/system/bin/linker"
@@ -458,7 +458,7 @@
 	// The original path becomes a symlink to the corresponding file in the
 	// runtime APEX.
 	translatedArch := ctx.Target().NativeBridge == android.NativeBridgeEnabled
-	if InstallToBootstrap(ctx.baseModuleName(), ctx.Config()) && !translatedArch && ctx.apexName() == "" && !ctx.inRecovery() {
+	if InstallToBootstrap(ctx.baseModuleName(), ctx.Config()) && !translatedArch && ctx.apexName() == "" && !ctx.inRamdisk() && !ctx.inRecovery() {
 		if ctx.Device() && isBionic(ctx.baseModuleName()) {
 			binary.installSymlinkToRuntimeApex(ctx, file)
 		}
diff --git a/cc/cc.go b/cc/cc.go
index 022e350..ce3a2ed 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -223,11 +223,15 @@
 	// file
 	Logtags []string
 
+	// Make this module available when building for ramdisk
+	Ramdisk_available *bool
+
 	// Make this module available when building for recovery
 	Recovery_available *bool
 
 	// Set by imageMutator
 	CoreVariantNeeded     bool     `blueprint:"mutated"`
+	RamdiskVariantNeeded  bool     `blueprint:"mutated"`
 	RecoveryVariantNeeded bool     `blueprint:"mutated"`
 	ExtraVariants         []string `blueprint:"mutated"`
 
@@ -290,6 +294,7 @@
 	isVndkExt() bool
 	inProduct() bool
 	inVendor() bool
+	inRamdisk() bool
 	inRecovery() bool
 	shouldCreateSourceAbiDump() bool
 	selectedStl() string
@@ -878,10 +883,18 @@
 	return c.Properties.ImageVariationPrefix == VendorVariationPrefix
 }
 
+func (c *Module) InRamdisk() bool {
+	return c.ModuleBase.InRamdisk() || c.ModuleBase.InstallInRamdisk()
+}
+
 func (c *Module) InRecovery() bool {
 	return c.ModuleBase.InRecovery() || c.ModuleBase.InstallInRecovery()
 }
 
+func (c *Module) OnlyInRamdisk() bool {
+	return c.ModuleBase.InstallInRamdisk()
+}
+
 func (c *Module) OnlyInRecovery() bool {
 	return c.ModuleBase.InstallInRecovery()
 }
@@ -1018,7 +1031,7 @@
 }
 
 func (ctx *moduleContextImpl) useSdk() bool {
-	if ctx.ctx.Device() && !ctx.useVndk() && !ctx.inRecovery() && !ctx.ctx.Fuchsia() {
+	if ctx.ctx.Device() && !ctx.useVndk() && !ctx.inRamdisk() && !ctx.inRecovery() && !ctx.ctx.Fuchsia() {
 		return String(ctx.mod.Properties.Sdk_version) != ""
 	}
 	return false
@@ -1090,6 +1103,10 @@
 	return ctx.mod.inVendor()
 }
 
+func (ctx *moduleContextImpl) inRamdisk() bool {
+	return ctx.mod.InRamdisk()
+}
+
 func (ctx *moduleContextImpl) inRecovery() bool {
 	return ctx.mod.InRecovery()
 }
@@ -1335,6 +1352,8 @@
 		// .vendor suffix is added for backward compatibility with VNDK snapshot whose names with
 		// such suffixes are already hard-coded in prebuilts/vndk/.../Android.bp.
 		c.Properties.SubName += vendorSuffix
+	} else if c.InRamdisk() && !c.OnlyInRamdisk() {
+		c.Properties.SubName += ramdiskSuffix
 	} else if c.InRecovery() && !c.OnlyInRecovery() {
 		c.Properties.SubName += recoverySuffix
 	}
@@ -1444,7 +1463,7 @@
 		// (unless it is explicitly referenced via .bootstrap suffix or the
 		// module is marked with 'bootstrap: true').
 		if c.HasStubsVariants() &&
-			android.DirectlyInAnyApex(ctx, ctx.baseModuleName()) &&
+			android.DirectlyInAnyApex(ctx, ctx.baseModuleName()) && !c.InRamdisk() &&
 			!c.InRecovery() && !c.UseVndk() && !c.static() && !c.isCoverageVariant() &&
 			c.IsStubs() {
 			c.Properties.HideFromMake = false // unhide
@@ -1732,7 +1751,7 @@
 	addSharedLibDependencies := func(depTag DependencyTag, name string, version string) {
 		var variations []blueprint.Variation
 		variations = append(variations, blueprint.Variation{Mutator: "link", Variation: "shared"})
-		versionVariantAvail := !ctx.useVndk() && !c.InRecovery()
+		versionVariantAvail := !ctx.useVndk() && !c.InRecovery() && !c.InRamdisk()
 		if version != "" && versionVariantAvail {
 			// Version is explicitly specified. i.e. libFoo#30
 			variations = append(variations, blueprint.Variation{Mutator: "version", Variation: version})
@@ -1863,6 +1882,10 @@
 		// Platform code can link to anything
 		return
 	}
+	if from.InRamdisk() {
+		// Ramdisk code is not NDK
+		return
+	}
 	if from.InRecovery() {
 		// Recovery code is not NDK
 		return
@@ -2123,8 +2146,8 @@
 					// If not building for APEX, use stubs only when it is from
 					// an APEX (and not from platform)
 					useThisDep = (depInPlatform != depIsStubs)
-					if c.InRecovery() || c.bootstrap() {
-						// However, for recovery or bootstrap modules,
+					if c.InRamdisk() || c.InRecovery() || c.bootstrap() {
+						// However, for ramdisk, recovery or bootstrap modules,
 						// always link to non-stub variant
 						useThisDep = !depIsStubs
 					}
@@ -2275,7 +2298,7 @@
 			isVendorPublicLib := inList(libName, *vendorPublicLibraries)
 			bothVendorAndCoreVariantsExist := ccDep.HasVendorVariant() || isLLndk
 
-			if ctx.DeviceConfig().VndkUseCoreVariant() && ccDep.IsVndk() && !ccDep.MustUseVendorVariant() && !c.InRecovery() {
+			if ctx.DeviceConfig().VndkUseCoreVariant() && ccDep.IsVndk() && !ccDep.MustUseVendorVariant() && !c.InRamdisk() && !c.InRecovery() {
 				// The vendor module is a no-vendor-variant VNDK library.  Depend on the
 				// core module instead.
 				return libName
@@ -2285,6 +2308,8 @@
 				return libName + c.getNameSuffixWithVndkVersion(ctx)
 			} else if (ctx.Platform() || ctx.ProductSpecific()) && isVendorPublicLib {
 				return libName + vendorPublicLibrarySuffix
+			} else if ccDep.InRamdisk() && !ccDep.OnlyInRamdisk() {
+				return libName + ramdiskSuffix
 			} else if ccDep.InRecovery() && !ccDep.OnlyInRecovery() {
 				return libName + recoverySuffix
 			} else if ccDep.Module().Target().NativeBridge == android.NativeBridgeEnabled {
@@ -2369,6 +2394,10 @@
 	return c.installer.inSanitizerDir()
 }
 
+func (c *Module) InstallInRamdisk() bool {
+	return c.InRamdisk()
+}
+
 func (c *Module) InstallInRecovery() bool {
 	return c.InRecovery()
 }
@@ -2441,6 +2470,8 @@
 			return "native:product"
 		}
 		return "native:vendor"
+	} else if c.InRamdisk() {
+		return "native:ramdisk"
 	} else if c.InRecovery() {
 		return "native:recovery"
 	} else if c.Target().Os == android.Android && String(c.Properties.Sdk_version) != "" {
@@ -2647,6 +2678,7 @@
 	}
 
 	var coreVariantNeeded bool = false
+	var ramdiskVariantNeeded bool = false
 	var recoveryVariantNeeded bool = false
 
 	var vendorVariants []string
@@ -2729,6 +2761,15 @@
 		productVariants = []string{}
 	}
 
+	if Bool(m.Properties.Ramdisk_available) {
+		ramdiskVariantNeeded = true
+	}
+
+	if m.ModuleBase.InstallInRamdisk() {
+		ramdiskVariantNeeded = true
+		coreVariantNeeded = false
+	}
+
 	if Bool(m.Properties.Recovery_available) {
 		recoveryVariantNeeded = true
 	}
@@ -2746,6 +2787,7 @@
 		m.Properties.ExtraVariants = append(m.Properties.ExtraVariants, ProductVariationPrefix+variant)
 	}
 
+	m.Properties.RamdiskVariantNeeded = ramdiskVariantNeeded
 	m.Properties.RecoveryVariantNeeded = recoveryVariantNeeded
 	m.Properties.CoreVariantNeeded = coreVariantNeeded
 }
@@ -2754,6 +2796,10 @@
 	return c.Properties.CoreVariantNeeded
 }
 
+func (c *Module) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+	return c.Properties.RamdiskVariantNeeded
+}
+
 func (c *Module) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
 	return c.Properties.RecoveryVariantNeeded
 }
@@ -2764,7 +2810,9 @@
 
 func (c *Module) SetImageVariation(ctx android.BaseModuleContext, variant string, module android.Module) {
 	m := module.(*Module)
-	if variant == android.RecoveryVariation {
+	if variant == android.RamdiskVariation {
+		m.MakeAsPlatform()
+	} else if variant == android.RecoveryVariation {
 		m.MakeAsPlatform()
 		squashRecoverySrcs(m)
 	} else if strings.HasPrefix(variant, VendorVariationPrefix) {
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 4d02f4f..332cc45 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -2303,13 +2303,13 @@
 	// Check the shared version of lib2.
 	variant := "android_arm64_armv8-a_shared"
 	module := ctx.ModuleForTests("lib2", variant).Module().(*Module)
-	checkStaticLibs(t, []string{"lib1", "libc++demangle", "libclang_rt.builtins-aarch64-android", "libatomic", "libgcc_stripped"}, module)
+	checkStaticLibs(t, []string{"lib1", "libc++demangle", "libclang_rt.builtins-aarch64-android", "libatomic"}, module)
 
 	// Check the static version of lib2.
 	variant = "android_arm64_armv8-a_static"
 	module = ctx.ModuleForTests("lib2", variant).Module().(*Module)
 	// libc++_static is linked additionally.
-	checkStaticLibs(t, []string{"lib1", "libc++_static", "libc++demangle", "libclang_rt.builtins-aarch64-android", "libatomic", "libgcc_stripped"}, module)
+	checkStaticLibs(t, []string{"lib1", "libc++_static", "libc++demangle", "libclang_rt.builtins-aarch64-android", "libatomic"}, module)
 }
 
 var compilerFlagsTestCases = []struct {
diff --git a/cc/config/global.go b/cc/config/global.go
index bae5555..87314dc 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -88,6 +88,7 @@
 		"-Wl,--no-undefined-version",
 		"-Wl,--exclude-libs,libgcc.a",
 		"-Wl,--exclude-libs,libgcc_stripped.a",
+		"-Wl,--exclude-libs,libunwind_llvm.a",
 	}
 
 	deviceGlobalLldflags = append(ClangFilterUnknownLldflags(deviceGlobalLdflags),
diff --git a/cc/config/vndk.go b/cc/config/vndk.go
index 3b16c78..52bd9f0 100644
--- a/cc/config/vndk.go
+++ b/cc/config/vndk.go
@@ -18,56 +18,20 @@
 // For these libraries, the vendor variants must be installed even if the device
 // has VndkUseCoreVariant set.
 var VndkMustUseVendorVariantList = []string{
-	"android.frameworks.sensorservice@1.0",
-	"android.hardware.atrace@1.0",
-	"android.hardware.audio.common@5.0",
-	"android.hardware.audio.effect@2.0",
-	"android.hardware.audio.effect@4.0",
-	"android.hardware.audio.effect@5.0",
-	"android.hardware.audio@2.0",
-	"android.hardware.audio@4.0",
-	"android.hardware.audio@5.0",
 	"android.hardware.automotive.evs@1.0",
 	"android.hardware.automotive.vehicle@2.0",
-	"android.hardware.bluetooth.audio@2.0",
-	"android.hardware.boot@1.0",
-	"android.hardware.broadcastradio@1.0",
-	"android.hardware.broadcastradio@1.1",
 	"android.hardware.broadcastradio@2.0",
 	"android.hardware.camera.device@1.0",
 	"android.hardware.camera.device@3.2",
 	"android.hardware.camera.device@3.3",
 	"android.hardware.camera.device@3.4",
 	"android.hardware.camera.provider@2.4",
-	"android.hardware.cas.native@1.0",
-	"android.hardware.cas@1.0",
-	"android.hardware.configstore@1.0",
-	"android.hardware.configstore@1.1",
-	"android.hardware.contexthub@1.0",
-	"android.hardware.drm@1.0",
-	"android.hardware.drm@1.1",
 	"android.hardware.fastboot@1.0",
-	"android.hardware.gatekeeper@1.0",
-	"android.hardware.gnss@1.0",
-	"android.hardware.graphics.allocator@2.0",
-	"android.hardware.graphics.bufferqueue@1.0",
-	"android.hardware.graphics.composer@2.1",
-	"android.hardware.graphics.composer@2.2",
-	"android.hardware.health@1.0",
-	"android.hardware.health@2.0",
-	"android.hardware.ir@1.0",
-	"android.hardware.keymaster@3.0",
-	"android.hardware.keymaster@4.0",
-	"android.hardware.light@2.0",
 	"android.hardware.media.bufferpool@1.0",
-	"android.hardware.media.omx@1.0",
-	"android.hardware.memtrack@1.0",
 	"android.hardware.neuralnetworks@1.0",
 	"android.hardware.neuralnetworks@1.1",
 	"android.hardware.neuralnetworks@1.2",
 	"android.hardware.neuralnetworks@1.3",
-	"android.hardware.nfc@1.0",
-	"android.hardware.nfc@1.1",
 	"android.hardware.nfc@1.2",
 	"android.hardware.oemlock@1.0",
 	"android.hardware.power.stats@1.0",
@@ -82,51 +46,31 @@
 	"android.hardware.soundtrigger@2.1",
 	"android.hardware.tetheroffload.config@1.0",
 	"android.hardware.tetheroffload.control@1.0",
-	"android.hardware.thermal@1.0",
-	"android.hardware.tv.cec@1.0",
-	"android.hardware.tv.input@1.0",
 	"android.hardware.vibrator-ndk_platform",
-	"android.hardware.vibrator@1.0",
-	"android.hardware.vibrator@1.1",
-	"android.hardware.vibrator@1.2",
 	"android.hardware.weaver@1.0",
 	"android.hardware.wifi.hostapd@1.0",
 	"android.hardware.wifi.offload@1.0",
 	"android.hardware.wifi.supplicant@1.0",
 	"android.hardware.wifi.supplicant@1.1",
-	"android.hardware.wifi@1.0",
 	"android.hardware.wifi@1.1",
 	"android.hardware.wifi@1.2",
 	"android.hardwareundtrigger@2.0",
 	"android.hardwareundtrigger@2.0-core",
 	"android.hardwareundtrigger@2.1",
-	"android.hidl.allocator@1.0",
-	"android.hidl.token@1.0",
-	"android.hidl.token@1.0-utils",
-	"android.system.net.netd@1.0",
-	"android.system.wifi.keystore@1.0",
 	"libaudioroute",
-	"libaudioutils",
 	"libbinder",
 	"libcamera_metadata",
 	"libcrypto",
-	"libdiskconfig",
-	"libdumpstateutil",
 	"libexpat",
-	"libfmq",
 	"libgatekeeper",
 	"libgui",
 	"libhidlcache",
 	"libkeymaster_messages",
 	"libkeymaster_portable",
-	"libmedia_helper",
 	"libmedia_omx",
-	"libmemtrack",
-	"libnetutils",
 	"libprotobuf-cpp-full",
 	"libprotobuf-cpp-lite",
 	"libpuresoftkeymasterdevice",
-	"libradio_metadata",
 	"libselinux",
 	"libsoftkeymasterdevice",
 	"libsqlite",
@@ -161,11 +105,8 @@
 	"libstagefright_soft_vpxdec",
 	"libstagefright_soft_vpxenc",
 	"libstagefright_xmlparser",
-	"libsysutils",
-	"libtinyxml2",
 	"libui",
 	"libvorbisidec",
 	"libxml2",
 	"libyuv",
-	"libziparchive",
 }
diff --git a/cc/coverage.go b/cc/coverage.go
index b6451ee..b94b628 100644
--- a/cc/coverage.go
+++ b/cc/coverage.go
@@ -43,7 +43,7 @@
 	return []interface{}{&cov.Properties}
 }
 
-func getProfileLibraryName(ctx ModuleContextIntf) string {
+func getGcovProfileLibraryName(ctx ModuleContextIntf) string {
 	// This function should only ever be called for a cc.Module, so the
 	// following statement should always succeed.
 	if ctx.useSdk() {
@@ -53,28 +53,47 @@
 	}
 }
 
+func getClangProfileLibraryName(ctx ModuleContextIntf) string {
+	if ctx.useSdk() {
+		return "libprofile-clang-extras_ndk"
+	} else {
+		return "libprofile-clang-extras"
+	}
+}
+
 func (cov *coverage) deps(ctx DepsContext, deps Deps) Deps {
 	if cov.Properties.NeedCoverageVariant {
 		ctx.AddVariationDependencies([]blueprint.Variation{
 			{Mutator: "link", Variation: "static"},
-		}, coverageDepTag, getProfileLibraryName(ctx))
+		}, coverageDepTag, getGcovProfileLibraryName(ctx))
+		ctx.AddVariationDependencies([]blueprint.Variation{
+			{Mutator: "link", Variation: "static"},
+		}, coverageDepTag, getClangProfileLibraryName(ctx))
 	}
 	return deps
 }
 
 func (cov *coverage) flags(ctx ModuleContext, flags Flags, deps PathDeps) (Flags, PathDeps) {
-	if !ctx.DeviceConfig().NativeCoverageEnabled() {
+	gcovCoverage := ctx.DeviceConfig().NativeCoverageEnabled()
+	clangCoverage := ctx.DeviceConfig().ClangCoverageEnabled()
+
+	if !gcovCoverage && !clangCoverage {
 		return flags, deps
 	}
 
 	if cov.Properties.CoverageEnabled {
 		flags.Coverage = true
-		flags.Local.CommonFlags = append(flags.Local.CommonFlags, "--coverage", "-O0")
 		cov.linkCoverage = true
 
-		// Override -Wframe-larger-than and non-default optimization
-		// flags that the module may use.
-		flags.Local.CFlags = append(flags.Local.CFlags, "-Wno-frame-larger-than=", "-O0")
+		if gcovCoverage {
+			flags.Local.CommonFlags = append(flags.Local.CommonFlags, "--coverage", "-O0")
+
+			// Override -Wframe-larger-than and non-default optimization
+			// flags that the module may use.
+			flags.Local.CFlags = append(flags.Local.CFlags, "-Wno-frame-larger-than=", "-O0")
+		} else if clangCoverage {
+			flags.Local.CommonFlags = append(flags.Local.CommonFlags, "-fprofile-instr-generate", "-fcoverage-mapping")
+		}
 	}
 
 	// Even if we don't have coverage enabled, if any of our object files were compiled
@@ -112,12 +131,19 @@
 	}
 
 	if cov.linkCoverage {
-		flags.Local.LdFlags = append(flags.Local.LdFlags, "--coverage")
+		if gcovCoverage {
+			flags.Local.LdFlags = append(flags.Local.LdFlags, "--coverage")
 
-		coverage := ctx.GetDirectDepWithTag(getProfileLibraryName(ctx), coverageDepTag).(*Module)
-		deps.WholeStaticLibs = append(deps.WholeStaticLibs, coverage.OutputFile().Path())
+			coverage := ctx.GetDirectDepWithTag(getGcovProfileLibraryName(ctx), coverageDepTag).(*Module)
+			deps.WholeStaticLibs = append(deps.WholeStaticLibs, coverage.OutputFile().Path())
 
-		flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,--wrap,getenv")
+			flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,--wrap,getenv")
+		} else if clangCoverage {
+			flags.Local.LdFlags = append(flags.Local.LdFlags, "-fprofile-instr-generate")
+
+			coverage := ctx.GetDirectDepWithTag(getClangProfileLibraryName(ctx), coverageDepTag).(*Module)
+			deps.WholeStaticLibs = append(deps.WholeStaticLibs, coverage.OutputFile().Path())
+		}
 	}
 
 	return flags, deps
@@ -125,7 +151,7 @@
 
 func (cov *coverage) begin(ctx BaseModuleContext) {
 	// Coverage is disabled globally
-	if !ctx.DeviceConfig().NativeCoverageEnabled() {
+	if !ctx.DeviceConfig().NativeCoverageEnabled() && !ctx.DeviceConfig().ClangCoverageEnabled() {
 		return
 	}
 
diff --git a/cc/fuzz.go b/cc/fuzz.go
index 8b84be8..ee24300 100644
--- a/cc/fuzz.go
+++ b/cc/fuzz.go
@@ -355,10 +355,10 @@
 			return
 		}
 
-		// Discard vendor-NDK-linked + recovery modules, they're duplicates of
+		// Discard vendor-NDK-linked + ramdisk + recovery modules, they're duplicates of
 		// fuzz targets we're going to package anyway.
 		if !ccModule.Enabled() || ccModule.Properties.PreventInstall ||
-			ccModule.UseVndk() || ccModule.InRecovery() {
+			ccModule.UseVndk() || ccModule.InRamdisk() || ccModule.InRecovery() {
 			return
 		}
 
diff --git a/cc/genrule.go b/cc/genrule.go
index 548d5f2..155e410 100644
--- a/cc/genrule.go
+++ b/cc/genrule.go
@@ -25,6 +25,7 @@
 
 type GenruleExtraProperties struct {
 	Vendor_available   *bool
+	Ramdisk_available  *bool
 	Recovery_available *bool
 }
 
@@ -62,6 +63,10 @@
 	return Bool(g.Vendor_available) || !(ctx.SocSpecific() || ctx.DeviceSpecific())
 }
 
+func (g *GenruleExtraProperties) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+	return Bool(g.Ramdisk_available)
+}
+
 func (g *GenruleExtraProperties) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
 	return Bool(g.Recovery_available)
 }
diff --git a/cc/library.go b/cc/library.go
index f29c4d0..99a3e16 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -757,6 +757,13 @@
 		deps.ReexportSharedLibHeaders = removeListFromList(deps.ReexportSharedLibHeaders, library.baseLinker.Properties.Target.Recovery.Exclude_shared_libs)
 		deps.ReexportStaticLibHeaders = removeListFromList(deps.ReexportStaticLibHeaders, library.baseLinker.Properties.Target.Recovery.Exclude_static_libs)
 	}
+	if ctx.inRamdisk() {
+		deps.WholeStaticLibs = removeListFromList(deps.WholeStaticLibs, library.baseLinker.Properties.Target.Ramdisk.Exclude_static_libs)
+		deps.SharedLibs = removeListFromList(deps.SharedLibs, library.baseLinker.Properties.Target.Ramdisk.Exclude_shared_libs)
+		deps.StaticLibs = removeListFromList(deps.StaticLibs, library.baseLinker.Properties.Target.Ramdisk.Exclude_static_libs)
+		deps.ReexportSharedLibHeaders = removeListFromList(deps.ReexportSharedLibHeaders, library.baseLinker.Properties.Target.Ramdisk.Exclude_shared_libs)
+		deps.ReexportStaticLibHeaders = removeListFromList(deps.ReexportStaticLibHeaders, library.baseLinker.Properties.Target.Ramdisk.Exclude_static_libs)
+	}
 
 	return deps
 }
@@ -1040,7 +1047,7 @@
 			isVendor := ctx.useVndk()
 			isOwnerPlatform := Bool(library.Properties.Sysprop.Platform)
 
-			if !ctx.inRecovery() && (isProduct || (isOwnerPlatform == isVendor)) {
+			if !ctx.inRamdisk() && !ctx.inRecovery() && (isProduct || (isOwnerPlatform == isVendor)) {
 				dir = android.PathForModuleGen(ctx, "sysprop/public", "include")
 			}
 		}
@@ -1118,7 +1125,7 @@
 			// The original path becomes a symlink to the corresponding file in the
 			// runtime APEX.
 			translatedArch := ctx.Target().NativeBridge == android.NativeBridgeEnabled
-			if InstallToBootstrap(ctx.baseModuleName(), ctx.Config()) && !library.buildStubs() && !translatedArch && !ctx.inRecovery() {
+			if InstallToBootstrap(ctx.baseModuleName(), ctx.Config()) && !library.buildStubs() && !translatedArch && !ctx.inRamdisk() && !ctx.inRecovery() {
 				if ctx.Device() {
 					library.installSymlinkToRuntimeApex(ctx, file)
 				}
@@ -1133,7 +1140,7 @@
 	}
 
 	if Bool(library.Properties.Static_ndk_lib) && library.static() &&
-		!ctx.useVndk() && !ctx.inRecovery() && ctx.Device() &&
+		!ctx.useVndk() && !ctx.inRamdisk() && !ctx.inRecovery() && ctx.Device() &&
 		library.baseLinker.sanitize.isUnsanitizedVariant() &&
 		!library.buildStubs() {
 		installPath := getNdkSysrootBase(ctx).Join(
diff --git a/cc/linkable.go b/cc/linkable.go
index 106092b..3c46d9d 100644
--- a/cc/linkable.go
+++ b/cc/linkable.go
@@ -38,6 +38,9 @@
 	Shared() bool
 	Toc() android.OptionalPath
 
+	InRamdisk() bool
+	OnlyInRamdisk() bool
+
 	InRecovery() bool
 	OnlyInRecovery() bool
 
diff --git a/cc/linker.go b/cc/linker.go
index 61ae757..c2b4a3a 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -141,6 +141,19 @@
 			// of the C/C++ module.
 			Exclude_header_libs []string
 		}
+		Ramdisk struct {
+			// list of static libs that only should be used to build the recovery
+			// variant of the C/C++ module.
+			Static_libs []string
+
+			// list of shared libs that should not be used to build
+			// the ramdisk variant of the C/C++ module.
+			Exclude_shared_libs []string
+
+			// list of static libs that should not be used to build
+			// the ramdisk variant of the C/C++ module.
+			Exclude_static_libs []string
+		}
 	}
 
 	// make android::build:GetBuildNumber() available containing the build ID.
@@ -223,12 +236,20 @@
 		deps.WholeStaticLibs = removeListFromList(deps.WholeStaticLibs, linker.Properties.Target.Recovery.Exclude_static_libs)
 	}
 
+	if ctx.inRamdisk() {
+		deps.SharedLibs = removeListFromList(deps.SharedLibs, linker.Properties.Target.Recovery.Exclude_shared_libs)
+		deps.ReexportSharedLibHeaders = removeListFromList(deps.ReexportSharedLibHeaders, linker.Properties.Target.Recovery.Exclude_shared_libs)
+		deps.StaticLibs = append(deps.StaticLibs, linker.Properties.Target.Recovery.Static_libs...)
+		deps.StaticLibs = removeListFromList(deps.StaticLibs, linker.Properties.Target.Recovery.Exclude_static_libs)
+		deps.ReexportStaticLibHeaders = removeListFromList(deps.ReexportStaticLibHeaders, linker.Properties.Target.Recovery.Exclude_static_libs)
+		deps.WholeStaticLibs = removeListFromList(deps.WholeStaticLibs, linker.Properties.Target.Recovery.Exclude_static_libs)
+	}
+
 	if ctx.toolchain().Bionic() {
-		// libclang_rt.builtins, libgcc and libatomic have to be last on the command line
+		// libclang_rt.builtins and libatomic have to be last on the command line
 		if !Bool(linker.Properties.No_libcrt) {
 			deps.LateStaticLibs = append(deps.LateStaticLibs, config.BuiltinsRuntimeLibrary(ctx.toolchain()))
 			deps.LateStaticLibs = append(deps.LateStaticLibs, "libatomic")
-			deps.LateStaticLibs = append(deps.LateStaticLibs, "libgcc_stripped")
 		}
 
 		systemSharedLibs := linker.Properties.System_shared_libs
diff --git a/cc/sanitize.go b/cc/sanitize.go
index c4aeb96..93c4b41 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -351,8 +351,8 @@
 	}
 
 	// HWASan ramdisk (which is built from recovery) goes over some bootloader limit.
-	// Keep libc instrumented so that recovery can run hwasan-instrumented code if necessary.
-	if ctx.inRecovery() && !strings.HasPrefix(ctx.ModuleDir(), "bionic/libc") {
+	// Keep libc instrumented so that ramdisk / recovery can run hwasan-instrumented code if necessary.
+	if (ctx.inRamdisk() || ctx.inRecovery()) && !strings.HasPrefix(ctx.ModuleDir(), "bionic/libc") {
 		s.Hwaddress = nil
 	}
 
diff --git a/cc/stl.go b/cc/stl.go
index 5ccd44a..af015f9 100644
--- a/cc/stl.go
+++ b/cc/stl.go
@@ -171,11 +171,13 @@
 			deps.StaticLibs = append(deps.StaticLibs, "libc++demangle")
 		}
 		if ctx.toolchain().Bionic() {
-			if ctx.Arch().ArchType == android.Arm {
-				deps.StaticLibs = append(deps.StaticLibs, "libunwind_llvm")
-			}
 			if ctx.staticBinary() {
 				deps.StaticLibs = append(deps.StaticLibs, "libm", "libc")
+				if ctx.Arch().ArchType == android.Arm {
+					deps.StaticLibs = append(deps.StaticLibs, "libunwind_llvm")
+				} else {
+					deps.StaticLibs = append(deps.StaticLibs, "libgcc_stripped")
+				}
 			}
 		}
 	case "":
@@ -196,6 +198,8 @@
 		}
 		if ctx.Arch().ArchType == android.Arm {
 			deps.StaticLibs = append(deps.StaticLibs, "ndk_libunwind")
+		} else {
+			deps.StaticLibs = append(deps.StaticLibs, "libgcc_stripped")
 		}
 	default:
 		panic(fmt.Errorf("Unknown stl: %q", stl.Properties.SelectedStl))
diff --git a/cc/testing.go b/cc/testing.go
index 198a346..ba8ed95 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -136,6 +136,7 @@
 			name: "libc",
 			no_libcrt: true,
 			nocrt: true,
+			stl: "none",
 			system_shared_libs: [],
 			recovery_available: true,
 		}
@@ -147,6 +148,7 @@
 			name: "libm",
 			no_libcrt: true,
 			nocrt: true,
+			stl: "none",
 			system_shared_libs: [],
 			recovery_available: true,
 		}
@@ -158,6 +160,7 @@
 			name: "libdl",
 			no_libcrt: true,
 			nocrt: true,
+			stl: "none",
 			system_shared_libs: [],
 			recovery_available: true,
 		}
diff --git a/cc/vndk.go b/cc/vndk.go
index 872a473..ab73035 100644
--- a/cc/vndk.go
+++ b/cc/vndk.go
@@ -351,7 +351,7 @@
 	if lib, ok := m.linker.(libraryInterface); ok {
 		useCoreVariant := m.VndkVersion() == mctx.DeviceConfig().PlatformVndkVersion() &&
 			mctx.DeviceConfig().VndkUseCoreVariant() && !m.MustUseVendorVariant()
-		return lib.shared() && m.UseVndk() && m.IsVndk() && !m.isVndkExt() && !useCoreVariant
+		return lib.shared() && m.inVendor() && m.IsVndk() && !m.isVndkExt() && !useCoreVariant
 	}
 	return false
 }
@@ -670,7 +670,7 @@
 		if m.Target().NativeBridge == android.NativeBridgeEnabled {
 			return nil, "", false
 		}
-		if !m.UseVndk() || !m.IsForPlatform() || !m.installable() || !m.inVendor() {
+		if !m.UseVndk() || !m.installable() || !m.inVendor() {
 			return nil, "", false
 		}
 		l, ok := m.linker.(vndkSnapshotLibraryInterface)
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index 974c644..db61fba 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -174,6 +174,10 @@
 	stat.AddOutput(status.NewProtoErrorLog(log, filepath.Join(logsDir, c.logsPrefix+"build_error")))
 	stat.AddOutput(status.NewCriticalPath(log))
 
+	buildCtx.Verbosef("Detected %.3v GB total RAM", float32(config.TotalRAM())/(1024*1024*1024))
+	buildCtx.Verbosef("Parallelism (local/remote/highmem): %v/%v/%v",
+		config.Parallel(), config.RemoteParallel(), config.HighmemParallel())
+
 	defer met.Dump(filepath.Join(logsDir, c.logsPrefix+"soong_metrics"))
 
 	if start, ok := os.LookupEnv("TRACE_BEGIN_SOONG"); ok {
diff --git a/dexpreopt/Android.bp b/dexpreopt/Android.bp
index c5f24e2..b8f7ea6 100644
--- a/dexpreopt/Android.bp
+++ b/dexpreopt/Android.bp
@@ -4,6 +4,7 @@
     srcs: [
         "config.go",
         "dexpreopt.go",
+        "testing.go",
     ],
     testSrcs: [
         "dexpreopt_test.go",
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index 2a929c5..72c01d0 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -16,14 +16,16 @@
 
 import (
 	"encoding/json"
+	"fmt"
 	"strings"
 
+	"github.com/google/blueprint"
+
 	"android/soong/android"
 )
 
 // GlobalConfig stores the configuration for dex preopting. The fields are set
-// from product variables via dex_preopt_config.mk, except for SoongConfig
-// which come from CreateGlobalSoongConfig.
+// from product variables via dex_preopt_config.mk.
 type GlobalConfig struct {
 	DisablePreopt        bool     // disable preopt for all modules
 	DisablePreoptModules []string // modules with preopt disabled by product-specific config
@@ -82,8 +84,6 @@
 	BootFlags         string               // extra flags to pass to dex2oat for the boot image
 	Dex2oatImageXmx   string               // max heap size for dex2oat for the boot image
 	Dex2oatImageXms   string               // initial heap size for dex2oat for the boot image
-
-	SoongConfig GlobalSoongConfig // settings read from dexpreopt_soong.config
 }
 
 // GlobalSoongConfig contains the global config that is generated from Soong,
@@ -179,12 +179,9 @@
 	return constructPath(ctx, path).(android.WritablePath)
 }
 
-// LoadGlobalConfig reads the global dexpreopt.config file into a GlobalConfig
-// struct, except the SoongConfig field which is set from the provided
-// soongConfig argument. LoadGlobalConfig is used directly in Soong and in
-// dexpreopt_gen called from Make to read the $OUT/dexpreopt.config written by
-// Make.
-func LoadGlobalConfig(ctx android.PathContext, data []byte, soongConfig GlobalSoongConfig) (GlobalConfig, error) {
+// ParseGlobalConfig parses the given data assumed to be read from the global
+// dexpreopt.config file into a GlobalConfig struct.
+func ParseGlobalConfig(ctx android.PathContext, data []byte) (GlobalConfig, error) {
 	type GlobalJSONConfig struct {
 		GlobalConfig
 
@@ -204,17 +201,68 @@
 	config.GlobalConfig.DirtyImageObjects = android.OptionalPathForPath(constructPath(ctx, config.DirtyImageObjects))
 	config.GlobalConfig.BootImageProfiles = constructPaths(ctx, config.BootImageProfiles)
 
-	// Set this here to force the caller to provide a value for this struct (from
-	// either CreateGlobalSoongConfig or LoadGlobalSoongConfig).
-	config.GlobalConfig.SoongConfig = soongConfig
-
 	return config.GlobalConfig, nil
 }
 
-// LoadModuleConfig reads a per-module dexpreopt.config file into a ModuleConfig struct.  It is not used in Soong, which
-// receives a ModuleConfig struct directly from java/dexpreopt.go.  It is used in dexpreopt_gen called from oMake to
-// read the module dexpreopt.config written by Make.
-func LoadModuleConfig(ctx android.PathContext, data []byte) (ModuleConfig, error) {
+type globalConfigAndRaw struct {
+	global GlobalConfig
+	data   []byte
+}
+
+// GetGlobalConfig returns the global dexpreopt.config that's created in the
+// make config phase. It is loaded once the first time it is called for any
+// ctx.Config(), and returns the same data for all future calls with the same
+// ctx.Config(). A value can be inserted for tests using
+// setDexpreoptTestGlobalConfig.
+func GetGlobalConfig(ctx android.PathContext) GlobalConfig {
+	return getGlobalConfigRaw(ctx).global
+}
+
+// GetGlobalConfigRawData is the same as GetGlobalConfig, except that it returns
+// the literal content of dexpreopt.config.
+func GetGlobalConfigRawData(ctx android.PathContext) []byte {
+	return getGlobalConfigRaw(ctx).data
+}
+
+var globalConfigOnceKey = android.NewOnceKey("DexpreoptGlobalConfig")
+var testGlobalConfigOnceKey = android.NewOnceKey("TestDexpreoptGlobalConfig")
+
+func getGlobalConfigRaw(ctx android.PathContext) globalConfigAndRaw {
+	return ctx.Config().Once(globalConfigOnceKey, func() interface{} {
+		if data, err := ctx.Config().DexpreoptGlobalConfig(ctx); err != nil {
+			panic(err)
+		} else if data != nil {
+			globalConfig, err := ParseGlobalConfig(ctx, data)
+			if err != nil {
+				panic(err)
+			}
+			return globalConfigAndRaw{globalConfig, data}
+		}
+
+		// No global config filename set, see if there is a test config set
+		return ctx.Config().Once(testGlobalConfigOnceKey, func() interface{} {
+			// Nope, return a config with preopting disabled
+			return globalConfigAndRaw{GlobalConfig{
+				DisablePreopt:          true,
+				DisableGenerateProfile: true,
+			}, nil}
+		})
+	}).(globalConfigAndRaw)
+}
+
+// SetTestGlobalConfig sets a GlobalConfig that future calls to GetGlobalConfig
+// will return. It must be called before the first call to GetGlobalConfig for
+// the config.
+func SetTestGlobalConfig(config android.Config, globalConfig GlobalConfig) {
+	config.Once(testGlobalConfigOnceKey, func() interface{} { return globalConfigAndRaw{globalConfig, nil} })
+}
+
+// ParseModuleConfig parses a per-module dexpreopt.config file into a
+// ModuleConfig struct. It is not used in Soong, which receives a ModuleConfig
+// struct directly from java/dexpreopt.go. It is used in dexpreopt_gen called
+// from Make to read the module dexpreopt.config written in the Make config
+// stage.
+func ParseModuleConfig(ctx android.PathContext, data []byte) (ModuleConfig, error) {
 	type ModuleJSONConfig struct {
 		ModuleConfig
 
@@ -253,21 +301,84 @@
 	return config.ModuleConfig, nil
 }
 
-// CreateGlobalSoongConfig creates a GlobalSoongConfig from the current context.
-// Should not be used in dexpreopt_gen.
-func CreateGlobalSoongConfig(ctx android.PathContext) GlobalSoongConfig {
-	// Default to debug version to help find bugs.
+// dex2oatModuleName returns the name of the module to use for the dex2oat host
+// tool. It should be a binary module with public visibility that is compiled
+// and installed for host.
+func dex2oatModuleName(config android.Config) string {
+	// Default to the debug variant of dex2oat to help find bugs.
 	// Set USE_DEX2OAT_DEBUG to false for only building non-debug versions.
-	var dex2oatBinary string
-	if ctx.Config().Getenv("USE_DEX2OAT_DEBUG") == "false" {
-		dex2oatBinary = "dex2oat"
+	if config.Getenv("USE_DEX2OAT_DEBUG") == "false" {
+		return "dex2oat"
 	} else {
-		dex2oatBinary = "dex2oatd"
+		return "dex2oatd"
+	}
+}
+
+var dex2oatDepTag = struct {
+	blueprint.BaseDependencyTag
+}{}
+
+type DexPreoptModule interface {
+	dexPreoptModuleSignature() // Not called - only for type detection.
+}
+
+// RegisterToolDepsMutator registers a mutator that adds the necessary
+// dependencies to binary modules for tools that are required later when
+// Get(Cached)GlobalSoongConfig is called. It should be passed to
+// android.RegistrationContext.FinalDepsMutators, and module types that need
+// dependencies also need to embed DexPreoptModule.
+func RegisterToolDepsMutator(ctx android.RegisterMutatorsContext) {
+	ctx.BottomUp("dexpreopt_tool_deps", toolDepsMutator).Parallel()
+}
+
+func toolDepsMutator(ctx android.BottomUpMutatorContext) {
+	if GetGlobalConfig(ctx).DisablePreopt {
+		// Only register dependencies if dexpreopting is enabled. Necessary to avoid
+		// them in non-platform builds where dex2oat etc isn't available.
+		//
+		// It would be nice to not register this mutator at all then, but
+		// RegisterMutatorsContext available at registration doesn't have the state
+		// necessary to pass as PathContext to constructPath etc.
+		return
+	}
+	if _, ok := ctx.Module().(DexPreoptModule); !ok {
+		return
+	}
+	dex2oatBin := dex2oatModuleName(ctx.Config())
+	v := ctx.Config().BuildOSTarget.Variations()
+	ctx.AddFarVariationDependencies(v, dex2oatDepTag, dex2oatBin)
+}
+
+func dex2oatPathFromDep(ctx android.ModuleContext) android.Path {
+	dex2oatBin := dex2oatModuleName(ctx.Config())
+
+	dex2oatModule := ctx.GetDirectDepWithTag(dex2oatBin, dex2oatDepTag)
+	if dex2oatModule == nil {
+		// If this happens there's probably a missing call to AddToolDeps in DepsMutator.
+		panic(fmt.Sprintf("Failed to lookup %s dependency", dex2oatBin))
+	}
+
+	dex2oatPath := dex2oatModule.(android.HostToolProvider).HostToolPath()
+	if !dex2oatPath.Valid() {
+		panic(fmt.Sprintf("Failed to find host tool path in %s", dex2oatModule))
+	}
+
+	return dex2oatPath.Path()
+}
+
+// createGlobalSoongConfig creates a GlobalSoongConfig from the current context.
+// Should not be used in dexpreopt_gen.
+func createGlobalSoongConfig(ctx android.ModuleContext) GlobalSoongConfig {
+	if ctx.Config().TestProductVariables != nil {
+		// If we're called in a test there'll be a confusing error from the path
+		// functions below that gets reported without a stack trace, so let's panic
+		// properly with a more helpful message.
+		panic("This should not be called from tests. Please call GlobalSoongConfigForTests somewhere in the test setup.")
 	}
 
 	return GlobalSoongConfig{
 		Profman:          ctx.Config().HostToolPath(ctx, "profman"),
-		Dex2oat:          ctx.Config().HostToolPath(ctx, dex2oatBinary),
+		Dex2oat:          dex2oatPathFromDep(ctx),
 		Aapt:             ctx.Config().HostToolPath(ctx, "aapt"),
 		SoongZip:         ctx.Config().HostToolPath(ctx, "soong_zip"),
 		Zip2zip:          ctx.Config().HostToolPath(ctx, "zip2zip"),
@@ -276,6 +387,44 @@
 	}
 }
 
+// The main reason for this Once cache for GlobalSoongConfig is to make the
+// dex2oat path available to singletons. In ordinary modules we get it through a
+// dex2oatDepTag dependency, but in singletons there's no simple way to do the
+// same thing and ensure the right variant is selected, hence this cache to make
+// the resolved path available to singletons. This means we depend on there
+// being at least one ordinary module with a dex2oatDepTag dependency.
+//
+// TODO(b/147613152): Implement a way to deal with dependencies from singletons,
+// and then possibly remove this cache altogether (but the use in
+// GlobalSoongConfigForTests also needs to be rethought).
+var globalSoongConfigOnceKey = android.NewOnceKey("DexpreoptGlobalSoongConfig")
+
+// GetGlobalSoongConfig creates a GlobalSoongConfig the first time it's called,
+// and later returns the same cached instance.
+func GetGlobalSoongConfig(ctx android.ModuleContext) GlobalSoongConfig {
+	globalSoong := ctx.Config().Once(globalSoongConfigOnceKey, func() interface{} {
+		return createGlobalSoongConfig(ctx)
+	}).(GlobalSoongConfig)
+
+	// Always resolve the tool path from the dependency, to ensure that every
+	// module has the dependency added properly.
+	myDex2oat := dex2oatPathFromDep(ctx)
+	if myDex2oat != globalSoong.Dex2oat {
+		panic(fmt.Sprintf("Inconsistent dex2oat path in cached config: expected %s, got %s", globalSoong.Dex2oat, myDex2oat))
+	}
+
+	return globalSoong
+}
+
+// GetCachedGlobalSoongConfig returns a cached GlobalSoongConfig created by an
+// earlier GetGlobalSoongConfig call. This function works with any context
+// compatible with a basic PathContext, since it doesn't try to create a
+// GlobalSoongConfig (which requires a full ModuleContext). It will panic if
+// called before the first GetGlobalSoongConfig call.
+func GetCachedGlobalSoongConfig(ctx android.PathContext) GlobalSoongConfig {
+	return ctx.Config().Get(globalSoongConfigOnceKey).(GlobalSoongConfig)
+}
+
 type globalJsonSoongConfig struct {
 	Profman          string
 	Dex2oat          string
@@ -286,9 +435,10 @@
 	ConstructContext string
 }
 
-// LoadGlobalSoongConfig reads the dexpreopt_soong.config file into a
-// GlobalSoongConfig struct. It is only used in dexpreopt_gen.
-func LoadGlobalSoongConfig(ctx android.PathContext, data []byte) (GlobalSoongConfig, error) {
+// ParseGlobalSoongConfig parses the given data assumed to be read from the
+// global dexpreopt_soong.config file into a GlobalSoongConfig struct. It is
+// only used in dexpreopt_gen.
+func ParseGlobalSoongConfig(ctx android.PathContext, data []byte) (GlobalSoongConfig, error) {
 	var jc globalJsonSoongConfig
 
 	err := json.Unmarshal(data, &jc)
@@ -310,7 +460,11 @@
 }
 
 func (s *globalSoongConfigSingleton) GenerateBuildActions(ctx android.SingletonContext) {
-	config := CreateGlobalSoongConfig(ctx)
+	if GetGlobalConfig(ctx).DisablePreopt {
+		return
+	}
+
+	config := GetCachedGlobalSoongConfig(ctx)
 	jc := globalJsonSoongConfig{
 		Profman:          config.Profman.String(),
 		Dex2oat:          config.Dex2oat.String(),
@@ -337,7 +491,11 @@
 }
 
 func (s *globalSoongConfigSingleton) MakeVars(ctx android.MakeVarsContext) {
-	config := CreateGlobalSoongConfig(ctx)
+	if GetGlobalConfig(ctx).DisablePreopt {
+		return
+	}
+
+	config := GetCachedGlobalSoongConfig(ctx)
 
 	ctx.Strict("DEX2OAT", config.Dex2oat.String())
 	ctx.Strict("DEXPREOPT_GEN_DEPS", strings.Join([]string{
@@ -390,7 +548,14 @@
 		BootFlags:                          "",
 		Dex2oatImageXmx:                    "",
 		Dex2oatImageXms:                    "",
-		SoongConfig: GlobalSoongConfig{
+	}
+}
+
+func GlobalSoongConfigForTests(config android.Config) GlobalSoongConfig {
+	// Install the test GlobalSoongConfig in the Once cache so that later calls to
+	// Get(Cached)GlobalSoongConfig returns it without trying to create a real one.
+	return config.Once(globalSoongConfigOnceKey, func() interface{} {
+		return GlobalSoongConfig{
 			Profman:          android.PathForTesting("profman"),
 			Dex2oat:          android.PathForTesting("dex2oat"),
 			Aapt:             android.PathForTesting("aapt"),
@@ -398,6 +563,6 @@
 			Zip2zip:          android.PathForTesting("zip2zip"),
 			ManifestCheck:    android.PathForTesting("manifest_check"),
 			ConstructContext: android.PathForTesting("construct_context.sh"),
-		},
-	}
+		}
+	}).(GlobalSoongConfig)
 }
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index ac5b691..a69498a 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -49,7 +49,7 @@
 
 // GenerateDexpreoptRule generates a set of commands that will preopt a module based on a GlobalConfig and a
 // ModuleConfig.  The produced files and their install locations will be available through rule.Installs().
-func GenerateDexpreoptRule(ctx android.PathContext,
+func GenerateDexpreoptRule(ctx android.PathContext, globalSoong GlobalSoongConfig,
 	global GlobalConfig, module ModuleConfig) (rule *android.RuleBuilder, err error) {
 
 	defer func() {
@@ -72,10 +72,10 @@
 
 	var profile android.WritablePath
 	if generateProfile {
-		profile = profileCommand(ctx, global, module, rule)
+		profile = profileCommand(ctx, globalSoong, global, module, rule)
 	}
 	if generateBootProfile {
-		bootProfileCommand(ctx, global, module, rule)
+		bootProfileCommand(ctx, globalSoong, global, module, rule)
 	}
 
 	if !dexpreoptDisabled(global, module) {
@@ -87,7 +87,7 @@
 			generateDM := shouldGenerateDM(module, global)
 
 			for archIdx, _ := range module.Archs {
-				dexpreoptCommand(ctx, global, module, rule, archIdx, profile, appImage, generateDM)
+				dexpreoptCommand(ctx, globalSoong, global, module, rule, archIdx, profile, appImage, generateDM)
 			}
 		}
 	}
@@ -119,8 +119,8 @@
 	return false
 }
 
-func profileCommand(ctx android.PathContext, global GlobalConfig, module ModuleConfig,
-	rule *android.RuleBuilder) android.WritablePath {
+func profileCommand(ctx android.PathContext, globalSoong GlobalSoongConfig, global GlobalConfig,
+	module ModuleConfig, rule *android.RuleBuilder) android.WritablePath {
 
 	profilePath := module.BuildPath.InSameDir(ctx, "profile.prof")
 	profileInstalledPath := module.DexLocation + ".prof"
@@ -131,7 +131,7 @@
 
 	cmd := rule.Command().
 		Text(`ANDROID_LOG_TAGS="*:e"`).
-		Tool(global.SoongConfig.Profman)
+		Tool(globalSoong.Profman)
 
 	if module.ProfileIsTextListing {
 		// The profile is a test listing of classes (used for framework jars).
@@ -158,8 +158,8 @@
 	return profilePath
 }
 
-func bootProfileCommand(ctx android.PathContext, global GlobalConfig, module ModuleConfig,
-	rule *android.RuleBuilder) android.WritablePath {
+func bootProfileCommand(ctx android.PathContext, globalSoong GlobalSoongConfig, global GlobalConfig,
+	module ModuleConfig, rule *android.RuleBuilder) android.WritablePath {
 
 	profilePath := module.BuildPath.InSameDir(ctx, "profile.bprof")
 	profileInstalledPath := module.DexLocation + ".bprof"
@@ -170,7 +170,7 @@
 
 	cmd := rule.Command().
 		Text(`ANDROID_LOG_TAGS="*:e"`).
-		Tool(global.SoongConfig.Profman)
+		Tool(globalSoong.Profman)
 
 	// The profile is a test listing of methods.
 	// We need to generate the actual binary profile.
@@ -190,8 +190,9 @@
 	return profilePath
 }
 
-func dexpreoptCommand(ctx android.PathContext, global GlobalConfig, module ModuleConfig, rule *android.RuleBuilder,
-	archIdx int, profile android.WritablePath, appImage bool, generateDM bool) {
+func dexpreoptCommand(ctx android.PathContext, globalSoong GlobalSoongConfig, global GlobalConfig,
+	module ModuleConfig, rule *android.RuleBuilder, archIdx int, profile android.WritablePath,
+	appImage bool, generateDM bool) {
 
 	arch := module.Archs[archIdx]
 
@@ -299,14 +300,14 @@
 	if module.EnforceUsesLibraries {
 		if module.ManifestPath != nil {
 			rule.Command().Text(`target_sdk_version="$(`).
-				Tool(global.SoongConfig.ManifestCheck).
+				Tool(globalSoong.ManifestCheck).
 				Flag("--extract-target-sdk-version").
 				Input(module.ManifestPath).
 				Text(`)"`)
 		} else {
 			// No manifest to extract targetSdkVersion from, hope that DexJar is an APK
 			rule.Command().Text(`target_sdk_version="$(`).
-				Tool(global.SoongConfig.Aapt).
+				Tool(globalSoong.Aapt).
 				Flag("dump badging").
 				Input(module.DexPath).
 				Text(`| grep "targetSdkVersion" | sed -n "s/targetSdkVersion:'\(.*\)'/\1/p"`).
@@ -327,7 +328,7 @@
 			Implicits(conditionalClassLoaderContextHost29)
 		rule.Command().Textf(`conditional_target_libs_29="%s"`,
 			strings.Join(conditionalClassLoaderContextTarget29, " "))
-		rule.Command().Text("source").Tool(global.SoongConfig.ConstructContext).Input(module.DexPath)
+		rule.Command().Text("source").Tool(globalSoong.ConstructContext).Input(module.DexPath)
 	}
 
 	// Devices that do not have a product partition use a symlink from /product to /system/product.
@@ -340,7 +341,7 @@
 
 	cmd := rule.Command().
 		Text(`ANDROID_LOG_TAGS="*:e"`).
-		Tool(global.SoongConfig.Dex2oat).
+		Tool(globalSoong.Dex2oat).
 		Flag("--avoid-storing-invocation").
 		FlagWithOutput("--write-invocation-to=", invocationPath).ImplicitOutput(invocationPath).
 		Flag("--runtime-arg").FlagWithArg("-Xms", global.Dex2oatXms).
@@ -409,7 +410,7 @@
 		dmInstalledPath := pathtools.ReplaceExtension(module.DexLocation, "dm")
 		tmpPath := module.BuildPath.InSameDir(ctx, "primary.vdex")
 		rule.Command().Text("cp -f").Input(vdexPath).Output(tmpPath)
-		rule.Command().Tool(global.SoongConfig.SoongZip).
+		rule.Command().Tool(globalSoong.SoongZip).
 			FlagWithArg("-L", "9").
 			FlagWithOutput("-o", dmPath).
 			Flag("-j").
diff --git a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
index e2818bb..4da003e 100644
--- a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
+++ b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
@@ -80,13 +80,13 @@
 
 	globalSoongConfigData, err := ioutil.ReadFile(*globalSoongConfigPath)
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "error reading global config %q: %s\n", *globalSoongConfigPath, err)
+		fmt.Fprintf(os.Stderr, "error reading global Soong config %q: %s\n", *globalSoongConfigPath, err)
 		os.Exit(2)
 	}
 
-	globalSoongConfig, err := dexpreopt.LoadGlobalSoongConfig(ctx, globalSoongConfigData)
+	globalSoongConfig, err := dexpreopt.ParseGlobalSoongConfig(ctx, globalSoongConfigData)
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "error loading global config %q: %s\n", *globalSoongConfigPath, err)
+		fmt.Fprintf(os.Stderr, "error parsing global Soong config %q: %s\n", *globalSoongConfigPath, err)
 		os.Exit(2)
 	}
 
@@ -96,9 +96,9 @@
 		os.Exit(2)
 	}
 
-	globalConfig, err := dexpreopt.LoadGlobalConfig(ctx, globalConfigData, globalSoongConfig)
+	globalConfig, err := dexpreopt.ParseGlobalConfig(ctx, globalConfigData)
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "error parse global config %q: %s\n", *globalConfigPath, err)
+		fmt.Fprintf(os.Stderr, "error parsing global config %q: %s\n", *globalConfigPath, err)
 		os.Exit(2)
 	}
 
@@ -108,9 +108,9 @@
 		os.Exit(2)
 	}
 
-	moduleConfig, err := dexpreopt.LoadModuleConfig(ctx, moduleConfigData)
+	moduleConfig, err := dexpreopt.ParseModuleConfig(ctx, moduleConfigData)
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "error loading module config %q: %s\n", *moduleConfigPath, err)
+		fmt.Fprintf(os.Stderr, "error parsing module config %q: %s\n", *moduleConfigPath, err)
 		os.Exit(2)
 	}
 
@@ -130,12 +130,12 @@
 		}
 	}()
 
-	writeScripts(ctx, globalConfig, moduleConfig, *dexpreoptScriptPath)
+	writeScripts(ctx, globalSoongConfig, globalConfig, moduleConfig, *dexpreoptScriptPath)
 }
 
-func writeScripts(ctx android.PathContext, global dexpreopt.GlobalConfig, module dexpreopt.ModuleConfig,
-	dexpreoptScriptPath string) {
-	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, global, module)
+func writeScripts(ctx android.PathContext, globalSoong dexpreopt.GlobalSoongConfig,
+	global dexpreopt.GlobalConfig, module dexpreopt.ModuleConfig, dexpreoptScriptPath string) {
+	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, globalSoong, global, module)
 	if err != nil {
 		panic(err)
 	}
@@ -150,7 +150,7 @@
 		dexpreoptRule.Command().Text("mkdir -p").Flag(filepath.Dir(installPath.String()))
 		dexpreoptRule.Command().Text("cp -f").Input(install.From).Output(installPath)
 	}
-	dexpreoptRule.Command().Tool(global.SoongConfig.SoongZip).
+	dexpreoptRule.Command().Tool(globalSoong.SoongZip).
 		FlagWithArg("-o ", "$2").
 		FlagWithArg("-C ", installDir.String()).
 		FlagWithArg("-D ", installDir.String())
diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go
index a128dc0..44bbbc2 100644
--- a/dexpreopt/dexpreopt_test.go
+++ b/dexpreopt/dexpreopt_test.go
@@ -61,10 +61,13 @@
 }
 
 func TestDexPreopt(t *testing.T) {
-	ctx := android.PathContextForTesting(android.TestConfig("out", nil, "", nil))
-	global, module := GlobalConfigForTests(ctx), testSystemModuleConfig(ctx, "test")
+	config := android.TestConfig("out", nil, "", nil)
+	ctx := android.PathContextForTesting(config)
+	globalSoong := GlobalSoongConfigForTests(config)
+	global := GlobalConfigForTests(ctx)
+	module := testSystemModuleConfig(ctx, "test")
 
-	rule, err := GenerateDexpreoptRule(ctx, global, module)
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -80,7 +83,9 @@
 }
 
 func TestDexPreoptSystemOther(t *testing.T) {
-	ctx := android.PathContextForTesting(android.TestConfig("out", nil, "", nil))
+	config := android.TestConfig("out", nil, "", nil)
+	ctx := android.PathContextForTesting(config)
+	globalSoong := GlobalSoongConfigForTests(config)
 	global := GlobalConfigForTests(ctx)
 	systemModule := testSystemModuleConfig(ctx, "Stest")
 	systemProductModule := testSystemProductModuleConfig(ctx, "SPtest")
@@ -118,7 +123,7 @@
 	for _, test := range tests {
 		global.PatternsOnSystemOther = test.patterns
 		for _, mt := range test.moduleTests {
-			rule, err := GenerateDexpreoptRule(ctx, global, mt.module)
+			rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, mt.module)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -138,12 +143,15 @@
 }
 
 func TestDexPreoptProfile(t *testing.T) {
-	ctx := android.PathContextForTesting(android.TestConfig("out", nil, "", nil))
-	global, module := GlobalConfigForTests(ctx), testSystemModuleConfig(ctx, "test")
+	config := android.TestConfig("out", nil, "", nil)
+	ctx := android.PathContextForTesting(config)
+	globalSoong := GlobalSoongConfigForTests(config)
+	global := GlobalConfigForTests(ctx)
+	module := testSystemModuleConfig(ctx, "test")
 
 	module.ProfileClassListing = android.OptionalPathForPath(android.PathForTesting("profile"))
 
-	rule, err := GenerateDexpreoptRule(ctx, global, module)
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/dexpreopt/testing.go b/dexpreopt/testing.go
new file mode 100644
index 0000000..b572eb3
--- /dev/null
+++ b/dexpreopt/testing.go
@@ -0,0 +1,47 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dexpreopt
+
+import (
+	"android/soong/android"
+)
+
+type dummyToolBinary struct {
+	android.ModuleBase
+}
+
+func (m *dummyToolBinary) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
+
+func (m *dummyToolBinary) HostToolPath() android.OptionalPath {
+	return android.OptionalPathForPath(android.PathForTesting("dex2oat"))
+}
+
+func dummyToolBinaryFactory() android.Module {
+	module := &dummyToolBinary{}
+	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
+	return module
+}
+
+func RegisterToolModulesForTest(ctx *android.TestContext) {
+	ctx.RegisterModuleType("dummy_tool_binary", dummyToolBinaryFactory)
+}
+
+func BpToolModulesForTest() string {
+	return `
+		dummy_tool_binary {
+			name: "dex2oatd",
+		}
+	`
+}
diff --git a/docs/perf.md b/docs/perf.md
index c3a2647..538adff 100644
--- a/docs/perf.md
+++ b/docs/perf.md
@@ -8,7 +8,7 @@
 viewed.  Just open `$OUT_DIR/build.trace.gz` in Chrome's <chrome://tracing>, or
 with [catapult's trace viewer][catapult trace_viewer]. The last few traces are
 stored in `build.trace.#.gz` (larger numbers are older). The associated logs
-are stored in `soong.#.log`.
+are stored in `soong.#.log` and `verbose.#.log.gz`.
 
 ![trace example](./trace_example.png)
 
@@ -31,29 +31,29 @@
 
 In most cases, we've found that the fast-path is slow because all of the
 `$(shell)` commands need to be re-executed to determine if their output changed.
-The `$OUT_DIR/soong.log` contains statistics from the regen check:
+The `$OUT_DIR/verbose.log.gz` contains statistics from the regen check:
 
 ```
-.../kati.go:127: *kati*: regen check time: 1.699207
-.../kati.go:127: *kati*: glob time (regen): 0.377193 / 33609
-.../kati.go:127: *kati*: shell time (regen): 1.313529 / 184
-.../kati.go:127: *kati*:  0.217 find device vendor -type f -name \*.pk8 -o -name verifiedboot\* -o -name \*.x509.pem -o -name oem\*.prop | sort
-.../kati.go:127: *kati*:  0.105 cd packages/apps/Dialer ; find -L . -type d -name "res"
-.../kati.go:127: *kati*:  0.035 find device vendor -maxdepth 4 -name '*_aux_variant_config.mk' -o -name '*_aux_os_config.mk' | sort
-.../kati.go:127: *kati*:  0.029 cd frameworks/base ; find -L  core/java graphics/java location/java media/java media/mca/effect/java media/mca/filterfw/java media/mca/filterpacks/java drm/java opengl/java sax/java telecomm/java telephony/java wifi/java lowpan/java keystore/java rs/java ../opt/telephony/src/java/android/telephony ../opt/telephony/src/java/android/telephony/gsm ../opt/net/voip/src/java/android/net/rtp ../opt/net/voip/src/java/android/net/sip   -name "*.html" -and -not -name ".*"
-.../kati.go:127: *kati*:  0.025 test -d device && find -L device -maxdepth 4 -path '*/marlin/BoardConfig.mk'
-.../kati.go:127: *kati*:  0.023 find packages/apps/Settings/tests/robotests -type f -name '*Test.java' | sed -e 's!.*\(com/google.*Test\)\.java!\1!' -e 's!.*\(com/android.*Test\)\.java!\1!' | sed 's!/!\.!g' | cat
-.../kati.go:127: *kati*:  0.022 test -d vendor && find -L vendor -maxdepth 4 -path '*/marlin/BoardConfig.mk'
-.../kati.go:127: *kati*:  0.017 cd cts/tests/tests/shortcutmanager/packages/launchermanifest ; find -L  ../src -name "*.java" -and -not -name ".*"
-.../kati.go:127: *kati*:  0.016 cd cts/tests/tests/shortcutmanager/packages/launchermanifest ; find -L  ../../common/src -name "*.java" -and -not -name ".*"
-.../kati.go:127: *kati*:  0.015 cd libcore && (find luni/src/test/java -name "*.java" 2> /dev/null) | grep -v -f java_tests_blacklist
-.../kati.go:127: *kati*: stat time (regen): 0.250384 / 4405
+verbose: *kati*: regen check time: 0.754030
+verbose: *kati*: glob time (regen): 0.545859 / 43840
+verbose: *kati*: shell time (regen): 0.278095 / 66 (59 unique)
+verbose: *kati*:   0.012 / 1 mkdir -p out/target/product/generic && echo Android/aosp_arm/generic:R/AOSP.MASTER/$(date -d @$(cat out/build_date.txt) +%m%d%H%M):eng/test-keys >out/target/product/generic/build_fingerprint.txt && grep " " out/target/product/generic/build_fingerprint.txt
+verbose: *kati*:   0.010 / 1 echo 'com.android.launcher3.config.FlagOverrideSampleTest com.android.launcher3.logging.FileLogTest com.android.launcher3.model.AddWorkspaceItemsTaskTest com.android.launcher3.model.CacheDataUpdatedTaskTest com.android.launcher3.model.DbDowngradeHelperTest com.android.launcher3.model.GridBackupTableTest com.android.launcher3.model.GridSizeMigrationTaskTest com.android.launcher3.model.PackageInstallStateChangedTaskTest com.android.launcher3.popup.PopupPopulatorTest com.android.launcher3.util.GridOccupancyTest com.android.launcher3.util.IntSetTest' | tr ' ' '\n' | cat
+verbose: *kati*:   0.010 / 1 cd cts/tests/framework/base/windowmanager ; find -L  * -name "Components.java" -and -not -name ".*"
+verbose: *kati*:   0.010 / 1 git -C test/framework/build log -s -n 1 --format="%cd" --date=format:"%Y%m%d_%H%M%S" 2>/dev/null
+verbose: *kati*:   0.009 / 2 cd development/samples/ShortcutDemo/publisher ; find -L  ../common/src -name "*.java" -and -not -name ".*"
+verbose: *kati*:   0.009 / 2 cd development/samples/ShortcutDemo/launcher ; find -L  ../common/src -name "*.java" -and -not -name ".*"
+verbose: *kati*:   0.009 / 1 if ! cmp -s out/target/product/generic/obj/CONFIG/kati_packaging/dist.mk.tmp out/target/product/generic/obj/CONFIG/kati_packaging/dist.mk; then mv out/target/product/generic/obj/CONFIG/kati_packaging/dist.mk.tmp out/target/product/generic/obj/CONFIG/kati_packaging/dist.mk; else rm out/target/product/generic/obj/CONFIG/kati_packaging/dist.mk.tmp; fi
+verbose: *kati*:   0.008 / 1 mkdir -p out/target/product/generic && echo R/AOSP.MASTER/$(cat out/build_number.txt):eng/test-keys >out/target/product/generic/build_thumbprint.txt && grep " " out/target/product/generic/build_thumbprint.txt
+verbose: *kati*:   0.007 / 1 echo 'com.android.customization.model.clock.BaseClockManagerTest com.android.customization.model.clock.ClockManagerTest com.android.customization.model.grid.GridOptionsManagerTest com.android.customization.model.theme.ThemeManagerTest' | tr ' ' '\n' | cat
+verbose: *kati*:   0.007 / 1 uname -sm
+verbose: *kati*: stat time (regen): 0.361907 / 1241
 ```
 
-In this case, the total time spent checking was 1.69 seconds, even though the
+In this case, the total time spent checking was 0.75 seconds, even though the
 other "(regen)" numbers add up to more than that (some parts are parallelized
-where possible). The biggest contributor is the `$(shell)` times -- 184
-executions took a total of 1.31 seconds. The top 10 longest shell functions are
+where possible). Often times, the biggest contributor is the `$(shell)` times
+-- in this case, 66 calls took 0.27s. The top 10 longest shell functions are
 printed.
 
 All the longest commands in this case are all variants of a call to `find`, but
@@ -96,7 +96,8 @@
 $(sort $(shell find device vendor -type -f -a -name \*.pk8 -o -name verifiedboot\* -o -name \*.x509.pem -o -name oem\*.prop))
 ```
 
-Kati is learning about the implicit `-a` in [this change](https://github.com/google/kati/pull/132)
+Kati has now learned about the implicit `-a`, so this particular change is no
+longer necessary, but the basic concept holds.
 
 #### Kati regens too often
 
@@ -113,6 +114,46 @@
 is available when ckati is run with `--regen_debug`, but that can be a lot of
 data to understand.
 
+#### Debugging the slow path
+
+Kati will now dump out information about which Makefiles took the most time to
+execute. This is also in the `verbose.log.gz` file:
+
+```
+verbose: *kati*: included makefiles: 73.640833 / 232810 (1066 unique)
+verbose: *kati*:  18.389 /     1 out/soong/Android-aosp_arm.mk
+verbose: *kati*:  13.137 / 20144 build/make/core/soong_cc_prebuilt.mk
+verbose: *kati*:  11.743 / 27666 build/make/core/base_rules.mk
+verbose: *kati*:   2.289 /     1 art/Android.mk
+verbose: *kati*:   2.054 /     1 art/build/Android.cpplint.mk
+verbose: *kati*:   1.955 / 28269 build/make/core/clear_vars.mk
+verbose: *kati*:   1.795 /   283 build/make/core/package.mk
+verbose: *kati*:   1.790 /   283 build/make/core/package_internal.mk
+verbose: *kati*:   1.757 / 17382 build/make/core/link_type.mk
+verbose: *kati*:   1.078 /   297 build/make/core/aapt2.mk
+```
+
+This shows that soong_cc_prebuilt.mk was included 20144 times, for a total time
+spent of 13.137 secounds. While Android-aosp_arm.mk was only included once, and
+took 18.389 seconds. In this case, Android-aosp_arm.mk is the only file that
+includes soong_cc_prebuilt.mk, so we can safely assume that 13 of the 18 seconds
+in Android-aosp_arm.mk was actually spent within soong_cc_prebuilt.mk (or
+something that it included, like base_rules.mk).
+
+By default this only includes the top 10 entries, but you can ask for the stats
+for any makefile to be printed with `$(KATI_profile_makefile)`:
+
+```
+$(KATI_profile_makefile build/make/core/product.mk)
+```
+
+With these primitives, it's possible to get the timing information for small
+chunks, or even single lines, of a makefile. Just move the lines you want to
+measure into a new makefile, and replace their use with an `include` of the
+new makefile. It's possible to analyze where the time is being spent by doing
+a binary search using this method, but you do need to be careful not to split
+conditionals across two files (the ifeq/else/endif must all be in the same file).
+
 ### Ninja
 
 #### Understanding why something rebuilt
@@ -164,15 +205,14 @@
 
 ### Common
 
-#### mm
+### <= Android 10 (Q): mm
 
 Soong always loads the entire module graph, so as modules convert from Make to
 Soong, `mm` is becoming closer to `mma`. This produces more correct builds, but
 does slow down builds, as we need to verify/produce/load a larger build graph.
 
-We're exploring a few options to speed up build startup, one being [an
-experimental set of ninja patches][ninja parse optimization],
-though that's not the current path we're working towards.
+As of Android Q, loading large build graphs is fast, and in Android R, `mm` is
+now an alias of `mma`.
 
 ### Android 8.1 (Oreo MR1)
 
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 57ca9bc..a0008d3 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -30,10 +30,18 @@
 )
 
 func init() {
-	android.RegisterModuleType("genrule_defaults", defaultsFactory)
+	registerGenruleBuildComponents(android.InitRegistrationContext)
+}
 
-	android.RegisterModuleType("gensrcs", GenSrcsFactory)
-	android.RegisterModuleType("genrule", GenRuleFactory)
+func registerGenruleBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("genrule_defaults", defaultsFactory)
+
+	ctx.RegisterModuleType("gensrcs", GenSrcsFactory)
+	ctx.RegisterModuleType("genrule", GenRuleFactory)
+
+	ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) {
+		ctx.BottomUp("genrule_tool_deps", toolDepsMutator).Parallel()
+	})
 }
 
 var (
@@ -166,7 +174,7 @@
 	return g.outputDeps
 }
 
-func (g *Module) DepsMutator(ctx android.BottomUpMutatorContext) {
+func toolDepsMutator(ctx android.BottomUpMutatorContext) {
 	if g, ok := ctx.Module().(*Module); ok {
 		for _, tool := range g.properties.Tools {
 			tag := hostToolDependencyTag{label: tool}
@@ -542,6 +550,7 @@
 
 func (x noopImageInterface) ImageMutatorBegin(android.BaseModuleContext)                 {}
 func (x noopImageInterface) CoreVariantNeeded(android.BaseModuleContext) bool            { return false }
+func (x noopImageInterface) RamdiskVariantNeeded(android.BaseModuleContext) bool         { return false }
 func (x noopImageInterface) RecoveryVariantNeeded(android.BaseModuleContext) bool        { return false }
 func (x noopImageInterface) ExtraImageVariations(ctx android.BaseModuleContext) []string { return nil }
 func (x noopImageInterface) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
diff --git a/genrule/genrule_test.go b/genrule/genrule_test.go
index ea49e08..7eb43ac 100644
--- a/genrule/genrule_test.go
+++ b/genrule/genrule_test.go
@@ -55,10 +55,10 @@
 
 	ctx := android.NewTestArchContext()
 	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
-	ctx.RegisterModuleType("genrule", GenRuleFactory)
-	ctx.RegisterModuleType("gensrcs", GenSrcsFactory)
-	ctx.RegisterModuleType("genrule_defaults", defaultsFactory)
 	ctx.RegisterModuleType("tool", toolFactory)
+
+	registerGenruleBuildComponents(ctx)
+
 	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
 	ctx.Register(config)
 
diff --git a/java/aar.go b/java/aar.go
index ae064e5..1ba65dc 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -15,11 +15,12 @@
 package java
 
 import (
-	"android/soong/android"
 	"fmt"
 	"path/filepath"
 	"strings"
 
+	"android/soong/android"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
diff --git a/java/androidmk.go b/java/androidmk.go
index 04bf15c..7c19180 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -695,3 +695,16 @@
 	}
 	entries.AddStrings("LOCAL_COMPATIBILITY_SUPPORT_FILES", testFiles...)
 }
+
+func (r *RuntimeResourceOverlay) AndroidMkEntries() []android.AndroidMkEntries {
+	return []android.AndroidMkEntries{android.AndroidMkEntries{
+		Class:      "ETC",
+		OutputFile: android.OptionalPathForPath(r.outputFile),
+		Include:    "$(BUILD_SYSTEM)/soong_app_prebuilt.mk",
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(entries *android.AndroidMkEntries) {
+				entries.SetPath("LOCAL_MODULE_PATH", r.installDir.ToMakePath())
+			},
+		},
+	}}
+}
diff --git a/java/app.go b/java/app.go
index e9941f2..4f06087 100755
--- a/java/app.go
+++ b/java/app.go
@@ -27,6 +27,7 @@
 
 	"android/soong/android"
 	"android/soong/cc"
+	"android/soong/dexpreopt"
 	"android/soong/tradefed"
 )
 
@@ -47,6 +48,7 @@
 	ctx.RegisterModuleType("override_android_test", OverrideAndroidTestModuleFactory)
 	ctx.RegisterModuleType("android_app_import", AndroidAppImportFactory)
 	ctx.RegisterModuleType("android_test_import", AndroidTestImportFactory)
+	ctx.RegisterModuleType("runtime_resource_overlay", RuntimeResourceOverlayFactory)
 }
 
 // AndroidManifest.xml merging
@@ -849,6 +851,7 @@
 	android.ModuleBase
 	android.DefaultableModuleBase
 	prebuilt android.Prebuilt
+	dexpreopt.DexPreoptModule
 
 	properties   AndroidAppImportProperties
 	dpiVariants  interface{}
@@ -1212,6 +1215,96 @@
 	return module
 }
 
+type RuntimeResourceOverlay struct {
+	android.ModuleBase
+	android.DefaultableModuleBase
+	aapt
+
+	properties RuntimeResourceOverlayProperties
+
+	outputFile android.Path
+	installDir android.InstallPath
+}
+
+type RuntimeResourceOverlayProperties struct {
+	// the name of a certificate in the default certificate directory or an android_app_certificate
+	// module name in the form ":module".
+	Certificate *string
+
+	// optional theme name. If specified, the overlay package will be applied
+	// only when the ro.boot.vendor.overlay.theme system property is set to the same value.
+	Theme *string
+
+	// if not blank, set to the version of the sdk to compile against.
+	// Defaults to compiling against the current platform.
+	Sdk_version *string
+
+	// if not blank, set the minimum version of the sdk that the compiled artifacts will run against.
+	// Defaults to sdk_version if not set.
+	Min_sdk_version *string
+}
+
+func (r *RuntimeResourceOverlay) DepsMutator(ctx android.BottomUpMutatorContext) {
+	sdkDep := decodeSdkDep(ctx, sdkContext(r))
+	if sdkDep.hasFrameworkLibs() {
+		r.aapt.deps(ctx, sdkDep)
+	}
+
+	cert := android.SrcIsModule(String(r.properties.Certificate))
+	if cert != "" {
+		ctx.AddDependency(ctx.Module(), certificateTag, cert)
+	}
+}
+
+func (r *RuntimeResourceOverlay) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// Compile and link resources
+	r.aapt.hasNoCode = true
+	// Do not remove resources without default values nor dedupe resource configurations with the same value
+	r.aapt.buildActions(ctx, r, "--no-resource-deduping", "--no-resource-removal")
+
+	// Sign the built package
+	_, certificates := collectAppDeps(ctx, false)
+	certificates = processMainCert(r.ModuleBase, String(r.properties.Certificate), certificates, ctx)
+	signed := android.PathForModuleOut(ctx, "signed", r.Name()+".apk")
+	SignAppPackage(ctx, signed, r.aapt.exportPackage, certificates)
+
+	r.outputFile = signed
+	r.installDir = android.PathForModuleInstall(ctx, "overlay", String(r.properties.Theme))
+	ctx.InstallFile(r.installDir, r.outputFile.Base(), r.outputFile)
+}
+
+func (r *RuntimeResourceOverlay) sdkVersion() string {
+	return String(r.properties.Sdk_version)
+}
+
+func (r *RuntimeResourceOverlay) systemModules() string {
+	return ""
+}
+
+func (r *RuntimeResourceOverlay) minSdkVersion() string {
+	if r.properties.Min_sdk_version != nil {
+		return *r.properties.Min_sdk_version
+	}
+	return r.sdkVersion()
+}
+
+func (r *RuntimeResourceOverlay) targetSdkVersion() string {
+	return r.sdkVersion()
+}
+
+// runtime_resource_overlay generates a resource-only apk file that can overlay application and
+// system resources at run time.
+func RuntimeResourceOverlayFactory() android.Module {
+	module := &RuntimeResourceOverlay{}
+	module.AddProperties(
+		&module.properties,
+		&module.aaptProperties)
+
+	InitJavaModule(module, android.DeviceSupported)
+
+	return module
+}
+
 type UsesLibraryProperties struct {
 	// A list of shared library modules that will be listed in uses-library tags in the AndroidManifest.xml file.
 	Uses_libs []string
diff --git a/java/app_test.go b/java/app_test.go
index 6f89da4..2682682 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -2207,3 +2207,54 @@
 		}
 	}
 }
+
+func TestRuntimeResourceOverlay(t *testing.T) {
+	ctx, config := testJava(t, `
+		runtime_resource_overlay {
+			name: "foo",
+			certificate: "platform",
+			product_specific: true,
+			aaptflags: ["--keep-raw-values"],
+		}
+
+		runtime_resource_overlay {
+			name: "foo_themed",
+			certificate: "platform",
+			product_specific: true,
+			theme: "faza",
+		}
+		`)
+
+	m := ctx.ModuleForTests("foo", "android_common")
+
+	// Check AAPT2 link flags.
+	aapt2Flags := m.Output("package-res.apk").Args["flags"]
+	expectedFlags := []string{"--keep-raw-values", "--no-resource-deduping", "--no-resource-removal"}
+	absentFlags := android.RemoveListFromList(expectedFlags, strings.Split(aapt2Flags, " "))
+	if len(absentFlags) > 0 {
+		t.Errorf("expected values, %q are missing in aapt2 link flags, %q", absentFlags, aapt2Flags)
+	}
+
+	// Check cert signing flag.
+	signedApk := m.Output("signed/foo.apk")
+	signingFlag := signedApk.Args["certificates"]
+	expected := "build/make/target/product/security/platform.x509.pem build/make/target/product/security/platform.pk8"
+	if expected != signingFlag {
+		t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag)
+	}
+
+	// Check device location.
+	path := android.AndroidMkEntriesForTest(t, config, "", m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
+	expectedPath := []string{"/tmp/target/product/test_device/product/overlay"}
+	if !reflect.DeepEqual(path, expectedPath) {
+		t.Errorf("Unexpected LOCAL_MODULE_PATH value: %v, expected: %v", path, expectedPath)
+	}
+
+	// A themed module has a different device location
+	m = ctx.ModuleForTests("foo_themed", "android_common")
+	path = android.AndroidMkEntriesForTest(t, config, "", m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
+	expectedPath = []string{"/tmp/target/product/test_device/product/overlay/faza"}
+	if !reflect.DeepEqual(path, expectedPath) {
+		t.Errorf("Unexpected LOCAL_MODULE_PATH value: %v, expected: %v", path, expectedPath)
+	}
+}
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index da68660..0fd1f28 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -59,7 +59,7 @@
 }
 
 func (d *dexpreopter) dexpreoptDisabled(ctx android.ModuleContext) bool {
-	global := dexpreoptGlobalConfig(ctx)
+	global := dexpreopt.GetGlobalConfig(ctx)
 
 	if global.DisablePreopt {
 		return true
@@ -96,7 +96,7 @@
 }
 
 func odexOnSystemOther(ctx android.ModuleContext, installPath android.InstallPath) bool {
-	return dexpreopt.OdexOnSystemOtherByName(ctx.ModuleName(), android.InstallPathToOnDevicePath(ctx, installPath), dexpreoptGlobalConfig(ctx))
+	return dexpreopt.OdexOnSystemOtherByName(ctx.ModuleName(), android.InstallPathToOnDevicePath(ctx, installPath), dexpreopt.GetGlobalConfig(ctx))
 }
 
 func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.ModuleOutPath) android.ModuleOutPath {
@@ -104,7 +104,8 @@
 		return dexJarFile
 	}
 
-	global := dexpreoptGlobalConfig(ctx)
+	globalSoong := dexpreopt.GetGlobalSoongConfig(ctx)
+	global := dexpreopt.GetGlobalConfig(ctx)
 	bootImage := defaultBootImageConfig(ctx)
 	if global.UseApexImage {
 		bootImage = frameworkJZBootImageConfig(ctx)
@@ -189,7 +190,7 @@
 		PresignedPrebuilt: d.isPresignedPrebuilt,
 	}
 
-	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, global, dexpreoptConfig)
+	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, globalSoong, global, dexpreoptConfig)
 	if err != nil {
 		ctx.ModuleErrorf("error generating dexpreopt rule: %s", err.Error())
 		return dexJarFile
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 66840b5..0082d03 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -162,7 +162,7 @@
 }
 
 func skipDexpreoptBootJars(ctx android.PathContext) bool {
-	if dexpreoptGlobalConfig(ctx).DisablePreopt {
+	if dexpreopt.GetGlobalConfig(ctx).DisablePreopt {
 		return true
 	}
 
@@ -190,7 +190,18 @@
 	if skipDexpreoptBootJars(ctx) {
 		return nil
 	}
-	return artBootImageConfig(ctx).imagesDeps
+
+	// Include dexpreopt files for the primary boot image.
+	files := artBootImageConfig(ctx).imagesDeps
+
+	// For JIT-zygote config, also include dexpreopt files for the primary JIT-zygote image.
+	if dexpreopt.GetGlobalConfig(ctx).UseApexImage {
+		for arch, paths := range artJZBootImageConfig(ctx).imagesDeps {
+			files[arch] = append(files[arch], paths...)
+		}
+	}
+
+	return files
 }
 
 // dexpreoptBoot singleton rules
@@ -202,7 +213,7 @@
 	d.dexpreoptConfigForMake = android.PathForOutput(ctx, ctx.Config().DeviceName(), "dexpreopt.config")
 	writeGlobalConfigForMake(ctx, d.dexpreoptConfigForMake)
 
-	global := dexpreoptGlobalConfig(ctx)
+	global := dexpreopt.GetGlobalConfig(ctx)
 
 	// Skip recompiling the boot image for the second sanitization phase. We'll get separate paths
 	// and invalidate first-stage artifacts which are crucial to SANITIZE_LITE builds.
@@ -293,7 +304,8 @@
 func buildBootImageRuleForArch(ctx android.SingletonContext, image *bootImage,
 	arch android.ArchType, profile android.Path, missingDeps []string) android.WritablePaths {
 
-	global := dexpreoptGlobalConfig(ctx)
+	globalSoong := dexpreopt.GetCachedGlobalSoongConfig(ctx)
+	global := dexpreopt.GetGlobalConfig(ctx)
 
 	symbolsDir := image.symbolsDir.Join(ctx, image.installSubdir, arch.String())
 	symbolsFile := symbolsDir.Join(ctx, image.stem+".oat")
@@ -328,7 +340,7 @@
 
 	invocationPath := outputPath.ReplaceExtension(ctx, "invocation")
 
-	cmd.Tool(global.SoongConfig.Dex2oat).
+	cmd.Tool(globalSoong.Dex2oat).
 		Flag("--avoid-storing-invocation").
 		FlagWithOutput("--write-invocation-to=", invocationPath).ImplicitOutput(invocationPath).
 		Flag("--runtime-arg").FlagWithArg("-Xms", global.Dex2oatImageXms).
@@ -431,7 +443,8 @@
 Rebuild with ART_BOOT_IMAGE_EXTRA_ARGS="--runtime-arg -verbose:verifier" to see verification errors.`
 
 func bootImageProfileRule(ctx android.SingletonContext, image *bootImage, missingDeps []string) android.WritablePath {
-	global := dexpreoptGlobalConfig(ctx)
+	globalSoong := dexpreopt.GetCachedGlobalSoongConfig(ctx)
+	global := dexpreopt.GetGlobalConfig(ctx)
 
 	if global.DisableGenerateProfile || ctx.Config().IsPdkBuild() || ctx.Config().UnbundledBuild() {
 		return nil
@@ -462,7 +475,7 @@
 
 		rule.Command().
 			Text(`ANDROID_LOG_TAGS="*:e"`).
-			Tool(global.SoongConfig.Profman).
+			Tool(globalSoong.Profman).
 			FlagWithInput("--create-profile-from=", bootImageProfile).
 			FlagForEachInput("--apk=", image.dexPathsDeps.Paths()).
 			FlagForEachArg("--dex-location=", image.dexLocationsDeps).
@@ -485,7 +498,8 @@
 var bootImageProfileRuleKey = android.NewOnceKey("bootImageProfileRule")
 
 func bootFrameworkProfileRule(ctx android.SingletonContext, image *bootImage, missingDeps []string) android.WritablePath {
-	global := dexpreoptGlobalConfig(ctx)
+	globalSoong := dexpreopt.GetCachedGlobalSoongConfig(ctx)
+	global := dexpreopt.GetGlobalConfig(ctx)
 
 	if global.DisableGenerateProfile || ctx.Config().IsPdkBuild() || ctx.Config().UnbundledBuild() {
 		return nil
@@ -511,7 +525,7 @@
 
 		rule.Command().
 			Text(`ANDROID_LOG_TAGS="*:e"`).
-			Tool(global.SoongConfig.Profman).
+			Tool(globalSoong.Profman).
 			Flag("--generate-boot-profile").
 			FlagWithInput("--create-profile-from=", bootFrameworkProfile).
 			FlagForEachInput("--apk=", image.dexPathsDeps.Paths()).
@@ -573,7 +587,7 @@
 }
 
 func writeGlobalConfigForMake(ctx android.SingletonContext, path android.WritablePath) {
-	data := dexpreoptGlobalConfigRaw(ctx).data
+	data := dexpreopt.GetGlobalConfigRawData(ctx)
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   android.WriteFile,
diff --git a/java/dexpreopt_bootjars_test.go b/java/dexpreopt_bootjars_test.go
index 4ce30f6..c3b2133 100644
--- a/java/dexpreopt_bootjars_test.go
+++ b/java/dexpreopt_bootjars_test.go
@@ -49,7 +49,7 @@
 	pathCtx := android.PathContextForTesting(config)
 	dexpreoptConfig := dexpreopt.GlobalConfigForTests(pathCtx)
 	dexpreoptConfig.BootJars = []string{"foo", "bar", "baz"}
-	setDexpreoptTestGlobalConfig(config, dexpreoptConfig)
+	dexpreopt.SetTestGlobalConfig(config, dexpreoptConfig)
 
 	ctx := testContext()
 
diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go
index 31bec93..a1a9a76 100644
--- a/java/dexpreopt_config.go
+++ b/java/dexpreopt_config.go
@@ -22,57 +22,12 @@
 	"android/soong/dexpreopt"
 )
 
-// dexpreoptGlobalConfig returns the global dexpreopt.config.  It is loaded once the first time it is called for any
-// ctx.Config(), and returns the same data for all future calls with the same ctx.Config().  A value can be inserted
-// for tests using setDexpreoptTestGlobalConfig.
-func dexpreoptGlobalConfig(ctx android.PathContext) dexpreopt.GlobalConfig {
-	return dexpreoptGlobalConfigRaw(ctx).global
-}
-
-type globalConfigAndRaw struct {
-	global dexpreopt.GlobalConfig
-	data   []byte
-}
-
-func dexpreoptGlobalConfigRaw(ctx android.PathContext) globalConfigAndRaw {
-	return ctx.Config().Once(dexpreoptGlobalConfigKey, func() interface{} {
-		if data, err := ctx.Config().DexpreoptGlobalConfig(ctx); err != nil {
-			panic(err)
-		} else if data != nil {
-			soongConfig := dexpreopt.CreateGlobalSoongConfig(ctx)
-			globalConfig, err := dexpreopt.LoadGlobalConfig(ctx, data, soongConfig)
-			if err != nil {
-				panic(err)
-			}
-			return globalConfigAndRaw{globalConfig, data}
-		}
-
-		// No global config filename set, see if there is a test config set
-		return ctx.Config().Once(dexpreoptTestGlobalConfigKey, func() interface{} {
-			// Nope, return a config with preopting disabled
-			return globalConfigAndRaw{dexpreopt.GlobalConfig{
-				DisablePreopt:          true,
-				DisableGenerateProfile: true,
-			}, nil}
-		})
-	}).(globalConfigAndRaw)
-}
-
-// setDexpreoptTestGlobalConfig sets a GlobalConfig that future calls to dexpreoptGlobalConfig will return.  It must
-// be called before the first call to dexpreoptGlobalConfig for the config.
-func setDexpreoptTestGlobalConfig(config android.Config, globalConfig dexpreopt.GlobalConfig) {
-	config.Once(dexpreoptTestGlobalConfigKey, func() interface{} { return globalConfigAndRaw{globalConfig, nil} })
-}
-
-var dexpreoptGlobalConfigKey = android.NewOnceKey("DexpreoptGlobalConfig")
-var dexpreoptTestGlobalConfigKey = android.NewOnceKey("TestDexpreoptGlobalConfig")
-
 // systemServerClasspath returns the on-device locations of the modules in the system server classpath.  It is computed
 // once the first time it is called for any ctx.Config(), and returns the same slice for all future calls with the same
 // ctx.Config().
 func systemServerClasspath(ctx android.PathContext) []string {
 	return ctx.Config().OnceStringSlice(systemServerClasspathKey, func() []string {
-		global := dexpreoptGlobalConfig(ctx)
+		global := dexpreopt.GetGlobalConfig(ctx)
 
 		var systemServerClasspathLocations []string
 		for _, m := range global.SystemServerJars {
@@ -133,7 +88,7 @@
 func genBootImageConfigs(ctx android.PathContext) map[string]*bootImageConfig {
 	return ctx.Config().Once(bootImageConfigKey, func() interface{} {
 
-		global := dexpreoptGlobalConfig(ctx)
+		global := dexpreopt.GetGlobalConfig(ctx)
 		targets := dexpreoptTargets(ctx)
 		deviceDir := android.PathForOutput(ctx, ctx.Config().DeviceName())
 
@@ -274,7 +229,7 @@
 
 func defaultBootclasspath(ctx android.PathContext) []string {
 	return ctx.Config().OnceStringSlice(defaultBootclasspathKey, func() []string {
-		global := dexpreoptGlobalConfig(ctx)
+		global := dexpreopt.GetGlobalConfig(ctx)
 		image := defaultBootImageConfig(ctx)
 
 		updatableBootclasspath := make([]string, len(global.UpdatableBootJars))
diff --git a/java/droiddoc.go b/java/droiddoc.go
index f62f5f9..a10ec81 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -1456,6 +1456,8 @@
 
 func metalavaCmd(ctx android.ModuleContext, rule *android.RuleBuilder, javaVersion javaVersion, srcs android.Paths,
 	srcJarList android.Path, bootclasspath, classpath classpath, sourcepaths android.Paths) *android.RuleBuilderCommand {
+	// Metalava uses lots of memory, restrict the number of metalava jobs that can run in parallel.
+	rule.HighMem()
 	cmd := rule.Command().BuiltTool(ctx, "metalava").
 		Flag(config.JavacVmFlags).
 		FlagWithArg("-encoding ", "UTF-8").
diff --git a/java/java.go b/java/java.go
index 4c6a5a5..320cb7b 100644
--- a/java/java.go
+++ b/java/java.go
@@ -29,6 +29,7 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/dexpreopt"
 	"android/soong/java/config"
 	"android/soong/tradefed"
 )
@@ -79,6 +80,8 @@
 	ctx.RegisterModuleType("java_host_for_device", HostForDeviceFactory)
 	ctx.RegisterModuleType("dex_import", DexImportFactory)
 
+	ctx.FinalDepsMutators(dexpreopt.RegisterToolDepsMutator)
+
 	ctx.RegisterSingletonType("logtags", LogtagsSingleton)
 	ctx.RegisterSingletonType("kythe_java_extract", kytheExtractJavaFactory)
 }
@@ -335,6 +338,7 @@
 	android.DefaultableModuleBase
 	android.ApexModuleBase
 	android.SdkBase
+	dexpreopt.DexPreoptModule
 
 	properties       CompilerProperties
 	protoProperties  android.ProtoProperties
@@ -1525,6 +1529,16 @@
 		}
 	} else {
 		outputFile = implementationAndResourcesJar
+
+		// dexpreopt.GetGlobalSoongConfig needs to be called at least once even if
+		// no module actually is dexpreopted, to ensure there's a cached
+		// GlobalSoongConfig for the dexpreopt singletons, which will run
+		// regardless.
+		// TODO(b/147613152): Remove when the singletons no longer rely on the
+		// cached GlobalSoongConfig.
+		if !dexpreopt.GetGlobalConfig(ctx).DisablePreopt {
+			_ = dexpreopt.GetGlobalSoongConfig(ctx)
+		}
 	}
 
 	ctx.CheckbuildFile(outputFile)
@@ -2275,6 +2289,7 @@
 	android.ApexModuleBase
 	prebuilt android.Prebuilt
 	android.SdkBase
+	dexpreopt.DexPreoptModule
 
 	properties ImportProperties
 
@@ -2477,6 +2492,7 @@
 	android.DefaultableModuleBase
 	android.ApexModuleBase
 	prebuilt android.Prebuilt
+	dexpreopt.DexPreoptModule
 
 	properties DexImportProperties
 
diff --git a/java/java_test.go b/java/java_test.go
index a2788cb..b724b4d 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -57,7 +57,15 @@
 }
 
 func testConfig(env map[string]string, bp string, fs map[string][]byte) android.Config {
-	return TestConfig(buildDir, env, bp, fs)
+	bp += dexpreopt.BpToolModulesForTest()
+
+	config := TestConfig(buildDir, env, bp, fs)
+
+	// Set up the global Once cache used for dexpreopt.GlobalSoongConfig, so that
+	// it doesn't create a real one, which would fail.
+	_ = dexpreopt.GlobalSoongConfigForTests(config)
+
+	return config
 }
 
 func testContext() *android.TestContext {
@@ -86,6 +94,8 @@
 	cc.RegisterRequiredBuildComponentsForTest(ctx)
 	ctx.RegisterModuleType("ndk_prebuilt_shared_stl", cc.NdkPrebuiltSharedStlFactory)
 
+	dexpreopt.RegisterToolModulesForTest(ctx)
+
 	return ctx
 }
 
@@ -93,7 +103,7 @@
 	t.Helper()
 
 	pathCtx := android.PathContextForTesting(config)
-	setDexpreoptTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx))
+	dexpreopt.SetTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx))
 
 	ctx.Register(config)
 	_, errs := ctx.ParseBlueprintsFiles("Android.bp")
@@ -112,7 +122,7 @@
 	ctx := testContext()
 
 	pathCtx := android.PathContextForTesting(config)
-	setDexpreoptTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx))
+	dexpreopt.SetTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx))
 
 	ctx.Register(config)
 	_, errs := ctx.ParseBlueprintsFiles("Android.bp")
diff --git a/java/sdk.go b/java/sdk.go
index 73b7dcf..2dbcf4a 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -15,14 +15,15 @@
 package java
 
 import (
-	"android/soong/android"
-	"android/soong/java/config"
 	"fmt"
 	"path/filepath"
 	"sort"
 	"strconv"
 	"strings"
 
+	"android/soong/android"
+	"android/soong/java/config"
+
 	"github.com/google/blueprint/pathtools"
 )
 
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 9b30e2c..72c4a7c 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -838,16 +838,8 @@
 
 	Sdk_version *string
 
-	Installable *bool
-
 	// List of shared java libs that this module has dependencies to
 	Libs []string
-
-	// List of files to remove from the jar file(s)
-	Exclude_files []string
-
-	// List of directories to remove from the jar file(s)
-	Exclude_dirs []string
 }
 
 type sdkLibraryImport struct {
diff --git a/python/python.go b/python/python.go
index c67c577..8b912be 100644
--- a/python/python.go
+++ b/python/python.go
@@ -340,6 +340,11 @@
 			// dependencies later.
 			ctx.AddFarVariationDependencies(ctx.Target().Variations(), launcherSharedLibTag, "libsqlite")
 
+			if ctx.Device() {
+				ctx.AddFarVariationDependencies(ctx.Target().Variations(), launcherSharedLibTag,
+					"liblog")
+			}
+
 			if ctx.Target().Os.Bionic() {
 				ctx.AddFarVariationDependencies(ctx.Target().Variations(), launcherSharedLibTag,
 					"libc", "libdl", "libm")
diff --git a/rust/rust.go b/rust/rust.go
index 14513fb..e2af6f0 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -85,6 +85,10 @@
 	return true
 }
 
+func (mod *Module) RamdiskVariantNeeded(android.BaseModuleContext) bool {
+	return mod.InRamdisk()
+}
+
 func (mod *Module) RecoveryVariantNeeded(android.BaseModuleContext) bool {
 	return mod.InRecovery()
 }
@@ -152,6 +156,10 @@
 	panic(fmt.Errorf("Toc() called on non-library module: %q", mod.BaseModuleName()))
 }
 
+func (mod *Module) OnlyInRamdisk() bool {
+	return false
+}
+
 func (mod *Module) OnlyInRecovery() bool {
 	return false
 }
diff --git a/sdk/bp.go b/sdk/bp.go
index 19fb70d..ae06a09 100644
--- a/sdk/bp.go
+++ b/sdk/bp.go
@@ -51,13 +51,23 @@
 	return s.properties[name]
 }
 
-func (s *bpPropertySet) copy() bpPropertySet {
+func (s *bpPropertySet) deepCopy() *bpPropertySet {
 	propertiesCopy := make(map[string]interface{})
 	for p, v := range s.properties {
-		propertiesCopy[p] = v
+		var valueCopy interface{}
+		if ps, ok := v.(*bpPropertySet); ok {
+			valueCopy = ps.deepCopy()
+		} else if values, ok := v.([]string); ok {
+			valuesCopy := make([]string, len(values))
+			copy(valuesCopy, values)
+			valueCopy = valuesCopy
+		} else {
+			valueCopy = v
+		}
+		propertiesCopy[p] = valueCopy
 	}
 
-	return bpPropertySet{
+	return &bpPropertySet{
 		properties: propertiesCopy,
 		order:      append([]string(nil), s.order...),
 	}
@@ -95,15 +105,15 @@
 }
 
 type bpModule struct {
-	bpPropertySet
+	*bpPropertySet
 	moduleType string
 }
 
 var _ android.BpModule = (*bpModule)(nil)
 
-func (m *bpModule) copy() *bpModule {
+func (m *bpModule) deepCopy() *bpModule {
 	return &bpModule{
-		bpPropertySet: m.bpPropertySet.copy(),
+		bpPropertySet: m.bpPropertySet.deepCopy(),
 		moduleType:    m.moduleType,
 	}
 }
@@ -134,8 +144,9 @@
 
 func (f *bpFile) newModule(moduleType string) *bpModule {
 	module := &bpModule{
-		moduleType: moduleType,
+		moduleType:    moduleType,
+		bpPropertySet: &bpPropertySet{},
 	}
-	(&module.bpPropertySet).init()
+	module.bpPropertySet.init()
 	return module
 }
diff --git a/sdk/update.go b/sdk/update.go
index 5bc3b83..2731d50 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -196,7 +196,7 @@
 
 	for _, unversioned := range builder.prebuiltOrder {
 		// Copy the unversioned module so it can be modified to make it versioned.
-		versioned := unversioned.copy()
+		versioned := unversioned.deepCopy()
 		name := versioned.properties["name"].(string)
 		versioned.setProperty("name", builder.versionedSdkMemberName(name))
 		versioned.insertAfter("name", "sdk_member_name", name)
@@ -286,7 +286,7 @@
 	for _, bpModule := range bpFile.order {
 		contents.Printfln("")
 		contents.Printfln("%s {", bpModule.moduleType)
-		outputPropertySet(contents, &bpModule.bpPropertySet)
+		outputPropertySet(contents, bpModule.bpPropertySet)
 		contents.Printfln("}")
 	}
 }
diff --git a/ui/build/build.go b/ui/build/build.go
index 69ef003..f3feac2 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -48,8 +48,11 @@
 
 var combinedBuildNinjaTemplate = template.Must(template.New("combined").Parse(`
 builddir = {{.OutDir}}
-pool local_pool
+{{if .UseRemoteBuild }}pool local_pool
  depth = {{.Parallel}}
+{{end -}}
+pool highmem_pool
+ depth = {{.HighmemParallel}}
 build _kati_always_build_: phony
 {{if .HasKatiSuffix}}subninja {{.KatiBuildNinjaFile}}
 subninja {{.KatiPackageNinjaFile}}
diff --git a/ui/build/config.go b/ui/build/config.go
index c084171..9b19ede 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -722,6 +722,33 @@
 	return c.parallel
 }
 
+func (c *configImpl) HighmemParallel() int {
+	if i, ok := c.environ.GetInt("NINJA_HIGHMEM_NUM_JOBS"); ok {
+		return i
+	}
+
+	const minMemPerHighmemProcess = 8 * 1024 * 1024 * 1024
+	parallel := c.Parallel()
+	if c.UseRemoteBuild() {
+		// Ninja doesn't support nested pools, and when remote builds are enabled the total ninja parallelism
+		// is set very high (i.e. 500).  Using a large value here would cause the total number of running jobs
+		// to be the sum of the sizes of the local and highmem pools, which will cause extra CPU contention.
+		// Return 1/16th of the size of the local pool, rounding up.
+		return (parallel + 15) / 16
+	} else if c.totalRAM == 0 {
+		// Couldn't detect the total RAM, don't restrict highmem processes.
+		return parallel
+	} else if c.totalRAM <= 32*1024*1024*1024 {
+		// Less than 32GB of ram, restrict to 2 highmem processes
+		return 2
+	} else if p := int(c.totalRAM / minMemPerHighmemProcess); p < parallel {
+		// If less than 8GB total RAM per process, reduce the number of highmem processes
+		return p
+	}
+	// No restriction on highmem processes
+	return parallel
+}
+
 func (c *configImpl) TotalRAM() uint64 {
 	return c.totalRAM
 }
@@ -782,10 +809,11 @@
 // gomacc) are run in parallel.  Note the parallelism of all other jobs is
 // still limited by Parallel()
 func (c *configImpl) RemoteParallel() int {
-	if v, ok := c.environ.Get("NINJA_REMOTE_NUM_JOBS"); ok {
-		if i, err := strconv.Atoi(v); err == nil {
-			return i
-		}
+	if !c.UseRemoteBuild() {
+		return 0
+	}
+	if i, ok := c.environ.GetInt("NINJA_REMOTE_NUM_JOBS"); ok {
+		return i
 	}
 	return 500
 }
diff --git a/ui/build/config_darwin.go b/ui/build/config_darwin.go
index 480d8d1..fe74e31 100644
--- a/ui/build/config_darwin.go
+++ b/ui/build/config_darwin.go
@@ -22,7 +22,7 @@
 func detectTotalRAM(ctx Context) uint64 {
 	s, err := syscall.Sysctl("hw.memsize")
 	if err != nil {
-		ctx.Printf("Failed to get system memory size: %s")
+		ctx.Printf("Failed to get system memory size: %v", err)
 		return 0
 	}
 
@@ -32,7 +32,7 @@
 	}
 
 	if len(s) != 8 {
-		ctx.Printf("Failed to get system memory size, returned %d bytes, 8", len(s))
+		ctx.Printf("Failed to get system memory size, returned %d bytes, expecting 8 bytes", len(s))
 		return 0
 	}
 
diff --git a/ui/build/config_linux.go b/ui/build/config_linux.go
index 9e1bdc7..162d372 100644
--- a/ui/build/config_linux.go
+++ b/ui/build/config_linux.go
@@ -20,9 +20,8 @@
 	var info syscall.Sysinfo_t
 	err := syscall.Sysinfo(&info)
 	if err != nil {
-		ctx.Printf("Failed to get system memory size: %s")
+		ctx.Printf("Failed to get system memory size: %v", err)
 		return 0
 	}
-	memBytes := uint64(info.Totalram) * uint64(info.Unit)
-	return memBytes
+	return uint64(info.Totalram) * uint64(info.Unit)
 }
diff --git a/ui/build/environment.go b/ui/build/environment.go
index d8ff7f2..9bca7c0 100644
--- a/ui/build/environment.go
+++ b/ui/build/environment.go
@@ -19,6 +19,7 @@
 	"fmt"
 	"io"
 	"os"
+	"strconv"
 	"strings"
 )
 
@@ -44,6 +45,17 @@
 	return "", false
 }
 
+// Get returns the int value associated with the key, and whether it exists
+// and is a valid int.
+func (e *Environment) GetInt(key string) (int, bool) {
+	if v, ok := e.Get(key); ok {
+		if i, err := strconv.Atoi(v); err == nil {
+			return i, true
+		}
+	}
+	return 0, false
+}
+
 // Set sets the value associated with the key, overwriting the current value
 // if it exists.
 func (e *Environment) Set(key, value string) {