Merge "If input zip file cannot opened, print its name."
diff --git a/Android.bp b/Android.bp
index ab03a36..9b55c8c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -36,6 +36,7 @@
         "blueprint",
         "blueprint-bootstrap",
         "soong",
+        "soong-android-soongconfig",
         "soong-env",
         "soong-shared",
     ],
@@ -73,6 +74,7 @@
         "android/sdk.go",
         "android/sh_binary.go",
         "android/singleton.go",
+        "android/soong_config_modules.go",
         "android/testing.go",
         "android/util.go",
         "android/variable.go",
@@ -101,6 +103,7 @@
         "android/prebuilt_test.go",
         "android/prebuilt_etc_test.go",
         "android/rule_builder_test.go",
+        "android/soong_config_modules_test.go",
         "android/util_test.go",
         "android/variable_test.go",
         "android/visibility_test.go",
@@ -109,6 +112,20 @@
 }
 
 bootstrap_go_package {
+    name: "soong-android-soongconfig",
+    pkgPath: "android/soong/android/soongconfig",
+    deps: [
+        "blueprint",
+        "blueprint-parser",
+        "blueprint-proptools",
+    ],
+    srcs: [
+        "android/soongconfig/config.go",
+        "android/soongconfig/modules.go",
+    ],
+}
+
+bootstrap_go_package {
     name: "soong-cc-config",
     pkgPath: "android/soong/cc/config",
     deps: [
@@ -473,6 +490,7 @@
     ],
     testSrcs: [
         "apex/apex_test.go",
+        "apex/vndk_test.go",
     ],
     pluginFor: ["soong_build"],
 }
@@ -548,6 +566,7 @@
     name: "libatomic",
     defaults: ["linux_bionic_supported"],
     vendor_available: true,
+    ramdisk_available: true,
     recovery_available: true,
     native_bridge_supported: true,
 
@@ -594,6 +613,7 @@
     name: "libgcc_stripped",
     defaults: ["linux_bionic_supported"],
     vendor_available: true,
+    ramdisk_available: true,
     recovery_available: true,
     native_bridge_supported: true,
     sdk_version: "current",
diff --git a/README.md b/README.md
index b6fda50..b1bb425 100644
--- a/README.md
+++ b/README.md
@@ -376,36 +376,14 @@
 be resolved by hand to a single module with any differences inside
 `target: { android: { }, host: { } }` blocks.
 
-## Build logic
+### Conditionals
 
-The build logic is written in Go using the
-[blueprint](http://godoc.org/github.com/google/blueprint) framework.  Build
-logic receives module definitions parsed into Go structures using reflection
-and produces build rules.  The build rules are collected by blueprint and
-written to a [ninja](http://ninja-build.org) build file.
-
-## Other documentation
-
-* [Best Practices](docs/best_practices.md)
-* [Build Performance](docs/perf.md)
-* [Generating CLion Projects](docs/clion.md)
-* [Generating YouCompleteMe/VSCode compile\_commands.json file](docs/compdb.md)
-* Make-specific documentation: [build/make/README.md](https://android.googlesource.com/platform/build/+/master/README.md)
-
-## FAQ
-
-### How do I write conditionals?
-
-Soong deliberately does not support conditionals in Android.bp files.  We
+Soong deliberately does not support most conditionals in Android.bp files.  We
 suggest removing most conditionals from the build.  See
 [Best Practices](docs/best_practices.md#removing-conditionals) for some
 examples on how to remove conditionals.
 
-In cases where build time conditionals are unavoidable, complexity in build
-rules that would require conditionals are handled in Go through Soong plugins.
-This allows Go language features to be used for better readability and
-testability, and implicit dependencies introduced by conditionals can be
-tracked.  Most conditionals supported natively by Soong are converted to a map
+Most conditionals supported natively by Soong are converted to a map
 property.  When building the module one of the properties in the map will be
 selected, and its values appended to the property with the same name at the
 top level of the module.
@@ -430,6 +408,106 @@
 be built.  When building for x86 the `generic.cpp` and 'x86.cpp' sources will
 be built.
 
+#### Soong Config Variables
+
+When converting vendor modules that contain conditionals, simple conditionals
+can be supported through Soong config variables using `soong_config_*`
+modules that describe the module types, variables and possible values:
+
+```
+soong_config_module_type {
+    name: "acme_cc_defaults",
+    module_type: "cc_defaults",
+    config_namespace: "acme",
+    variables: ["board", "feature"],
+    properties: ["cflags", "srcs"],
+}
+
+soong_config_string_variable {
+    name: "board",
+    values: ["soc_a", "soc_b"],
+}
+
+soong_config_bool_variable {
+    name: "feature",
+}
+```
+
+This example describes a new `acme_cc_defaults` module type that extends the
+`cc_defaults` module type, with two additional conditionals based on variables
+`board` and `feature`, which can affect properties `cflags` and `srcs`.
+
+The values of the variables can be set from a product's `BoardConfig.mk` file:
+```
+SOONG_CONFIG_NAMESPACES += acme
+SOONG_CONFIG_acme += \
+    board \
+    feature \
+
+SOONG_CONFIG_acme_board := soc_a
+SOONG_CONFIG_acme_feature := true
+```
+
+The `acme_cc_defaults` module type can be used anywhere after the definition in
+the file where it is defined, or can be imported into another file with:
+```
+soong_config_module_type_import {
+    from: "device/acme/Android.bp",
+    module_types: ["acme_cc_defaults"],
+}
+```
+
+It can used like any other module type:
+```
+acme_cc_defaults {
+    name: "acme_defaults",
+    cflags: ["-DGENERIC"],
+    soong_config_variables: {
+        board: {
+            soc_a: {
+                cflags: ["-DSOC_A"],
+            },
+            soc_b: {
+                cflags: ["-DSOC_B"],
+            },
+        },
+        feature: {
+            cflags: ["-DFEATURE"],
+        },
+    },
+}
+
+cc_library {
+    name: "libacme_foo",
+    defaults: ["acme_defaults"],
+    srcs: ["*.cpp"],
+}
+```
+
+With the `BoardConfig.mk` snippet above, libacme_foo would build with
+cflags "-DGENERIC -DSOC_A -DFEATURE".
+
+`soong_config_module_type` modules will work best when used to wrap defaults
+modules (`cc_defaults`, `java_defaults`, etc.), which can then be referenced
+by all of the vendor's other modules using the normal namespace and visibility
+rules.
+
+## Build logic
+
+The build logic is written in Go using the
+[blueprint](http://godoc.org/github.com/google/blueprint) framework.  Build
+logic receives module definitions parsed into Go structures using reflection
+and produces build rules.  The build rules are collected by blueprint and
+written to a [ninja](http://ninja-build.org) build file.
+
+## Other documentation
+
+* [Best Practices](docs/best_practices.md)
+* [Build Performance](docs/perf.md)
+* [Generating CLion Projects](docs/clion.md)
+* [Generating YouCompleteMe/VSCode compile\_commands.json file](docs/compdb.md)
+* Make-specific documentation: [build/make/README.md](https://android.googlesource.com/platform/build/+/master/README.md)
+
 ## Developing for Soong
 
 To load Soong code in a Go-aware IDE, create a directory outside your android tree and then:
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..bab3477 100644
--- a/android/config.go
+++ b/android/config.go
@@ -29,11 +29,14 @@
 	"github.com/google/blueprint/bootstrap"
 	"github.com/google/blueprint/pathtools"
 	"github.com/google/blueprint/proptools"
+
+	"android/soong/android/soongconfig"
 )
 
 var Bool = proptools.Bool
 var String = proptools.String
-var FutureApiLevel = 10000
+
+const FutureApiLevel = 10000
 
 // The configuration file name
 const configFileName = "soong.config"
@@ -66,19 +69,7 @@
 	*deviceConfig
 }
 
-type VendorConfig interface {
-	// Bool interprets the variable named `name` as a boolean, returning true if, after
-	// lowercasing, it matches one of "1", "y", "yes", "on", or "true". Unset, or any other
-	// value will return false.
-	Bool(name string) bool
-
-	// String returns the string value of `name`. If the variable was not set, it will
-	// return the empty string.
-	String(name string) string
-
-	// IsSet returns whether the variable `name` was set by Make.
-	IsSet(name string) bool
-}
+type VendorConfig soongconfig.SoongConfig
 
 type config struct {
 	FileConfigurableOptions
@@ -128,8 +119,6 @@
 	OncePer
 }
 
-type vendorConfig map[string]string
-
 type jsonConfigurable interface {
 	SetDefaultConfig()
 }
@@ -827,6 +816,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 +1020,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 {
@@ -1133,21 +1130,7 @@
 }
 
 func (c *config) VendorConfig(name string) VendorConfig {
-	return vendorConfig(c.productVariables.VendorVars[name])
-}
-
-func (c vendorConfig) Bool(name string) bool {
-	v := strings.ToLower(c[name])
-	return v == "1" || v == "y" || v == "yes" || v == "on" || v == "true"
-}
-
-func (c vendorConfig) String(name string) string {
-	return c[name]
-}
-
-func (c vendorConfig) IsSet(name string) bool {
-	_, ok := c[name]
-	return ok
+	return soongconfig.Config(c.productVariables.VendorVars[name])
 }
 
 func (c *config) NdkAbis() bool {
@@ -1233,3 +1216,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/hooks.go b/android/hooks.go
index 04ba69e..e8cd81b 100644
--- a/android/hooks.go
+++ b/android/hooks.go
@@ -34,6 +34,9 @@
 	AppendProperties(...interface{})
 	PrependProperties(...interface{})
 	CreateModule(ModuleFactory, ...interface{}) Module
+
+	registerScopedModuleType(name string, factory blueprint.ModuleFactory)
+	moduleFactories() map[string]blueprint.ModuleFactory
 }
 
 func AddLoadHook(m blueprint.Module, hook func(LoadHookContext)) {
@@ -52,6 +55,10 @@
 	module Module
 }
 
+func (l *loadHookContext) moduleFactories() map[string]blueprint.ModuleFactory {
+	return l.bp.ModuleFactories()
+}
+
 func (l *loadHookContext) AppendProperties(props ...interface{}) {
 	for _, p := range props {
 		err := proptools.AppendMatchingProperties(l.Module().base().customizableProperties,
@@ -101,6 +108,10 @@
 	return module
 }
 
+func (l *loadHookContext) registerScopedModuleType(name string, factory blueprint.ModuleFactory) {
+	l.bp.RegisterScopedModuleType(name, factory)
+}
+
 type InstallHookContext interface {
 	ModuleContext
 	Path() InstallPath
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..96c2e1e 100644
--- a/android/module.go
+++ b/android/module.go
@@ -62,6 +62,7 @@
 	ModuleName() string
 	ModuleDir() string
 	ModuleType() string
+	BlueprintsFile() string
 
 	ContainsProperty(name string) bool
 	Errorf(pos scanner.Position, fmt string, args ...interface{})
@@ -167,6 +168,7 @@
 	InstallInData() bool
 	InstallInTestcases() bool
 	InstallInSanitizerDir() bool
+	InstallInRamdisk() bool
 	InstallInRecovery() bool
 	InstallInRoot() bool
 	InstallBypassMake() bool
@@ -207,6 +209,7 @@
 	InstallInData() bool
 	InstallInTestcases() bool
 	InstallInSanitizerDir() bool
+	InstallInRamdisk() bool
 	InstallInRecovery() bool
 	InstallInRoot() bool
 	InstallBypassMake() bool
@@ -384,6 +387,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"`
 
@@ -519,9 +525,13 @@
 	}
 }
 
+func initAndroidModuleBase(m Module) {
+	m.base().module = m
+}
+
 func InitAndroidModule(m Module) {
+	initAndroidModuleBase(m)
 	base := m.base()
-	base.module = m
 
 	m.AddProperties(
 		&base.nameProperties,
@@ -867,6 +877,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 +912,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 +1356,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 +1667,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/namespace.go b/android/namespace.go
index 64ad7e9..9d7e8ac 100644
--- a/android/namespace.go
+++ b/android/namespace.go
@@ -162,6 +162,12 @@
 	return namespace
 }
 
+// A NamelessModule can never be looked up by name.  It must still implement Name(), but the return
+// value doesn't have to be unique.
+type NamelessModule interface {
+	Nameless()
+}
+
 func (r *NameResolver) NewModule(ctx blueprint.NamespaceContext, moduleGroup blueprint.ModuleGroup, module blueprint.Module) (namespace blueprint.Namespace, errs []error) {
 	// if this module is a namespace, then save it to our list of namespaces
 	newNamespace, ok := module.(*NamespaceModule)
@@ -173,6 +179,10 @@
 		return nil, nil
 	}
 
+	if _, ok := module.(NamelessModule); ok {
+		return nil, nil
+	}
+
 	// if this module is not a namespace, then save it into the appropriate namespace
 	ns := r.findNamespaceFromCtx(ctx)
 
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/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/soong_config_modules.go b/android/soong_config_modules.go
new file mode 100644
index 0000000..bdec64b
--- /dev/null
+++ b/android/soong_config_modules.go
@@ -0,0 +1,369 @@
+// Copyright 2019 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+// This file provides module types that implement wrapper module types that add conditionals on
+// Soong config variables.
+
+import (
+	"fmt"
+	"path/filepath"
+	"strings"
+	"text/scanner"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/parser"
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android/soongconfig"
+)
+
+func init() {
+	RegisterModuleType("soong_config_module_type_import", soongConfigModuleTypeImportFactory)
+	RegisterModuleType("soong_config_module_type", soongConfigModuleTypeFactory)
+	RegisterModuleType("soong_config_string_variable", soongConfigStringVariableDummyFactory)
+	RegisterModuleType("soong_config_bool_variable", soongConfigBoolVariableDummyFactory)
+}
+
+type soongConfigModuleTypeImport struct {
+	ModuleBase
+	properties soongConfigModuleTypeImportProperties
+}
+
+type soongConfigModuleTypeImportProperties struct {
+	From         string
+	Module_types []string
+}
+
+// soong_config_module_type_import imports module types with conditionals on Soong config
+// variables from another Android.bp file.  The imported module type will exist for all
+// modules after the import in the Android.bp file.
+//
+// For example, an Android.bp file could have:
+//
+//     soong_config_module_type_import {
+//         from: "device/acme/Android.bp.bp",
+//         module_types: ["acme_cc_defaults"],
+//     }
+//
+//     acme_cc_defaults {
+//         name: "acme_defaults",
+//         cflags: ["-DGENERIC"],
+//         soong_config_variables: {
+//             board: {
+//                 soc_a: {
+//                     cflags: ["-DSOC_A"],
+//                 },
+//                 soc_b: {
+//                     cflags: ["-DSOC_B"],
+//                 },
+//             },
+//             feature: {
+//                 cflags: ["-DFEATURE"],
+//             },
+//         },
+//     }
+//
+//     cc_library {
+//         name: "libacme_foo",
+//         defaults: ["acme_defaults"],
+//         srcs: ["*.cpp"],
+//     }
+//
+// And device/acme/Android.bp could have:
+//
+//     soong_config_module_type {
+//         name: "acme_cc_defaults",
+//         module_type: "cc_defaults",
+//         config_namespace: "acme",
+//         variables: ["board", "feature"],
+//         properties: ["cflags", "srcs"],
+//     }
+//
+//     soong_config_string_variable {
+//         name: "board",
+//         values: ["soc_a", "soc_b"],
+//     }
+//
+//     soong_config_bool_variable {
+//         name: "feature",
+//     }
+//
+// If an acme BoardConfig.mk file contained:
+//
+//     SOONG_CONFIG_NAMESPACES += acme
+//     SOONG_CONFIG_acme += \
+//         board \
+//         feature \
+//
+//     SOONG_CONFIG_acme_board := soc_a
+//     SOONG_CONFIG_acme_feature := true
+//
+// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE".
+func soongConfigModuleTypeImportFactory() Module {
+	module := &soongConfigModuleTypeImport{}
+
+	module.AddProperties(&module.properties)
+	AddLoadHook(module, func(ctx LoadHookContext) {
+		importModuleTypes(ctx, module.properties.From, module.properties.Module_types...)
+	})
+
+	initAndroidModuleBase(module)
+	return module
+}
+
+func (m *soongConfigModuleTypeImport) Name() string {
+	return "soong_config_module_type_import_" + soongconfig.CanonicalizeToProperty(m.properties.From)
+}
+
+func (*soongConfigModuleTypeImport) Nameless()                                 {}
+func (*soongConfigModuleTypeImport) GenerateAndroidBuildActions(ModuleContext) {}
+
+// Create dummy modules for soong_config_module_type and soong_config_*_variable
+
+type soongConfigModuleTypeModule struct {
+	ModuleBase
+	properties soongconfig.ModuleTypeProperties
+}
+
+// soong_config_module_type defines module types with conditionals on Soong config
+// variables from another Android.bp file.  The new module type will exist for all
+// modules after the definition in an Android.bp file, and can be imported into other
+// Android.bp files using soong_config_module_type_import.
+//
+// For example, an Android.bp file could have:
+//
+//     soong_config_module_type {
+//         name: "acme_cc_defaults",
+//         module_type: "cc_defaults",
+//         config_namespace: "acme",
+//         variables: ["board", "feature"],
+//         properties: ["cflags", "srcs"],
+//     }
+//
+//     soong_config_string_variable {
+//         name: "board",
+//         values: ["soc_a", "soc_b"],
+//     }
+//
+//     soong_config_bool_variable {
+//         name: "feature",
+//     }
+//
+//     acme_cc_defaults {
+//         name: "acme_defaults",
+//         cflags: ["-DGENERIC"],
+//         soong_config_variables: {
+//             board: {
+//                 soc_a: {
+//                     cflags: ["-DSOC_A"],
+//                 },
+//                 soc_b: {
+//                     cflags: ["-DSOC_B"],
+//                 },
+//             },
+//             feature: {
+//                 cflags: ["-DFEATURE"],
+//             },
+//         },
+//     }
+//
+//     cc_library {
+//         name: "libacme_foo",
+//         defaults: ["acme_defaults"],
+//         srcs: ["*.cpp"],
+//     }
+//
+// And device/acme/Android.bp could have:
+//
+// If an acme BoardConfig.mk file contained:
+//
+//     SOONG_CONFIG_NAMESPACES += acme
+//     SOONG_CONFIG_acme += \
+//         board \
+//         feature \
+//
+//     SOONG_CONFIG_acme_board := soc_a
+//     SOONG_CONFIG_acme_feature := true
+//
+// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE".
+func soongConfigModuleTypeFactory() Module {
+	module := &soongConfigModuleTypeModule{}
+
+	module.AddProperties(&module.properties)
+
+	AddLoadHook(module, func(ctx LoadHookContext) {
+		// A soong_config_module_type module should implicitly import itself.
+		importModuleTypes(ctx, ctx.BlueprintsFile(), module.properties.Name)
+	})
+
+	initAndroidModuleBase(module)
+
+	return module
+}
+
+func (m *soongConfigModuleTypeModule) Name() string {
+	return m.properties.Name
+}
+func (*soongConfigModuleTypeModule) Nameless()                                     {}
+func (*soongConfigModuleTypeModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
+
+type soongConfigStringVariableDummyModule struct {
+	ModuleBase
+	properties       soongconfig.VariableProperties
+	stringProperties soongconfig.StringVariableProperties
+}
+
+type soongConfigBoolVariableDummyModule struct {
+	ModuleBase
+	properties soongconfig.VariableProperties
+}
+
+// soong_config_string_variable defines a variable and a set of possible string values for use
+// in a soong_config_module_type definition.
+func soongConfigStringVariableDummyFactory() Module {
+	module := &soongConfigStringVariableDummyModule{}
+	module.AddProperties(&module.properties, &module.stringProperties)
+	initAndroidModuleBase(module)
+	return module
+}
+
+// soong_config_string_variable defines a variable with true or false values for use
+// in a soong_config_module_type definition.
+func soongConfigBoolVariableDummyFactory() Module {
+	module := &soongConfigBoolVariableDummyModule{}
+	module.AddProperties(&module.properties)
+	initAndroidModuleBase(module)
+	return module
+}
+
+func (m *soongConfigStringVariableDummyModule) Name() string {
+	return m.properties.Name
+}
+func (*soongConfigStringVariableDummyModule) Nameless()                                     {}
+func (*soongConfigStringVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
+
+func (m *soongConfigBoolVariableDummyModule) Name() string {
+	return m.properties.Name
+}
+func (*soongConfigBoolVariableDummyModule) Nameless()                                     {}
+func (*soongConfigBoolVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
+
+func importModuleTypes(ctx LoadHookContext, from string, moduleTypes ...string) {
+	from = filepath.Clean(from)
+	if filepath.Ext(from) != ".bp" {
+		ctx.PropertyErrorf("from", "%q must be a file with extension .bp", from)
+		return
+	}
+
+	if strings.HasPrefix(from, "../") {
+		ctx.PropertyErrorf("from", "%q must not use ../ to escape the source tree",
+			from)
+		return
+	}
+
+	moduleTypeDefinitions := loadSoongConfigModuleTypeDefinition(ctx, from)
+	if moduleTypeDefinitions == nil {
+		return
+	}
+	for _, moduleType := range moduleTypes {
+		if factory, ok := moduleTypeDefinitions[moduleType]; ok {
+			ctx.registerScopedModuleType(moduleType, factory)
+		} else {
+			ctx.PropertyErrorf("module_types", "module type %q not defined in %q",
+				moduleType, from)
+		}
+	}
+}
+
+// loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file.  It caches the
+// result so each file is only parsed once.
+func loadSoongConfigModuleTypeDefinition(ctx LoadHookContext, from string) map[string]blueprint.ModuleFactory {
+	type onceKeyType string
+	key := NewCustomOnceKey(onceKeyType(filepath.Clean(from)))
+
+	reportErrors := func(ctx LoadHookContext, filename string, errs ...error) {
+		for _, err := range errs {
+			if parseErr, ok := err.(*parser.ParseError); ok {
+				ctx.Errorf(parseErr.Pos, "%s", parseErr.Err)
+			} else {
+				ctx.Errorf(scanner.Position{Filename: filename}, "%s", err)
+			}
+		}
+	}
+
+	return ctx.Config().Once(key, func() interface{} {
+		r, err := ctx.Config().fs.Open(from)
+		if err != nil {
+			ctx.PropertyErrorf("from", "failed to open %q: %s", from, err)
+			return (map[string]blueprint.ModuleFactory)(nil)
+		}
+
+		mtDef, errs := soongconfig.Parse(r, from)
+
+		if len(errs) > 0 {
+			reportErrors(ctx, from, errs...)
+			return (map[string]blueprint.ModuleFactory)(nil)
+		}
+
+		globalModuleTypes := ctx.moduleFactories()
+
+		factories := make(map[string]blueprint.ModuleFactory)
+
+		for name, moduleType := range mtDef.ModuleTypes {
+			factory := globalModuleTypes[moduleType.BaseModuleType]
+			if factory != nil {
+				factories[name] = soongConfigModuleFactory(factory, moduleType)
+			} else {
+				reportErrors(ctx, from,
+					fmt.Errorf("missing global module type factory for %q", moduleType.BaseModuleType))
+			}
+		}
+
+		if ctx.Failed() {
+			return (map[string]blueprint.ModuleFactory)(nil)
+		}
+
+		return factories
+	}).(map[string]blueprint.ModuleFactory)
+}
+
+// soongConfigModuleFactory takes an existing soongConfigModuleFactory and a ModuleType and returns
+// a new soongConfigModuleFactory that wraps the existing soongConfigModuleFactory and adds conditional on Soong config
+// variables.
+func soongConfigModuleFactory(factory blueprint.ModuleFactory,
+	moduleType *soongconfig.ModuleType) blueprint.ModuleFactory {
+
+	conditionalFactoryProps := soongconfig.CreateProperties(factory, moduleType)
+	if conditionalFactoryProps.IsValid() {
+		return func() (blueprint.Module, []interface{}) {
+			module, props := factory()
+
+			conditionalProps := proptools.CloneEmptyProperties(conditionalFactoryProps.Elem())
+			props = append(props, conditionalProps.Interface())
+
+			AddLoadHook(module, func(ctx LoadHookContext) {
+				config := ctx.Config().VendorConfig(moduleType.ConfigNamespace)
+				for _, ps := range soongconfig.PropertiesToApply(moduleType, conditionalProps, config) {
+					ctx.AppendProperties(ps)
+				}
+			})
+
+			return module, props
+		}
+	} else {
+		return factory
+	}
+}
diff --git a/android/soong_config_modules_test.go b/android/soong_config_modules_test.go
new file mode 100644
index 0000000..66feba8
--- /dev/null
+++ b/android/soong_config_modules_test.go
@@ -0,0 +1,141 @@
+// Copyright 2019 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"reflect"
+	"testing"
+)
+
+type soongConfigTestModule struct {
+	ModuleBase
+	props soongConfigTestModuleProperties
+}
+
+type soongConfigTestModuleProperties struct {
+	Cflags []string
+}
+
+func soongConfigTestModuleFactory() Module {
+	m := &soongConfigTestModule{}
+	m.AddProperties(&m.props)
+	InitAndroidModule(m)
+	return m
+}
+
+func (t soongConfigTestModule) GenerateAndroidBuildActions(ModuleContext) {}
+
+func TestSoongConfigModule(t *testing.T) {
+	configBp := `
+		soong_config_module_type {
+			name: "acme_test_defaults",
+			module_type: "test_defaults",
+			config_namespace: "acme",
+			variables: ["board", "feature1", "feature2", "feature3"],
+			properties: ["cflags", "srcs"],
+		}
+
+		soong_config_string_variable {
+			name: "board",
+			values: ["soc_a", "soc_b"],
+		}
+
+		soong_config_bool_variable {
+			name: "feature1",
+		}
+
+		soong_config_bool_variable {
+			name: "feature2",
+		}
+
+		soong_config_bool_variable {
+			name: "feature3",
+		}
+	`
+
+	importBp := `
+		soong_config_module_type_import {
+			from: "SoongConfig.bp",
+			module_types: ["acme_test_defaults"],
+		}
+	`
+
+	bp := `
+		acme_test_defaults {
+			name: "foo",
+			cflags: ["-DGENERIC"],
+			soong_config_variables: {
+				board: {
+					soc_a: {
+						cflags: ["-DSOC_A"],
+					},
+					soc_b: {
+						cflags: ["-DSOC_B"],
+					},
+				},
+				feature1: {
+					cflags: ["-DFEATURE1"],
+				},
+				feature2: {
+					cflags: ["-DFEATURE2"],
+				},
+				feature3: {
+					cflags: ["-DFEATURE3"],
+				},
+			},
+		}
+    `
+
+	run := func(t *testing.T, bp string, fs map[string][]byte) {
+		config := TestConfig(buildDir, nil, bp, fs)
+
+		config.TestProductVariables.VendorVars = map[string]map[string]string{
+			"acme": map[string]string{
+				"board":    "soc_a",
+				"feature1": "true",
+				"feature2": "false",
+				// FEATURE3 unset
+			},
+		}
+
+		ctx := NewTestContext()
+		ctx.RegisterModuleType("soong_config_module_type_import", soongConfigModuleTypeImportFactory)
+		ctx.RegisterModuleType("soong_config_module_type", soongConfigModuleTypeFactory)
+		ctx.RegisterModuleType("soong_config_string_variable", soongConfigStringVariableDummyFactory)
+		ctx.RegisterModuleType("soong_config_bool_variable", soongConfigBoolVariableDummyFactory)
+		ctx.RegisterModuleType("test_defaults", soongConfigTestModuleFactory)
+		ctx.Register(config)
+
+		_, errs := ctx.ParseBlueprintsFiles("Android.bp")
+		FailIfErrored(t, errs)
+		_, errs = ctx.PrepareBuildActions(config)
+		FailIfErrored(t, errs)
+
+		foo := ctx.ModuleForTests("foo", "").Module().(*soongConfigTestModule)
+		if g, w := foo.props.Cflags, []string{"-DGENERIC", "-DSOC_A", "-DFEATURE1"}; !reflect.DeepEqual(g, w) {
+			t.Errorf("wanted foo cflags %q, got %q", w, g)
+		}
+	}
+
+	t.Run("single file", func(t *testing.T) {
+		run(t, configBp+bp, nil)
+	})
+
+	t.Run("import", func(t *testing.T) {
+		run(t, importBp+bp, map[string][]byte{
+			"SoongConfig.bp": []byte(configBp),
+		})
+	})
+}
diff --git a/android/soongconfig/config.go b/android/soongconfig/config.go
new file mode 100644
index 0000000..39a776c
--- /dev/null
+++ b/android/soongconfig/config.go
@@ -0,0 +1,51 @@
+// 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 soongconfig
+
+import "strings"
+
+type SoongConfig interface {
+	// Bool interprets the variable named `name` as a boolean, returning true if, after
+	// lowercasing, it matches one of "1", "y", "yes", "on", or "true". Unset, or any other
+	// value will return false.
+	Bool(name string) bool
+
+	// String returns the string value of `name`. If the variable was not set, it will
+	// return the empty string.
+	String(name string) string
+
+	// IsSet returns whether the variable `name` was set by Make.
+	IsSet(name string) bool
+}
+
+func Config(vars map[string]string) SoongConfig {
+	return soongConfig(vars)
+}
+
+type soongConfig map[string]string
+
+func (c soongConfig) Bool(name string) bool {
+	v := strings.ToLower(c[name])
+	return v == "1" || v == "y" || v == "yes" || v == "on" || v == "true"
+}
+
+func (c soongConfig) String(name string) string {
+	return c[name]
+}
+
+func (c soongConfig) IsSet(name string) bool {
+	_, ok := c[name]
+	return ok
+}
diff --git a/android/soongconfig/modules.go b/android/soongconfig/modules.go
new file mode 100644
index 0000000..aa4f5c5
--- /dev/null
+++ b/android/soongconfig/modules.go
@@ -0,0 +1,517 @@
+// 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 soongconfig
+
+import (
+	"fmt"
+	"io"
+	"reflect"
+	"sort"
+	"strings"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/parser"
+	"github.com/google/blueprint/proptools"
+)
+
+var soongConfigProperty = proptools.FieldNameForProperty("soong_config_variables")
+
+// loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file.  It caches the
+// result so each file is only parsed once.
+func Parse(r io.Reader, from string) (*SoongConfigDefinition, []error) {
+	scope := parser.NewScope(nil)
+	file, errs := parser.ParseAndEval(from, r, scope)
+
+	if len(errs) > 0 {
+		return nil, errs
+	}
+
+	mtDef := &SoongConfigDefinition{
+		ModuleTypes: make(map[string]*ModuleType),
+		variables:   make(map[string]soongConfigVariable),
+	}
+
+	for _, def := range file.Defs {
+		switch def := def.(type) {
+		case *parser.Module:
+			newErrs := processImportModuleDef(mtDef, def)
+
+			if len(newErrs) > 0 {
+				errs = append(errs, newErrs...)
+			}
+
+		case *parser.Assignment:
+			// Already handled via Scope object
+		default:
+			panic("unknown definition type")
+		}
+	}
+
+	if len(errs) > 0 {
+		return nil, errs
+	}
+
+	for name, moduleType := range mtDef.ModuleTypes {
+		for _, varName := range moduleType.variableNames {
+			if v, ok := mtDef.variables[varName]; ok {
+				moduleType.Variables = append(moduleType.Variables, v)
+			} else {
+				return nil, []error{
+					fmt.Errorf("unknown variable %q in module type %q", varName, name),
+				}
+			}
+		}
+	}
+
+	return mtDef, nil
+}
+
+func processImportModuleDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
+	switch def.Type {
+	case "soong_config_module_type":
+		return processModuleTypeDef(v, def)
+	case "soong_config_string_variable":
+		return processStringVariableDef(v, def)
+	case "soong_config_bool_variable":
+		return processBoolVariableDef(v, def)
+	default:
+		// Unknown module types will be handled when the file is parsed as a normal
+		// Android.bp file.
+	}
+
+	return nil
+}
+
+type ModuleTypeProperties struct {
+	// the name of the new module type.  Unlike most modules, this name does not need to be unique,
+	// although only one module type with any name will be importable into an Android.bp file.
+	Name string
+
+	// the module type that this module type will extend.
+	Module_type string
+
+	// the SOONG_CONFIG_NAMESPACE value from a BoardConfig.mk that this module type will read
+	// configuration variables from.
+	Config_namespace string
+
+	// the list of SOONG_CONFIG variables that this module type will read
+	Variables []string
+
+	// the list of properties that this module type will extend.
+	Properties []string
+}
+
+func processModuleTypeDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
+
+	props := &ModuleTypeProperties{}
+
+	_, errs = proptools.UnpackProperties(def.Properties, props)
+	if len(errs) > 0 {
+		return errs
+	}
+
+	if props.Name == "" {
+		errs = append(errs, fmt.Errorf("name property must be set"))
+	}
+
+	if props.Config_namespace == "" {
+		errs = append(errs, fmt.Errorf("config_namespace property must be set"))
+	}
+
+	if props.Module_type == "" {
+		errs = append(errs, fmt.Errorf("module_type property must be set"))
+	}
+
+	if len(errs) > 0 {
+		return errs
+	}
+
+	mt := &ModuleType{
+		affectableProperties: props.Properties,
+		ConfigNamespace:      props.Config_namespace,
+		BaseModuleType:       props.Module_type,
+		variableNames:        props.Variables,
+	}
+	v.ModuleTypes[props.Name] = mt
+
+	return nil
+}
+
+type VariableProperties struct {
+	Name string
+}
+
+type StringVariableProperties struct {
+	Values []string
+}
+
+func processStringVariableDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
+	stringProps := &StringVariableProperties{}
+
+	base, errs := processVariableDef(def, stringProps)
+	if len(errs) > 0 {
+		return errs
+	}
+
+	if len(stringProps.Values) == 0 {
+		return []error{fmt.Errorf("values property must be set")}
+	}
+
+	v.variables[base.variable] = &stringVariable{
+		baseVariable: base,
+		values:       CanonicalizeToProperties(stringProps.Values),
+	}
+
+	return nil
+}
+
+func processBoolVariableDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
+	base, errs := processVariableDef(def)
+	if len(errs) > 0 {
+		return errs
+	}
+
+	v.variables[base.variable] = &boolVariable{
+		baseVariable: base,
+	}
+
+	return nil
+}
+
+func processVariableDef(def *parser.Module,
+	extraProps ...interface{}) (cond baseVariable, errs []error) {
+
+	props := &VariableProperties{}
+
+	allProps := append([]interface{}{props}, extraProps...)
+
+	_, errs = proptools.UnpackProperties(def.Properties, allProps...)
+	if len(errs) > 0 {
+		return baseVariable{}, errs
+	}
+
+	if props.Name == "" {
+		return baseVariable{}, []error{fmt.Errorf("name property must be set")}
+	}
+
+	return baseVariable{
+		variable: props.Name,
+	}, nil
+}
+
+type SoongConfigDefinition struct {
+	ModuleTypes map[string]*ModuleType
+
+	variables map[string]soongConfigVariable
+}
+
+// CreateProperties returns a reflect.Value of a newly constructed type that contains the desired
+// property layout for the Soong config variables, with each possible value an interface{} that
+// contains a nil pointer to another newly constructed type that contains the affectable properties.
+// The reflect.Value will be cloned for each call to the Soong config module type's factory method.
+//
+// For example, the acme_cc_defaults example above would
+// produce a reflect.Value whose type is:
+// *struct {
+//     Soong_config_variables struct {
+//         Board struct {
+//             Soc_a interface{}
+//             Soc_b interface{}
+//         }
+//     }
+// }
+// And whose value is:
+// &{
+//     Soong_config_variables: {
+//         Board: {
+//             Soc_a: (*struct{ Cflags []string })(nil),
+//             Soc_b: (*struct{ Cflags []string })(nil),
+//         },
+//     },
+// }
+func CreateProperties(factory blueprint.ModuleFactory, moduleType *ModuleType) reflect.Value {
+	var fields []reflect.StructField
+
+	_, factoryProps := factory()
+	affectablePropertiesType := createAffectablePropertiesType(moduleType.affectableProperties, factoryProps)
+	if affectablePropertiesType == nil {
+		return reflect.Value{}
+	}
+
+	for _, c := range moduleType.Variables {
+		fields = append(fields, reflect.StructField{
+			Name: proptools.FieldNameForProperty(c.variableProperty()),
+			Type: c.variableValuesType(),
+		})
+	}
+
+	typ := reflect.StructOf([]reflect.StructField{{
+		Name: soongConfigProperty,
+		Type: reflect.StructOf(fields),
+	}})
+
+	props := reflect.New(typ)
+	structConditions := props.Elem().FieldByName(soongConfigProperty)
+
+	for i, c := range moduleType.Variables {
+		c.initializeProperties(structConditions.Field(i), affectablePropertiesType)
+	}
+
+	return props
+}
+
+// createAffectablePropertiesType creates a reflect.Type of a struct that has a field for each affectable property
+// that exists in factoryProps.
+func createAffectablePropertiesType(affectableProperties []string, factoryProps []interface{}) reflect.Type {
+	affectableProperties = append([]string(nil), affectableProperties...)
+	sort.Strings(affectableProperties)
+
+	var recurse func(prefix string, aps []string) ([]string, reflect.Type)
+	recurse = func(prefix string, aps []string) ([]string, reflect.Type) {
+		var fields []reflect.StructField
+
+		for len(affectableProperties) > 0 {
+			p := affectableProperties[0]
+			if !strings.HasPrefix(affectableProperties[0], prefix) {
+				break
+			}
+			affectableProperties = affectableProperties[1:]
+
+			nestedProperty := strings.TrimPrefix(p, prefix)
+			if i := strings.IndexRune(nestedProperty, '.'); i >= 0 {
+				var nestedType reflect.Type
+				nestedPrefix := nestedProperty[:i+1]
+
+				affectableProperties, nestedType = recurse(prefix+nestedPrefix, affectableProperties)
+
+				if nestedType != nil {
+					nestedFieldName := proptools.FieldNameForProperty(strings.TrimSuffix(nestedPrefix, "."))
+
+					fields = append(fields, reflect.StructField{
+						Name: nestedFieldName,
+						Type: nestedType,
+					})
+				}
+			} else {
+				typ := typeForPropertyFromPropertyStructs(factoryProps, p)
+				if typ != nil {
+					fields = append(fields, reflect.StructField{
+						Name: proptools.FieldNameForProperty(nestedProperty),
+						Type: typ,
+					})
+				}
+			}
+		}
+
+		var typ reflect.Type
+		if len(fields) > 0 {
+			typ = reflect.StructOf(fields)
+		}
+		return affectableProperties, typ
+	}
+
+	affectableProperties, typ := recurse("", affectableProperties)
+	if len(affectableProperties) > 0 {
+		panic(fmt.Errorf("didn't handle all affectable properties"))
+	}
+
+	if typ != nil {
+		return reflect.PtrTo(typ)
+	}
+
+	return nil
+}
+
+func typeForPropertyFromPropertyStructs(psList []interface{}, property string) reflect.Type {
+	for _, ps := range psList {
+		if typ := typeForPropertyFromPropertyStruct(ps, property); typ != nil {
+			return typ
+		}
+	}
+
+	return nil
+}
+
+func typeForPropertyFromPropertyStruct(ps interface{}, property string) reflect.Type {
+	v := reflect.ValueOf(ps)
+	for len(property) > 0 {
+		if !v.IsValid() {
+			return nil
+		}
+
+		if v.Kind() == reflect.Interface {
+			if v.IsNil() {
+				return nil
+			} else {
+				v = v.Elem()
+			}
+		}
+
+		if v.Kind() == reflect.Ptr {
+			if v.IsNil() {
+				v = reflect.Zero(v.Type().Elem())
+			} else {
+				v = v.Elem()
+			}
+		}
+
+		if v.Kind() != reflect.Struct {
+			return nil
+		}
+
+		if index := strings.IndexRune(property, '.'); index >= 0 {
+			prefix := property[:index]
+			property = property[index+1:]
+
+			v = v.FieldByName(proptools.FieldNameForProperty(prefix))
+		} else {
+			f := v.FieldByName(proptools.FieldNameForProperty(property))
+			if !f.IsValid() {
+				return nil
+			}
+			return f.Type()
+		}
+	}
+	return nil
+}
+
+// PropertiesToApply returns the applicable properties from a ModuleType that should be applied
+// based on SoongConfig values.
+func PropertiesToApply(moduleType *ModuleType, props reflect.Value, config SoongConfig) []interface{} {
+	var ret []interface{}
+	props = props.Elem().FieldByName(soongConfigProperty)
+	for i, c := range moduleType.Variables {
+		if ps := c.PropertiesToApply(config, props.Field(i)); ps != nil {
+			ret = append(ret, ps)
+		}
+	}
+	return ret
+}
+
+type ModuleType struct {
+	BaseModuleType  string
+	ConfigNamespace string
+	Variables       []soongConfigVariable
+
+	affectableProperties []string
+	variableNames        []string
+}
+
+type soongConfigVariable interface {
+	// variableProperty returns the name of the variable.
+	variableProperty() string
+
+	// conditionalValuesType returns a reflect.Type that contains an interface{} for each possible value.
+	variableValuesType() reflect.Type
+
+	// initializeProperties is passed a reflect.Value of the reflect.Type returned by conditionalValuesType and a
+	// reflect.Type of the affectable properties, and should initialize each interface{} in the reflect.Value with
+	// the zero value of the affectable properties type.
+	initializeProperties(v reflect.Value, typ reflect.Type)
+
+	// PropertiesToApply should return one of the interface{} values set by initializeProperties to be applied
+	// to the module.
+	PropertiesToApply(config SoongConfig, values reflect.Value) interface{}
+}
+
+type baseVariable struct {
+	variable string
+}
+
+func (c *baseVariable) variableProperty() string {
+	return CanonicalizeToProperty(c.variable)
+}
+
+type stringVariable struct {
+	baseVariable
+	values []string
+}
+
+func (s *stringVariable) variableValuesType() reflect.Type {
+	var fields []reflect.StructField
+
+	for _, v := range s.values {
+		fields = append(fields, reflect.StructField{
+			Name: proptools.FieldNameForProperty(v),
+			Type: emptyInterfaceType,
+		})
+	}
+
+	return reflect.StructOf(fields)
+}
+
+func (s *stringVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
+	for i := range s.values {
+		v.Field(i).Set(reflect.Zero(typ))
+	}
+}
+
+func (s *stringVariable) PropertiesToApply(config SoongConfig, values reflect.Value) interface{} {
+	for j, v := range s.values {
+		if config.String(s.variable) == v {
+			return values.Field(j).Interface()
+		}
+	}
+
+	return nil
+}
+
+type boolVariable struct {
+	baseVariable
+}
+
+func (b boolVariable) variableValuesType() reflect.Type {
+	return emptyInterfaceType
+}
+
+func (b boolVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
+	v.Set(reflect.Zero(typ))
+}
+
+func (b boolVariable) PropertiesToApply(config SoongConfig, values reflect.Value) interface{} {
+	if config.Bool(b.variable) {
+		return values.Interface()
+	}
+
+	return nil
+}
+
+func CanonicalizeToProperty(v string) string {
+	return strings.Map(func(r rune) rune {
+		switch {
+		case r >= 'A' && r <= 'Z',
+			r >= 'a' && r <= 'z',
+			r >= '0' && r <= '9',
+			r == '_':
+			return r
+		default:
+			return '_'
+		}
+	}, v)
+}
+
+func CanonicalizeToProperties(values []string) []string {
+	ret := make([]string, len(values))
+	for i, v := range values {
+		ret[i] = CanonicalizeToProperty(v)
+	}
+	return ret
+}
+
+type emptyInterfaceStruct struct {
+	i interface{}
+}
+
+var emptyInterfaceType = reflect.TypeOf(emptyInterfaceStruct{}).Field(0).Type
diff --git a/android/soongconfig/modules_test.go b/android/soongconfig/modules_test.go
new file mode 100644
index 0000000..4190016
--- /dev/null
+++ b/android/soongconfig/modules_test.go
@@ -0,0 +1,249 @@
+// 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 soongconfig
+
+import (
+	"reflect"
+	"testing"
+)
+
+func Test_CanonicalizeToProperty(t *testing.T) {
+	tests := []struct {
+		name string
+		arg  string
+		want string
+	}{
+		{
+			name: "lowercase",
+			arg:  "board",
+			want: "board",
+		},
+		{
+			name: "uppercase",
+			arg:  "BOARD",
+			want: "BOARD",
+		},
+		{
+			name: "numbers",
+			arg:  "BOARD123",
+			want: "BOARD123",
+		},
+		{
+			name: "underscore",
+			arg:  "TARGET_BOARD",
+			want: "TARGET_BOARD",
+		},
+		{
+			name: "dash",
+			arg:  "TARGET-BOARD",
+			want: "TARGET_BOARD",
+		},
+		{
+			name: "unicode",
+			arg:  "boardλ",
+			want: "board_",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := CanonicalizeToProperty(tt.arg); got != tt.want {
+				t.Errorf("canonicalizeToProperty() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_typeForPropertyFromPropertyStruct(t *testing.T) {
+	tests := []struct {
+		name     string
+		ps       interface{}
+		property string
+		want     string
+	}{
+		{
+			name: "string",
+			ps: struct {
+				A string
+			}{},
+			property: "a",
+			want:     "string",
+		},
+		{
+			name: "list",
+			ps: struct {
+				A []string
+			}{},
+			property: "a",
+			want:     "[]string",
+		},
+		{
+			name: "missing",
+			ps: struct {
+				A []string
+			}{},
+			property: "b",
+			want:     "",
+		},
+		{
+			name: "nested",
+			ps: struct {
+				A struct {
+					B string
+				}
+			}{},
+			property: "a.b",
+			want:     "string",
+		},
+		{
+			name: "missing nested",
+			ps: struct {
+				A struct {
+					B string
+				}
+			}{},
+			property: "a.c",
+			want:     "",
+		},
+		{
+			name: "not a struct",
+			ps: struct {
+				A string
+			}{},
+			property: "a.b",
+			want:     "",
+		},
+		{
+			name: "nested pointer",
+			ps: struct {
+				A *struct {
+					B string
+				}
+			}{},
+			property: "a.b",
+			want:     "string",
+		},
+		{
+			name: "nested interface",
+			ps: struct {
+				A interface{}
+			}{
+				A: struct {
+					B string
+				}{},
+			},
+			property: "a.b",
+			want:     "string",
+		},
+		{
+			name: "nested interface pointer",
+			ps: struct {
+				A interface{}
+			}{
+				A: &struct {
+					B string
+				}{},
+			},
+			property: "a.b",
+			want:     "string",
+		},
+		{
+			name: "nested interface nil pointer",
+			ps: struct {
+				A interface{}
+			}{
+				A: (*struct {
+					B string
+				})(nil),
+			},
+			property: "a.b",
+			want:     "string",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			typ := typeForPropertyFromPropertyStruct(tt.ps, tt.property)
+			got := ""
+			if typ != nil {
+				got = typ.String()
+			}
+			if got != tt.want {
+				t.Errorf("typeForPropertyFromPropertyStruct() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_createAffectablePropertiesType(t *testing.T) {
+	tests := []struct {
+		name                 string
+		affectableProperties []string
+		factoryProps         interface{}
+		want                 string
+	}{
+		{
+			name:                 "string",
+			affectableProperties: []string{"cflags"},
+			factoryProps: struct {
+				Cflags string
+			}{},
+			want: "*struct { Cflags string }",
+		},
+		{
+			name:                 "list",
+			affectableProperties: []string{"cflags"},
+			factoryProps: struct {
+				Cflags []string
+			}{},
+			want: "*struct { Cflags []string }",
+		},
+		{
+			name:                 "string pointer",
+			affectableProperties: []string{"cflags"},
+			factoryProps: struct {
+				Cflags *string
+			}{},
+			want: "*struct { Cflags *string }",
+		},
+		{
+			name:                 "subset",
+			affectableProperties: []string{"cflags"},
+			factoryProps: struct {
+				Cflags  string
+				Ldflags string
+			}{},
+			want: "*struct { Cflags string }",
+		},
+		{
+			name:                 "none",
+			affectableProperties: []string{"cflags"},
+			factoryProps: struct {
+				Ldflags string
+			}{},
+			want: "",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			typ := createAffectablePropertiesType(tt.affectableProperties, []interface{}{tt.factoryProps})
+			got := ""
+			if typ != nil {
+				got = typ.String()
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("createAffectablePropertiesType() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
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/android/variable_test.go b/android/variable_test.go
index 451d43d..751677f 100644
--- a/android/variable_test.go
+++ b/android/variable_test.go
@@ -148,7 +148,7 @@
 		clonedProps := proptools.CloneProperties(reflect.ValueOf(props)).Interface()
 		m.AddProperties(clonedProps)
 
-		// Set a default variableProperties, this will be used as the input to the property struct filter
+		// Set a default soongConfigVariableProperties, this will be used as the input to the property struct filter
 		// for this test module.
 		m.variableProperties = testProductVariableProperties
 		InitAndroidModule(m)
diff --git a/apex/androidmk.go b/apex/androidmk.go
index f1194be..5fa5bf0 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -63,7 +63,6 @@
 			postInstallCommands = append(postInstallCommands, mkdirCmd, linkCmd)
 		}
 	}
-	postInstallCommands = append(postInstallCommands, a.compatSymlinks...)
 
 	for _, fi := range a.filesInfo {
 		if cc, ok := fi.module.(*cc.Module); ok && cc.Properties.HideFromMake {
@@ -178,17 +177,22 @@
 			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 apexType == flattenedApex && len(postInstallCommands) > 0 {
-					// For flattened apexes, compat symlinks are attached to apex_manifest.json which is guaranteed for every apex
+					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, " && "))
 				}
 			}
diff --git a/apex/apex.go b/apex/apex.go
index d908cd3..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()
@@ -1084,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() {
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 1a9b95c..b7dd7fb 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -291,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)
@@ -303,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)
@@ -599,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",
@@ -764,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",
@@ -1603,12 +1604,13 @@
 
 type fileInApex struct {
 	path   string // path in apex
+	src    string // src path
 	isLink bool
 }
 
-func getFiles(t *testing.T, ctx *android.TestContext, moduleName string) []fileInApex {
+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 ret []fileInApex
@@ -1618,7 +1620,7 @@
 			continue
 		}
 		terms := strings.Split(cmd, " ")
-		var dst string
+		var dst, src string
 		var isLink bool
 		switch terms[0] {
 		case "mkdir":
@@ -1627,6 +1629,7 @@
 				t.Fatal("copyCmds contains invalid cp command", cmd)
 			}
 			dst = terms[len(terms)-1]
+			src = terms[len(terms)-2]
 			isLink = false
 		case "ln":
 			if len(terms) != 3 && len(terms) != 4 {
@@ -1634,6 +1637,7 @@
 				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)
@@ -1644,17 +1648,18 @@
 				t.Fatal("copyCmds should copy a file to image.apex/", cmd)
 			}
 			dstFile := dst[index+len(imageApexDir):]
-			ret = append(ret, fileInApex{path: dstFile, isLink: isLink})
+			ret = append(ret, fileInApex{path: dstFile, src: src, isLink: isLink})
 		}
 	}
 	return ret
 }
 
-func ensureExactContents(t *testing.T, ctx *android.TestContext, moduleName string, files []string) {
+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) {
+	for _, file := range getFiles(t, ctx, moduleName, variant) {
 		for _, expected := range files {
 			if matched, _ := path.Match(expected, file.path); matched {
 				filesMatched[expected] = true
@@ -1725,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",
@@ -1785,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",
@@ -1876,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/*",
@@ -1949,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)
@@ -1997,7 +2002,7 @@
 			},
 		}))
 
-	ensureExactContents(t, ctx, "myapex", []string{
+	ensureExactContents(t, ctx, "myapex", "android_common_image", []string{
 		"lib/libvndk.so",
 		"lib64/libvndk.so",
 		"etc/*",
@@ -2093,7 +2098,7 @@
 		}),
 	)
 
-	ensureExactContents(t, ctx, "myapex_v27", []string{
+	ensureExactContents(t, ctx, "myapex_v27", "android_common_image", []string{
 		"lib/libvndk27binder32.so",
 		"etc/*",
 	})
@@ -3437,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",
 	})
@@ -3595,13 +3600,13 @@
 	}
 
 	ctx, _ := testApex(t, bp, withUnbundledBuild)
-	files := getFiles(t, ctx, "myapex")
+	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")
+	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
diff --git a/apex/builder.go b/apex/builder.go
index 8209f690..8ae2b5c 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -382,15 +382,17 @@
 		}
 
 		targetSdkVersion := ctx.Config().DefaultAppTargetSdk()
-		if targetSdkVersion == ctx.Config().PlatformSdkCodename() &&
-			ctx.Config().UnbundledBuild() &&
-			!ctx.Config().UnbundledBuildUsePrebuiltSdks() &&
-			ctx.Config().IsEnvTrue("UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT") {
-			apiFingerprint := java.ApiFingerprintPath(ctx)
-			targetSdkVersion += fmt.Sprintf(".$$(cat %s)", apiFingerprint.String())
-			implicitInputs = append(implicitInputs, apiFingerprint)
+		minSdkVersion := ctx.Config().DefaultAppTargetSdk()
+		if java.UseApiFingerprint(ctx, targetSdkVersion) {
+			targetSdkVersion += fmt.Sprintf(".$$(cat %s)", java.ApiFingerprintPath(ctx).String())
+			implicitInputs = append(implicitInputs, java.ApiFingerprintPath(ctx))
+		}
+		if java.UseApiFingerprint(ctx, minSdkVersion) {
+			minSdkVersion += fmt.Sprintf(".$$(cat %s)", java.ApiFingerprintPath(ctx).String())
+			implicitInputs = append(implicitInputs, java.ApiFingerprintPath(ctx))
 		}
 		optFlags = append(optFlags, "--target_sdk_version "+targetSdkVersion)
+		optFlags = append(optFlags, "--min_sdk_version "+minSdkVersion)
 
 		noticeFile := a.buildNoticeFile(ctx, a.Name()+suffix)
 		if noticeFile.Valid() {
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/config/global.go b/cc/config/global.go
index 1ce29b9..87314dc 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -166,9 +166,6 @@
 			flags = append(flags, "-ftrivial-auto-var-init=zero -enable-trivial-auto-var-init-zero-knowing-it-will-be-removed-from-clang")
 		} else if ctx.Config().IsEnvTrue("AUTO_PATTERN_INITIALIZE") {
 			flags = append(flags, "-ftrivial-auto-var-init=pattern")
-		} else {
-			// Default to pattern initialization.
-			flags = append(flags, "-ftrivial-auto-var-init=pattern")
 		}
 
 		return strings.Join(flags, " ")
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 6f2e5b7..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,6 +236,15 @@
 		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 and libatomic have to be last on the command line
 		if !Bool(linker.Properties.No_libcrt) {
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/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/genrule/genrule.go b/genrule/genrule.go
index c5aaed2..a0008d3 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -550,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/java/aar.go b/java/aar.go
index ae064e5..d707e36 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"
 )
@@ -176,7 +177,10 @@
 	linkDeps = append(linkDeps, assetFiles...)
 
 	// SDK version flags
-	minSdkVersion := sdkVersionOrDefault(ctx, sdkContext.minSdkVersion())
+	minSdkVersion, err := sdkContext.minSdkVersion().effectiveVersionString(ctx)
+	if err != nil {
+		ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
+	}
 
 	linkFlags = append(linkFlags, "--min-sdk-version "+minSdkVersion)
 	linkFlags = append(linkFlags, "--target-sdk-version "+minSdkVersion)
@@ -523,22 +527,22 @@
 	exportedStaticPackages android.Paths
 }
 
-func (a *AARImport) sdkVersion() string {
-	return String(a.properties.Sdk_version)
+func (a *AARImport) sdkVersion() sdkSpec {
+	return sdkSpecFrom(String(a.properties.Sdk_version))
 }
 
 func (a *AARImport) systemModules() string {
 	return ""
 }
 
-func (a *AARImport) minSdkVersion() string {
+func (a *AARImport) minSdkVersion() sdkSpec {
 	if a.properties.Min_sdk_version != nil {
-		return *a.properties.Min_sdk_version
+		return sdkSpecFrom(*a.properties.Min_sdk_version)
 	}
 	return a.sdkVersion()
 }
 
-func (a *AARImport) targetSdkVersion() string {
+func (a *AARImport) targetSdkVersion() sdkSpec {
 	return a.sdkVersion()
 }
 
diff --git a/java/android_manifest.go b/java/android_manifest.go
index 021883e..e3646f5 100644
--- a/java/android_manifest.go
+++ b/java/android_manifest.go
@@ -59,7 +59,7 @@
 	if isLibrary {
 		args = append(args, "--library")
 	} else {
-		minSdkVersion, err := sdkVersionToNumber(ctx, sdkContext.minSdkVersion())
+		minSdkVersion, err := sdkContext.minSdkVersion().effectiveVersion(ctx)
 		if err != nil {
 			ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
 		}
@@ -92,17 +92,28 @@
 	}
 
 	var deps android.Paths
-	targetSdkVersion := sdkVersionOrDefault(ctx, sdkContext.targetSdkVersion())
-	if targetSdkVersion == ctx.Config().PlatformSdkCodename() &&
-		ctx.Config().UnbundledBuild() &&
-		!ctx.Config().UnbundledBuildUsePrebuiltSdks() &&
-		ctx.Config().IsEnvTrue("UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT") {
-		apiFingerprint := ApiFingerprintPath(ctx)
-		targetSdkVersion += fmt.Sprintf(".$$(cat %s)", apiFingerprint.String())
-		deps = append(deps, apiFingerprint)
+	targetSdkVersion, err := sdkContext.targetSdkVersion().effectiveVersionString(ctx)
+	if err != nil {
+		ctx.ModuleErrorf("invalid targetSdkVersion: %s", err)
+	}
+	if UseApiFingerprint(ctx, targetSdkVersion) {
+		targetSdkVersion += fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(ctx).String())
+		deps = append(deps, ApiFingerprintPath(ctx))
+	}
+
+	minSdkVersion, err := sdkContext.minSdkVersion().effectiveVersionString(ctx)
+	if err != nil {
+		ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
+	}
+	if UseApiFingerprint(ctx, minSdkVersion) {
+		minSdkVersion += fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(ctx).String())
+		deps = append(deps, ApiFingerprintPath(ctx))
 	}
 
 	fixedManifest := android.PathForModuleOut(ctx, "manifest_fixer", "AndroidManifest.xml")
+	if err != nil {
+		ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
+	}
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        manifestFixerRule,
 		Description: "fix manifest",
@@ -110,7 +121,7 @@
 		Implicits:   deps,
 		Output:      fixedManifest,
 		Args: map[string]string{
-			"minSdkVersion":    sdkVersionOrDefault(ctx, sdkContext.minSdkVersion()),
+			"minSdkVersion":    minSdkVersion,
 			"targetSdkVersion": targetSdkVersion,
 			"args":             strings.Join(args, " "),
 		},
diff --git a/java/androidmk.go b/java/androidmk.go
index 04bf15c..f28ae10 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -93,7 +93,7 @@
 					if len(library.dexpreopter.builtInstalled) > 0 {
 						entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", library.dexpreopter.builtInstalled)
 					}
-					entries.SetString("LOCAL_SDK_VERSION", library.sdkVersion())
+					entries.SetString("LOCAL_SDK_VERSION", library.sdkVersion().raw)
 					entries.SetPath("LOCAL_SOONG_CLASSES_JAR", library.implementationAndResourcesJar)
 					entries.SetPath("LOCAL_SOONG_HEADER_JAR", library.headerJarFile)
 
@@ -174,7 +174,7 @@
 				entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", !Bool(prebuilt.properties.Installable))
 				entries.SetPath("LOCAL_SOONG_HEADER_JAR", prebuilt.combinedClasspathFile)
 				entries.SetPath("LOCAL_SOONG_CLASSES_JAR", prebuilt.combinedClasspathFile)
-				entries.SetString("LOCAL_SDK_VERSION", prebuilt.sdkVersion())
+				entries.SetString("LOCAL_SDK_VERSION", prebuilt.sdkVersion().raw)
 				entries.SetString("LOCAL_MODULE_STEM", prebuilt.Stem())
 			},
 		},
@@ -223,7 +223,7 @@
 				entries.SetPath("LOCAL_SOONG_EXPORT_PROGUARD_FLAGS", prebuilt.proguardFlags)
 				entries.SetPath("LOCAL_SOONG_STATIC_LIBRARY_EXTRA_PACKAGES", prebuilt.extraAaptPackagesFile)
 				entries.SetPath("LOCAL_FULL_MANIFEST_FILE", prebuilt.manifest)
-				entries.SetString("LOCAL_SDK_VERSION", prebuilt.sdkVersion())
+				entries.SetString("LOCAL_SDK_VERSION", prebuilt.sdkVersion().raw)
 			},
 		},
 	}}
@@ -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..2ed3d4d 100755
--- a/java/app.go
+++ b/java/app.go
@@ -47,6 +47,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
@@ -162,7 +163,7 @@
 func (a *AndroidApp) DepsMutator(ctx android.BottomUpMutatorContext) {
 	a.Module.deps(ctx)
 
-	if String(a.appProperties.Stl) == "c++_shared" && a.sdkVersion() == "" {
+	if String(a.appProperties.Stl) == "c++_shared" && !a.sdkVersion().specified() {
 		ctx.PropertyErrorf("stl", "sdk_version must be set in order to use c++_shared")
 	}
 
@@ -211,7 +212,7 @@
 // Returns true if the native libraries should be stored in the APK uncompressed and the
 // extractNativeLibs application flag should be set to false in the manifest.
 func (a *AndroidApp) useEmbeddedNativeLibs(ctx android.ModuleContext) bool {
-	minSdkVersion, err := sdkVersionToNumber(ctx, a.minSdkVersion())
+	minSdkVersion, err := a.minSdkVersion().effectiveVersion(ctx)
 	if err != nil {
 		ctx.PropertyErrorf("min_sdk_version", "invalid value %q: %s", a.minSdkVersion(), err)
 	}
@@ -1212,6 +1213,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() sdkSpec {
+	return sdkSpecFrom(String(r.properties.Sdk_version))
+}
+
+func (r *RuntimeResourceOverlay) systemModules() string {
+	return ""
+}
+
+func (r *RuntimeResourceOverlay) minSdkVersion() sdkSpec {
+	if r.properties.Min_sdk_version != nil {
+		return sdkSpecFrom(*r.properties.Min_sdk_version)
+	}
+	return r.sdkVersion()
+}
+
+func (r *RuntimeResourceOverlay) targetSdkVersion() sdkSpec {
+	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/dex.go b/java/dex.go
index cd6d90de..ed4dfcf 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -73,12 +73,12 @@
 			"--verbose")
 	}
 
-	minSdkVersion, err := sdkVersionToNumberAsString(ctx, j.minSdkVersion())
+	minSdkVersion, err := j.minSdkVersion().effectiveVersion(ctx)
 	if err != nil {
 		ctx.PropertyErrorf("min_sdk_version", "%s", err)
 	}
 
-	flags = append(flags, "--min-api "+minSdkVersion)
+	flags = append(flags, "--min-api "+minSdkVersion.asNumberString())
 	return flags
 }
 
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 66840b5..c6aa7fe 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -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 dexpreoptGlobalConfig(ctx).UseApexImage {
+		for arch, paths := range artJZBootImageConfig(ctx).imagesDeps {
+			files[arch] = append(files[arch], paths...)
+		}
+	}
+
+	return files
 }
 
 // dexpreoptBoot singleton rules
diff --git a/java/droiddoc.go b/java/droiddoc.go
index f62f5f9..abdceba 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -424,19 +424,19 @@
 
 var _ android.OutputFileProducer = (*Javadoc)(nil)
 
-func (j *Javadoc) sdkVersion() string {
-	return String(j.properties.Sdk_version)
+func (j *Javadoc) sdkVersion() sdkSpec {
+	return sdkSpecFrom(String(j.properties.Sdk_version))
 }
 
 func (j *Javadoc) systemModules() string {
 	return proptools.String(j.properties.System_modules)
 }
 
-func (j *Javadoc) minSdkVersion() string {
+func (j *Javadoc) minSdkVersion() sdkSpec {
 	return j.sdkVersion()
 }
 
-func (j *Javadoc) targetSdkVersion() string {
+func (j *Javadoc) targetSdkVersion() sdkSpec {
 	return j.sdkVersion()
 }
 
@@ -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..794ee68 100644
--- a/java/java.go
+++ b/java/java.go
@@ -87,7 +87,7 @@
 	if j.SocSpecific() || j.DeviceSpecific() ||
 		(j.ProductSpecific() && ctx.Config().EnforceProductPartitionInterface()) {
 		if sc, ok := ctx.Module().(sdkContext); ok {
-			if sc.sdkVersion() == "" {
+			if !sc.sdkVersion().specified() {
 				ctx.PropertyErrorf("sdk_version",
 					"sdk_version must have a value when the module is located at vendor or product(only if PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE is set).")
 			}
@@ -98,12 +98,11 @@
 func (j *Module) checkPlatformAPI(ctx android.ModuleContext) {
 	if sc, ok := ctx.Module().(sdkContext); ok {
 		usePlatformAPI := proptools.Bool(j.deviceProperties.Platform_apis)
-		if usePlatformAPI != (sc.sdkVersion() == "") {
-			if usePlatformAPI {
-				ctx.PropertyErrorf("platform_apis", "platform_apis must be false when sdk_version is not empty.")
-			} else {
-				ctx.PropertyErrorf("platform_apis", "platform_apis must be true when sdk_version is empty.")
-			}
+		sdkVersionSpecified := sc.sdkVersion().specified()
+		if usePlatformAPI && sdkVersionSpecified {
+			ctx.PropertyErrorf("platform_apis", "platform_apis must be false when sdk_version is not empty.")
+		} else if !usePlatformAPI && !sdkVersionSpecified {
+			ctx.PropertyErrorf("platform_apis", "platform_apis must be true when sdk_version is empty.")
 		}
 
 	}
@@ -451,8 +450,8 @@
 }
 
 type SdkLibraryDependency interface {
-	SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths
-	SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths
+	SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths
+	SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths
 }
 
 type xref interface {
@@ -553,24 +552,24 @@
 			ctx.Config().UnbundledBuild())
 }
 
-func (j *Module) sdkVersion() string {
-	return String(j.deviceProperties.Sdk_version)
+func (j *Module) sdkVersion() sdkSpec {
+	return sdkSpecFrom(String(j.deviceProperties.Sdk_version))
 }
 
 func (j *Module) systemModules() string {
 	return proptools.String(j.deviceProperties.System_modules)
 }
 
-func (j *Module) minSdkVersion() string {
+func (j *Module) minSdkVersion() sdkSpec {
 	if j.deviceProperties.Min_sdk_version != nil {
-		return *j.deviceProperties.Min_sdk_version
+		return sdkSpecFrom(*j.deviceProperties.Min_sdk_version)
 	}
 	return j.sdkVersion()
 }
 
-func (j *Module) targetSdkVersion() string {
+func (j *Module) targetSdkVersion() sdkSpec {
 	if j.deviceProperties.Target_sdk_version != nil {
-		return *j.deviceProperties.Target_sdk_version
+		return sdkSpecFrom(*j.deviceProperties.Target_sdk_version)
 	}
 	return j.sdkVersion()
 }
@@ -776,26 +775,25 @@
 		name == "stub-annotations" || name == "private-stub-annotations-jar" ||
 		name == "core-lambda-stubs" || name == "core-generated-annotation-stubs":
 		return javaCore, true
-	case ver == "core_current":
+	case ver.kind == sdkCore:
 		return javaCore, false
 	case name == "android_system_stubs_current":
 		return javaSystem, true
-	case strings.HasPrefix(ver, "system_"):
+	case ver.kind == sdkSystem:
 		return javaSystem, false
 	case name == "android_test_stubs_current":
 		return javaSystem, true
-	case strings.HasPrefix(ver, "test_"):
+	case ver.kind == sdkTest:
 		return javaPlatform, false
 	case name == "android_stubs_current":
 		return javaSdk, true
-	case ver == "current":
+	case ver.kind == sdkPublic:
 		return javaSdk, false
-	case ver == "" || ver == "none" || ver == "core_platform":
+	case ver.kind == sdkPrivate || ver.kind == sdkNone || ver.kind == sdkCorePlatform:
 		return javaPlatform, false
+	case !ver.valid():
+		panic(fmt.Errorf("sdk_version is invalid. got %q", ver.raw))
 	default:
-		if _, err := strconv.Atoi(ver); err != nil {
-			panic(fmt.Errorf("expected sdk_version to be a number, got %q", ver))
-		}
 		return javaSdk, false
 	}
 }
@@ -992,19 +990,7 @@
 }
 
 func getJavaVersion(ctx android.ModuleContext, javaVersion string, sdkContext sdkContext) javaVersion {
-	v := sdkContext.sdkVersion()
-	// For PDK builds, use the latest SDK version instead of "current"
-	if ctx.Config().IsPdkBuild() &&
-		(v == "" || v == "none" || v == "core_platform" || v == "current") {
-		sdkVersions := ctx.Config().Get(sdkVersionsKey).([]int)
-		latestSdkVersion := 0
-		if len(sdkVersions) > 0 {
-			latestSdkVersion = sdkVersions[len(sdkVersions)-1]
-		}
-		v = strconv.Itoa(latestSdkVersion)
-	}
-
-	sdk, err := sdkVersionToNumber(ctx, v)
+	sdk, err := sdkContext.sdkVersion().effectiveVersion(ctx)
 	if err != nil {
 		ctx.PropertyErrorf("sdk_version", "%s", err)
 	}
@@ -2282,11 +2268,11 @@
 	exportedSdkLibs       []string
 }
 
-func (j *Import) sdkVersion() string {
-	return String(j.properties.Sdk_version)
+func (j *Import) sdkVersion() sdkSpec {
+	return sdkSpecFrom(String(j.properties.Sdk_version))
 }
 
-func (j *Import) minSdkVersion() string {
+func (j *Import) minSdkVersion() sdkSpec {
 	return j.sdkVersion()
 }
 
diff --git a/java/sdk.go b/java/sdk.go
index 66eb284..f388358 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"
 )
 
@@ -37,83 +38,258 @@
 var apiFingerprintPathKey = android.NewOnceKey("apiFingerprintPathKey")
 
 type sdkContext interface {
-	// sdkVersion returns the sdk_version property of the current module, or an empty string if it is not set.
-	sdkVersion() string
+	// sdkVersion returns sdkSpec that corresponds to the sdk_version property of the current module
+	sdkVersion() sdkSpec
 	// systemModules returns the system_modules property of the current module, or an empty string if it is not set.
 	systemModules() string
-	// minSdkVersion returns the min_sdk_version property of the current module, or sdkVersion() if it is not set.
-	minSdkVersion() string
-	// targetSdkVersion returns the target_sdk_version property of the current module, or sdkVersion() if it is not set.
-	targetSdkVersion() string
+	// minSdkVersion returns sdkSpec that corresponds to the min_sdk_version property of the current module,
+	// or from sdk_version if it is not set.
+	minSdkVersion() sdkSpec
+	// targetSdkVersion returns the sdkSpec that corresponds to the target_sdk_version property of the current module,
+	// or from sdk_version if it is not set.
+	targetSdkVersion() sdkSpec
 }
 
-func sdkVersionOrDefault(ctx android.BaseModuleContext, v string) string {
-	switch v {
-	case "", "none", "current", "test_current", "system_current", "core_current", "core_platform":
-		return ctx.Config().DefaultAppTargetSdk()
+func UseApiFingerprint(ctx android.BaseModuleContext, v string) bool {
+	if v == ctx.Config().PlatformSdkCodename() &&
+		ctx.Config().UnbundledBuild() &&
+		!ctx.Config().UnbundledBuildUsePrebuiltSdks() &&
+		ctx.Config().IsEnvTrue("UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT") {
+		return true
+	}
+	return false
+}
+
+// sdkKind represents a particular category of an SDK spec like public, system, test, etc.
+type sdkKind int
+
+const (
+	sdkInvalid sdkKind = iota
+	sdkNone
+	sdkCore
+	sdkCorePlatform
+	sdkPublic
+	sdkSystem
+	sdkTest
+	sdkPrivate
+)
+
+// String returns the string representation of this sdkKind
+func (k sdkKind) String() string {
+	switch k {
+	case sdkPrivate:
+		return "private"
+	case sdkNone:
+		return "none"
+	case sdkPublic:
+		return "public"
+	case sdkSystem:
+		return "system"
+	case sdkTest:
+		return "test"
+	case sdkCore:
+		return "core"
+	case sdkCorePlatform:
+		return "core_platform"
 	default:
-		return v
+		return "invalid"
 	}
 }
 
-// Returns a sdk version as a number.  For modules targeting an unreleased SDK (meaning it does not yet have a number)
-// it returns android.FutureApiLevel (10000).
-func sdkVersionToNumber(ctx android.EarlyModuleContext, v string) (int, error) {
-	switch v {
-	case "", "none", "current", "test_current", "system_current", "core_current", "core_platform":
-		return ctx.Config().DefaultAppTargetSdkInt(), nil
-	default:
-		n := android.GetNumericSdkVersion(v)
-		if i, err := strconv.Atoi(n); err != nil {
-			return -1, fmt.Errorf("invalid sdk version %q", n)
-		} else {
-			return i, nil
+// sdkVersion represents a specific version number of an SDK spec of a particular kind
+type sdkVersion int
+
+const (
+	// special version number for a not-yet-frozen SDK
+	sdkVersionCurrent sdkVersion = sdkVersion(android.FutureApiLevel)
+	// special version number to be used for SDK specs where version number doesn't
+	// make sense, e.g. "none", "", etc.
+	sdkVersionNone sdkVersion = sdkVersion(0)
+)
+
+// isCurrent checks if the sdkVersion refers to the not-yet-published version of an sdkKind
+func (v sdkVersion) isCurrent() bool {
+	return v == sdkVersionCurrent
+}
+
+// isNumbered checks if the sdkVersion refers to the published (a.k.a numbered) version of an sdkKind
+func (v sdkVersion) isNumbered() bool {
+	return !v.isCurrent() && v != sdkVersionNone
+}
+
+// String returns the string representation of this sdkVersion.
+func (v sdkVersion) String() string {
+	if v.isCurrent() {
+		return "current"
+	} else if v.isNumbered() {
+		return strconv.Itoa(int(v))
+	}
+	return "(no version)"
+}
+
+// asNumberString directly converts the numeric value of this sdk version as a string.
+// When isNumbered() is true, this method is the same as String(). However, for sdkVersionCurrent
+// and sdkVersionNone, this returns 10000 and 0 while String() returns "current" and "(no version"),
+// respectively.
+func (v sdkVersion) asNumberString() string {
+	return strconv.Itoa(int(v))
+}
+
+// sdkSpec represents the kind and the version of an SDK for a module to build against
+type sdkSpec struct {
+	kind    sdkKind
+	version sdkVersion
+	raw     string
+}
+
+// valid checks if this sdkSpec is well-formed. Note however that true doesn't mean that the
+// specified SDK actually exists.
+func (s sdkSpec) valid() bool {
+	return s.kind != sdkInvalid
+}
+
+// specified checks if this sdkSpec is well-formed and is not "".
+func (s sdkSpec) specified() bool {
+	return s.valid() && s.kind != sdkPrivate
+}
+
+// prebuiltSdkAvailableForUnbundledBuilt tells whether this sdkSpec can have a prebuilt SDK
+// that can be used for unbundled builds.
+func (s sdkSpec) prebuiltSdkAvailableForUnbundledBuild() bool {
+	// "", "none", and "core_platform" are not available for unbundled build
+	// as we don't/can't have prebuilt stub for the versions
+	return s.kind != sdkPrivate && s.kind != sdkNone && s.kind != sdkCorePlatform
+}
+
+// forPdkBuild converts this sdkSpec into another sdkSpec that is for the PDK builds.
+func (s sdkSpec) forPdkBuild(ctx android.EarlyModuleContext) sdkSpec {
+	// For PDK builds, use the latest SDK version instead of "current" or ""
+	if s.kind == sdkPrivate || s.kind == sdkPublic {
+		kind := s.kind
+		if kind == sdkPrivate {
+			// We don't have prebuilt SDK for private APIs, so use the public SDK
+			// instead. This looks odd, but that's how it has been done.
+			// TODO(b/148271073): investigate the need for this.
+			kind = sdkPublic
 		}
+		version := sdkVersion(LatestSdkVersionInt(ctx))
+		return sdkSpec{kind, version, s.raw}
 	}
+	return s
 }
 
-func sdkVersionToNumberAsString(ctx android.EarlyModuleContext, v string) (string, error) {
-	n, err := sdkVersionToNumber(ctx, v)
-	if err != nil {
-		return "", err
+// usePrebuilt determines whether prebuilt SDK should be used for this sdkSpec with the given context.
+func (s sdkSpec) usePrebuilt(ctx android.EarlyModuleContext) bool {
+	if s.version.isCurrent() {
+		// "current" can be built from source and be from prebuilt SDK
+		return ctx.Config().UnbundledBuildUsePrebuiltSdks()
+	} else if s.version.isNumbered() {
+		// sanity check
+		if s.kind != sdkPublic && s.kind != sdkSystem && s.kind != sdkTest {
+			panic(fmt.Errorf("prebuilt SDK is not not available for sdkKind=%q", s.kind))
+			return false
+		}
+		// numbered SDKs are always from prebuilt
+		return true
 	}
-	return strconv.Itoa(n), nil
+	// "", "none", "core_platform" fall here
+	return false
+}
+
+// effectiveVersion converts an sdkSpec into the concrete sdkVersion that the module
+// should use. For modules targeting an unreleased SDK (meaning it does not yet have a number)
+// it returns android.FutureApiLevel(10000).
+func (s sdkSpec) effectiveVersion(ctx android.EarlyModuleContext) (sdkVersion, error) {
+	if !s.valid() {
+		return s.version, fmt.Errorf("invalid sdk version %q", s.raw)
+	}
+	if ctx.Config().IsPdkBuild() {
+		s = s.forPdkBuild(ctx)
+	}
+	if s.version.isNumbered() {
+		return s.version, nil
+	}
+	return sdkVersion(ctx.Config().DefaultAppTargetSdkInt()), nil
+}
+
+// effectiveVersionString converts an sdkSpec into the concrete version string that the module
+// should use. For modules targeting an unreleased SDK (meaning it does not yet have a number)
+// it returns the codename (P, Q, R, etc.)
+func (s sdkSpec) effectiveVersionString(ctx android.EarlyModuleContext) (string, error) {
+	ver, err := s.effectiveVersion(ctx)
+	if err == nil && int(ver) == ctx.Config().DefaultAppTargetSdkInt() {
+		return ctx.Config().DefaultAppTargetSdk(), nil
+	}
+	return ver.String(), err
+}
+
+func sdkSpecFrom(str string) sdkSpec {
+	switch str {
+	// special cases first
+	case "":
+		return sdkSpec{sdkPrivate, sdkVersionNone, str}
+	case "none":
+		return sdkSpec{sdkNone, sdkVersionNone, str}
+	case "core_platform":
+		return sdkSpec{sdkCorePlatform, sdkVersionNone, str}
+	default:
+		// the syntax is [kind_]version
+		sep := strings.LastIndex(str, "_")
+
+		var kindString string
+		if sep == 0 {
+			return sdkSpec{sdkInvalid, sdkVersionNone, str}
+		} else if sep == -1 {
+			kindString = ""
+		} else {
+			kindString = str[0:sep]
+		}
+		versionString := str[sep+1 : len(str)]
+
+		var kind sdkKind
+		switch kindString {
+		case "":
+			kind = sdkPublic
+		case "core":
+			kind = sdkCore
+		case "system":
+			kind = sdkSystem
+		case "test":
+			kind = sdkTest
+		default:
+			return sdkSpec{sdkInvalid, sdkVersionNone, str}
+		}
+
+		var version sdkVersion
+		if versionString == "current" {
+			version = sdkVersionCurrent
+		} else if i, err := strconv.Atoi(versionString); err == nil {
+			version = sdkVersion(i)
+		} else {
+			return sdkSpec{sdkInvalid, sdkVersionNone, str}
+		}
+
+		return sdkSpec{kind, version, str}
+	}
 }
 
 func decodeSdkDep(ctx android.EarlyModuleContext, sdkContext sdkContext) sdkDep {
-	v := sdkContext.sdkVersion()
-
-	// For PDK builds, use the latest SDK version instead of "current"
-	if ctx.Config().IsPdkBuild() && (v == "" || v == "current") {
-		sdkVersions := ctx.Config().Get(sdkVersionsKey).([]int)
-		latestSdkVersion := 0
-		if len(sdkVersions) > 0 {
-			latestSdkVersion = sdkVersions[len(sdkVersions)-1]
-		}
-		v = strconv.Itoa(latestSdkVersion)
-	}
-
-	numericSdkVersion, err := sdkVersionToNumber(ctx, v)
-	if err != nil {
-		ctx.PropertyErrorf("sdk_version", "%s", err)
+	sdkVersion := sdkContext.sdkVersion()
+	if !sdkVersion.valid() {
+		ctx.PropertyErrorf("sdk_version", "invalid version %q", sdkVersion.raw)
 		return sdkDep{}
 	}
 
-	toPrebuilt := func(sdk string) sdkDep {
-		var api, v string
-		if strings.Contains(sdk, "_") {
-			t := strings.Split(sdk, "_")
-			api = t[0]
-			v = t[1]
-		} else {
-			api = "public"
-			v = sdk
-		}
-		dir := filepath.Join("prebuilts", "sdk", v, api)
+	if ctx.Config().IsPdkBuild() {
+		sdkVersion = sdkVersion.forPdkBuild(ctx)
+	}
+
+	if sdkVersion.usePrebuilt(ctx) {
+		dir := filepath.Join("prebuilts", "sdk", sdkVersion.version.String(), sdkVersion.kind.String())
 		jar := filepath.Join(dir, "android.jar")
 		// There's no aidl for other SDKs yet.
 		// TODO(77525052): Add aidl files for other SDKs too.
-		public_dir := filepath.Join("prebuilts", "sdk", v, "public")
+		public_dir := filepath.Join("prebuilts", "sdk", sdkVersion.version.String(), "public")
 		aidl := filepath.Join(public_dir, "framework.aidl")
 		jarPath := android.ExistentPathForSource(ctx, jar)
 		aidlPath := android.ExistentPathForSource(ctx, aidl)
@@ -122,17 +298,17 @@
 		if (!jarPath.Valid() || !aidlPath.Valid()) && ctx.Config().AllowMissingDependencies() {
 			return sdkDep{
 				invalidVersion: true,
-				bootclasspath:  []string{fmt.Sprintf("sdk_%s_%s_android", api, v)},
+				bootclasspath:  []string{fmt.Sprintf("sdk_%s_%s_android", sdkVersion.kind, sdkVersion.version.String())},
 			}
 		}
 
 		if !jarPath.Valid() {
-			ctx.PropertyErrorf("sdk_version", "invalid sdk version %q, %q does not exist", sdk, jar)
+			ctx.PropertyErrorf("sdk_version", "invalid sdk version %q, %q does not exist", sdkVersion.raw, jar)
 			return sdkDep{}
 		}
 
 		if !aidlPath.Valid() {
-			ctx.PropertyErrorf("sdk_version", "invalid sdk version %q, %q does not exist", sdk, aidl)
+			ctx.PropertyErrorf("sdk_version", "invalid sdk version %q, %q does not exist", sdkVersion.raw, aidl)
 			return sdkDep{}
 		}
 
@@ -156,31 +332,26 @@
 
 	// Ensures that the specificed system SDK version is one of BOARD_SYSTEMSDK_VERSIONS (for vendor apks)
 	// or PRODUCT_SYSTEMSDK_VERSIONS (for other apks or when BOARD_SYSTEMSDK_VERSIONS is not set)
-	if strings.HasPrefix(v, "system_") && numericSdkVersion != android.FutureApiLevel {
+	if sdkVersion.kind == sdkSystem && sdkVersion.version.isNumbered() {
 		allowed_versions := ctx.DeviceConfig().PlatformSystemSdkVersions()
 		if ctx.DeviceSpecific() || ctx.SocSpecific() {
 			if len(ctx.DeviceConfig().SystemSdkVersions()) > 0 {
 				allowed_versions = ctx.DeviceConfig().SystemSdkVersions()
 			}
 		}
-		if len(allowed_versions) > 0 && !android.InList(strconv.Itoa(numericSdkVersion), allowed_versions) {
+		if len(allowed_versions) > 0 && !android.InList(sdkVersion.version.String(), allowed_versions) {
 			ctx.PropertyErrorf("sdk_version", "incompatible sdk version %q. System SDK version should be one of %q",
-				v, allowed_versions)
+				sdkVersion.raw, allowed_versions)
 		}
 	}
 
-	if ctx.Config().UnbundledBuildUsePrebuiltSdks() &&
-		v != "" && v != "none" && v != "core_platform" {
-		return toPrebuilt(v)
-	}
-
-	switch v {
-	case "":
+	switch sdkVersion.kind {
+	case sdkPrivate:
 		return sdkDep{
 			useDefaultLibs:     true,
 			frameworkResModule: "framework-res",
 		}
-	case "none":
+	case sdkNone:
 		systemModules := sdkContext.systemModules()
 		if systemModules == "" {
 			ctx.PropertyErrorf("sdk_version",
@@ -197,22 +368,22 @@
 			systemModules:  systemModules,
 			bootclasspath:  []string{systemModules},
 		}
-	case "core_platform":
+	case sdkCorePlatform:
 		return sdkDep{
 			useDefaultLibs:     true,
 			frameworkResModule: "framework-res",
 			noFrameworksLibs:   true,
 		}
-	case "current":
+	case sdkPublic:
 		return toModule("android_stubs_current", "framework-res", sdkFrameworkAidlPath(ctx))
-	case "system_current":
+	case sdkSystem:
 		return toModule("android_system_stubs_current", "framework-res", sdkFrameworkAidlPath(ctx))
-	case "test_current":
+	case sdkTest:
 		return toModule("android_test_stubs_current", "framework-res", sdkFrameworkAidlPath(ctx))
-	case "core_current":
+	case sdkCore:
 		return toModule("core.current.stubs", "", nil)
 	default:
-		return toPrebuilt(v)
+		panic(fmt.Errorf("invalid sdk %q", sdkVersion.raw))
 	}
 }
 
@@ -245,6 +416,15 @@
 	ctx.Config().Once(sdkVersionsKey, func() interface{} { return sdkVersions })
 }
 
+func LatestSdkVersionInt(ctx android.EarlyModuleContext) int {
+	sdkVersions := ctx.Config().Get(sdkVersionsKey).([]int)
+	latestSdkVersion := 0
+	if len(sdkVersions) > 0 {
+		latestSdkVersion = sdkVersions[len(sdkVersions)-1]
+	}
+	return latestSdkVersion
+}
+
 func sdkSingletonFactory() android.Singleton {
 	return sdkSingleton{}
 }
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 72c4a7c..530e02c 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -650,27 +650,27 @@
 	mctx.CreateModule(android.PrebuiltEtcFactory, &etcProps)
 }
 
-func (module *SdkLibrary) PrebuiltJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths {
-	var api, v string
-	if sdkVersion == "" || sdkVersion == "none" {
-		api = "system"
-		v = "current"
-	} else if strings.Contains(sdkVersion, "_") {
-		t := strings.Split(sdkVersion, "_")
-		api = t[0]
-		v = t[1]
+func (module *SdkLibrary) PrebuiltJars(ctx android.BaseModuleContext, s sdkSpec) android.Paths {
+	var ver sdkVersion
+	var kind sdkKind
+	if s.usePrebuilt(ctx) {
+		ver = s.version
+		kind = s.kind
 	} else {
-		api = "public"
-		v = sdkVersion
+		// We don't have prebuilt SDK for the specific sdkVersion.
+		// Instead of breaking the build, fallback to use "system_current"
+		ver = sdkVersionCurrent
+		kind = sdkSystem
 	}
-	dir := filepath.Join("prebuilts", "sdk", v, api)
+
+	dir := filepath.Join("prebuilts", "sdk", ver.String(), kind.String())
 	jar := filepath.Join(dir, module.BaseModuleName()+".jar")
 	jarPath := android.ExistentPathForSource(ctx, jar)
 	if !jarPath.Valid() {
 		if ctx.Config().AllowMissingDependencies() {
 			return android.Paths{android.PathForSource(ctx, jar)}
 		} else {
-			ctx.PropertyErrorf("sdk_library", "invalid sdk version %q, %q does not exist", sdkVersion, jar)
+			ctx.PropertyErrorf("sdk_library", "invalid sdk version %q, %q does not exist", s.raw, jar)
 		}
 		return nil
 	}
@@ -678,32 +678,34 @@
 }
 
 // to satisfy SdkLibraryDependency interface
-func (module *SdkLibrary) SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths {
+func (module *SdkLibrary) SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths {
 	// This module is just a wrapper for the stubs.
 	if ctx.Config().UnbundledBuildUsePrebuiltSdks() {
 		return module.PrebuiltJars(ctx, sdkVersion)
 	} else {
-		if strings.HasPrefix(sdkVersion, "system_") {
+		switch sdkVersion.kind {
+		case sdkSystem:
 			return module.systemApiStubsPath
-		} else if sdkVersion == "" {
+		case sdkPrivate:
 			return module.Library.HeaderJars()
-		} else {
+		default:
 			return module.publicApiStubsPath
 		}
 	}
 }
 
 // to satisfy SdkLibraryDependency interface
-func (module *SdkLibrary) SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths {
+func (module *SdkLibrary) SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths {
 	// This module is just a wrapper for the stubs.
 	if ctx.Config().UnbundledBuildUsePrebuiltSdks() {
 		return module.PrebuiltJars(ctx, sdkVersion)
 	} else {
-		if strings.HasPrefix(sdkVersion, "system_") {
+		switch sdkVersion.kind {
+		case sdkSystem:
 			return module.systemApiStubsImplPath
-		} else if sdkVersion == "" {
+		case sdkPrivate:
 			return module.Library.ImplementationJars()
-		} else {
+		default:
 			return module.publicApiStubsImplPath
 		}
 	}
@@ -923,13 +925,13 @@
 }
 
 // to satisfy SdkLibraryDependency interface
-func (module *sdkLibraryImport) SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths {
+func (module *sdkLibraryImport) SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths {
 	// This module is just a wrapper for the prebuilt stubs.
 	return module.stubsPath
 }
 
 // to satisfy SdkLibraryDependency interface
-func (module *sdkLibraryImport) SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths {
+func (module *sdkLibraryImport) SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths {
 	// This module is just a wrapper for the stubs.
 	return module.stubsPath
 }
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/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) {