Merge "Revert "Flag to control if generated SDK prebuilts are preferred.""
diff --git a/Android.bp b/Android.bp
index 45e661e..d6260b4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -116,3 +116,13 @@
 dex_bootjars {
     name: "dex_bootjars",
 }
+
+// Pseudo-test that's run on checkbuilds to ensure that get_clang_version can
+// parse cc/config/global.go.
+genrule {
+    name: "get_clang_version_test",
+    cmd: "$(location get_clang_version) > $(out)",
+    tools: ["get_clang_version"],
+    srcs: ["cc/config/global.go"],
+    out: ["clang-prebuilts-version.txt"],
+}
diff --git a/README.md b/README.md
index b7e93f4..10ddd73 100644
--- a/README.md
+++ b/README.md
@@ -213,8 +213,8 @@
 
 A module name's **scope** is the smallest namespace containing it. Suppose a
 source tree has `device/my` and `device/my/display` namespaces. If `libfoo`
-module is defined in `device/co/display/lib/Android.bp`, its namespace is
-`device/co/display`.
+module is defined in `device/my/display/lib/Android.bp`, its namespace is
+`device/my/display`.
 
 The name uniqueness thus means that module's name is unique within its scope. In
 other words, "//_scope_:_name_" is globally unique module reference, e.g,
diff --git a/android/mutator.go b/android/mutator.go
index 365bf29..819dd0f 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -19,6 +19,7 @@
 	"fmt"
 	"reflect"
 	"strings"
+	"sync"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
@@ -229,6 +230,9 @@
 var bp2buildDepsMutators = []RegisterMutatorFunc{}
 var bp2buildMutators = map[string]RegisterMutatorFunc{}
 
+// See http://b/192523357
+var bp2buildLock sync.Mutex
+
 // RegisterBp2BuildMutator registers specially crafted mutators for
 // converting Blueprint/Android modules into special modules that can
 // be code-generated into Bazel BUILD targets.
@@ -238,6 +242,9 @@
 	f := func(ctx RegisterMutatorsContext) {
 		ctx.TopDown(moduleType, m)
 	}
+	// Use a lock to avoid a concurrent map write if RegisterBp2BuildMutator is called in parallel
+	bp2buildLock.Lock()
+	defer bp2buildLock.Unlock()
 	bp2buildMutators[moduleType] = f
 }
 
diff --git a/apex/Android.bp b/apex/Android.bp
index 6269757..b9b5428 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -9,6 +9,7 @@
         "blueprint",
         "soong",
         "soong-android",
+        "soong-bazel",
         "soong-bpf",
         "soong-cc",
         "soong-filesystem",
diff --git a/apex/apex.go b/apex/apex.go
index baaf874..11df288 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -27,6 +27,7 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/bazel"
 	"android/soong/bpf"
 	"android/soong/cc"
 	prebuilt_etc "android/soong/etc"
@@ -53,6 +54,8 @@
 	ctx.PreArchMutators(registerPreArchMutators)
 	ctx.PreDepsMutators(RegisterPreDepsMutators)
 	ctx.PostDepsMutators(RegisterPostDepsMutators)
+
+	android.RegisterBp2BuildMutator("apex", ApexBundleBp2Build)
 }
 
 func registerPreArchMutators(ctx android.RegisterMutatorsContext) {
@@ -327,6 +330,7 @@
 	android.DefaultableModuleBase
 	android.OverridableModuleBase
 	android.SdkBase
+	android.BazelModuleBase
 
 	// Properties
 	properties            apexBundleProperties
@@ -3178,3 +3182,63 @@
 		},
 	}
 }
+
+// For Bazel / bp2build
+
+type bazelApexBundleAttributes struct {
+	Manifest bazel.LabelAttribute
+}
+
+type bazelApexBundle struct {
+	android.BazelTargetModuleBase
+	bazelApexBundleAttributes
+}
+
+func BazelApexBundleFactory() android.Module {
+	module := &bazelApexBundle{}
+	module.AddProperties(&module.bazelApexBundleAttributes)
+	android.InitBazelTargetModule(module)
+	return module
+}
+
+func ApexBundleBp2Build(ctx android.TopDownMutatorContext) {
+	module, ok := ctx.Module().(*apexBundle)
+	if !ok {
+		// Not an APEX bundle
+		return
+	}
+	if !module.ConvertWithBp2build(ctx) {
+		return
+	}
+	if ctx.ModuleType() != "apex" {
+		return
+	}
+
+	apexBundleBp2BuildInternal(ctx, module)
+}
+
+func apexBundleBp2BuildInternal(ctx android.TopDownMutatorContext, module *apexBundle) {
+	var manifestLabelAttribute bazel.LabelAttribute
+
+	manifestStringPtr := module.properties.Manifest
+	if module.properties.Manifest != nil {
+		manifestLabelAttribute.SetValue(android.BazelLabelForModuleSrcSingle(ctx, *manifestStringPtr))
+	}
+
+	attrs := &bazelApexBundleAttributes{
+		Manifest: manifestLabelAttribute,
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class:        "apex",
+		Bzl_load_location: "//build/bazel/rules:apex.bzl",
+	}
+
+	ctx.CreateBazelTargetModule(BazelApexBundleFactory, module.Name(), props, attrs)
+}
+
+func (m *bazelApexBundle) Name() string {
+	return m.BaseModuleName()
+}
+
+func (m *bazelApexBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index f783172..1bb0fb5 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -376,6 +376,12 @@
 			}
 		}
 
+		// Ignore any modules that do not implement ApexModule as they cannot have an APEX specific
+		// variant.
+		if _, ok := child.(android.ApexModule); !ok {
+			return false
+		}
+
 		// Strip off the prebuilt_ prefix if present before storing content to ensure consistent
 		// behavior whether there is a corresponding source module present or not.
 		depName = android.RemoveOptionalPrebuiltPrefix(depName)
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
index 0e6030e..dded14b 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -19,6 +19,7 @@
     ],
     deps: [
         "soong-android",
+        "soong-apex",
         "soong-bazel",
         "soong-cc",
         "soong-cc-config",
@@ -27,6 +28,7 @@
         "soong-sh",
     ],
     testSrcs: [
+        "apex_conversion_test.go",
         "build_conversion_test.go",
         "bzl_conversion_test.go",
         "cc_library_conversion_test.go",
diff --git a/bp2build/apex_conversion_test.go b/bp2build/apex_conversion_test.go
new file mode 100644
index 0000000..fbf6fa2
--- /dev/null
+++ b/bp2build/apex_conversion_test.go
@@ -0,0 +1,48 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"android/soong/android"
+	"android/soong/apex"
+	"testing"
+)
+
+func runApexTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, registerApexModuleTypes, tc)
+}
+
+func registerApexModuleTypes(ctx android.RegistrationContext) {
+}
+
+func TestApexBundleSimple(t *testing.T) {
+	runApexTestCase(t, bp2buildTestCase{
+		description:                        "apex - simple example",
+		moduleTypeUnderTest:                "apex",
+		moduleTypeUnderTestFactory:         apex.BundleFactory,
+		moduleTypeUnderTestBp2BuildMutator: apex.ApexBundleBp2Build,
+		filesystem:                         map[string]string{},
+		blueprint: `
+apex {
+	name: "apogee",
+	manifest: "manifest.json",
+}
+`,
+		expectedBazelTargets: []string{`apex(
+    name = "apogee",
+    manifest = "manifest.json",
+)`}})
+}
diff --git a/bp2build/cc_object_conversion_test.go b/bp2build/cc_object_conversion_test.go
index 57f75ea..8ede226 100644
--- a/bp2build/cc_object_conversion_test.go
+++ b/bp2build/cc_object_conversion_test.go
@@ -46,6 +46,7 @@
 		blueprint: `cc_object {
     name: "foo",
     local_include_dirs: ["include"],
+    default_shared_libs: [],
     cflags: [
         "-Wno-gcc-compat",
         "-Wall",
@@ -83,6 +84,7 @@
 		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
 		blueprint: `cc_object {
     name: "foo",
+    default_shared_libs: [],
     local_include_dirs: ["include"],
     srcs: [
         "a/b/*.h",
@@ -135,12 +137,14 @@
 		},
 		blueprint: `cc_object {
     name: "foo",
+    default_shared_libs: [],
     srcs: ["a/b/c.c"],
     objs: ["bar"],
 }
 
 cc_object {
     name: "bar",
+    default_shared_libs: [],
     srcs: ["x/y/z.c"],
 }
 `,
@@ -178,6 +182,7 @@
 		},
 		blueprint: `cc_object {
     name: "foo",
+    default_shared_libs: [],
     srcs: ["a/b/c.c"],
     include_build_directory: false,
 }
@@ -199,6 +204,7 @@
 		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
 		blueprint: `cc_object {
     name: "foo",
+    default_shared_libs: [],
     include_build_directory: false,
     product_variables: {
         platform_sdk_version: {
@@ -227,6 +233,7 @@
 		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
 		blueprint: `cc_object {
     name: "foo",
+    default_shared_libs: [],
     srcs: ["a.cpp"],
     arch: {
         x86: {
@@ -266,6 +273,7 @@
 		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
 		blueprint: `cc_object {
     name: "foo",
+    default_shared_libs: [],
     srcs: ["base.cpp"],
     arch: {
         x86: {
@@ -321,6 +329,7 @@
 		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
 		blueprint: `cc_object {
     name: "foo",
+    default_shared_libs: [],
     srcs: ["base.cpp"],
     target: {
         android: {
diff --git a/cc/cc.go b/cc/cc.go
index 15cb39a..1f73149 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -337,6 +337,7 @@
 
 	// Used by vendor snapshot to record dependencies from snapshot modules.
 	SnapshotSharedLibs  []string `blueprint:"mutated"`
+	SnapshotStaticLibs  []string `blueprint:"mutated"`
 	SnapshotRuntimeLibs []string `blueprint:"mutated"`
 
 	Installable *bool
@@ -525,8 +526,6 @@
 // feature represents additional (optional) steps to building cc-related modules, such as invocation
 // of clang-tidy.
 type feature interface {
-	begin(ctx BaseModuleContext)
-	deps(ctx DepsContext, deps Deps) Deps
 	flags(ctx ModuleContext, flags Flags) Flags
 	props() []interface{}
 }
@@ -571,7 +570,8 @@
 	sharedLibs []string
 	// Note nil and [] are semantically distinct. [] prevents linking against the defaults (usually
 	// libc, libm, etc.)
-	systemSharedLibs []string
+	systemSharedLibs  []string
+	defaultSharedLibs []string
 }
 
 // installer is the interface for an installer helper object. This helper is responsible for
@@ -1897,21 +1897,12 @@
 	if c.coverage != nil {
 		c.coverage.begin(ctx)
 	}
-	if c.sabi != nil {
-		c.sabi.begin(ctx)
-	}
-	if c.vndkdep != nil {
-		c.vndkdep.begin(ctx)
-	}
 	if c.lto != nil {
 		c.lto.begin(ctx)
 	}
 	if c.pgo != nil {
 		c.pgo.begin(ctx)
 	}
-	for _, feature := range c.features {
-		feature.begin(ctx)
-	}
 	if ctx.useSdk() && c.IsSdkVariant() {
 		version, err := nativeApiLevelFromUser(ctx, ctx.sdkVersion())
 		if err != nil {
@@ -1935,24 +1926,9 @@
 	if c.stl != nil {
 		deps = c.stl.deps(ctx, deps)
 	}
-	if c.sanitize != nil {
-		deps = c.sanitize.deps(ctx, deps)
-	}
 	if c.coverage != nil {
 		deps = c.coverage.deps(ctx, deps)
 	}
-	if c.sabi != nil {
-		deps = c.sabi.deps(ctx, deps)
-	}
-	if c.vndkdep != nil {
-		deps = c.vndkdep.deps(ctx, deps)
-	}
-	if c.lto != nil {
-		deps = c.lto.deps(ctx, deps)
-	}
-	for _, feature := range c.features {
-		deps = feature.deps(ctx, deps)
-	}
 
 	deps.WholeStaticLibs = android.LastUniqueStrings(deps.WholeStaticLibs)
 	deps.StaticLibs = android.LastUniqueStrings(deps.StaticLibs)
@@ -2855,6 +2831,8 @@
 					c.Properties.AndroidMkStaticLibs = append(
 						c.Properties.AndroidMkStaticLibs, makeLibName)
 				}
+				// Record BaseLibName for snapshots.
+				c.Properties.SnapshotStaticLibs = append(c.Properties.SnapshotStaticLibs, BaseLibName(depName))
 			}
 		} else if !c.IsStubs() {
 			// Stubs lib doesn't link to the runtime lib, object, crt, etc. dependencies.
@@ -3178,6 +3156,13 @@
 	return false
 }
 
+func (c *Module) StaticExecutable() bool {
+	if b, ok := c.linker.(*binaryDecorator); ok {
+		return b.static()
+	}
+	return false
+}
+
 func (c *Module) Object() bool {
 	if o, ok := c.linker.(interface {
 		object() bool
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 401fd6f..0a74e58 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -4046,12 +4046,12 @@
 		"${config.ArmToolchainClangCflags}",
 		"${config.ArmClangArmv7ANeonCflags}",
 		"${config.ArmClangGenericCflags}",
-		"export_include_dirs",
-		"linux_export_include_dirs",
-		"android_export_include_dirs",
-		"arm_export_include_dirs",
-		"lib32_export_include_dirs",
 		"android_arm_export_include_dirs",
+		"lib32_export_include_dirs",
+		"arm_export_include_dirs",
+		"android_export_include_dirs",
+		"linux_export_include_dirs",
+		"export_include_dirs",
 		"android_arm_local_include_dirs",
 		"lib32_local_include_dirs",
 		"arm_local_include_dirs",
diff --git a/cc/compiler.go b/cc/compiler.go
index 78a5a5d..69ead30 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -92,7 +92,7 @@
 
 	// list of generated headers to add to the include path. These are the names
 	// of genrule modules.
-	Generated_headers []string `android:"arch_variant"`
+	Generated_headers []string `android:"arch_variant,variant_prepend"`
 
 	// pass -frtti instead of -fno-rtti
 	Rtti *bool
diff --git a/cc/library.go b/cc/library.go
index 95f9b0a..4fd7c74 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -147,11 +147,12 @@
 
 	Cflags []string `android:"arch_variant"`
 
-	Enabled            *bool    `android:"arch_variant"`
-	Whole_static_libs  []string `android:"arch_variant"`
-	Static_libs        []string `android:"arch_variant"`
-	Shared_libs        []string `android:"arch_variant"`
-	System_shared_libs []string `android:"arch_variant"`
+	Enabled             *bool    `android:"arch_variant"`
+	Whole_static_libs   []string `android:"arch_variant"`
+	Static_libs         []string `android:"arch_variant"`
+	Shared_libs         []string `android:"arch_variant"`
+	System_shared_libs  []string `android:"arch_variant"`
+	Default_shared_libs []string `android:"arch_variant"`
 
 	Export_shared_lib_headers []string `android:"arch_variant"`
 	Export_static_lib_headers []string `android:"arch_variant"`
@@ -184,11 +185,11 @@
 	// be added to the include path (using -I) for this module and any module that links
 	// against this module.  Directories listed in export_include_dirs do not need to be
 	// listed in local_include_dirs.
-	Export_include_dirs []string `android:"arch_variant"`
+	Export_include_dirs []string `android:"arch_variant,variant_prepend"`
 
 	// list of directories that will be added to the system include path
 	// using -isystem for this module and any module that links against this module.
-	Export_system_include_dirs []string `android:"arch_variant"`
+	Export_system_include_dirs []string `android:"arch_variant,variant_prepend"`
 
 	Target struct {
 		Vendor, Product struct {
@@ -1156,11 +1157,17 @@
 		if library.StaticProperties.Static.System_shared_libs != nil {
 			library.baseLinker.Properties.System_shared_libs = library.StaticProperties.Static.System_shared_libs
 		}
+		if library.StaticProperties.Static.Default_shared_libs != nil {
+			library.baseLinker.Properties.Default_shared_libs = library.StaticProperties.Static.Default_shared_libs
+		}
 	} else if library.shared() {
 		// Compare with nil because an empty list needs to be propagated.
 		if library.SharedProperties.Shared.System_shared_libs != nil {
 			library.baseLinker.Properties.System_shared_libs = library.SharedProperties.Shared.System_shared_libs
 		}
+		if library.SharedProperties.Shared.Default_shared_libs != nil {
+			library.baseLinker.Properties.Default_shared_libs = library.SharedProperties.Shared.Default_shared_libs
+		}
 	}
 
 	deps = library.baseLinker.linkerDeps(ctx, deps)
@@ -1242,6 +1249,11 @@
 	} else {
 		specifiedDeps.systemSharedLibs = append(specifiedDeps.systemSharedLibs, properties.System_shared_libs...)
 	}
+	if specifiedDeps.defaultSharedLibs == nil {
+		specifiedDeps.defaultSharedLibs = properties.Default_shared_libs
+	} else {
+		specifiedDeps.defaultSharedLibs = append(specifiedDeps.defaultSharedLibs, properties.Default_shared_libs...)
+	}
 
 	specifiedDeps.sharedLibs = android.FirstUniqueStrings(specifiedDeps.sharedLibs)
 	if len(specifiedDeps.systemSharedLibs) > 0 {
@@ -1249,6 +1261,11 @@
 		// retained.
 		specifiedDeps.systemSharedLibs = android.FirstUniqueStrings(specifiedDeps.systemSharedLibs)
 	}
+	if len(specifiedDeps.defaultSharedLibs) > 0 {
+		// Skip this if defaultSharedLibs is either nil or [], to ensure they are
+		// retained.
+		specifiedDeps.defaultSharedLibs = android.FirstUniqueStrings(specifiedDeps.defaultSharedLibs)
+	}
 	return specifiedDeps
 }
 
diff --git a/cc/library_sdk_member.go b/cc/library_sdk_member.go
index 9010a1a..9ad2742 100644
--- a/cc/library_sdk_member.go
+++ b/cc/library_sdk_member.go
@@ -258,6 +258,12 @@
 		outputProperties.AddPropertyWithTag("system_shared_libs", libInfo.SystemSharedLibs, builder.SdkMemberReferencePropertyTag(false))
 	}
 
+	// SystemSharedLibs needs to be propagated if it's a list, even if it's empty,
+	// so check for non-nil instead of nonzero length.
+	if libInfo.DefaultSharedLibs != nil {
+		outputProperties.AddPropertyWithTag("default_shared_libs", libInfo.DefaultSharedLibs, builder.SdkMemberReferencePropertyTag(false))
+	}
+
 	// Map from property name to the include dirs to add to the prebuilt module in the snapshot.
 	includeDirs := make(map[string][]string)
 
@@ -387,6 +393,12 @@
 	// This field is exported as its contents may not be arch specific.
 	SystemSharedLibs []string `android:"arch_variant"`
 
+	// The set of default shared libraries. Note nil and [] are semantically
+	// distinct - see BaseLinkerProperties.Default_shared_libs.
+	//
+	// This field is exported as its contents may not be arch specific.
+	DefaultSharedLibs []string `android:"arch_variant"`
+
 	// The specific stubs version for the lib variant, or empty string if stubs
 	// are not in use.
 	//
@@ -462,6 +474,7 @@
 			}
 		}
 		p.SystemSharedLibs = specifiedDeps.systemSharedLibs
+		p.DefaultSharedLibs = specifiedDeps.defaultSharedLibs
 	}
 	p.ExportedGeneratedHeaders = exportedInfo.GeneratedHeaders
 
diff --git a/cc/linkable.go b/cc/linkable.go
index 231626e..dd87334 100644
--- a/cc/linkable.go
+++ b/cc/linkable.go
@@ -94,6 +94,9 @@
 	// SnapshotSharedLibs returns the list of shared library dependencies for this module.
 	SnapshotSharedLibs() []string
 
+	// SnapshotStaticLibs returns the list of static library dependencies for this module.
+	SnapshotStaticLibs() []string
+
 	// IsSnapshotPrebuilt returns true if this module is a snapshot prebuilt.
 	IsSnapshotPrebuilt() bool
 }
@@ -226,6 +229,9 @@
 	// Header returns true if this is a library headers module.
 	Header() bool
 
+	// StaticExecutable returns true if this is a binary module with "static_executable: true".
+	StaticExecutable() bool
+
 	// EverInstallable returns true if the module is ever installable
 	EverInstallable() bool
 
diff --git a/cc/linker.go b/cc/linker.go
index 7b16b40..13df232 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -45,11 +45,18 @@
 	// list of module-specific flags that will be used for all link steps
 	Ldflags []string `android:"arch_variant"`
 
-	// list of system libraries that will be dynamically linked to
-	// shared library and executable modules.  If unset, generally defaults to libc,
-	// libm, and libdl.  Set to [] to prevent linking against the defaults.
+	// list of system libraries that will be dynamically linked to shared library and executable
+	// modules that build against bionic (device or Linux bionic modules).  If unset, generally
+	// defaults to libc, libm, and libdl.  Set to [] to prevent linking against the defaults.
+	// Equivalent to default_shared_libs for modules that build against bionic, and ignored on
+	// modules that do not build against bionic.
 	System_shared_libs []string `android:"arch_variant"`
 
+	// list of system libraries that will be dynamically linked to shared library and executable
+	// modules.  If unset, generally defaults to libc, libm, and libdl.  Set to [] to prevent
+	// linking against the defaults.  Equivalent to system_shared_libs, but applies to all modules.
+	Default_shared_libs []string `android:"arch_variant"`
+
 	// allow the module to contain undefined symbols.  By default,
 	// modules cannot contain undefined symbols that are not satisified by their immediate
 	// dependencies.  Set this flag to true to remove --no-undefined from the linker flags.
@@ -231,6 +238,19 @@
 	linker.Properties.Ldflags = append(linker.Properties.Ldflags, flags...)
 }
 
+// overrideDefaultSharedLibraries returns the contents of the default_shared_libs or
+// system_shared_libs properties, and records an error if both are set.
+func (linker *baseLinker) overrideDefaultSharedLibraries(ctx BaseModuleContext) []string {
+	if linker.Properties.System_shared_libs != nil && linker.Properties.Default_shared_libs != nil {
+		ctx.PropertyErrorf("system_shared_libs", "cannot be specified if default_shared_libs is also specified")
+	}
+	if ctx.toolchain().Bionic() && linker.Properties.System_shared_libs != nil {
+		// system_shared_libs is only honored when building against bionic.
+		return linker.Properties.System_shared_libs
+	}
+	return linker.Properties.Default_shared_libs
+}
+
 // linkerInit initializes dynamic properties of the linker (such as runpath).
 func (linker *baseLinker) linkerInit(ctx BaseModuleContext) {
 	if ctx.toolchain().Is64Bit() {
@@ -331,22 +351,22 @@
 		deps.SharedLibs = append(deps.SharedLibs, linker.Properties.Target.Platform.Shared_libs...)
 	}
 
+	deps.SystemSharedLibs = linker.overrideDefaultSharedLibraries(ctx)
+	// In Bazel conversion mode, variations have not been specified, so SystemSharedLibs may
+	// inaccuarately appear unset, which can cause issues with circular dependencies.
+	if deps.SystemSharedLibs == nil && !ctx.BazelConversionMode() {
+		// Provide a default set of shared libraries if default_shared_libs and system_shared_libs
+		// are unspecified.  Note: If an empty list [] is specified, it implies that the module
+		// declines the default shared libraries.
+		deps.SystemSharedLibs = append(deps.SystemSharedLibs, ctx.toolchain().DefaultSharedLibraries()...)
+	}
+
 	if ctx.toolchain().Bionic() {
 		// libclang_rt.builtins has to be last on the command line
 		if !Bool(linker.Properties.No_libcrt) && !ctx.header() {
 			deps.LateStaticLibs = append(deps.LateStaticLibs, config.BuiltinsRuntimeLibrary(ctx.toolchain()))
 		}
 
-		deps.SystemSharedLibs = linker.Properties.System_shared_libs
-		// In Bazel conversion mode, variations have not been specified, so SystemSharedLibs may
-		// inaccuarately appear unset, which can cause issues with circular dependencies.
-		if deps.SystemSharedLibs == nil && !ctx.BazelConversionMode() {
-			// Provide a default system_shared_libs if it is unspecified. Note: If an
-			// empty list [] is specified, it implies that the module declines the
-			// default system_shared_libs.
-			deps.SystemSharedLibs = append(deps.SystemSharedLibs, ctx.toolchain().DefaultSharedLibraries()...)
-		}
-
 		if inList("libdl", deps.SharedLibs) {
 			// If system_shared_libs has libc but not libdl, make sure shared_libs does not
 			// have libdl to avoid loading libdl before libc.
@@ -573,6 +593,11 @@
 	} else {
 		specifiedDeps.systemSharedLibs = append(specifiedDeps.systemSharedLibs, linker.Properties.System_shared_libs...)
 	}
+	if specifiedDeps.defaultSharedLibs == nil {
+		specifiedDeps.defaultSharedLibs = linker.Properties.Default_shared_libs
+	} else {
+		specifiedDeps.defaultSharedLibs = append(specifiedDeps.defaultSharedLibs, linker.Properties.Default_shared_libs...)
+	}
 
 	return specifiedDeps
 }
diff --git a/cc/lto.go b/cc/lto.go
index a3b28d9..fccb597 100644
--- a/cc/lto.go
+++ b/cc/lto.go
@@ -80,10 +80,6 @@
 	}
 }
 
-func (lto *lto) deps(ctx BaseModuleContext, deps Deps) Deps {
-	return deps
-}
-
 func (lto *lto) useClangLld(ctx BaseModuleContext) bool {
 	if lto.Properties.Use_clang_lld != nil {
 		return Bool(lto.Properties.Use_clang_lld)
diff --git a/cc/object.go b/cc/object.go
index 39fc43d..9f2db2e 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -67,9 +67,19 @@
 }
 
 type ObjectLinkerProperties struct {
+	// list of static library modules that should only provide headers for this module.
+	Static_libs []string `android:"arch_variant,variant_prepend"`
+
+	// list of shared library modules should only provide headers for this module.
+	Shared_libs []string `android:"arch_variant"`
+
 	// list of modules that should only provide headers for this module.
 	Header_libs []string `android:"arch_variant,variant_prepend"`
 
+	// list of default libraries that will provide headers for this module.  If unset, generally
+	// defaults to libc, libm, and libdl.  Set to [] to prevent using headers from the defaults.
+	Default_shared_libs []string `android:"arch_variant"`
+
 	// names of other cc_object modules to link into this module using partial linking
 	Objs []string `android:"arch_variant"`
 
@@ -84,8 +94,8 @@
 	Crt *bool
 }
 
-func newObject() *Module {
-	module := newBaseModule(android.HostAndDeviceSupported, android.MultilibBoth)
+func newObject(hod android.HostOrDeviceSupported) *Module {
+	module := newBaseModule(hod, android.MultilibBoth)
 	module.sanitize = &sanitize{}
 	module.stl = &stl{}
 	return module
@@ -95,7 +105,7 @@
 // necessary, but sometimes used to generate .s files from .c files to use as
 // input to a cc_genrule module.
 func ObjectFactory() android.Module {
-	module := newObject()
+	module := newObject(android.HostAndDeviceSupported)
 	module.linker = &objectLinker{
 		baseLinker: NewBaseLinker(module.sanitize),
 	}
@@ -198,7 +208,18 @@
 
 func (object *objectLinker) linkerDeps(ctx DepsContext, deps Deps) Deps {
 	deps.HeaderLibs = append(deps.HeaderLibs, object.Properties.Header_libs...)
+	deps.SharedLibs = append(deps.SharedLibs, object.Properties.Shared_libs...)
+	deps.StaticLibs = append(deps.StaticLibs, object.Properties.Static_libs...)
 	deps.ObjFiles = append(deps.ObjFiles, object.Properties.Objs...)
+
+	deps.SystemSharedLibs = object.Properties.Default_shared_libs
+	if deps.SystemSharedLibs == nil {
+		// Provide a default set of shared libraries if default_shared_libs is unspecified.
+		// Note: If an empty list [] is specified, it implies that the module declines the
+		// default shared libraries.
+		deps.SystemSharedLibs = append(deps.SystemSharedLibs, ctx.toolchain().DefaultSharedLibraries()...)
+	}
+	deps.LateSharedLibs = append(deps.LateSharedLibs, deps.SystemSharedLibs...)
 	return deps
 }
 
@@ -247,6 +268,20 @@
 	return outputFile
 }
 
+func (object *objectLinker) linkerSpecifiedDeps(specifiedDeps specifiedDeps) specifiedDeps {
+	specifiedDeps.sharedLibs = append(specifiedDeps.sharedLibs, object.Properties.Shared_libs...)
+
+	// Must distinguish nil and [] in default_shared_libs - ensure that [] in
+	// either input list doesn't come out as nil.
+	if specifiedDeps.defaultSharedLibs == nil {
+		specifiedDeps.defaultSharedLibs = object.Properties.Default_shared_libs
+	} else {
+		specifiedDeps.defaultSharedLibs = append(specifiedDeps.defaultSharedLibs, object.Properties.Default_shared_libs...)
+	}
+
+	return specifiedDeps
+}
+
 func (object *objectLinker) unstrippedOutputFilePath() android.Path {
 	return nil
 }
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index fd310a2..f7154ec 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -388,8 +388,8 @@
 	return true
 }
 
-func newPrebuiltObject() *Module {
-	module := newObject()
+func NewPrebuiltObject(hod android.HostOrDeviceSupported) *Module {
+	module := newObject(hod)
 	prebuilt := &prebuiltObjectLinker{
 		objectLinker: objectLinker{
 			baseLinker: NewBaseLinker(nil),
@@ -403,7 +403,7 @@
 }
 
 func prebuiltObjectFactory() android.Module {
-	module := newPrebuiltObject()
+	module := NewPrebuiltObject(android.HostAndDeviceSupported)
 	return module.Init()
 }
 
diff --git a/cc/sabi.go b/cc/sabi.go
index 1f331cb..5fd6f5d 100644
--- a/cc/sabi.go
+++ b/cc/sabi.go
@@ -45,12 +45,6 @@
 	return []interface{}{&sabi.Properties}
 }
 
-func (sabi *sabi) begin(ctx BaseModuleContext) {}
-
-func (sabi *sabi) deps(ctx BaseModuleContext, deps Deps) Deps {
-	return deps
-}
-
 func (sabi *sabi) flags(ctx ModuleContext, flags Flags) Flags {
 	// Filter out flags which libTooling don't understand.
 	// This is here for legacy reasons and future-proof, in case the version of libTooling and clang
diff --git a/cc/sanitize.go b/cc/sanitize.go
index b0eb0c6..cdd7dfb 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -499,14 +499,6 @@
 	}
 }
 
-func (sanitize *sanitize) deps(ctx BaseModuleContext, deps Deps) Deps {
-	if !sanitize.Properties.SanitizerEnabled { // || c.static() {
-		return deps
-	}
-
-	return deps
-}
-
 func toDisableImplicitIntegerChange(flags []string) bool {
 	// Returns true if any flag is fsanitize*integer, and there is
 	// no explicit flag about sanitize=implicit-integer-sign-change.
diff --git a/cc/snapshot_prebuilt.go b/cc/snapshot_prebuilt.go
index 3a382a1..fb89224 100644
--- a/cc/snapshot_prebuilt.go
+++ b/cc/snapshot_prebuilt.go
@@ -931,7 +931,7 @@
 // development/vendor_snapshot/update.py. As a part of vendor snapshot, vendor_snapshot_object
 // overrides the vendor variant of the cc object with the same name, if BOARD_VNDK_VERSION is set.
 func VendorSnapshotObjectFactory() android.Module {
-	module := newObject()
+	module := newObject(android.DeviceSupported)
 
 	prebuilt := &snapshotObjectLinker{
 		objectLinker: objectLinker{
@@ -949,7 +949,7 @@
 // development/vendor_snapshot/update.py. As a part of recovery snapshot, recovery_snapshot_object
 // overrides the recovery variant of the cc object with the same name, if BOARD_VNDK_VERSION is set.
 func RecoverySnapshotObjectFactory() android.Module {
-	module := newObject()
+	module := newObject(android.DeviceSupported)
 
 	prebuilt := &snapshotObjectLinker{
 		objectLinker: objectLinker{
diff --git a/cc/snapshot_utils.go b/cc/snapshot_utils.go
index a6c8ed5..b0538be 100644
--- a/cc/snapshot_utils.go
+++ b/cc/snapshot_utils.go
@@ -53,6 +53,10 @@
 	return m.Properties.SnapshotSharedLibs
 }
 
+func (m *Module) SnapshotStaticLibs() []string {
+	return m.Properties.SnapshotStaticLibs
+}
+
 // snapshotLibraryInterface is an interface for libraries captured to VNDK / vendor snapshots.
 type snapshotLibraryInterface interface {
 	libraryInterface
diff --git a/cc/testing.go b/cc/testing.go
index 80cc0ef..b9d84f6 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -367,6 +367,7 @@
 			stl: "none",
 			min_sdk_version: "16",
 			crt: true,
+			default_shared_libs: [],
 			apex_available: [
 				"//apex_available:platform",
 				"//apex_available:anyapex",
diff --git a/cc/tidy.go b/cc/tidy.go
index 616cf8a..b2382e8 100644
--- a/cc/tidy.go
+++ b/cc/tidy.go
@@ -61,13 +61,6 @@
 	return []interface{}{&tidy.Properties}
 }
 
-func (tidy *tidyFeature) begin(ctx BaseModuleContext) {
-}
-
-func (tidy *tidyFeature) deps(ctx DepsContext, deps Deps) Deps {
-	return deps
-}
-
 func (tidy *tidyFeature) flags(ctx ModuleContext, flags Flags) Flags {
 	CheckBadTidyFlags(ctx, "tidy_flags", tidy.Properties.Tidy_flags)
 	CheckBadTidyChecks(ctx, "tidy_checks", tidy.Properties.Tidy_checks)
diff --git a/cc/vendor_snapshot.go b/cc/vendor_snapshot.go
index 5a303c8..7ba2363 100644
--- a/cc/vendor_snapshot.go
+++ b/cc/vendor_snapshot.go
@@ -242,10 +242,12 @@
 	SanitizeUbsanDep   bool     `json:",omitempty"`
 
 	// binary flags
-	Symlinks []string `json:",omitempty"`
+	Symlinks         []string `json:",omitempty"`
+	StaticExecutable bool     `json:",omitempty"`
 
 	// dependencies
 	SharedLibs  []string `json:",omitempty"`
+	StaticLibs  []string `json:",omitempty"`
 	RuntimeLibs []string `json:",omitempty"`
 	Required    []string `json:",omitempty"`
 
@@ -381,6 +383,8 @@
 			if m.Shared() {
 				prop.SharedLibs = m.SnapshotSharedLibs()
 			}
+			// static libs dependencies are required to collect the NOTICE files.
+			prop.StaticLibs = m.SnapshotStaticLibs()
 			if sanitizable, ok := m.(PlatformSanitizeable); ok {
 				if sanitizable.Static() && sanitizable.SanitizePropDefined() {
 					prop.SanitizeMinimalDep = sanitizable.MinimalRuntimeDep() || sanitizable.MinimalRuntimeNeeded()
@@ -426,8 +430,10 @@
 		} else if m.Binary() {
 			// binary flags
 			prop.Symlinks = m.Symlinks()
+			prop.StaticExecutable = m.StaticExecutable()
 			prop.SharedLibs = m.SnapshotSharedLibs()
-
+			// static libs dependencies are required to collect the NOTICE files.
+			prop.StaticLibs = m.SnapshotStaticLibs()
 			// install bin
 			binPath := m.OutputFile().Path()
 			snapshotBinOut := filepath.Join(snapshotArchDir, targetArch, "binary", binPath.Base())
diff --git a/cc/vndk.go b/cc/vndk.go
index 0b40076..499d428 100644
--- a/cc/vndk.go
+++ b/cc/vndk.go
@@ -100,12 +100,6 @@
 	return []interface{}{&vndk.Properties}
 }
 
-func (vndk *vndkdep) begin(ctx BaseModuleContext) {}
-
-func (vndk *vndkdep) deps(ctx BaseModuleContext, deps Deps) Deps {
-	return deps
-}
-
 func (vndk *vndkdep) isVndk() bool {
 	return Bool(vndk.Properties.Vndk.Enabled)
 }
diff --git a/java/base.go b/java/base.go
index 77f6fc6..6b81196 100644
--- a/java/base.go
+++ b/java/base.go
@@ -870,6 +870,7 @@
 	if aaptSrcJar != nil {
 		srcJars = append(srcJars, aaptSrcJar)
 	}
+	srcFiles = srcFiles.FilterOutByExt(".srcjar")
 
 	if j.properties.Jarjar_rules != nil {
 		j.expandJarjarRules = android.PathForModuleSrc(ctx, *j.properties.Jarjar_rules)
diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go
index 515dd89..bdf0dae 100644
--- a/java/bootclasspath_fragment.go
+++ b/java/bootclasspath_fragment.go
@@ -162,11 +162,12 @@
 	// versioned sdk.
 	produceHiddenAPIOutput(ctx android.ModuleContext, contents []android.Module, input HiddenAPIFlagInput) *HiddenAPIOutput
 
-	// produceBootImageFiles produces the boot image (i.e. .art, .oat and .vdex) files for each of the
-	// required android.ArchType values in the returned map.
+	// produceBootImageFiles will attempt to produce rules to create the boot image files at the paths
+	// predefined in the bootImageConfig.
 	//
-	// It must return nil if the boot image files cannot be produced for whatever reason.
-	produceBootImageFiles(ctx android.ModuleContext, imageConfig *bootImageConfig, contents []android.Module) bootImageFilesByArch
+	// If it could not create the files then it will return nil. Otherwise, it will return a map from
+	// android.ArchType to the predefined paths of the boot image files.
+	produceBootImageFiles(ctx android.ModuleContext, imageConfig *bootImageConfig) bootImageFilesByArch
 }
 
 var _ commonBootclasspathFragment = (*BootclasspathFragmentModule)(nil)
@@ -454,9 +455,13 @@
 		if imageConfig != nil {
 			// Delegate the production of the boot image files to a module type specific method.
 			common := ctx.Module().(commonBootclasspathFragment)
-			bootImageFilesByArch = common.produceBootImageFiles(ctx, imageConfig, contents)
+			bootImageFilesByArch = common.produceBootImageFiles(ctx, imageConfig)
 
 			if shouldCopyBootFilesToPredefinedLocations(ctx, imageConfig) {
+				// Zip the boot image files up, if available. This will generate the zip file in a
+				// predefined location.
+				buildBootImageZipInPredefinedLocation(ctx, imageConfig, bootImageFilesByArch)
+
 				// Copy the dex jars of this fragment's content modules to their predefined locations.
 				copyBootJarsToPredefinedLocations(ctx, hiddenAPIOutput.EncodedBootDexFilesByModule, imageConfig.dexPathsByModule)
 			}
@@ -532,27 +537,18 @@
 
 	global := dexpreopt.GetGlobalConfig(ctx)
 
-	// Convert content names to their appropriate stems, in case a test library is overriding an actual boot jar
-	var stems []string
-	for _, name := range b.properties.Contents {
-		dep := ctx.GetDirectDepWithTag(name, bootclasspathFragmentContentDepTag)
-		if m, ok := dep.(ModuleWithStem); ok {
-			stems = append(stems, m.Stem())
-		} else {
-			ctx.PropertyErrorf("contents", "%v is not a ModuleWithStem", name)
-		}
-	}
+	possibleUpdatableModules := gatherPossibleUpdatableModuleNamesAndStems(ctx, b.properties.Contents, bootclasspathFragmentContentDepTag)
 
 	// Only create configs for updatable boot jars. Non-updatable boot jars must be part of the
 	// platform_bootclasspath's classpath proto config to guarantee that they come before any
 	// updatable jars at runtime.
-	jars := global.UpdatableBootJars.Filter(stems)
+	jars := global.UpdatableBootJars.Filter(possibleUpdatableModules)
 
 	// TODO(satayev): for apex_test we want to include all contents unconditionally to classpaths
 	// config. However, any test specific jars would not be present in UpdatableBootJars. Instead,
 	// we should check if we are creating a config for apex_test via ApexInfo and amend the values.
 	// This is an exception to support end-to-end test for SdkExtensions, until such support exists.
-	if android.InList("test_framework-sdkextensions", stems) {
+	if android.InList("test_framework-sdkextensions", possibleUpdatableModules) {
 		jars = jars.Append("com.android.sdkext", "test_framework-sdkextensions")
 	}
 	return jars
@@ -673,55 +669,49 @@
 }
 
 // produceBootImageFiles builds the boot image files from the source if it is required.
-func (b *BootclasspathFragmentModule) produceBootImageFiles(ctx android.ModuleContext, imageConfig *bootImageConfig, contents []android.Module) bootImageFilesByArch {
+func (b *BootclasspathFragmentModule) produceBootImageFiles(ctx android.ModuleContext, imageConfig *bootImageConfig) bootImageFilesByArch {
 	if SkipDexpreoptBootJars(ctx) {
 		return nil
 	}
 
-	// Force the GlobalSoongConfig to be created and cached for use by the dex_bootjars
-	// GenerateSingletonBuildActions method as it cannot create it for itself.
-	dexpreopt.GetGlobalSoongConfig(ctx)
-
 	// Only generate the boot image if the configuration does not skip it.
-	if !b.generateBootImageBuildActions(ctx, contents, imageConfig) {
-		return nil
-	}
-
-	// Only make the files available to an apex if they were actually generated.
-	files := bootImageFilesByArch{}
-	for _, variant := range imageConfig.apexVariants() {
-		files[variant.target.Arch.ArchType] = variant.imagesDeps.Paths()
-	}
-
-	return files
+	return b.generateBootImageBuildActions(ctx, imageConfig)
 }
 
 // generateBootImageBuildActions generates ninja rules to create the boot image if required for this
 // module.
 //
-// Returns true if the boot image is created, false otherwise.
-func (b *BootclasspathFragmentModule) generateBootImageBuildActions(ctx android.ModuleContext, contents []android.Module, imageConfig *bootImageConfig) bool {
+// If it could not create the files then it will return nil. Otherwise, it will return a map from
+// android.ArchType to the predefined paths of the boot image files.
+func (b *BootclasspathFragmentModule) generateBootImageBuildActions(ctx android.ModuleContext, imageConfig *bootImageConfig) bootImageFilesByArch {
 	global := dexpreopt.GetGlobalConfig(ctx)
 	if !shouldBuildBootImages(ctx.Config(), global) {
-		return false
+		return nil
 	}
 
 	// Bootclasspath fragment modules that are for the platform do not produce a boot image.
 	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
 	if apexInfo.IsForPlatform() {
-		return false
+		return nil
 	}
 
 	// Bootclasspath fragment modules that are versioned do not produce a boot image.
 	if android.IsModuleInVersionedSdk(ctx.Module()) {
-		return false
+		return nil
 	}
 
 	// Build a profile for the image config and then use that to build the boot image.
 	profile := bootImageProfileRule(ctx, imageConfig)
-	buildBootImage(ctx, imageConfig, profile)
 
-	return true
+	// Build boot image files for the host variants.
+	buildBootImageVariantsForBuildOs(ctx, imageConfig, profile)
+
+	// Build boot image files for the android variants.
+	androidBootImageFilesByArch := buildBootImageVariantsForAndroidOs(ctx, imageConfig, profile)
+
+	// Return the boot image files for the android variants for inclusion in an APEX and to be zipped
+	// up for the dist.
+	return androidBootImageFilesByArch
 }
 
 // Collect information for opening IDE project files in java/jdeps.go.
@@ -949,7 +939,7 @@
 }
 
 // produceBootImageFiles extracts the boot image files from the APEX if available.
-func (module *prebuiltBootclasspathFragmentModule) produceBootImageFiles(ctx android.ModuleContext, imageConfig *bootImageConfig, contents []android.Module) bootImageFilesByArch {
+func (module *prebuiltBootclasspathFragmentModule) produceBootImageFiles(ctx android.ModuleContext, imageConfig *bootImageConfig) bootImageFilesByArch {
 	if !shouldCopyBootFilesToPredefinedLocations(ctx, imageConfig) {
 		return nil
 	}
@@ -971,6 +961,7 @@
 	}
 
 	di := ctx.OtherModuleProvider(deapexerModule, android.DeapexerProvider).(android.DeapexerInfo)
+	files := bootImageFilesByArch{}
 	for _, variant := range imageConfig.apexVariants() {
 		arch := variant.target.Arch.ArchType
 		for _, toPath := range variant.imagesDeps {
@@ -978,6 +969,10 @@
 			// Get the path to the file that the deapexer extracted from the prebuilt apex file.
 			fromPath := di.PrebuiltExportPath(apexRelativePath)
 
+			// Return the toPath as the calling code expects the paths in the returned map to be the
+			// paths predefined in the bootImageConfig.
+			files[arch] = append(files[arch], toPath)
+
 			// Copy the file to the predefined location.
 			ctx.Build(pctx, android.BuildParams{
 				Rule:   android.Cp,
@@ -987,10 +982,13 @@
 		}
 	}
 
-	// The returned files will be made available to APEXes that include a bootclasspath_fragment.
-	// However, as a prebuilt_bootclasspath_fragment can never contribute to an APEX there is no point
-	// in returning any files.
-	return nil
+	// Build the boot image files for the host variants. These are built from the dex files provided
+	// by the contents of this module as prebuilt versions of the host boot image files are not
+	// available, i.e. there is no host specific prebuilt apex containing them. This has to be built
+	// without a profile as the prebuilt modules do not provide a profile.
+	buildBootImageVariantsForBuildOs(ctx, imageConfig, nil)
+
+	return files
 }
 
 var _ commonBootclasspathFragment = (*prebuiltBootclasspathFragmentModule)(nil)
diff --git a/java/bootclasspath_fragment_test.go b/java/bootclasspath_fragment_test.go
index b469886..3d0e155 100644
--- a/java/bootclasspath_fragment_test.go
+++ b/java/bootclasspath_fragment_test.go
@@ -197,7 +197,7 @@
 				],
 			},
 			core_platform_api: {
-				stub_libs: ["mycoreplatform"],
+				stub_libs: ["mycoreplatform.stubs"],
 			},
 		}
 
diff --git a/java/classpath_fragment.go b/java/classpath_fragment.go
index f7a200a..12bb711 100644
--- a/java/classpath_fragment.go
+++ b/java/classpath_fragment.go
@@ -91,6 +91,29 @@
 	maxSdkVersion int32
 }
 
+// gatherPossibleUpdatableModuleNamesAndStems returns a set of module and stem names from the
+// supplied contents that may be in the updatable boot jars.
+//
+// The module names are included because sometimes the stem is set to just change the name of
+// the installed file and it expects the configuration to still use the actual module name.
+//
+// The stem names are included because sometimes the stem is set to change the effective name of the
+// module that is used in the configuration as well,e .g. when a test library is overriding an
+// actual boot jar
+func gatherPossibleUpdatableModuleNamesAndStems(ctx android.ModuleContext, contents []string, tag blueprint.DependencyTag) []string {
+	set := map[string]struct{}{}
+	for _, name := range contents {
+		dep := ctx.GetDirectDepWithTag(name, tag)
+		set[name] = struct{}{}
+		if m, ok := dep.(ModuleWithStem); ok {
+			set[m.Stem()] = struct{}{}
+		} else {
+			ctx.PropertyErrorf("contents", "%v is not a ModuleWithStem", name)
+		}
+	}
+	return android.SortedStringKeys(set)
+}
+
 // Converts android.ConfiguredJarList into a list of classpathJars for each given classpathType.
 func configuredJarListToClasspathJars(ctx android.ModuleContext, configuredJars android.ConfiguredJarList, classpaths ...classpathType) []classpathJar {
 	paths := configuredJars.DevicePaths(ctx.Config(), android.Android)
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 03769fa..19c65ca 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -502,32 +502,72 @@
 	}
 }
 
-// buildBootImage takes a bootImageConfig, and creates rules to build it.
-func buildBootImage(ctx android.ModuleContext, image *bootImageConfig, profile android.WritablePath) {
-	var zipFiles android.Paths
+// buildBootImageVariantsForAndroidOs generates rules to build the boot image variants for the
+// android.Android OsType and returns a map from the architectures to the paths of the generated
+// boot image files.
+//
+// The paths are returned because they are needed elsewhere in Soong, e.g. for populating an APEX.
+func buildBootImageVariantsForAndroidOs(ctx android.ModuleContext, image *bootImageConfig, profile android.WritablePath) bootImageFilesByArch {
+	return buildBootImageForOsType(ctx, image, profile, android.Android)
+}
+
+// buildBootImageVariantsForBuildOs generates rules to build the boot image variants for the
+// android.BuildOs OsType, i.e. the type of OS on which the build is being running.
+//
+// The files need to be generated into their predefined location because they are used from there
+// both within Soong and outside, e.g. for ART based host side testing and also for use by some
+// cloud based tools. However, they are not needed by callers of this function and so the paths do
+// not need to be returned from this func, unlike the buildBootImageVariantsForAndroidOs func.
+func buildBootImageVariantsForBuildOs(ctx android.ModuleContext, image *bootImageConfig, profile android.WritablePath) {
+	buildBootImageForOsType(ctx, image, profile, android.BuildOs)
+}
+
+// buildBootImageForOsType takes a bootImageConfig, a profile file and an android.OsType
+// boot image files are required for and it creates rules to build the boot image
+// files for all the required architectures for them.
+//
+// It returns a map from android.ArchType to the predefined paths of the boot image files.
+func buildBootImageForOsType(ctx android.ModuleContext, image *bootImageConfig, profile android.WritablePath, requiredOsType android.OsType) bootImageFilesByArch {
+	filesByArch := bootImageFilesByArch{}
 	for _, variant := range image.variants {
-		files := buildBootImageVariant(ctx, variant, profile)
-		if variant.target.Os == android.Android {
-			zipFiles = append(zipFiles, files.Paths()...)
+		if variant.target.Os == requiredOsType {
+			buildBootImageVariant(ctx, variant, profile)
+			filesByArch[variant.target.Arch.ArchType] = variant.imagesDeps.Paths()
 		}
 	}
 
-	if image.zip != nil {
-		rule := android.NewRuleBuilder(pctx, ctx)
-		rule.Command().
-			BuiltTool("soong_zip").
-			FlagWithOutput("-o ", image.zip).
-			FlagWithArg("-C ", image.dir.Join(ctx, android.Android.String()).String()).
-			FlagWithInputList("-f ", zipFiles, " -f ")
+	return filesByArch
+}
 
-		rule.Build("zip_"+image.name, "zip "+image.name+" image")
+// buildBootImageZipInPredefinedLocation generates a zip file containing all the boot image files.
+//
+// The supplied filesByArch is nil when the boot image files have not been generated. Otherwise, it
+// is a map from android.ArchType to the predefined locations.
+func buildBootImageZipInPredefinedLocation(ctx android.ModuleContext, image *bootImageConfig, filesByArch bootImageFilesByArch) {
+	if filesByArch == nil {
+		return
 	}
+
+	// Compute the list of files from all the architectures.
+	zipFiles := android.Paths{}
+	for _, archType := range android.ArchTypeList() {
+		zipFiles = append(zipFiles, filesByArch[archType]...)
+	}
+
+	rule := android.NewRuleBuilder(pctx, ctx)
+	rule.Command().
+		BuiltTool("soong_zip").
+		FlagWithOutput("-o ", image.zip).
+		FlagWithArg("-C ", image.dir.Join(ctx, android.Android.String()).String()).
+		FlagWithInputList("-f ", zipFiles, " -f ")
+
+	rule.Build("zip_"+image.name, "zip "+image.name+" image")
 }
 
 // Generate boot image build rules for a specific target.
-func buildBootImageVariant(ctx android.ModuleContext, image *bootImageVariant, profile android.Path) android.WritablePaths {
+func buildBootImageVariant(ctx android.ModuleContext, image *bootImageVariant, profile android.Path) {
 
-	globalSoong := dexpreopt.GetCachedGlobalSoongConfig(ctx)
+	globalSoong := dexpreopt.GetGlobalSoongConfig(ctx)
 	global := dexpreopt.GetGlobalConfig(ctx)
 
 	arch := image.target.Arch.ArchType
@@ -641,11 +681,8 @@
 	var vdexInstalls android.RuleBuilderInstalls
 	var unstrippedInstalls android.RuleBuilderInstalls
 
-	var zipFiles android.WritablePaths
-
 	for _, artOrOat := range image.moduleFiles(ctx, outputDir, ".art", ".oat") {
 		cmd.ImplicitOutput(artOrOat)
-		zipFiles = append(zipFiles, artOrOat)
 
 		// Install the .oat and .art files
 		rule.Install(artOrOat, filepath.Join(installDir, artOrOat.Base()))
@@ -653,7 +690,6 @@
 
 	for _, vdex := range image.moduleFiles(ctx, outputDir, ".vdex") {
 		cmd.ImplicitOutput(vdex)
-		zipFiles = append(zipFiles, vdex)
 
 		// Note that the vdex files are identical between architectures.
 		// Make rules will create symlinks to share them between architectures.
@@ -675,8 +711,6 @@
 	image.installs = rule.Installs()
 	image.vdexInstalls = vdexInstalls
 	image.unstrippedInstalls = unstrippedInstalls
-
-	return zipFiles
 }
 
 const failureMessage = `ERROR: Dex2oat failed to compile a boot image.
@@ -684,7 +718,7 @@
 Rebuild with ART_BOOT_IMAGE_EXTRA_ARGS="--runtime-arg -verbose:verifier" to see verification errors.`
 
 func bootImageProfileRule(ctx android.ModuleContext, image *bootImageConfig) android.WritablePath {
-	globalSoong := dexpreopt.GetCachedGlobalSoongConfig(ctx)
+	globalSoong := dexpreopt.GetGlobalSoongConfig(ctx)
 	global := dexpreopt.GetGlobalConfig(ctx)
 
 	if global.DisableGenerateProfile {
diff --git a/java/droidstubs.go b/java/droidstubs.go
index d348b55..ec1b04a 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -128,12 +128,15 @@
 	// whicih can be used for scheduling purposes
 	High_mem *bool
 
-	// is set to true, Metalava will allow framework SDK to contain API levels annotations.
+	// if set to true, Metalava will allow framework SDK to contain API levels annotations.
 	Api_levels_annotations_enabled *bool
 
 	// the dirs which Metalava extracts API levels annotations from.
 	Api_levels_annotations_dirs []string
 
+	// the sdk kind which Metalava extracts API levels annotations from. Supports 'public' and 'system' for now; defaults to public.
+	Api_levels_sdk_type *string
+
 	// the filename which Metalava extracts API levels annotations from. Defaults to android.jar.
 	Api_levels_jar_filename *string
 
@@ -367,6 +370,7 @@
 
 	filename := proptools.StringDefault(d.properties.Api_levels_jar_filename, "android.jar")
 
+	var dirs []string
 	ctx.VisitDirectDepsWithTag(metalavaAPILevelsAnnotationsDirTag, func(m android.Module) {
 		if t, ok := m.(*ExportedDroiddocDir); ok {
 			for _, dep := range t.deps {
@@ -383,12 +387,32 @@
 					cmd.Implicit(dep)
 				}
 			}
-			cmd.FlagWithArg("--android-jar-pattern ", t.dir.String()+"/%/public/"+filename)
+
+			dirs = append(dirs, t.dir.String())
 		} else {
 			ctx.PropertyErrorf("api_levels_annotations_dirs",
 				"module %q is not a metalava api-levels-annotations dir", ctx.OtherModuleName(m))
 		}
 	})
+
+	// Add all relevant --android-jar-pattern patterns for Metalava.
+	// When parsing a stub jar for a specific version, Metalava picks the first pattern that defines
+	// an actual file present on disk (in the order the patterns were passed). For system APIs for
+	// privileged apps that are only defined since API level 21 (Lollipop), fallback to public stubs
+	// for older releases.
+	if sdkType := proptools.StringDefault(d.properties.Api_levels_sdk_type, "public"); sdkType != "public" {
+		if sdkType != "system" {
+			ctx.PropertyErrorf("api_levels_sdk_type", "only 'public' and 'system' are supported")
+		}
+		// If building non public stubs, add all sdkType patterns first...
+		for _, dir := range dirs {
+			cmd.FlagWithArg("--android-jar-pattern ", fmt.Sprintf("%s/%%/%s/%s", dir, sdkType, filename))
+		}
+	}
+	for _, dir := range dirs {
+		// ... and fallback to public ones, for Metalava to use if needed.
+		cmd.FlagWithArg("--android-jar-pattern ", fmt.Sprintf("%s/%%/%s/%s", dir, "public", filename))
+	}
 }
 
 func metalavaCmd(ctx android.ModuleContext, rule *android.RuleBuilder, javaVersion javaVersion, srcs android.Paths,
diff --git a/java/droidstubs_test.go b/java/droidstubs_test.go
index db664c1..60d0bea 100644
--- a/java/droidstubs_test.go
+++ b/java/droidstubs_test.go
@@ -16,6 +16,7 @@
 
 import (
 	"reflect"
+	"regexp"
 	"strings"
 	"testing"
 
@@ -81,6 +82,46 @@
 	}
 }
 
+func TestSystemDroidstubs(t *testing.T) {
+	ctx, _ := testJavaWithFS(t, `
+		droiddoc_exported_dir {
+			name: "some-exported-dir",
+			path: "somedir",
+		}
+
+		droiddoc_exported_dir {
+			name: "some-other-exported-dir",
+			path: "someotherdir",
+		}
+
+		droidstubs {
+			name: "foo-stubs",
+			srcs: ["foo-doc/a.java"],
+			api_levels_annotations_dirs: [
+				"some-exported-dir",
+				"some-other-exported-dir",
+			],
+			api_levels_annotations_enabled: true,
+            api_levels_sdk_type: "system",
+		}
+		`,
+		map[string][]byte{
+			"foo-doc/a.java": nil,
+		})
+
+	m := ctx.ModuleForTests("foo-stubs", "android_common")
+	manifest := m.Output("metalava.sbox.textproto")
+	cmd := String(android.RuleBuilderSboxProtoForTests(t, manifest).Commands[0].Command)
+	r := regexp.MustCompile(`--android-jar-pattern [^ ]+/android.jar`)
+	matches := r.FindAllString(cmd, -1)
+	android.AssertArrayString(t, "order of patterns", []string{
+		"--android-jar-pattern somedir/%/system/android.jar",
+		"--android-jar-pattern someotherdir/%/system/android.jar",
+		"--android-jar-pattern somedir/%/public/android.jar",
+		"--android-jar-pattern someotherdir/%/public/android.jar",
+	}, matches)
+}
+
 func TestDroidstubsSandbox(t *testing.T) {
 	ctx, _ := testJavaWithFS(t, `
 		genrule {
diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go
index 0895951..c4832d2 100644
--- a/java/hiddenapi_modular.go
+++ b/java/hiddenapi_modular.go
@@ -586,6 +586,13 @@
 // addStubDexJar adds a stub dex jar path provided by the specified module for the specified scope.
 func (s StubDexJarsByModule) addStubDexJar(ctx android.ModuleContext, module android.Module, scope *HiddenAPIScope, stubDexJar android.Path) {
 	name := android.RemoveOptionalPrebuiltPrefix(module.Name())
+
+	// Each named module provides one dex jar for each scope. However, in some cases different API
+	// versions of a single classes are provided by separate modules. e.g. the core platform
+	// version of java.lang.Object is provided by the legacy.art.module.platform.api module but the
+	// public version is provided by the art.module.public.api module. In those cases it is necessary
+	// to treat all those modules as they were the same name, otherwise it will result in multiple
+	// definitions of a single class being passed to hidden API processing which will cause an error.
 	if name == scope.nonUpdatablePrebuiltModule || name == scope.nonUpdatableSourceModule {
 		// Treat all *android-non-updatable* modules as if they were part of an android-non-updatable
 		// java_sdk_library.
@@ -606,6 +613,14 @@
 		// conscrypt.module.public.api java_sdk_library which will be the case once the former has been
 		// migrated to a module_lib API.
 		name = "conscrypt.module.public.api"
+	} else if d, ok := module.(SdkLibraryComponentDependency); ok {
+		sdkLibraryName := d.SdkLibraryName()
+		if sdkLibraryName != nil {
+			// The module is a component of a java_sdk_library so use the name of the java_sdk_library.
+			// e.g. if this module is `foo.system.stubs` and is part of the `foo` java_sdk_library then
+			// use `foo` as the name.
+			name = *sdkLibraryName
+		}
 	}
 	stubDexJarsByScope := s[name]
 	if stubDexJarsByScope == nil {
@@ -1146,6 +1161,14 @@
 		return true
 	}
 
+	// A bootclasspath module that is part of a versioned sdk never provides a boot dex jar as there
+	// is no equivalently versioned prebuilt APEX file from which it can be obtained. However,
+	// versioned bootclasspath modules are processed by Soong so in order to avoid them causing build
+	// failures missing boot dex jars need to be deferred.
+	if android.IsModuleInVersionedSdk(ctx.Module()) {
+		return true
+	}
+
 	// This is called for both platform_bootclasspath and bootclasspath_fragment modules.
 	//
 	// A bootclasspath_fragment module should only use the APEX variant of source or prebuilt modules.
@@ -1182,6 +1205,14 @@
 	//
 	// TODO(b/187910671): Remove this once platform variants are no longer created unnecessarily.
 	if android.IsModulePrebuilt(module) {
+		// An inactive source module can still contribute to the APEX but an inactive prebuilt module
+		// should not contribute to anything. So, rather than have a missing dex jar cause a Soong
+		// failure defer the error reporting to Ninja. Unless the prebuilt build target is explicitly
+		// built Ninja should never use the dex jar file.
+		if !isActiveModule(module) {
+			return true
+		}
+
 		if am, ok := module.(android.ApexModule); ok && am.InAnyApex() {
 			apexInfo := ctx.OtherModuleProvider(module, android.ApexInfoProvider).(android.ApexInfo)
 			if apexInfo.IsForPlatform() {
@@ -1190,14 +1221,6 @@
 		}
 	}
 
-	// A bootclasspath module that is part of a versioned sdk never provides a boot dex jar as there
-	// is no equivalently versioned prebuilt APEX file from which it can be obtained. However,
-	// versioned bootclasspath modules are processed by Soong so in order to avoid them causing build
-	// failures missing boot dex jars need to be deferred.
-	if android.IsModuleInVersionedSdk(ctx.Module()) {
-		return true
-	}
-
 	return false
 }
 
diff --git a/java/jacoco.go b/java/jacoco.go
index 9162161..e11c2ce 100644
--- a/java/jacoco.go
+++ b/java/jacoco.go
@@ -94,7 +94,7 @@
 	if len(includes) > 0 {
 		specs += strings.Join(includes, " ")
 	} else {
-		specs += "**/*.class"
+		specs += "'**/*.class'"
 	}
 	return specs
 }
diff --git a/java/jacoco_test.go b/java/jacoco_test.go
index 91f0553..1882908 100644
--- a/java/jacoco_test.go
+++ b/java/jacoco_test.go
@@ -74,7 +74,7 @@
 		{
 			name:     "implicit wildcard",
 			includes: []string{},
-			out:      "**/*.class",
+			out:      "'**/*.class'",
 		},
 		{
 			name:     "only include",
diff --git a/java/lint_defaults.txt b/java/lint_defaults.txt
index 2de05b0..75de7dc 100644
--- a/java/lint_defaults.txt
+++ b/java/lint_defaults.txt
@@ -78,3 +78,7 @@
 --disable_check HardcodedDebugMode
 
 --warning_check QueryAllPackagesPermission
+
+--warning_check CoarseFineLocation
+--warning_check IntentFilterExportedReceiver
+--warning_check RemoteViewLayout
diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go
index d72cee0..c1e14b2 100644
--- a/java/platform_bootclasspath.go
+++ b/java/platform_bootclasspath.go
@@ -437,7 +437,15 @@
 
 	// Build a profile for the image config and then use that to build the boot image.
 	profile := bootImageProfileRule(ctx, imageConfig)
-	buildBootImage(ctx, imageConfig, profile)
+
+	// Build boot image files for the android variants.
+	androidBootImageFilesByArch := buildBootImageVariantsForAndroidOs(ctx, imageConfig, profile)
+
+	// Zip the android variant boot image files up.
+	buildBootImageZipInPredefinedLocation(ctx, imageConfig, androidBootImageFilesByArch)
+
+	// Build boot image files for the host variants. There are use directly by ART host side tests.
+	buildBootImageVariantsForBuildOs(ctx, imageConfig, profile)
 
 	dumpOatRules(ctx, imageConfig)
 }
diff --git a/java/sdk.go b/java/sdk.go
index cbd873d..d1b899e 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -175,10 +175,24 @@
 		}
 	case android.SdkModule:
 		// TODO(146757305): provide .apk and .aidl that have more APIs for modules
-		return toModule([]string{"android_module_lib_stubs_current"}, "framework-res", nonUpdatableFrameworkAidlPath(ctx))
+		return sdkDep{
+			useModule:          true,
+			bootclasspath:      []string{"android_module_lib_stubs_current", config.DefaultLambdaStubsLibrary},
+			systemModules:      "core-module-lib-stubs-system-modules",
+			java9Classpath:     []string{"android_module_lib_stubs_current"},
+			frameworkResModule: "framework-res",
+			aidl:               android.OptionalPathForPath(nonUpdatableFrameworkAidlPath(ctx)),
+		}
 	case android.SdkSystemServer:
 		// TODO(146757305): provide .apk and .aidl that have more APIs for modules
-		return toModule([]string{"android_system_server_stubs_current"}, "framework-res", sdkFrameworkAidlPath(ctx))
+		return sdkDep{
+			useModule:          true,
+			bootclasspath:      []string{"android_system_server_stubs_current", config.DefaultLambdaStubsLibrary},
+			systemModules:      "core-module-lib-stubs-system-modules",
+			java9Classpath:     []string{"android_system_server_stubs_current"},
+			frameworkResModule: "framework-res",
+			aidl:               android.OptionalPathForPath(sdkFrameworkAidlPath(ctx)),
+		}
 	default:
 		panic(fmt.Errorf("invalid sdk %q", sdkVersion.Raw))
 	}
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 567e292..ed9aeff 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -675,10 +675,13 @@
 		return false
 	}
 
+	namePtr := proptools.StringPtr(c.module.BaseModuleName())
+	c.sdkLibraryComponentProperties.SdkLibraryName = namePtr
+
 	// Only track this sdk library if this can be used as a shared library.
 	if c.sharedLibrary() {
 		// Use the name specified in the module definition as the owner.
-		c.sdkLibraryComponentProperties.SdkLibraryToImplicitlyTrack = proptools.StringPtr(c.module.BaseModuleName())
+		c.sdkLibraryComponentProperties.SdkLibraryToImplicitlyTrack = namePtr
 	}
 
 	return true
@@ -922,15 +925,19 @@
 
 func (c *commonToSdkLibraryAndImport) sdkComponentPropertiesForChildLibrary() interface{} {
 	componentProps := &struct {
+		SdkLibraryName              *string
 		SdkLibraryToImplicitlyTrack *string
 	}{}
 
+	namePtr := proptools.StringPtr(c.module.BaseModuleName())
+	componentProps.SdkLibraryName = namePtr
+
 	if c.sharedLibrary() {
 		// Mark the stubs library as being components of this java_sdk_library so that
 		// any app that includes code which depends (directly or indirectly) on the stubs
 		// library will have the appropriate <uses-library> invocation inserted into its
 		// manifest if necessary.
-		componentProps.SdkLibraryToImplicitlyTrack = proptools.StringPtr(c.module.BaseModuleName())
+		componentProps.SdkLibraryToImplicitlyTrack = namePtr
 	}
 
 	return componentProps
@@ -949,6 +956,8 @@
 
 // Properties related to the use of a module as an component of a java_sdk_library.
 type SdkLibraryComponentProperties struct {
+	// The name of the java_sdk_library/_import module.
+	SdkLibraryName *string `blueprint:"mutated"`
 
 	// The name of the java_sdk_library/_import to add to a <uses-library> entry
 	// in the AndroidManifest.xml of any Android app that includes code that references
@@ -967,6 +976,11 @@
 }
 
 // to satisfy SdkLibraryComponentDependency
+func (e *EmbeddableSdkLibraryComponent) SdkLibraryName() *string {
+	return e.sdkLibraryComponentProperties.SdkLibraryName
+}
+
+// to satisfy SdkLibraryComponentDependency
 func (e *EmbeddableSdkLibraryComponent) OptionalImplicitSdkLibrary() *string {
 	return e.sdkLibraryComponentProperties.SdkLibraryToImplicitlyTrack
 }
@@ -982,6 +996,9 @@
 type SdkLibraryComponentDependency interface {
 	UsesLibraryDependency
 
+	// SdkLibraryName returns the name of the java_sdk_library/_import module.
+	SdkLibraryName() *string
+
 	// The optional name of the sdk library that should be implicitly added to the
 	// AndroidManifest of an app that contains code which references the sdk library.
 	//
diff --git a/java/sdk_test.go b/java/sdk_test.go
index 2b18465..bb595a5 100644
--- a/java/sdk_test.go
+++ b/java/sdk_test.go
@@ -219,7 +219,7 @@
 			name:           "module_current",
 			properties:     `sdk_version: "module_current",`,
 			bootclasspath:  []string{"android_module_lib_stubs_current", "core-lambda-stubs"},
-			system:         "core-current-stubs-system-modules",
+			system:         "core-module-lib-stubs-system-modules",
 			java9classpath: []string{"android_module_lib_stubs_current"},
 			aidl:           "-pout/soong/framework_non_updatable.aidl",
 		},
@@ -227,7 +227,7 @@
 			name:           "system_server_current",
 			properties:     `sdk_version: "system_server_current",`,
 			bootclasspath:  []string{"android_system_server_stubs_current", "core-lambda-stubs"},
-			system:         "core-current-stubs-system-modules",
+			system:         "core-module-lib-stubs-system-modules",
 			java9classpath: []string{"android_system_server_stubs_current"},
 			aidl:           "-pout/soong/framework.aidl",
 		},
diff --git a/java/systemserver_classpath_fragment.go b/java/systemserver_classpath_fragment.go
index 252c615..28a5a2c 100644
--- a/java/systemserver_classpath_fragment.go
+++ b/java/systemserver_classpath_fragment.go
@@ -106,21 +106,12 @@
 func (s *SystemServerClasspathModule) configuredJars(ctx android.ModuleContext) android.ConfiguredJarList {
 	global := dexpreopt.GetGlobalConfig(ctx)
 
-	// Convert content names to their appropriate stems, in case a test library is overriding an actual boot jar
-	var stems []string
-	for _, name := range s.properties.Contents {
-		dep := ctx.GetDirectDepWithTag(name, systemServerClasspathFragmentContentDepTag)
-		if m, ok := dep.(ModuleWithStem); ok {
-			stems = append(stems, m.Stem())
-		} else {
-			ctx.PropertyErrorf("contents", "%v is not a ModuleWithStem", name)
-		}
-	}
+	possibleUpdatableModules := gatherPossibleUpdatableModuleNamesAndStems(ctx, s.properties.Contents, systemServerClasspathFragmentContentDepTag)
 
 	// Only create configs for updatable boot jars. Non-updatable system server jars must be part of the
 	// platform_systemserverclasspath's classpath proto config to guarantee that they come before any
 	// updatable jars at runtime.
-	return global.UpdatableSystemServerJars.Filter(stems)
+	return global.UpdatableSystemServerJars.Filter(possibleUpdatableModules)
 }
 
 type systemServerClasspathFragmentContentDependencyTag struct {
diff --git a/java/testing.go b/java/testing.go
index c3803c8..e2ff5cd 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -312,6 +312,7 @@
 
 	systemModules := []string{
 		"core-current-stubs-system-modules",
+		"core-module-lib-stubs-system-modules",
 		"legacy-core-platform-api-stubs-system-modules",
 		"stable-core-platform-api-stubs-system-modules",
 	}
diff --git a/rust/rust.go b/rust/rust.go
index 05f6399..a11a04c 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -87,6 +87,7 @@
 
 	// Used by vendor snapshot to record dependencies from snapshot modules.
 	SnapshotSharedLibs []string `blueprint:"mutated"`
+	SnapshotStaticLibs []string `blueprint:"mutated"`
 
 	// Make this module available when building for vendor ramdisk.
 	// On device without a dedicated recovery partition, the module is only
@@ -259,6 +260,13 @@
 	return false
 }
 
+func (mod *Module) StaticExecutable() bool {
+	if !mod.Binary() {
+		return false
+	}
+	return Bool(mod.compiler.(*binaryDecorator).Properties.Static_executable)
+}
+
 func (mod *Module) Object() bool {
 	// Rust has no modules which produce only object files.
 	return false
@@ -1097,6 +1105,7 @@
 
 				// Record baseLibName for snapshots.
 				mod.Properties.SnapshotSharedLibs = append(mod.Properties.SnapshotSharedLibs, cc.BaseLibName(depName))
+				mod.Properties.SnapshotStaticLibs = append(mod.Properties.SnapshotStaticLibs, cc.BaseLibName(depName))
 
 				mod.Properties.AndroidMkSharedLibs = append(mod.Properties.AndroidMkSharedLibs, makeLibName)
 				exportDep = true
diff --git a/rust/snapshot_utils.go b/rust/snapshot_utils.go
index bd7ca7f..8dabd9b 100644
--- a/rust/snapshot_utils.go
+++ b/rust/snapshot_utils.go
@@ -57,6 +57,10 @@
 	return mod.Properties.SnapshotSharedLibs
 }
 
+func (mod *Module) SnapshotStaticLibs() []string {
+	return mod.Properties.SnapshotStaticLibs
+}
+
 func (mod *Module) Symlinks() []string {
 	// TODO update this to return the list of symlinks when Rust supports defining symlinks
 	return nil
diff --git a/scripts/Android.bp b/scripts/Android.bp
index 1c02bd0..635be10 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -265,3 +265,19 @@
         "linker_config_proto",
     ],
 }
+
+python_binary_host {
+    name: "get_clang_version",
+    main: "get_clang_version.py",
+    srcs: [
+        "get_clang_version.py",
+    ],
+    version: {
+        py2: {
+            enabled: false,
+        },
+        py3: {
+            enabled: true,
+        },
+    },
+}
diff --git a/scripts/get_clang_version.py b/scripts/get_clang_version.py
new file mode 100755
index 0000000..622fca1
--- /dev/null
+++ b/scripts/get_clang_version.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""A tool to report the current clang version used during build"""
+
+import os
+import re
+import sys
+
+
+ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP", ".")
+
+def get_clang_prebuilts_version(global_go):
+  # TODO(b/187231324): Get clang version from the json file once it is no longer
+  # hard-coded in global.go
+  if global_go is None:
+      global_go = ANDROID_BUILD_TOP + '/build/soong/cc/config/global.go'
+  with open(global_go) as infile:
+    contents = infile.read()
+
+  regex_rev = r'\tClangDefaultVersion\s+= "clang-(?P<rev>r\d+[a-z]?\d?)"'
+  match_rev = re.search(regex_rev, contents)
+  if match_rev is None:
+    raise RuntimeError('Parsing clang info failed')
+  return match_rev.group('rev')
+
+
+def main():
+  global_go = sys.argv[1] if len(sys.argv) > 1 else None
+  print(get_clang_prebuilts_version(global_go));
+
+
+if __name__ == '__main__':
+  main()
diff --git a/scripts/get_clang_version_test.py b/scripts/get_clang_version_test.py
new file mode 100644
index 0000000..e57df6c
--- /dev/null
+++ b/scripts/get_clang_version_test.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Unit tests for get_clang_version.py."""
+
+import unittest
+
+import get_clang_version
+
+class GetClangVersionTest(unittest.TestCase):
+  """Unit tests for get_clang_version."""
+
+  def test_get_clang_version(self):
+    """Test parsing of clang prebuilts version."""
+    self.assertIsNotNone(get_clang_version.get_clang_prebuilts_version())
+
+
+if __name__ == '__main__':
+  unittest.main(verbosity=2)
diff --git a/sdk/cc_sdk_test.go b/sdk/cc_sdk_test.go
index 31555c0..60fbccf 100644
--- a/sdk/cc_sdk_test.go
+++ b/sdk/cc_sdk_test.go
@@ -347,6 +347,7 @@
 		cc_object {
 			name: "crtobj",
 			stl: "none",
+			default_shared_libs: [],
 			sanitize: {
 				never: true,
 			},
@@ -364,6 +365,7 @@
     apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
+    default_shared_libs: [],
     sanitize: {
         never: true,
     },
@@ -388,6 +390,7 @@
     apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
+    default_shared_libs: [],
     sanitize: {
         never: true,
     },
@@ -2189,7 +2192,7 @@
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
-			native_shared_libs: ["sslnil", "sslempty", "sslnonempty"],
+			native_shared_libs: ["sslnil", "sslempty", "sslnonempty", "dslnil", "dslempty", "dslnonempty"],
 		}
 
 		cc_library {
@@ -2206,6 +2209,21 @@
 			name: "sslnonempty",
 			system_shared_libs: ["sslnil"],
 		}
+
+		cc_library {
+			name: "dslnil",
+			host_supported: true,
+		}
+
+		cc_library {
+			name: "dslempty",
+			default_shared_libs: [],
+		}
+
+		cc_library {
+			name: "dslnonempty",
+			default_shared_libs: ["sslnil"],
+		}
 	`)
 
 	CheckSnapshot(t, result, "mysdk", "",
@@ -2261,13 +2279,62 @@
         },
     },
 }
-`))
+
+cc_prebuilt_library_shared {
+    name: "dslnil",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    compile_multilib: "both",
+    arch: {
+        arm64: {
+            srcs: ["arm64/lib/dslnil.so"],
+        },
+        arm: {
+            srcs: ["arm/lib/dslnil.so"],
+        },
+    },
+}
+
+cc_prebuilt_library_shared {
+    name: "dslempty",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    compile_multilib: "both",
+    default_shared_libs: [],
+    arch: {
+        arm64: {
+            srcs: ["arm64/lib/dslempty.so"],
+        },
+        arm: {
+            srcs: ["arm/lib/dslempty.so"],
+        },
+    },
+}
+
+cc_prebuilt_library_shared {
+    name: "dslnonempty",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    compile_multilib: "both",
+    default_shared_libs: ["sslnil"],
+    arch: {
+        arm64: {
+            srcs: ["arm64/lib/dslnonempty.so"],
+        },
+        arm: {
+            srcs: ["arm/lib/dslnonempty.so"],
+        },
+    },
+}`))
 
 	result = testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
 			host_supported: true,
-			native_shared_libs: ["sslvariants"],
+			native_shared_libs: ["sslvariants", "dslvariants"],
 		}
 
 		cc_library {
@@ -2279,6 +2346,16 @@
 				},
 			},
 		}
+
+		cc_library {
+			name: "dslvariants",
+			host_supported: true,
+			target: {
+				android: {
+					default_shared_libs: [],
+				},
+			},
+		}
 	`)
 
 	CheckSnapshot(t, result, "mysdk", "",
@@ -2315,6 +2392,37 @@
         },
     },
 }
+
+cc_prebuilt_library_shared {
+    name: "dslvariants",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    host_supported: true,
+    compile_multilib: "both",
+    target: {
+        host: {
+            enabled: false,
+        },
+        android: {
+            default_shared_libs: [],
+        },
+        android_arm64: {
+            srcs: ["android/arm64/lib/dslvariants.so"],
+        },
+        android_arm: {
+            srcs: ["android/arm/lib/dslvariants.so"],
+        },
+        linux_glibc_x86_64: {
+            enabled: true,
+            srcs: ["linux_glibc/x86_64/lib/dslvariants.so"],
+        },
+        linux_glibc_x86: {
+            enabled: true,
+            srcs: ["linux_glibc/x86/lib/dslvariants.so"],
+        },
+    },
+}
 `),
 		checkVersionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
@@ -2351,11 +2459,46 @@
     },
 }
 
+cc_prebuilt_library_shared {
+    name: "mysdk_dslvariants@current",
+    sdk_member_name: "dslvariants",
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    host_supported: true,
+    installable: false,
+    compile_multilib: "both",
+    target: {
+        host: {
+            enabled: false,
+        },
+        android: {
+            default_shared_libs: [],
+        },
+        android_arm64: {
+            srcs: ["android/arm64/lib/dslvariants.so"],
+        },
+        android_arm: {
+            srcs: ["android/arm/lib/dslvariants.so"],
+        },
+        linux_glibc_x86_64: {
+            enabled: true,
+            srcs: ["linux_glibc/x86_64/lib/dslvariants.so"],
+        },
+        linux_glibc_x86: {
+            enabled: true,
+            srcs: ["linux_glibc/x86/lib/dslvariants.so"],
+        },
+    },
+}
+
 sdk_snapshot {
     name: "mysdk@current",
     visibility: ["//visibility:public"],
     host_supported: true,
-    native_shared_libs: ["mysdk_sslvariants@current"],
+    native_shared_libs: [
+        "mysdk_sslvariants@current",
+        "mysdk_dslvariants@current",
+    ],
     target: {
         host: {
             enabled: false,
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index 4805846..1647428 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -495,6 +495,10 @@
 	module := &ShTest{}
 	InitShBinaryModule(&module.ShBinary)
 	module.AddProperties(&module.testProperties)
+	// Default sh_test_host to unit_tests = true
+	if module.testProperties.Test_options.Unit_test == nil {
+		module.testProperties.Test_options.Unit_test = proptools.BoolPtr(true)
+	}
 
 	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
 	return module
diff --git a/tests/bootstrap_test.sh b/tests/bootstrap_test.sh
index 8c8dc82..1258684 100755
--- a/tests/bootstrap_test.sh
+++ b/tests/bootstrap_test.sh
@@ -486,6 +486,39 @@
   fi
 }
 
+function test_write_to_source_tree {
+  setup
+  mkdir -p a
+  cat > a/Android.bp <<EOF
+genrule {
+  name: "write_to_source_tree",
+  out: ["write_to_source_tree"],
+  cmd: "touch file_in_source_tree && touch \$(out)",
+}
+EOF
+  readonly EXPECTED_OUT=out/soong/.intermediates/a/write_to_source_tree/gen/write_to_source_tree
+  readonly ERROR_LOG=${MOCK_TOP}/out/error.log
+  readonly ERROR_MSG="Read-only file system"
+  readonly ERROR_HINT_PATTERN="BUILD_BROKEN_SRC_DIR"
+  # Test in ReadOnly source tree
+  run_ninja BUILD_BROKEN_SRC_DIR_IS_WRITABLE=false ${EXPECTED_OUT} &> /dev/null && \
+    fail "Write to source tree should not work in a ReadOnly source tree"
+
+  if grep -q "${ERROR_MSG}" ${ERROR_LOG} && grep -q "${ERROR_HINT_PATTERN}" ${ERROR_LOG} ; then
+    echo Error message and error hint found in logs >/dev/null
+  else
+    fail "Did not find Read-only error AND error hint in error.log"
+  fi
+
+  # Test in ReadWrite source tree
+  run_ninja BUILD_BROKEN_SRC_DIR_IS_WRITABLE=true ${EXPECTED_OUT} &> /dev/null || \
+    fail "Write to source tree did not succeed in a ReadWrite source tree"
+
+  if  grep -q "${ERROR_MSG}\|${ERROR_HINT_PATTERN}" ${ERROR_LOG} ; then
+    fail "Found read-only error OR error hint in error.log"
+  fi
+}
+
 function test_bp2build_smoke {
   setup
   GENERATE_BAZEL_FILES=1 run_soong
@@ -692,6 +725,7 @@
 test_glob_during_bootstrapping
 test_soong_build_rerun_iff_environment_changes
 test_dump_json_module_graph
+test_write_to_source_tree
 test_bp2build_smoke
 test_bp2build_generates_fake_ninja_file
 test_bp2build_null_build
diff --git a/tests/lib.sh b/tests/lib.sh
index 35ccea9..f1e1efa 100644
--- a/tests/lib.sh
+++ b/tests/lib.sh
@@ -126,6 +126,10 @@
   GENERATE_BAZEL_FILES=true build/soong/soong_ui.bash --make-mode --skip-ninja --skip-make --skip-soong-tests nothing
 }
 
+run_ninja() {
+  build/soong/soong_ui.bash --make-mode --skip-make --skip-soong-tests "$@"
+}
+
 info "Starting Soong integration test suite $(basename $0)"
 info "Mock top: $MOCK_TOP"
 
diff --git a/ui/build/build.go b/ui/build/build.go
index 1187aa2..1ed9014 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -28,16 +28,20 @@
 func SetupOutDir(ctx Context, config Config) {
 	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "Android.mk"))
 	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "CleanSpec.mk"))
-	if !config.SkipKati() {
-		// Run soong_build with Kati for a hybrid build, e.g. running the
-		// AndroidMk singleton and postinstall commands. Communicate this to
-		// soong_build by writing an empty .soong.kati_enabled marker file in the
-		// soong_build output directory for the soong_build primary builder to
-		// know if the user wants to run Kati after.
-		//
-		// This does not preclude running Kati for *product configuration purposes*.
-		ensureEmptyFileExists(ctx, filepath.Join(config.SoongOutDir(), ".soong.kati_enabled"))
+
+	// Potentially write a marker file for whether kati is enabled. This is used by soong_build to
+	// potentially run the AndroidMk singleton and postinstall commands.
+	// Note that the absence of the  file does not not preclude running Kati for product
+	// configuration purposes.
+	katiEnabledMarker := filepath.Join(config.SoongOutDir(), ".soong.kati_enabled")
+	if config.SkipKatiNinja() {
+		os.Remove(katiEnabledMarker)
+		// Note that we can not remove the file for SkipKati builds yet -- some continuous builds
+		// --skip-make builds rely on kati targets being defined.
+	} else if !config.SkipKati() {
+		ensureEmptyFileExists(ctx, katiEnabledMarker)
 	}
+
 	// The ninja_build file is used by our buildbots to understand that the output
 	// can be parsed as ninja output.
 	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "ninja_build"))
diff --git a/ui/build/config.go b/ui/build/config.go
index 4806721..7370627 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -161,6 +161,10 @@
 		ret.distDir = filepath.Join(ret.OutDir(), "dist")
 	}
 
+	if srcDirIsWritable, ok := ret.environ.Get("BUILD_BROKEN_SRC_DIR_IS_WRITABLE"); ok {
+		ret.sandboxConfig.SetSrcDirIsRO(srcDirIsWritable == "false")
+	}
+
 	ret.environ.Unset(
 		// We're already using it
 		"USE_SOONG_UI",
diff --git a/ui/build/sandbox_linux.go b/ui/build/sandbox_linux.go
index b0a6748..5b2046e 100644
--- a/ui/build/sandbox_linux.go
+++ b/ui/build/sandbox_linux.go
@@ -97,8 +97,11 @@
 			"-u", "nobody",
 			"-g", sandboxConfig.group,
 			"-R", "/",
-			"-B", sandboxConfig.srcDir,
+			// Mount tmp before srcDir
+			// srcDir is /tmp/.* in integration tests, which is a child dir of /tmp
+			// nsjail throws an error if a child dir is mounted before its parent
 			"-B", "/tmp",
+			"-B", sandboxConfig.srcDir,
 			"-B", sandboxConfig.outDir,
 		}
 
diff --git a/ui/status/ninja.go b/ui/status/ninja.go
index 765679f..2445972 100644
--- a/ui/status/ninja.go
+++ b/ui/status/ninja.go
@@ -19,6 +19,8 @@
 	"fmt"
 	"io"
 	"os"
+	"regexp"
+	"strings"
 	"syscall"
 	"time"
 
@@ -158,9 +160,10 @@
 					err = fmt.Errorf("exited with code: %d", exitCode)
 				}
 
+				outputWithErrorHint := errorHintGenerator.GetOutputWithErrorHint(msg.EdgeFinished.GetOutput(), exitCode)
 				n.status.FinishAction(ActionResult{
 					Action: started,
-					Output: msg.EdgeFinished.GetOutput(),
+					Output: outputWithErrorHint,
 					Error:  err,
 					Stats: ActionResultStats{
 						UserTime:                   msg.EdgeFinished.GetUserTime(),
@@ -219,3 +222,53 @@
 
 	return ret, nil
 }
+
+// key is pattern in stdout/stderr
+// value is error hint
+var allErrorHints = map[string]string{
+	"Read-only file system": `\nWrite to a read-only file system detected. Possible fixes include
+1. Generate file directly to out/ which is ReadWrite, #recommend solution
+2. BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST := <my/path/1> <my/path/2> #discouraged, subset of source tree will be RW
+3. BUILD_BROKEN_SRC_DIR_IS_WRITABLE := true #highly discouraged, entire source tree will be RW
+`,
+}
+var errorHintGenerator = *newErrorHintGenerator(allErrorHints)
+
+type ErrorHintGenerator struct {
+	allErrorHints                map[string]string
+	allErrorHintPatternsCompiled *regexp.Regexp
+}
+
+func newErrorHintGenerator(allErrorHints map[string]string) *ErrorHintGenerator {
+	var allErrorHintPatterns []string
+	for errorHintPattern, _ := range allErrorHints {
+		allErrorHintPatterns = append(allErrorHintPatterns, errorHintPattern)
+	}
+	allErrorHintPatternsRegex := strings.Join(allErrorHintPatterns[:], "|")
+	re := regexp.MustCompile(allErrorHintPatternsRegex)
+	return &ErrorHintGenerator{
+		allErrorHints:                allErrorHints,
+		allErrorHintPatternsCompiled: re,
+	}
+}
+
+func (errorHintGenerator *ErrorHintGenerator) GetOutputWithErrorHint(rawOutput string, buildExitCode int) string {
+	if buildExitCode == 0 {
+		return rawOutput
+	}
+	errorHint := errorHintGenerator.getErrorHint(rawOutput)
+	if errorHint == nil {
+		return rawOutput
+	}
+	return rawOutput + *errorHint
+}
+
+// Returns the error hint corresponding to the FIRST match in raw output
+func (errorHintGenerator *ErrorHintGenerator) getErrorHint(rawOutput string) *string {
+	firstMatch := errorHintGenerator.allErrorHintPatternsCompiled.FindString(rawOutput)
+	if _, found := errorHintGenerator.allErrorHints[firstMatch]; found {
+		errorHint := errorHintGenerator.allErrorHints[firstMatch]
+		return &errorHint
+	}
+	return nil
+}
diff --git a/ui/status/ninja_test.go b/ui/status/ninja_test.go
index c400c97..f3638b3 100644
--- a/ui/status/ninja_test.go
+++ b/ui/status/ninja_test.go
@@ -43,3 +43,53 @@
 		t.Errorf("nr.Close timed out, %s > %s", g, w)
 	}
 }
+
+// Test that error hint is added to output if available
+func TestNinjaReader_CorrectErrorHint(t *testing.T) {
+	errorPattern1 := "pattern-1 in input"
+	errorHint1 := "\n Fix by doing task 1"
+	errorPattern2 := "pattern-2 in input"
+	errorHint2 := "\n Fix by doing task 2"
+	mockErrorHints := make(map[string]string)
+	mockErrorHints[errorPattern1] = errorHint1
+	mockErrorHints[errorPattern2] = errorHint2
+
+	errorHintGenerator := *newErrorHintGenerator(mockErrorHints)
+	testCases := []struct {
+		rawOutput            string
+		buildExitCode        int
+		expectedFinalOutput  string
+		testCaseErrorMessage string
+	}{
+		{
+			rawOutput:            "ninja build was successful",
+			buildExitCode:        0,
+			expectedFinalOutput:  "ninja build was successful",
+			testCaseErrorMessage: "raw output changed when build was successful",
+		},
+		{
+			rawOutput:            "ninja build failed",
+			buildExitCode:        1,
+			expectedFinalOutput:  "ninja build failed",
+			testCaseErrorMessage: "raw output changed even when no error hint pattern was found",
+		},
+		{
+			rawOutput:            "ninja build failed: " + errorPattern1 + "some footnotes",
+			buildExitCode:        1,
+			expectedFinalOutput:  "ninja build failed: " + errorPattern1 + "some footnotes" + errorHint1,
+			testCaseErrorMessage: "error hint not added despite pattern match",
+		},
+		{
+			rawOutput:            "ninja build failed: " + errorPattern2 + errorPattern1,
+			buildExitCode:        1,
+			expectedFinalOutput:  "ninja build failed: " + errorPattern2 + errorPattern1 + errorHint2,
+			testCaseErrorMessage: "error hint should be added for first pattern match in raw output",
+		},
+	}
+	for _, testCase := range testCases {
+		actualFinalOutput := errorHintGenerator.GetOutputWithErrorHint(testCase.rawOutput, testCase.buildExitCode)
+		if actualFinalOutput != testCase.expectedFinalOutput {
+			t.Errorf(testCase.testCaseErrorMessage+"\nexpected: %s\ngot: %s", testCase.expectedFinalOutput, actualFinalOutput)
+		}
+	}
+}