Merge "Generate tradefed config for rust device tests."
diff --git a/Android.bp b/Android.bp
index 0ca11d3..6910d75 100644
--- a/Android.bp
+++ b/Android.bp
@@ -503,6 +503,7 @@
         "soong-java",
     ],
     srcs: [
+        "sdk/bp.go",
         "sdk/sdk.go",
         "sdk/update.go",
     ],
diff --git a/android/module.go b/android/module.go
index 2ae2961..b4f8f1a 100644
--- a/android/module.go
+++ b/android/module.go
@@ -778,6 +778,13 @@
 				*m.hostAndDeviceProperties.Device_supported)
 }
 
+func (m *ModuleBase) HostSupported() bool {
+	return m.commonProperties.HostOrDeviceSupported == HostSupported ||
+		m.commonProperties.HostOrDeviceSupported == HostAndDeviceSupported &&
+			(m.hostAndDeviceProperties.Host_supported != nil &&
+				*m.hostAndDeviceProperties.Host_supported)
+}
+
 func (m *ModuleBase) Platform() bool {
 	return !m.DeviceSpecific() && !m.SocSpecific() && !m.ProductSpecific() && !m.SystemExtSpecific()
 }
diff --git a/android/paths.go b/android/paths.go
index 8dbb086..1a37a34 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -247,6 +247,33 @@
 	return ret
 }
 
+// OutputPaths is a slice of OutputPath objects, with helpers to operate on the collection.
+type OutputPaths []OutputPath
+
+// Paths returns the OutputPaths as a Paths
+func (p OutputPaths) Paths() Paths {
+	if p == nil {
+		return nil
+	}
+	ret := make(Paths, len(p))
+	for i, path := range p {
+		ret[i] = path
+	}
+	return ret
+}
+
+// Strings returns the string forms of the writable paths.
+func (p OutputPaths) Strings() []string {
+	if p == nil {
+		return nil
+	}
+	ret := make([]string, len(p))
+	for i, path := range p {
+		ret[i] = path.String()
+	}
+	return ret
+}
+
 // PathsAndMissingDepsForModuleSrcExcludes returns Paths rooted from the module's local source directory, excluding
 // paths listed in the excludes arguments, and a list of missing dependencies.  It expands globs, references to
 // SourceFileProducer modules using the ":name" syntax, and references to OutputFileProducer modules using the
diff --git a/android/sdk.go b/android/sdk.go
index 73cb256..01e18ed 100644
--- a/android/sdk.go
+++ b/android/sdk.go
@@ -167,16 +167,39 @@
 	// Unzip the supplied zip into the snapshot relative directory destDir.
 	UnzipToSnapshot(zipPath Path, destDir string)
 
-	// Get the AndroidBpFile for the snapshot.
-	AndroidBpFile() GeneratedSnapshotFile
-
-	// Get a versioned name appropriate for the SDK snapshot version being taken.
-	VersionedSdkMemberName(unversionedName string) interface{}
+	// Add a new prebuilt module to the snapshot. The returned module
+	// must be populated with the module type specific properties. The following
+	// properties will be automatically populated.
+	//
+	// * name
+	// * sdk_member_name
+	// * prefer
+	//
+	// This will result in two Soong modules being generated in the Android. One
+	// that is versioned, coupled to the snapshot version and marked as
+	// prefer=true. And one that is not versioned, not marked as prefer=true and
+	// will only be used if the equivalently named non-prebuilt module is not
+	// present.
+	AddPrebuiltModule(name string, moduleType string) BpModule
 }
 
-// Provides support for generating a file, e.g. the Android.bp file.
-type GeneratedSnapshotFile interface {
-	Printfln(format string, args ...interface{})
-	Indent()
-	Dedent()
+// A set of properties for use in a .bp file.
+type BpPropertySet interface {
+	// Add a property, the value can be one of the following types:
+	// * string
+	// * array of the above
+	// * bool
+	// * BpPropertySet
+	//
+	// It is an error is multiples properties with the same name are added.
+	AddProperty(name string, value interface{})
+
+	// Add a property set with the specified name and return so that additional
+	// properties can be added.
+	AddPropertySet(name string) BpPropertySet
+}
+
+// A .bp module definition.
+type BpModule interface {
+	BpPropertySet
 }
diff --git a/apex/apex.go b/apex/apex.go
index 45184b5..289175b 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -370,20 +370,6 @@
 	Apps []string
 }
 
-type apexFileClass int
-
-const (
-	etc apexFileClass = iota
-	nativeSharedLib
-	nativeExecutable
-	shBinary
-	pyBinary
-	goBinary
-	javaSharedLib
-	nativeTest
-	app
-)
-
 type apexPackaging int
 
 const (
@@ -415,6 +401,20 @@
 	}
 }
 
+type apexFileClass int
+
+const (
+	etc apexFileClass = iota
+	nativeSharedLib
+	nativeExecutable
+	shBinary
+	pyBinary
+	goBinary
+	javaSharedLib
+	nativeTest
+	app
+)
+
 func (class apexFileClass) NameInMake() string {
 	switch class {
 	case etc:
@@ -441,13 +441,30 @@
 	}
 }
 
+// apexFile represents a file in an APEX bundle
 type apexFile struct {
 	builtFile  android.Path
 	moduleName string
 	installDir string
 	class      apexFileClass
 	module     android.Module
-	symlinks   []string
+	// list of symlinks that will be created in installDir that point to this apexFile
+	symlinks      []string
+	transitiveDep bool
+}
+
+func newApexFile(builtFile android.Path, moduleName string, installDir string, class apexFileClass, module android.Module) apexFile {
+	return apexFile{
+		builtFile:  builtFile,
+		moduleName: moduleName,
+		installDir: installDir,
+		class:      class,
+		module:     module,
+	}
+}
+
+func (af *apexFile) Ok() bool {
+	return af.builtFile != nil || af.builtFile.String() == ""
 }
 
 type apexBundle struct {
@@ -761,9 +778,11 @@
 	a.properties.HideFromMake = true
 }
 
-func getCopyManifestForNativeLibrary(ccMod *cc.Module, config android.Config, handleSpecialLibs bool) (fileToCopy android.Path, dirInApex string) {
+// TODO(jiyong) move apexFileFor* close to the apexFile type definition
+func apexFileForNativeLibrary(ccMod *cc.Module, config android.Config, handleSpecialLibs bool) apexFile {
 	// Decide the APEX-local directory by the multilib of the library
 	// In the future, we may query this to the module.
+	var dirInApex string
 	switch ccMod.Arch().ArchType.Multilib {
 	case "lib32":
 		dirInApex = "lib"
@@ -788,83 +807,84 @@
 		dirInApex = filepath.Join(dirInApex, "bionic")
 	}
 
-	fileToCopy = ccMod.OutputFile().Path()
-	return
+	fileToCopy := ccMod.OutputFile().Path()
+	return newApexFile(fileToCopy, ccMod.Name(), dirInApex, nativeSharedLib, ccMod)
 }
 
-func getCopyManifestForExecutable(cc *cc.Module) (fileToCopy android.Path, dirInApex string) {
-	dirInApex = filepath.Join("bin", cc.RelativeInstallPath())
+func apexFileForExecutable(cc *cc.Module) apexFile {
+	dirInApex := filepath.Join("bin", cc.RelativeInstallPath())
 	if cc.Target().NativeBridge == android.NativeBridgeEnabled {
 		dirInApex = filepath.Join(dirInApex, cc.Target().NativeBridgeRelativePath)
 	}
-	fileToCopy = cc.OutputFile().Path()
-	return
+	fileToCopy := cc.OutputFile().Path()
+	af := newApexFile(fileToCopy, cc.Name(), dirInApex, nativeExecutable, cc)
+	af.symlinks = cc.Symlinks()
+	return af
 }
 
-func getCopyManifestForPyBinary(py *python.Module) (fileToCopy android.Path, dirInApex string) {
-	dirInApex = "bin"
-	fileToCopy = py.HostToolPath().Path()
-	return
+func apexFileForPyBinary(py *python.Module) apexFile {
+	dirInApex := "bin"
+	fileToCopy := py.HostToolPath().Path()
+	return newApexFile(fileToCopy, py.Name(), dirInApex, pyBinary, py)
 }
-func getCopyManifestForGoBinary(ctx android.ModuleContext, gb bootstrap.GoBinaryTool) (fileToCopy android.Path, dirInApex string) {
-	dirInApex = "bin"
+func apexFileForGoBinary(ctx android.ModuleContext, depName string, gb bootstrap.GoBinaryTool) apexFile {
+	dirInApex := "bin"
 	s, err := filepath.Rel(android.PathForOutput(ctx).String(), gb.InstallPath())
 	if err != nil {
 		ctx.ModuleErrorf("Unable to use compiled binary at %s", gb.InstallPath())
-		return
+		return apexFile{}
 	}
-	fileToCopy = android.PathForOutput(ctx, s)
-	return
+	fileToCopy := android.PathForOutput(ctx, s)
+	// NB: Since go binaries are static we don't need the module for anything here, which is
+	// good since the go tool is a blueprint.Module not an android.Module like we would
+	// normally use.
+	return newApexFile(fileToCopy, depName, dirInApex, goBinary, nil)
 }
 
-func getCopyManifestForShBinary(sh *android.ShBinary) (fileToCopy android.Path, dirInApex string) {
-	dirInApex = filepath.Join("bin", sh.SubDir())
-	fileToCopy = sh.OutputFile()
-	return
+func apexFileForShBinary(sh *android.ShBinary) apexFile {
+	dirInApex := filepath.Join("bin", sh.SubDir())
+	fileToCopy := sh.OutputFile()
+	af := newApexFile(fileToCopy, sh.Name(), dirInApex, shBinary, sh)
+	af.symlinks = sh.Symlinks()
+	return af
 }
 
-func getCopyManifestForJavaLibrary(java *java.Library) (fileToCopy android.Path, dirInApex string) {
-	dirInApex = "javalib"
-	fileToCopy = java.DexJarFile()
-	return
+func apexFileForJavaLibrary(java *java.Library) apexFile {
+	dirInApex := "javalib"
+	fileToCopy := java.DexJarFile()
+	return newApexFile(fileToCopy, java.Name(), dirInApex, javaSharedLib, java)
 }
 
-func getCopyManifestForPrebuiltJavaLibrary(java *java.Import) (fileToCopy android.Path, dirInApex string) {
-	dirInApex = "javalib"
+func apexFileForPrebuiltJavaLibrary(java *java.Import) apexFile {
+	dirInApex := "javalib"
 	// The output is only one, but for some reason, ImplementationJars returns Paths, not Path
 	implJars := java.ImplementationJars()
 	if len(implJars) != 1 {
 		panic(fmt.Errorf("java.ImplementationJars() must return single Path, but got: %s",
 			strings.Join(implJars.Strings(), ", ")))
 	}
-	fileToCopy = implJars[0]
-	return
+	fileToCopy := implJars[0]
+	return newApexFile(fileToCopy, java.Name(), dirInApex, javaSharedLib, java)
 }
 
-func getCopyManifestForPrebuiltEtc(prebuilt android.PrebuiltEtcModule) (fileToCopy android.Path, dirInApex string) {
-	dirInApex = filepath.Join("etc", prebuilt.SubDir())
-	fileToCopy = prebuilt.OutputFile()
-	return
+func apexFileForPrebuiltEtc(prebuilt android.PrebuiltEtcModule, depName string) apexFile {
+	dirInApex := filepath.Join("etc", prebuilt.SubDir())
+	fileToCopy := prebuilt.OutputFile()
+	return newApexFile(fileToCopy, depName, dirInApex, etc, prebuilt)
 }
 
-func getCopyManifestForAndroidApp(app *java.AndroidApp, pkgName string) (fileToCopy android.Path, dirInApex string) {
+func apexFileForAndroidApp(aapp interface {
+	android.Module
+	Privileged() bool
+	OutputFile() android.Path
+}, pkgName string) apexFile {
 	appDir := "app"
-	if app.Privileged() {
+	if aapp.Privileged() {
 		appDir = "priv-app"
 	}
-	dirInApex = filepath.Join(appDir, pkgName)
-	fileToCopy = app.OutputFile()
-	return
-}
-
-func getCopyManifestForAndroidAppImport(app *java.AndroidAppImport, pkgName string) (fileToCopy android.Path, dirInApex string) {
-	appDir := "app"
-	if app.Privileged() {
-		appDir = "priv-app"
-	}
-	dirInApex = filepath.Join(appDir, pkgName)
-	fileToCopy = app.OutputFile()
-	return
+	dirInApex := filepath.Join(appDir, pkgName)
+	fileToCopy := aapp.OutputFile()
+	return newApexFile(fileToCopy, aapp.Name(), dirInApex, app, aapp)
 }
 
 // Context "decorator", overriding the InstallBypassMake method to always reply `true`.
@@ -877,8 +897,6 @@
 }
 
 func (a *apexBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	filesInfo := []apexFile{}
-
 	buildFlattenedAsDefault := ctx.Config().FlattenApex() && !ctx.Config().UnbundledBuild()
 	switch a.properties.ApexType {
 	case imageApex:
@@ -939,68 +957,67 @@
 		providedNativeSharedLibs = append(providedNativeSharedLibs, other.properties.Native_shared_libs...)
 	})
 
+	var filesInfo []apexFile
 	ctx.WalkDepsBlueprint(func(child, parent blueprint.Module) bool {
 		depTag := ctx.OtherModuleDependencyTag(child)
 		depName := ctx.OtherModuleName(child)
-		if _, ok := parent.(*apexBundle); ok {
-			// direct dependencies
+		if _, isDirectDep := parent.(*apexBundle); isDirectDep {
 			switch depTag {
 			case sharedLibTag:
 				if cc, ok := child.(*cc.Module); ok {
 					if cc.HasStubsVariants() {
 						provideNativeLibs = append(provideNativeLibs, cc.OutputFile().Path().Base())
 					}
-					fileToCopy, dirInApex := getCopyManifestForNativeLibrary(cc, ctx.Config(), handleSpecialLibs)
-					filesInfo = append(filesInfo, apexFile{fileToCopy, depName, dirInApex, nativeSharedLib, cc, nil})
-					return true
+					filesInfo = append(filesInfo, apexFileForNativeLibrary(cc, ctx.Config(), handleSpecialLibs))
+					return true // track transitive dependencies
 				} else {
 					ctx.PropertyErrorf("native_shared_libs", "%q is not a cc_library or cc_library_shared module", depName)
 				}
 			case executableTag:
 				if cc, ok := child.(*cc.Module); ok {
-					fileToCopy, dirInApex := getCopyManifestForExecutable(cc)
-					filesInfo = append(filesInfo, apexFile{fileToCopy, depName, dirInApex, nativeExecutable, cc, cc.Symlinks()})
-					return true
+					filesInfo = append(filesInfo, apexFileForExecutable(cc))
+					return true // track transitive dependencies
 				} else if sh, ok := child.(*android.ShBinary); ok {
-					fileToCopy, dirInApex := getCopyManifestForShBinary(sh)
-					filesInfo = append(filesInfo, apexFile{fileToCopy, depName, dirInApex, shBinary, sh, sh.Symlinks()})
+					filesInfo = append(filesInfo, apexFileForShBinary(sh))
 				} else if py, ok := child.(*python.Module); ok && py.HostToolPath().Valid() {
-					fileToCopy, dirInApex := getCopyManifestForPyBinary(py)
-					filesInfo = append(filesInfo, apexFile{fileToCopy, depName, dirInApex, pyBinary, py, nil})
+					filesInfo = append(filesInfo, apexFileForPyBinary(py))
 				} else if gb, ok := child.(bootstrap.GoBinaryTool); ok && a.Host() {
-					fileToCopy, dirInApex := getCopyManifestForGoBinary(ctx, gb)
-					// NB: Since go binaries are static we don't need the module for anything here, which is
-					// good since the go tool is a blueprint.Module not an android.Module like we would
-					// normally use.
-					filesInfo = append(filesInfo, apexFile{fileToCopy, depName, dirInApex, goBinary, nil, nil})
+					filesInfo = append(filesInfo, apexFileForGoBinary(ctx, depName, gb))
 				} else {
 					ctx.PropertyErrorf("binaries", "%q is neither cc_binary, (embedded) py_binary, (host) blueprint_go_binary, (host) bootstrap_go_binary, nor sh_binary", depName)
 				}
 			case javaLibTag:
 				if javaLib, ok := child.(*java.Library); ok {
-					fileToCopy, dirInApex := getCopyManifestForJavaLibrary(javaLib)
-					if fileToCopy == nil {
+					af := apexFileForJavaLibrary(javaLib)
+					if !af.Ok() {
 						ctx.PropertyErrorf("java_libs", "%q is not configured to be compiled into dex", depName)
 					} else {
-						filesInfo = append(filesInfo, apexFile{fileToCopy, depName, dirInApex, javaSharedLib, javaLib, nil})
+						filesInfo = append(filesInfo, af)
+						return true // track transitive dependencies
 					}
-					return true
 				} else if javaLib, ok := child.(*java.Import); ok {
-					fileToCopy, dirInApex := getCopyManifestForPrebuiltJavaLibrary(javaLib)
-					if fileToCopy == nil {
+					af := apexFileForPrebuiltJavaLibrary(javaLib)
+					if !af.Ok() {
 						ctx.PropertyErrorf("java_libs", "%q does not have a jar output", depName)
 					} else {
-						filesInfo = append(filesInfo, apexFile{fileToCopy, depName, dirInApex, javaSharedLib, javaLib, nil})
+						filesInfo = append(filesInfo, af)
 					}
-					return true
 				} else {
 					ctx.PropertyErrorf("java_libs", "%q of type %q is not supported", depName, ctx.OtherModuleType(child))
 				}
+			case androidAppTag:
+				pkgName := ctx.DeviceConfig().OverridePackageNameFor(depName)
+				if ap, ok := child.(*java.AndroidApp); ok {
+					filesInfo = append(filesInfo, apexFileForAndroidApp(ap, pkgName))
+					return true // track transitive dependencies
+				} else if ap, ok := child.(*java.AndroidAppImport); ok {
+					filesInfo = append(filesInfo, apexFileForAndroidApp(ap, pkgName))
+				} else {
+					ctx.PropertyErrorf("apps", "%q is not an android_app module", depName)
+				}
 			case prebuiltTag:
 				if prebuilt, ok := child.(android.PrebuiltEtcModule); ok {
-					fileToCopy, dirInApex := getCopyManifestForPrebuiltEtc(prebuilt)
-					filesInfo = append(filesInfo, apexFile{fileToCopy, depName, dirInApex, etc, prebuilt, nil})
-					return true
+					filesInfo = append(filesInfo, apexFileForPrebuiltEtc(prebuilt, depName))
 				} else {
 					ctx.PropertyErrorf("prebuilts", "%q is not a prebuilt_etc module", depName)
 				}
@@ -1016,10 +1033,10 @@
 						return true
 					} else {
 						// Single-output test module (where `test_per_src: false`).
-						fileToCopy, dirInApex := getCopyManifestForExecutable(ccTest)
-						filesInfo = append(filesInfo, apexFile{fileToCopy, depName, dirInApex, nativeTest, ccTest, nil})
+						af := apexFileForExecutable(ccTest)
+						af.class = nativeTest
+						filesInfo = append(filesInfo, af)
 					}
-					return true
 				} else {
 					ctx.PropertyErrorf("tests", "%q is not a cc module", depName)
 				}
@@ -1027,15 +1044,14 @@
 				if key, ok := child.(*apexKey); ok {
 					a.private_key_file = key.private_key_file
 					a.public_key_file = key.public_key_file
-					return false
 				} else {
 					ctx.PropertyErrorf("key", "%q is not an apex_key module", depName)
 				}
+				return false
 			case certificateTag:
 				if dep, ok := child.(*java.AndroidAppCertificate); ok {
 					a.container_certificate_file = dep.Certificate.Pem
 					a.container_private_key_file = dep.Certificate.Key
-					return false
 				} else {
 					ctx.ModuleErrorf("certificate dependency %q must be an android_app_certificate module", depName)
 				}
@@ -1045,17 +1061,6 @@
 				if prebuilt, ok := child.(*Prebuilt); ok && prebuilt.isForceDisabled() {
 					a.prebuiltFileToDelete = prebuilt.InstallFilename()
 				}
-			case androidAppTag:
-				if ap, ok := child.(*java.AndroidApp); ok {
-					fileToCopy, dirInApex := getCopyManifestForAndroidApp(ap, ctx.DeviceConfig().OverridePackageNameFor(depName))
-					filesInfo = append(filesInfo, apexFile{fileToCopy, depName, dirInApex, app, ap, nil})
-					return true
-				} else if ap, ok := child.(*java.AndroidAppImport); ok {
-					fileToCopy, dirInApex := getCopyManifestForAndroidAppImport(ap, ctx.DeviceConfig().OverridePackageNameFor(depName))
-					filesInfo = append(filesInfo, apexFile{fileToCopy, depName, dirInApex, app, ap, nil})
-				} else {
-					ctx.PropertyErrorf("apps", "%q is not an android_app module", depName)
-				}
 			}
 		} else if !a.vndkApex {
 			// indirect dependencies
@@ -1084,23 +1089,26 @@
 							// Don't track further
 							return false
 						}
-						fileToCopy, dirInApex := getCopyManifestForNativeLibrary(cc, ctx.Config(), handleSpecialLibs)
-						filesInfo = append(filesInfo, apexFile{fileToCopy, depName, dirInApex, nativeSharedLib, cc, nil})
-						return true
+						af := apexFileForNativeLibrary(cc, ctx.Config(), handleSpecialLibs)
+						af.transitiveDep = true
+						filesInfo = append(filesInfo, af)
+						return true // track transitive dependencies
 					}
 				} else if cc.IsTestPerSrcDepTag(depTag) {
 					if cc, ok := child.(*cc.Module); ok {
-						fileToCopy, dirInApex := getCopyManifestForExecutable(cc)
+						af := apexFileForExecutable(cc)
 						// Handle modules created as `test_per_src` variations of a single test module:
 						// use the name of the generated test binary (`fileToCopy`) instead of the name
 						// of the original test module (`depName`, shared by all `test_per_src`
 						// variations of that module).
-						moduleName := filepath.Base(fileToCopy.String())
-						filesInfo = append(filesInfo, apexFile{fileToCopy, moduleName, dirInApex, nativeTest, cc, nil})
-						return true
+						af.moduleName = filepath.Base(af.builtFile.String())
+						af.transitiveDep = true
+						filesInfo = append(filesInfo, af)
+						return true // track transitive dependencies
 					}
 				} else if java.IsJniDepTag(depTag) {
 					// Do nothing for JNI dep. JNI libraries are always embedded in APK-in-APEX.
+					return true
 				} else if am.CanHaveApexVariants() && am.IsInstallableToApex() {
 					ctx.ModuleErrorf("unexpected tag %q for indirect dependency %q", depTag, depName)
 				}
@@ -1117,7 +1125,8 @@
 			dirInApex := filepath.Join("javalib", arch.String())
 			for _, f := range files {
 				localModule := "javalib_" + arch.String() + "_" + filepath.Base(f.String())
-				filesInfo = append(filesInfo, apexFile{f, localModule, dirInApex, etc, nil, nil})
+				af := newApexFile(f, localModule, dirInApex, etc, nil)
+				filesInfo = append(filesInfo, af)
 			}
 		}
 	}
diff --git a/apex/apex_test.go b/apex/apex_test.go
index a41f914..509d760 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -2994,10 +2994,11 @@
 	var builder strings.Builder
 	data.Custom(&builder, name, "TARGET_", "", data)
 	androidMk := builder.String()
-	ensureContains(t, androidMk, "LOCAL_MODULE := app.override_myapex")
+	ensureContains(t, androidMk, "LOCAL_MODULE := override_app.override_myapex")
 	ensureContains(t, androidMk, "LOCAL_MODULE := apex_manifest.pb.override_myapex")
 	ensureContains(t, androidMk, "LOCAL_MODULE_STEM := override_myapex.apex")
 	ensureNotContains(t, androidMk, "LOCAL_MODULE := app.myapex")
+	ensureNotContains(t, androidMk, "LOCAL_MODULE := override_app.myapex")
 	ensureNotContains(t, androidMk, "LOCAL_MODULE := apex_manifest.pb.myapex")
 	ensureNotContains(t, androidMk, "LOCAL_MODULE_STEM := myapex.apex")
 }
diff --git a/apex/builder.go b/apex/builder.go
index 70f3e1a..f199bd4 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -501,8 +501,8 @@
 	if a.installable() {
 		// For flattened APEX, do nothing but make sure that apex_manifest.json and apex_pubkey are also copied along
 		// with other ordinary files.
-		a.filesInfo = append(a.filesInfo, apexFile{a.manifestJsonOut, "apex_manifest.json." + a.Name() + a.suffix, ".", etc, nil, nil})
-		a.filesInfo = append(a.filesInfo, apexFile{a.manifestPbOut, "apex_manifest.pb." + a.Name() + a.suffix, ".", etc, nil, nil})
+		a.filesInfo = append(a.filesInfo, newApexFile(a.manifestJsonOut, "apex_manifest.json."+a.Name()+a.suffix, ".", etc, nil))
+		a.filesInfo = append(a.filesInfo, newApexFile(a.manifestPbOut, "apex_manifest.pb."+a.Name()+a.suffix, ".", etc, nil))
 
 		// rename to apex_pubkey
 		copiedPubkey := android.PathForModuleOut(ctx, "apex_pubkey")
@@ -511,7 +511,7 @@
 			Input:  a.public_key_file,
 			Output: copiedPubkey,
 		})
-		a.filesInfo = append(a.filesInfo, apexFile{copiedPubkey, "apex_pubkey." + a.Name() + a.suffix, ".", etc, nil, nil})
+		a.filesInfo = append(a.filesInfo, newApexFile(copiedPubkey, "apex_pubkey."+a.Name()+a.suffix, ".", etc, nil))
 
 		if a.properties.ApexType == flattenedApex {
 			apexName := proptools.StringDefault(a.properties.Apex_name, a.Name())
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 55bdd1c..b4082d3 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -39,7 +39,16 @@
 
 	hwasanCflags = []string{"-fno-omit-frame-pointer", "-Wno-frame-larger-than=",
 		"-fsanitize-hwaddress-abi=platform",
-		"-fno-experimental-new-pass-manager"}
+		"-fno-experimental-new-pass-manager",
+		// The following improves debug location information
+		// availability at the cost of its accuracy. It increases
+		// the likelihood of a stack variable's frame offset
+		// to be recorded in the debug info, which is important
+		// for the quality of hwasan reports. The downside is a
+		// higher number of "optimized out" stack variables.
+		// b/112437883.
+		"-mllvm", "-instcombine-lower-dbg-declare=0",
+	}
 
 	cfiCflags = []string{"-flto", "-fsanitize-cfi-cross-dso",
 		"-fsanitize-blacklist=external/compiler-rt/lib/cfi/cfi_blacklist.txt"}
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index f38d892..83e3673 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -38,11 +38,10 @@
 	DisableGenerateProfile bool   // don't generate profiles
 	ProfileDir             string // directory to find profiles in
 
-	BootJars []string // modules for jars that form the boot class path
+	BootJars          []string // modules for jars that form the boot class path
+	UpdatableBootJars []string // jars within apex that form the boot class path
 
-	ArtApexJars                   []string // modules for jars that are in the ART APEX
-	ProductUpdatableBootModules   []string
-	ProductUpdatableBootLocations []string
+	ArtApexJars []string // modules for jars that are in the ART APEX
 
 	SystemServerJars          []string // jars that form the system server
 	SystemServerApps          []string // apps that are loaded into system server
@@ -118,9 +117,10 @@
 	UsesLibraries                []string
 	LibraryPaths                 map[string]android.Path
 
-	Archs               []android.ArchType
-	DexPreoptImages     []android.Path
-	DexPreoptImagesDeps []android.Paths
+	Archs                   []android.ArchType
+	DexPreoptImages         []android.Path
+	DexPreoptImagesDeps     []android.OutputPaths
+	DexPreoptImageLocations []string
 
 	PreoptBootClassPathDexFiles     android.Paths // file paths of boot class path files
 	PreoptBootClassPathDexLocations []string      // virtual locations of boot class path files
@@ -226,6 +226,7 @@
 		ProfileClassListing         string
 		LibraryPaths                map[string]string
 		DexPreoptImages             []string
+		DexPreoptImageLocations     []string
 		PreoptBootClassPathDexFiles []string
 	}
 
@@ -243,10 +244,11 @@
 	config.ModuleConfig.ProfileClassListing = android.OptionalPathForPath(constructPath(ctx, config.ProfileClassListing))
 	config.ModuleConfig.LibraryPaths = constructPathMap(ctx, config.LibraryPaths)
 	config.ModuleConfig.DexPreoptImages = constructPaths(ctx, config.DexPreoptImages)
+	config.ModuleConfig.DexPreoptImageLocations = config.DexPreoptImageLocations
 	config.ModuleConfig.PreoptBootClassPathDexFiles = constructPaths(ctx, config.PreoptBootClassPathDexFiles)
 
 	// This needs to exist, but dependencies are already handled in Make, so we don't need to pass them through JSON.
-	config.ModuleConfig.DexPreoptImagesDeps = make([]android.Paths, len(config.ModuleConfig.DexPreoptImages))
+	config.ModuleConfig.DexPreoptImagesDeps = make([]android.OutputPaths, len(config.ModuleConfig.DexPreoptImages))
 
 	return config.ModuleConfig, nil
 }
@@ -281,9 +283,8 @@
 		DisableGenerateProfile:             false,
 		ProfileDir:                         "",
 		BootJars:                           nil,
+		UpdatableBootJars:                  nil,
 		ArtApexJars:                        nil,
-		ProductUpdatableBootModules:        nil,
-		ProductUpdatableBootLocations:      nil,
 		SystemServerJars:                   nil,
 		SystemServerApps:                   nil,
 		UpdatableSystemServerJars:          nil,
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index 18a38fb..40986c3 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -86,10 +86,8 @@
 
 			generateDM := shouldGenerateDM(module, global)
 
-			for i, arch := range module.Archs {
-				image := module.DexPreoptImages[i]
-				imageDeps := module.DexPreoptImagesDeps[i]
-				dexpreoptCommand(ctx, global, module, rule, arch, profile, image, imageDeps, appImage, generateDM)
+			for archIdx, _ := range module.Archs {
+				dexpreoptCommand(ctx, global, module, rule, archIdx, profile, appImage, generateDM)
 			}
 		}
 	}
@@ -193,7 +191,9 @@
 }
 
 func dexpreoptCommand(ctx android.PathContext, global GlobalConfig, module ModuleConfig, rule *android.RuleBuilder,
-	arch android.ArchType, profile, bootImage android.Path, bootImageDeps android.Paths, appImage, generateDM bool) {
+	archIdx int, profile android.WritablePath, appImage bool, generateDM bool) {
+
+	arch := module.Archs[archIdx]
 
 	// HACK: make soname in Soong-generated .odex files match Make.
 	base := filepath.Base(module.DexLocation)
@@ -222,13 +222,6 @@
 
 	invocationPath := odexPath.ReplaceExtension(ctx, "invocation")
 
-	// bootImage is .../dex_bootjars/system/framework/arm64/boot.art, but dex2oat wants
-	// .../dex_bootjars/system/framework/boot.art on the command line
-	var bootImageLocation string
-	if bootImage != nil {
-		bootImageLocation = PathToLocation(bootImage, arch)
-	}
-
 	// The class loader context using paths in the build
 	var classLoaderContextHost android.Paths
 
@@ -356,7 +349,7 @@
 		Flag("--runtime-arg").FlagWithList("-Xbootclasspath-locations:", module.PreoptBootClassPathDexLocations, ":").
 		Flag("${class_loader_context_arg}").
 		Flag("${stored_class_loader_context_arg}").
-		FlagWithArg("--boot-image=", bootImageLocation).Implicits(bootImageDeps).
+		FlagWithArg("--boot-image=", strings.Join(module.DexPreoptImageLocations, ":")).Implicits(module.DexPreoptImagesDeps[archIdx].Paths()).
 		FlagWithInput("--dex-file=", module.DexPath).
 		FlagWithArg("--dex-location=", dexLocationArg).
 		FlagWithOutput("--oat-file=", odexPath).ImplicitOutput(vdexPath).
@@ -554,6 +547,12 @@
 	return apexJarPair[0], apexJarPair[1]
 }
 
+// Expected format for apexJarValue = <apex name>:<jar name>
+func GetJarLocationFromApexJarPair(apexJarValue string) string {
+	apex, jar := SplitApexJarPair(apexJarValue)
+	return filepath.Join("/apex", apex, "javalib", jar+".jar")
+}
+
 func contains(l []string, s string) bool {
 	for _, e := range l {
 		if e == s {
diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go
index 6f8120e..254be0a 100644
--- a/dexpreopt/dexpreopt_test.go
+++ b/dexpreopt/dexpreopt_test.go
@@ -49,7 +49,8 @@
 		LibraryPaths:                    nil,
 		Archs:                           []android.ArchType{android.Arm},
 		DexPreoptImages:                 android.Paths{android.PathForTesting("system/framework/arm/boot.art")},
-		DexPreoptImagesDeps:             []android.Paths{android.Paths{}},
+		DexPreoptImagesDeps:             []android.OutputPaths{android.OutputPaths{}},
+		DexPreoptImageLocations:         []string{},
 		PreoptBootClassPathDexFiles:     nil,
 		PreoptBootClassPathDexLocations: nil,
 		PreoptExtractedApk:              false,
diff --git a/java/app_test.go b/java/app_test.go
index 7e461bc..fc8cf8e 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -399,18 +399,21 @@
 
 			android_library {
 				name: "lib",
+				sdk_version: "current",
 				resource_dirs: ["lib/res"],
 				static_libs: ["lib2"],
 			}
 
 			android_library {
 				name: "lib2",
+				sdk_version: "current",
 				resource_dirs: ["lib2/res"],
 			}
 
 			// This library has the same resources as lib (should not lead to dupe RROs)
 			android_library {
 				name: "lib3",
+				sdk_version: "current",
 				resource_dirs: ["lib/res"]
 			}
 		`
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 2b1c994..479dec6 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -124,7 +124,7 @@
 	}
 
 	var images android.Paths
-	var imagesDeps []android.Paths
+	var imagesDeps []android.OutputPaths
 	for _, arch := range archs {
 		images = append(images, bootImage.images[arch])
 		imagesDeps = append(imagesDeps, bootImage.imagesDeps[arch])
@@ -169,15 +169,16 @@
 		UsesLibraries:                d.usesLibs,
 		LibraryPaths:                 d.libraryPaths,
 
-		Archs:               archs,
-		DexPreoptImages:     images,
-		DexPreoptImagesDeps: imagesDeps,
+		Archs:                   archs,
+		DexPreoptImages:         images,
+		DexPreoptImagesDeps:     imagesDeps,
+		DexPreoptImageLocations: bootImage.imageLocations,
 
 		// We use the dex paths and dex locations of the default boot image, as it
 		// contains the full dexpreopt boot classpath. Other images may just contain a subset of
 		// the dexpreopt boot classpath.
-		PreoptBootClassPathDexFiles:     defaultBootImage.dexPaths.Paths(),
-		PreoptBootClassPathDexLocations: defaultBootImage.dexLocations,
+		PreoptBootClassPathDexFiles:     defaultBootImage.dexPathsDeps.Paths(),
+		PreoptBootClassPathDexLocations: defaultBootImage.dexLocationsDeps,
 
 		PreoptExtractedApk: false,
 
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index a29665e..5e72cee 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -22,7 +22,6 @@
 	"android/soong/android"
 	"android/soong/dexpreopt"
 
-	"github.com/google/blueprint/pathtools"
 	"github.com/google/blueprint/proptools"
 )
 
@@ -50,38 +49,77 @@
 // will then reconstruct the real path, so the rules must have a dependency on the real path.
 
 type bootImageConfig struct {
-	name         string
-	stem         string
-	modules      []string
-	dexLocations []string
-	dexPaths     android.WritablePaths
-	dir          android.OutputPath
-	symbolsDir   android.OutputPath
-	targets      []android.Target
-	images       map[android.ArchType]android.OutputPath
-	imagesDeps   map[android.ArchType]android.Paths
-	zip          android.WritablePath
+	// Whether this image is an extension.
+	extension bool
+
+	// Image name (used in directory names and ninja rule names).
+	name string
+
+	// Basename of the image: the resulting filenames are <stem>[-<jar>].{art,oat,vdex}.
+	stem string
+
+	// Output directory for the image files.
+	dir android.OutputPath
+
+	// Output directory for the image files with debug symbols.
+	symbolsDir android.OutputPath
+
+	// Subdirectory where the image files are installed.
+	installSubdir string
+
+	// Targets for which the image is generated.
+	targets []android.Target
+
+	// The names of jars that constitute this image.
+	modules []string
+
+	// The "locations" of jars.
+	dexLocations     []string // for this image
+	dexLocationsDeps []string // for the dependency images and in this image
+
+	// File paths to jars.
+	dexPaths     android.WritablePaths // for this image
+	dexPathsDeps android.WritablePaths // for the dependency images and in this image
+
+	// The "locations" of the dependency images and in this image.
+	imageLocations []string
+
+	// Paths to image files (grouped by target).
+	images     map[android.ArchType]android.OutputPath  // first image file
+	imagesDeps map[android.ArchType]android.OutputPaths // all files
+
+	// File path to a zip archive with all image files (or nil, if not needed).
+	zip android.WritablePath
 }
 
-func (image bootImageConfig) moduleFiles(ctx android.PathContext, dir android.OutputPath, exts ...string) []android.OutputPath {
-	ret := make([]android.OutputPath, 0, len(image.modules)*len(exts))
-
-	// dex preopt on the bootclasspath produces multiple files.  The first dex file
-	// is converted into to 'name'.art (to match the legacy assumption that 'name'.art
+func (image bootImageConfig) moduleName(idx int) string {
+	// Dexpreopt on the boot class path produces multiple files. The first dex file
+	// is converted into 'name'.art (to match the legacy assumption that 'name'.art
 	// exists), and the rest are converted to 'name'-<jar>.art.
-	// In addition, each .art file has an associated .oat and .vdex file, and an
-	// unstripped .oat file
-	for i, m := range image.modules {
-		name := image.stem
-		if i != 0 {
-			name += "-" + stemOf(m)
-		}
+	m := image.modules[idx]
+	name := image.stem
+	if idx != 0 || image.extension {
+		name += "-" + stemOf(m)
+	}
+	return name
+}
 
+func (image bootImageConfig) firstModuleNameOrStem() string {
+	if len(image.modules) > 0 {
+		return image.moduleName(0)
+	} else {
+		return image.stem
+	}
+}
+
+func (image bootImageConfig) moduleFiles(ctx android.PathContext, dir android.OutputPath, exts ...string) android.OutputPaths {
+	ret := make(android.OutputPaths, 0, len(image.modules)*len(exts))
+	for i := range image.modules {
+		name := image.moduleName(i)
 		for _, ext := range exts {
 			ret = append(ret, dir.Join(ctx, name+ext))
 		}
 	}
-
 	return ret
 }
 
@@ -140,12 +178,6 @@
 	return false
 }
 
-func skipDexpreoptArtBootJars(ctx android.BuilderContext) bool {
-	// with EMMA_INSTRUMENT_FRAMEWORK=true ART boot class path libraries have dependencies on framework,
-	// therefore dexpreopt ART libraries cannot be dexpreopted in isolation => no ART boot image
-	return ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK")
-}
-
 type dexpreoptBootJars struct {
 	defaultBootImage *bootImage
 	otherImages      []*bootImage
@@ -154,8 +186,8 @@
 }
 
 // Accessor function for the apex package. Returns nil if dexpreopt is disabled.
-func DexpreoptedArtApexJars(ctx android.BuilderContext) map[android.ArchType]android.Paths {
-	if skipDexpreoptBootJars(ctx) || skipDexpreoptArtBootJars(ctx) {
+func DexpreoptedArtApexJars(ctx android.BuilderContext) map[android.ArchType]android.OutputPaths {
+	if skipDexpreoptBootJars(ctx) {
 		return nil
 	}
 	return artBootImageConfig(ctx).imagesDeps
@@ -184,10 +216,8 @@
 
 	// Always create the default boot image first, to get a unique profile rule for all images.
 	d.defaultBootImage = buildBootImage(ctx, defaultBootImageConfig(ctx))
-	if !skipDexpreoptArtBootJars(ctx) {
-		// Create boot image for the ART apex (build artifacts are accessed via the global boot image config).
-		buildBootImage(ctx, artBootImageConfig(ctx))
-	}
+	// Create boot image for the ART apex (build artifacts are accessed via the global boot image config).
+	buildBootImage(ctx, artBootImageConfig(ctx))
 	if global.GenerateApexImage {
 		// Create boot images for the JIT-zygote experiment.
 		d.otherImages = append(d.otherImages, buildBootImage(ctx, apexBootImageConfig(ctx)))
@@ -201,7 +231,6 @@
 	image := newBootImage(ctx, config)
 
 	bootDexJars := make(android.Paths, len(image.modules))
-
 	ctx.VisitAllModules(func(module android.Module) {
 		// Collect dex jar paths for the modules listed above.
 		if j, ok := module.(interface{ DexJar() android.Path }); ok {
@@ -265,11 +294,12 @@
 
 	global := dexpreoptGlobalConfig(ctx)
 
-	symbolsDir := image.symbolsDir.Join(ctx, "system/framework", arch.String())
+	symbolsDir := image.symbolsDir.Join(ctx, image.installSubdir, arch.String())
 	symbolsFile := symbolsDir.Join(ctx, image.stem+".oat")
-	outputDir := image.dir.Join(ctx, "system/framework", arch.String())
-	outputPath := image.images[arch]
-	oatLocation := pathtools.ReplaceExtension(dexpreopt.PathToLocation(outputPath, arch), "oat")
+	outputDir := image.dir.Join(ctx, image.installSubdir, arch.String())
+	outputPath := outputDir.Join(ctx, image.stem+".oat")
+	oatLocation := dexpreopt.PathToLocation(outputPath, arch)
+	imagePath := outputPath.ReplaceExtension(ctx, "art")
 
 	rule := android.NewRuleBuilder()
 	rule.MissingDeps(missingDeps)
@@ -312,18 +342,27 @@
 		cmd.FlagWithInput("--dirty-image-objects=", global.DirtyImageObjects.Path())
 	}
 
+	if image.extension {
+		artImage := artBootImageConfig(ctx).images[arch]
+		cmd.
+			Flag("--runtime-arg").FlagWithInputList("-Xbootclasspath:", image.dexPathsDeps.Paths(), ":").
+			Flag("--runtime-arg").FlagWithList("-Xbootclasspath-locations:", image.dexLocationsDeps, ":").
+			FlagWithArg("--boot-image=", dexpreopt.PathToLocation(artImage, arch)).Implicit(artImage)
+	} else {
+		cmd.FlagWithArg("--base=", ctx.Config().LibartImgDeviceBaseAddress())
+	}
+
 	cmd.
 		FlagForEachInput("--dex-file=", image.dexPaths.Paths()).
 		FlagForEachArg("--dex-location=", image.dexLocations).
 		Flag("--generate-debug-info").
 		Flag("--generate-build-id").
 		Flag("--image-format=lz4hc").
-		FlagWithOutput("--oat-symbols=", symbolsFile).
+		FlagWithArg("--oat-symbols=", symbolsFile.String()).
 		Flag("--strip").
-		FlagWithOutput("--oat-file=", outputPath.ReplaceExtension(ctx, "oat")).
+		FlagWithArg("--oat-file=", outputPath.String()).
 		FlagWithArg("--oat-location=", oatLocation).
-		FlagWithOutput("--image=", outputPath).
-		FlagWithArg("--base=", ctx.Config().LibartImgDeviceBaseAddress()).
+		FlagWithArg("--image=", imagePath.String()).
 		FlagWithArg("--instruction-set=", arch.String()).
 		FlagWithArg("--instruction-set-variant=", global.CpuVariant[arch]).
 		FlagWithArg("--instruction-set-features=", global.InstructionSetFeatures[arch]).
@@ -341,8 +380,8 @@
 
 	cmd.Textf(`|| ( echo %s ; false )`, proptools.ShellEscape(failureMessage))
 
-	installDir := filepath.Join("/system/framework", arch.String())
-	vdexInstallDir := filepath.Join("/system/framework")
+	installDir := filepath.Join("/", image.installSubdir, arch.String())
+	vdexInstallDir := filepath.Join("/", image.installSubdir)
 
 	var vdexInstalls android.RuleBuilderInstalls
 	var unstrippedInstalls android.RuleBuilderInstalls
@@ -425,8 +464,8 @@
 			Text(`ANDROID_LOG_TAGS="*:e"`).
 			Tool(tools.Profman).
 			FlagWithInput("--create-profile-from=", bootImageProfile).
-			FlagForEachInput("--apk=", image.dexPaths.Paths()).
-			FlagForEachArg("--dex-location=", image.dexLocations).
+			FlagForEachInput("--apk=", image.dexPathsDeps.Paths()).
+			FlagForEachArg("--dex-location=", image.dexLocationsDeps).
 			FlagWithOutput("--reference-profile-file=", profile)
 
 		rule.Install(profile, "/system/etc/boot-image.prof")
@@ -477,8 +516,8 @@
 			Tool(tools.Profman).
 			Flag("--generate-boot-profile").
 			FlagWithInput("--create-profile-from=", bootFrameworkProfile).
-			FlagForEachInput("--apk=", image.dexPaths.Paths()).
-			FlagForEachArg("--dex-location=", image.dexLocations).
+			FlagForEachInput("--apk=", image.dexPathsDeps.Paths()).
+			FlagForEachArg("--dex-location=", image.dexLocationsDeps).
 			FlagWithOutput("--reference-profile-file=", profile)
 
 		rule.Install(profile, "/system/etc/boot-image.bprof")
@@ -506,8 +545,8 @@
 		rule.Command().
 			// TODO: for now, use the debug version for better error reporting
 			BuiltTool(ctx, "oatdumpd").
-			FlagWithInputList("--runtime-arg -Xbootclasspath:", image.dexPaths.Paths(), ":").
-			FlagWithList("--runtime-arg -Xbootclasspath-locations:", image.dexLocations, ":").
+			FlagWithInputList("--runtime-arg -Xbootclasspath:", image.dexPathsDeps.Paths(), ":").
+			FlagWithList("--runtime-arg -Xbootclasspath-locations:", image.dexLocationsDeps, ":").
 			FlagWithArg("--image=", dexpreopt.PathToLocation(image.images[arch], arch)).Implicit(image.images[arch]).
 			FlagWithOutput("--output=", output).
 			FlagWithArg("--instruction-set=", arch.String())
@@ -556,8 +595,9 @@
 	image := d.defaultBootImage
 	if image != nil {
 		ctx.Strict("DEXPREOPT_IMAGE_PROFILE_BUILT_INSTALLED", image.profileInstalls.String())
-		ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_FILES", strings.Join(image.dexPaths.Strings(), " "))
-		ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS", strings.Join(image.dexLocations, " "))
+		ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_FILES", strings.Join(image.dexPathsDeps.Strings(), " "))
+		ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS", strings.Join(image.dexLocationsDeps, " "))
+		ctx.Strict("DEXPREOPT_IMAGE_LOCATIONS", strings.Join(image.imageLocations, ":"))
 		ctx.Strict("DEXPREOPT_IMAGE_ZIP_"+image.name, image.zip.String())
 
 		var imageNames []string
diff --git a/java/dexpreopt_bootjars_test.go b/java/dexpreopt_bootjars_test.go
index 29a5abe..87d5e30 100644
--- a/java/dexpreopt_bootjars_test.go
+++ b/java/dexpreopt_bootjars_test.go
@@ -48,7 +48,7 @@
 
 	pathCtx := android.PathContextForTesting(config, nil)
 	dexpreoptConfig := dexpreopt.GlobalConfigForTests(pathCtx)
-	dexpreoptConfig.ArtApexJars = []string{"foo", "bar", "baz"}
+	dexpreoptConfig.BootJars = []string{"foo", "bar", "baz"}
 	setDexpreoptTestGlobalConfig(config, dexpreoptConfig)
 
 	ctx := testContext(bp, nil)
@@ -59,9 +59,10 @@
 
 	dexpreoptBootJars := ctx.SingletonForTests("dex_bootjars")
 
-	bootArt := dexpreoptBootJars.Output("boot.art")
+	bootArt := dexpreoptBootJars.Output("boot-foo.art")
 
 	expectedInputs := []string{
+		"dex_artjars/apex/com.android.art/javalib/arm64/boot.art",
 		"dex_bootjars_input/foo.jar",
 		"dex_bootjars_input/bar.jar",
 		"dex_bootjars_input/baz.jar",
@@ -82,19 +83,19 @@
 	expectedOutputs := []string{
 		"dex_bootjars/system/framework/arm64/boot.invocation",
 
-		"dex_bootjars/system/framework/arm64/boot.art",
+		"dex_bootjars/system/framework/arm64/boot-foo.art",
 		"dex_bootjars/system/framework/arm64/boot-bar.art",
 		"dex_bootjars/system/framework/arm64/boot-baz.art",
 
-		"dex_bootjars/system/framework/arm64/boot.oat",
+		"dex_bootjars/system/framework/arm64/boot-foo.oat",
 		"dex_bootjars/system/framework/arm64/boot-bar.oat",
 		"dex_bootjars/system/framework/arm64/boot-baz.oat",
 
-		"dex_bootjars/system/framework/arm64/boot.vdex",
+		"dex_bootjars/system/framework/arm64/boot-foo.vdex",
 		"dex_bootjars/system/framework/arm64/boot-bar.vdex",
 		"dex_bootjars/system/framework/arm64/boot-baz.vdex",
 
-		"dex_bootjars_unstripped/system/framework/arm64/boot.oat",
+		"dex_bootjars_unstripped/system/framework/arm64/boot-foo.oat",
 		"dex_bootjars_unstripped/system/framework/arm64/boot-bar.oat",
 		"dex_bootjars_unstripped/system/framework/arm64/boot-baz.oat",
 	}
diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go
index 4747c64..57a770e 100644
--- a/java/dexpreopt_config.go
+++ b/java/dexpreopt_config.go
@@ -78,9 +78,8 @@
 				filepath.Join("/system/framework", m+".jar"))
 		}
 		for _, m := range global.UpdatableSystemServerJars {
-			apex, jar := dexpreopt.SplitApexJarPair(m)
 			systemServerClasspathLocations = append(systemServerClasspathLocations,
-				filepath.Join("/apex", apex, "javalib", jar+".jar"))
+				dexpreopt.GetJarLocationFromApexJarPair(m))
 		}
 		return systemServerClasspathLocations
 	})
@@ -111,114 +110,156 @@
 	return moduleName
 }
 
-// Construct a variant of the global config for dexpreopted bootclasspath jars. The variants differ
-// in the list of input jars (libcore, framework, or both), in the naming scheme for the dexpreopt
-// files (ART recognizes "apex" names as special), and whether to include a zip archive.
-//
-// 'name' is a string unique for each profile (used in directory names and ninja rule names)
-// 'stem' is the basename of the image: the resulting filenames are <stem>[-<jar>].{art,oat,vdex}.
-func getBootImageConfig(ctx android.PathContext, key android.OnceKey, name string, stem string,
-	needZip bool, artApexJarsOnly bool) bootImageConfig {
+func getJarsFromApexJarPairs(apexJarPairs []string) []string {
+	modules := make([]string, len(apexJarPairs))
+	for i, p := range apexJarPairs {
+		_, jar := dexpreopt.SplitApexJarPair(p)
+		modules[i] = jar
+	}
+	return modules
+}
 
-	return ctx.Config().Once(key, func() interface{} {
+var (
+	bootImageConfigKey     = android.NewOnceKey("bootImageConfig")
+	artBootImageName       = "art"
+	frameworkBootImageName = "boot"
+	apexBootImageName      = "apex"
+)
+
+// Construct the global boot image configs.
+func genBootImageConfigs(ctx android.PathContext) map[string]*bootImageConfig {
+	return ctx.Config().Once(bootImageConfigKey, func() interface{} {
+
 		global := dexpreoptGlobalConfig(ctx)
+		targets := dexpreoptTargets(ctx)
+		deviceDir := android.PathForOutput(ctx, ctx.Config().DeviceName())
 
 		artModules := global.ArtApexJars
-		imageModules := artModules
+		// In coverage builds ART boot class path jars are instrumented and have additional dependencies.
+		if ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") {
+			artModules = append(artModules, "jacocoagent")
+		}
+		frameworkModules := android.RemoveListFromList(global.BootJars,
+			concat(artModules, getJarsFromApexJarPairs(global.UpdatableBootJars)))
 
-		var bootLocations []string
+		artSubdir := "apex/com.android.art/javalib"
+		frameworkSubdir := "system/framework"
 
+		var artLocations, frameworkLocations []string
 		for _, m := range artModules {
-			bootLocations = append(bootLocations,
-				filepath.Join("/apex/com.android.art/javalib", stemOf(m)+".jar"))
+			artLocations = append(artLocations, filepath.Join("/"+artSubdir, stemOf(m)+".jar"))
+		}
+		for _, m := range frameworkModules {
+			frameworkLocations = append(frameworkLocations, filepath.Join("/"+frameworkSubdir, stemOf(m)+".jar"))
 		}
 
-		if !artApexJarsOnly {
-			nonFrameworkModules := concat(artModules, global.ProductUpdatableBootModules)
-			frameworkModules := android.RemoveListFromList(global.BootJars, nonFrameworkModules)
-			imageModules = concat(imageModules, frameworkModules)
+		// ART config for the primary boot image in the ART apex.
+		// It includes the Core Libraries.
+		artCfg := bootImageConfig{
+			extension:        false,
+			name:             artBootImageName,
+			stem:             "boot",
+			installSubdir:    artSubdir,
+			modules:          artModules,
+			dexLocations:     artLocations,
+			dexLocationsDeps: artLocations,
+		}
 
-			for _, m := range frameworkModules {
-				bootLocations = append(bootLocations,
-					filepath.Join("/system/framework", stemOf(m)+".jar"))
+		// Framework config for the boot image extension.
+		// It includes framework libraries and depends on the ART config.
+		frameworkCfg := bootImageConfig{
+			extension:        true,
+			name:             frameworkBootImageName,
+			stem:             "boot",
+			installSubdir:    frameworkSubdir,
+			modules:          frameworkModules,
+			dexLocations:     frameworkLocations,
+			dexLocationsDeps: append(artLocations, frameworkLocations...),
+		}
+
+		// Apex config for the  boot image used in the JIT-zygote experiment.
+		// It includes both the Core libraries and framework.
+		apexCfg := bootImageConfig{
+			extension:        false,
+			name:             apexBootImageName,
+			stem:             "apex",
+			installSubdir:    frameworkSubdir,
+			modules:          concat(artModules, frameworkModules),
+			dexLocations:     concat(artLocations, frameworkLocations),
+			dexLocationsDeps: concat(artLocations, frameworkLocations),
+		}
+
+		configs := map[string]*bootImageConfig{
+			artBootImageName:       &artCfg,
+			frameworkBootImageName: &frameworkCfg,
+			apexBootImageName:      &apexCfg,
+		}
+
+		// common to all configs
+		for _, c := range configs {
+			c.targets = targets
+
+			c.dir = deviceDir.Join(ctx, "dex_"+c.name+"jars")
+			c.symbolsDir = deviceDir.Join(ctx, "dex_"+c.name+"jars_unstripped")
+
+			// expands to <stem>.art for primary image and <stem>-<1st module>.art for extension
+			imageName := c.firstModuleNameOrStem() + ".art"
+
+			c.imageLocations = []string{c.dir.Join(ctx, c.installSubdir, imageName).String()}
+
+			// The path to bootclasspath dex files needs to be known at module
+			// GenerateAndroidBuildAction time, before the bootclasspath modules have been compiled.
+			// Set up known paths for them, the singleton rules will copy them there.
+			// TODO(b/143682396): use module dependencies instead
+			inputDir := deviceDir.Join(ctx, "dex_"+c.name+"jars_input")
+			for _, m := range c.modules {
+				c.dexPaths = append(c.dexPaths, inputDir.Join(ctx, stemOf(m)+".jar"))
+			}
+			c.dexPathsDeps = c.dexPaths
+
+			c.images = make(map[android.ArchType]android.OutputPath)
+			c.imagesDeps = make(map[android.ArchType]android.OutputPaths)
+
+			for _, target := range targets {
+				arch := target.Arch.ArchType
+				imageDir := c.dir.Join(ctx, c.installSubdir, arch.String())
+				c.images[arch] = imageDir.Join(ctx, imageName)
+				c.imagesDeps[arch] = c.moduleFiles(ctx, imageDir, ".art", ".oat", ".vdex")
 			}
 		}
 
-		// The path to bootclasspath dex files needs to be known at module GenerateAndroidBuildAction time, before
-		// the bootclasspath modules have been compiled.  Set up known paths for them, the singleton rules will copy
-		// them there.
-		// TODO(b/143682396): use module dependencies instead
-		var bootDexPaths android.WritablePaths
-		for _, m := range imageModules {
-			bootDexPaths = append(bootDexPaths,
-				android.PathForOutput(ctx, ctx.Config().DeviceName(), "dex_"+name+"jars_input", m+".jar"))
-		}
+		// specific to the framework config
+		frameworkCfg.dexPathsDeps = append(artCfg.dexPathsDeps, frameworkCfg.dexPathsDeps...)
+		frameworkCfg.imageLocations = append(artCfg.imageLocations, frameworkCfg.imageLocations...)
+		frameworkCfg.zip = frameworkCfg.dir.Join(ctx, frameworkCfg.stem+".zip")
 
-		dir := android.PathForOutput(ctx, ctx.Config().DeviceName(), "dex_"+name+"jars")
-		symbolsDir := android.PathForOutput(ctx, ctx.Config().DeviceName(), "dex_"+name+"jars_unstripped")
-
-		var zip android.WritablePath
-		if needZip {
-			zip = dir.Join(ctx, stem+".zip")
-		}
-
-		targets := dexpreoptTargets(ctx)
-
-		imageConfig := bootImageConfig{
-			name:         name,
-			stem:         stem,
-			modules:      imageModules,
-			dexLocations: bootLocations,
-			dexPaths:     bootDexPaths,
-			dir:          dir,
-			symbolsDir:   symbolsDir,
-			targets:      targets,
-			images:       make(map[android.ArchType]android.OutputPath),
-			imagesDeps:   make(map[android.ArchType]android.Paths),
-			zip:          zip,
-		}
-
-		for _, target := range targets {
-			imageDir := dir.Join(ctx, "system/framework", target.Arch.ArchType.String())
-			imageConfig.images[target.Arch.ArchType] = imageDir.Join(ctx, stem+".art")
-
-			imagesDeps := make([]android.Path, 0, len(imageConfig.modules)*3)
-			for _, dep := range imageConfig.moduleFiles(ctx, imageDir, ".art", ".oat", ".vdex") {
-				imagesDeps = append(imagesDeps, dep)
-			}
-			imageConfig.imagesDeps[target.Arch.ArchType] = imagesDeps
-		}
-
-		return imageConfig
-	}).(bootImageConfig)
+		return configs
+	}).(map[string]*bootImageConfig)
 }
 
-// Default config is the one that goes in the system image. It includes both libcore and framework.
-var defaultBootImageConfigKey = android.NewOnceKey("defaultBootImageConfig")
-
-func defaultBootImageConfig(ctx android.PathContext) bootImageConfig {
-	return getBootImageConfig(ctx, defaultBootImageConfigKey, "boot", "boot", true, false)
-}
-
-// Apex config is used for the JIT-zygote experiment. It includes both libcore and framework, but AOT-compiles only libcore.
-var apexBootImageConfigKey = android.NewOnceKey("apexBootImageConfig")
-
-func apexBootImageConfig(ctx android.PathContext) bootImageConfig {
-	return getBootImageConfig(ctx, apexBootImageConfigKey, "apex", "apex", false, false)
-}
-
-// ART config is the one used for the ART apex. It includes only libcore.
-var artBootImageConfigKey = android.NewOnceKey("artBootImageConfig")
-
 func artBootImageConfig(ctx android.PathContext) bootImageConfig {
-	return getBootImageConfig(ctx, artBootImageConfigKey, "art", "boot", false, true)
+	return *genBootImageConfigs(ctx)[artBootImageName]
+}
+
+func defaultBootImageConfig(ctx android.PathContext) bootImageConfig {
+	return *genBootImageConfigs(ctx)[frameworkBootImageName]
+}
+
+func apexBootImageConfig(ctx android.PathContext) bootImageConfig {
+	return *genBootImageConfigs(ctx)[apexBootImageName]
 }
 
 func defaultBootclasspath(ctx android.PathContext) []string {
 	return ctx.Config().OnceStringSlice(defaultBootclasspathKey, func() []string {
 		global := dexpreoptGlobalConfig(ctx)
 		image := defaultBootImageConfig(ctx)
-		bootclasspath := append(copyOf(image.dexLocations), global.ProductUpdatableBootLocations...)
+
+		updatableBootclasspath := make([]string, len(global.UpdatableBootJars))
+		for i, p := range global.UpdatableBootJars {
+			updatableBootclasspath[i] = dexpreopt.GetJarLocationFromApexJarPair(p)
+		}
+
+		bootclasspath := append(copyOf(image.dexLocationsDeps), updatableBootclasspath...)
 		return bootclasspath
 	})
 }
@@ -233,7 +274,7 @@
 
 func dexpreoptConfigMakevars(ctx android.MakeVarsContext) {
 	ctx.Strict("PRODUCT_BOOTCLASSPATH", strings.Join(defaultBootclasspath(ctx), ":"))
-	ctx.Strict("PRODUCT_DEX2OAT_BOOTCLASSPATH", strings.Join(defaultBootImageConfig(ctx).dexLocations, ":"))
+	ctx.Strict("PRODUCT_DEX2OAT_BOOTCLASSPATH", strings.Join(defaultBootImageConfig(ctx).dexLocationsDeps, ":"))
 	ctx.Strict("PRODUCT_SYSTEM_SERVER_CLASSPATH", strings.Join(systemServerClasspath(ctx), ":"))
 
 	ctx.Strict("DEXPREOPT_BOOT_JARS_MODULES", strings.Join(defaultBootImageConfig(ctx).modules, ":"))
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 96c8416..16e6921 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -1979,28 +1979,6 @@
 	snapshotRelativeDir := filepath.Join("java", d.Name()+"_stubs_sources")
 	builder.UnzipToSnapshot(stubsSrcJar, snapshotRelativeDir)
 
-	d.generatePrebuiltStubsSources(builder, snapshotRelativeDir, true)
-
-	// This module is for the case when the source tree for the unversioned module
-	// doesn't exist (i.e. building in an unbundled tree). "prefer:" is set to false
-	// so that this module does not eclipse the unversioned module if it exists.
-	d.generatePrebuiltStubsSources(builder, snapshotRelativeDir, false)
-}
-
-func (d *Droidstubs) generatePrebuiltStubsSources(builder android.SnapshotBuilder, snapshotRelativeDir string, versioned bool) {
-	bp := builder.AndroidBpFile()
-	name := d.Name()
-	bp.Printfln("prebuilt_stubs_sources {")
-	bp.Indent()
-	if versioned {
-		bp.Printfln("name: %q,", builder.VersionedSdkMemberName(name))
-		bp.Printfln("sdk_member_name: %q,", name)
-	} else {
-		bp.Printfln("name: %q,", name)
-		bp.Printfln("prefer: false,")
-	}
-	bp.Printfln("srcs: [%q],", snapshotRelativeDir)
-	bp.Dedent()
-	bp.Printfln("}")
-	bp.Printfln("")
+	pbm := builder.AddPrebuiltModule(sdkModuleContext.OtherModuleName(d), "prebuilt_stubs_sources")
+	pbm.AddProperty("srcs", []string{snapshotRelativeDir})
 }
diff --git a/java/java.go b/java/java.go
index 1c52575..9c0fcba 100644
--- a/java/java.go
+++ b/java/java.go
@@ -689,7 +689,12 @@
 	javaPlatform
 )
 
-func getLinkType(m *Module, name string) (ret linkType, stubs bool) {
+type linkTypeContext interface {
+	android.Module
+	getLinkType(name string) (ret linkType, stubs bool)
+}
+
+func (m *Module) getLinkType(name string) (ret linkType, stubs bool) {
 	ver := m.sdkVersion()
 	switch {
 	case name == "core.current.stubs" || name == "core.platform.api.stubs" ||
@@ -720,16 +725,16 @@
 	}
 }
 
-func checkLinkType(ctx android.ModuleContext, from *Module, to *Library, tag dependencyTag) {
+func checkLinkType(ctx android.ModuleContext, from *Module, to linkTypeContext, tag dependencyTag) {
 	if ctx.Host() {
 		return
 	}
 
-	myLinkType, stubs := getLinkType(from, ctx.ModuleName())
+	myLinkType, stubs := from.getLinkType(ctx.ModuleName())
 	if stubs {
 		return
 	}
-	otherLinkType, _ := getLinkType(&to.Module, ctx.OtherModuleName(to))
+	otherLinkType, _ := to.getLinkType(ctx.OtherModuleName(to))
 	commonMessage := "Adjust sdk_version: property of the source or target module so that target module is built with the same or smaller API set than the source."
 
 	switch myLinkType {
@@ -786,11 +791,14 @@
 			// Handled by AndroidApp.collectAppDeps
 			return
 		}
-
-		if to, ok := module.(*Library); ok {
-			switch tag {
-			case bootClasspathTag, libTag, staticLibTag:
-				checkLinkType(ctx, j, to, tag.(dependencyTag))
+		switch module.(type) {
+		case *Library:
+		case *AndroidLibrary:
+			if to, ok := module.(linkTypeContext); ok {
+				switch tag {
+				case bootClasspathTag, libTag, staticLibTag:
+					checkLinkType(ctx, j, to, tag.(dependencyTag))
+				}
 			}
 		}
 		switch dep := module.(type) {
@@ -1729,30 +1737,8 @@
 		}
 	}
 
-	j.generateJavaImport(builder, snapshotRelativeJavaLibPath, true)
-
-	// This module is for the case when the source tree for the unversioned module
-	// doesn't exist (i.e. building in an unbundled tree). "prefer:" is set to false
-	// so that this module does not eclipse the unversioned module if it exists.
-	j.generateJavaImport(builder, snapshotRelativeJavaLibPath, false)
-}
-
-func (j *Library) generateJavaImport(builder android.SnapshotBuilder, snapshotRelativeJavaLibPath string, versioned bool) {
-	bp := builder.AndroidBpFile()
-	name := j.Name()
-	bp.Printfln("java_import {")
-	bp.Indent()
-	if versioned {
-		bp.Printfln("name: %q,", builder.VersionedSdkMemberName(name))
-		bp.Printfln("sdk_member_name: %q,", name)
-	} else {
-		bp.Printfln("name: %q,", name)
-		bp.Printfln("prefer: false,")
-	}
-	bp.Printfln("jars: [%q],", snapshotRelativeJavaLibPath)
-	bp.Dedent()
-	bp.Printfln("}")
-	bp.Printfln("")
+	module := builder.AddPrebuiltModule(sdkModuleContext.OtherModuleName(j), "java_import")
+	module.AddProperty("jars", []string{snapshotRelativeJavaLibPath})
 }
 
 // java_library builds and links sources into a `.jar` file for the device, and possibly for the host as well.
diff --git a/sdk/bp.go b/sdk/bp.go
new file mode 100644
index 0000000..19fb70d
--- /dev/null
+++ b/sdk/bp.go
@@ -0,0 +1,141 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package sdk
+
+import (
+	"fmt"
+
+	"android/soong/android"
+)
+
+type bpPropertySet struct {
+	properties map[string]interface{}
+	order      []string
+}
+
+var _ android.BpPropertySet = (*bpPropertySet)(nil)
+
+func (s *bpPropertySet) init() {
+	s.properties = make(map[string]interface{})
+}
+
+func (s *bpPropertySet) AddProperty(name string, value interface{}) {
+	if s.properties[name] != nil {
+		panic("Property %q already exists in property set")
+	}
+
+	s.properties[name] = value
+	s.order = append(s.order, name)
+}
+
+func (s *bpPropertySet) AddPropertySet(name string) android.BpPropertySet {
+	set := &bpPropertySet{}
+	set.init()
+	s.AddProperty(name, set)
+	return set
+}
+
+func (s *bpPropertySet) getValue(name string) interface{} {
+	return s.properties[name]
+}
+
+func (s *bpPropertySet) copy() bpPropertySet {
+	propertiesCopy := make(map[string]interface{})
+	for p, v := range s.properties {
+		propertiesCopy[p] = v
+	}
+
+	return bpPropertySet{
+		properties: propertiesCopy,
+		order:      append([]string(nil), s.order...),
+	}
+}
+
+func (s *bpPropertySet) setProperty(name string, value interface{}) {
+	if s.properties[name] == nil {
+		s.AddProperty(name, value)
+	} else {
+		s.properties[name] = value
+	}
+}
+
+func (s *bpPropertySet) insertAfter(position string, name string, value interface{}) {
+	if s.properties[name] != nil {
+		panic("Property %q already exists in property set")
+	}
+
+	// Add the name to the end of the order, to ensure it has necessary capacity
+	// and to handle the case when the position does not exist.
+	s.order = append(s.order, name)
+
+	// Search through the order for the item that matches supplied position. If
+	// found then insert the name of the new property after it.
+	for i, v := range s.order {
+		if v == position {
+			// Copy the items after the one where the new property should be inserted.
+			copy(s.order[i+2:], s.order[i+1:])
+			// Insert the item in the list.
+			s.order[i+1] = name
+		}
+	}
+
+	s.properties[name] = value
+}
+
+type bpModule struct {
+	bpPropertySet
+	moduleType string
+}
+
+var _ android.BpModule = (*bpModule)(nil)
+
+func (m *bpModule) copy() *bpModule {
+	return &bpModule{
+		bpPropertySet: m.bpPropertySet.copy(),
+		moduleType:    m.moduleType,
+	}
+}
+
+// A .bp file
+type bpFile struct {
+	modules map[string]*bpModule
+	order   []*bpModule
+}
+
+// Add a module.
+//
+// The module must have had its "name" property set to a string value that
+// is unique within this file.
+func (f *bpFile) AddModule(module android.BpModule) {
+	m := module.(*bpModule)
+	if name, ok := m.getValue("name").(string); ok {
+		if f.modules[name] != nil {
+			panic(fmt.Sprintf("Module %q already exists in bp file", name))
+		}
+
+		f.modules[name] = m
+		f.order = append(f.order, m)
+	} else {
+		panic("Module does not have a name property, or it is not a string")
+	}
+}
+
+func (f *bpFile) newModule(moduleType string) *bpModule {
+	module := &bpModule{
+		moduleType: moduleType,
+	}
+	(&module.bpPropertySet).init()
+	return module
+}
diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go
index 1bbd286..6b92d27 100644
--- a/sdk/sdk_test.go
+++ b/sdk/sdk_test.go
@@ -544,15 +544,9 @@
 
 sdk_snapshot {
     name: "mysdk@current",
-    java_libs: [
-        "mysdk_myjavalib@current",
-    ],
-    stubs_sources: [
-        "mysdk_myjavaapistubs@current",
-    ],
-    native_shared_libs: [
-        "mysdk_mynativelib@current",
-    ],
+    java_libs: ["mysdk_myjavalib@current"],
+    stubs_sources: ["mysdk_myjavaapistubs@current"],
+    native_shared_libs: ["mysdk_mynativelib@current"],
 }
 
 `)
@@ -602,6 +596,198 @@
 	}
 }
 
+func TestHostSnapshot(t *testing.T) {
+	ctx, config := testSdk(t, `
+		sdk {
+			name: "mysdk",
+			device_supported: false,
+			host_supported: true,
+			java_libs: ["myjavalib"],
+			native_shared_libs: ["mynativelib"],
+			stubs_sources: ["myjavaapistubs"],
+		}
+
+		java_library {
+			name: "myjavalib",
+			device_supported: false,
+			host_supported: true,
+			srcs: ["Test.java"],
+			aidl: {
+				export_include_dirs: ["aidl"],
+			},
+			system_modules: "none",
+			sdk_version: "none",
+			compile_dex: true,
+		}
+
+		cc_library_shared {
+			name: "mynativelib",
+			device_supported: false,
+			host_supported: true,
+			srcs: [
+				"Test.cpp",
+				"aidl/foo/bar/Test.aidl",
+			],
+			export_include_dirs: ["include"],
+			aidl: {
+				export_aidl_headers: true,
+			},
+			system_shared_libs: [],
+			stl: "none",
+		}
+
+		droidstubs {
+			name: "myjavaapistubs",
+			device_supported: false,
+			host_supported: true,
+			srcs: ["foo/bar/Foo.java"],
+			system_modules: "none",
+			sdk_version: "none",
+		}
+	`)
+
+	sdk := ctx.ModuleForTests("mysdk", "linux_glibc_common").Module().(*sdk)
+
+	checkSnapshotAndroidBpContents(t, sdk, `// This is auto-generated. DO NOT EDIT.
+
+java_import {
+    name: "mysdk_myjavalib@current",
+    sdk_member_name: "myjavalib",
+    device_supported: false,
+    host_supported: true,
+    jars: ["java/myjavalib.jar"],
+}
+
+java_import {
+    name: "myjavalib",
+    prefer: false,
+    device_supported: false,
+    host_supported: true,
+    jars: ["java/myjavalib.jar"],
+}
+
+prebuilt_stubs_sources {
+    name: "mysdk_myjavaapistubs@current",
+    sdk_member_name: "myjavaapistubs",
+    device_supported: false,
+    host_supported: true,
+    srcs: ["java/myjavaapistubs_stubs_sources"],
+}
+
+prebuilt_stubs_sources {
+    name: "myjavaapistubs",
+    prefer: false,
+    device_supported: false,
+    host_supported: true,
+    srcs: ["java/myjavaapistubs_stubs_sources"],
+}
+
+cc_prebuilt_library_shared {
+    name: "mysdk_mynativelib@current",
+    sdk_member_name: "mynativelib",
+    device_supported: false,
+    host_supported: true,
+    arch: {
+        x86_64: {
+            srcs: ["x86_64/lib/mynativelib.so"],
+            export_include_dirs: [
+                "x86_64/include/include",
+                "x86_64/include_gen/mynativelib",
+            ],
+        },
+        x86: {
+            srcs: ["x86/lib/mynativelib.so"],
+            export_include_dirs: [
+                "x86/include/include",
+                "x86/include_gen/mynativelib",
+            ],
+        },
+    },
+    stl: "none",
+    system_shared_libs: [],
+}
+
+cc_prebuilt_library_shared {
+    name: "mynativelib",
+    prefer: false,
+    device_supported: false,
+    host_supported: true,
+    arch: {
+        x86_64: {
+            srcs: ["x86_64/lib/mynativelib.so"],
+            export_include_dirs: [
+                "x86_64/include/include",
+                "x86_64/include_gen/mynativelib",
+            ],
+        },
+        x86: {
+            srcs: ["x86/lib/mynativelib.so"],
+            export_include_dirs: [
+                "x86/include/include",
+                "x86/include_gen/mynativelib",
+            ],
+        },
+    },
+    stl: "none",
+    system_shared_libs: [],
+}
+
+sdk_snapshot {
+    name: "mysdk@current",
+    device_supported: false,
+    host_supported: true,
+    java_libs: ["mysdk_myjavalib@current"],
+    stubs_sources: ["mysdk_myjavaapistubs@current"],
+    native_shared_libs: ["mysdk_mynativelib@current"],
+}
+
+`)
+
+	var copySrcs []string
+	var copyDests []string
+	buildParams := sdk.BuildParamsForTests()
+	var zipBp android.BuildParams
+	for _, bp := range buildParams {
+		ruleString := bp.Rule.String()
+		if ruleString == "android/soong/android.Cp" {
+			copySrcs = append(copySrcs, bp.Input.String())
+			copyDests = append(copyDests, bp.Output.Rel()) // rooted at the snapshot root
+		} else if ruleString == "<local rule>:m.mysdk_linux_glibc_common.snapshot" {
+			zipBp = bp
+		}
+	}
+
+	buildDir := config.BuildDir()
+	ensureListContains(t, copySrcs, "aidl/foo/bar/Test.aidl")
+	ensureListContains(t, copySrcs, "include/Test.h")
+	ensureListContains(t, copySrcs, filepath.Join(buildDir, ".intermediates/mynativelib/linux_glibc_x86_64_shared/gen/aidl/aidl/foo/bar/BnTest.h"))
+	ensureListContains(t, copySrcs, filepath.Join(buildDir, ".intermediates/mynativelib/linux_glibc_x86_64_shared/gen/aidl/aidl/foo/bar/BpTest.h"))
+	ensureListContains(t, copySrcs, filepath.Join(buildDir, ".intermediates/mynativelib/linux_glibc_x86_64_shared/gen/aidl/aidl/foo/bar/Test.h"))
+	ensureListContains(t, copySrcs, filepath.Join(buildDir, ".intermediates/myjavalib/linux_glibc_common/javac/myjavalib.jar"))
+	ensureListContains(t, copySrcs, filepath.Join(buildDir, ".intermediates/mynativelib/linux_glibc_x86_64_shared/mynativelib.so"))
+
+	ensureListContains(t, copyDests, "aidl/aidl/foo/bar/Test.aidl")
+	ensureListContains(t, copyDests, "x86_64/include/include/Test.h")
+	ensureListContains(t, copyDests, "x86_64/include_gen/mynativelib/aidl/foo/bar/BnTest.h")
+	ensureListContains(t, copyDests, "x86_64/include_gen/mynativelib/aidl/foo/bar/BpTest.h")
+	ensureListContains(t, copyDests, "x86_64/include_gen/mynativelib/aidl/foo/bar/Test.h")
+	ensureListContains(t, copyDests, "java/myjavalib.jar")
+	ensureListContains(t, copyDests, "x86_64/lib/mynativelib.so")
+
+	// Ensure that the droidstubs .srcjar as repackaged into a temporary zip file
+	// and then merged together with the intermediate snapshot zip.
+	snapshotCreationInputs := zipBp.Implicits.Strings()
+	ensureListContains(t, snapshotCreationInputs,
+		filepath.Join(buildDir, ".intermediates/mysdk/linux_glibc_common/tmp/java/myjavaapistubs_stubs_sources.zip"))
+	ensureListContains(t, snapshotCreationInputs,
+		filepath.Join(buildDir, ".intermediates/mysdk/linux_glibc_common/mysdk-current.unmerged.zip"))
+	actual := zipBp.Output.String()
+	expected := filepath.Join(buildDir, ".intermediates/mysdk/linux_glibc_common/mysdk-current.zip")
+	if actual != expected {
+		t.Errorf("Expected snapshot output to be %q but was %q", expected, actual)
+	}
+}
+
 func checkSnapshotAndroidBpContents(t *testing.T, s *sdk, expectedContents string) {
 	t.Helper()
 	androidBpContents := strings.NewReplacer("\\n", "\n").Replace(s.GetAndroidBpContentsForTests())
diff --git a/sdk/update.go b/sdk/update.go
index 000d200..8159d3b 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"path/filepath"
+	"reflect"
 	"strings"
 
 	"github.com/google/blueprint/proptools"
@@ -28,34 +29,37 @@
 
 var pctx = android.NewPackageContext("android/soong/sdk")
 
-// generatedFile abstracts operations for writing contents into a file and emit a build rule
-// for the file.
-type generatedFile struct {
-	path        android.OutputPath
+type generatedContents struct {
 	content     strings.Builder
 	indentLevel int
 }
 
+// generatedFile abstracts operations for writing contents into a file and emit a build rule
+// for the file.
+type generatedFile struct {
+	generatedContents
+	path android.OutputPath
+}
+
 func newGeneratedFile(ctx android.ModuleContext, path ...string) *generatedFile {
 	return &generatedFile{
-		path:        android.PathForModuleOut(ctx, path...).OutputPath,
-		indentLevel: 0,
+		path: android.PathForModuleOut(ctx, path...).OutputPath,
 	}
 }
 
-func (gf *generatedFile) Indent() {
-	gf.indentLevel++
+func (gc *generatedContents) Indent() {
+	gc.indentLevel++
 }
 
-func (gf *generatedFile) Dedent() {
-	gf.indentLevel--
+func (gc *generatedContents) Dedent() {
+	gc.indentLevel--
 }
 
-func (gf *generatedFile) Printfln(format string, args ...interface{}) {
+func (gc *generatedContents) Printfln(format string, args ...interface{}) {
 	// ninja consumes newline characters in rspfile_content. Prevent it by
 	// escaping the backslash in the newline character. The extra backslash
 	// is removed when the rspfile is written to the actual script file
-	fmt.Fprintf(&(gf.content), strings.Repeat("    ", gf.indentLevel)+format+"\\n", args...)
+	fmt.Fprintf(&(gc.content), strings.Repeat("    ", gc.indentLevel)+format+"\\n", args...)
 }
 
 func (gf *generatedFile) build(pctx android.PackageContext, ctx android.BuilderContext, implicits android.Paths) {
@@ -239,15 +243,19 @@
 	snapshotDir := android.PathForModuleOut(ctx, "snapshot")
 
 	bp := newGeneratedFile(ctx, "snapshot", "Android.bp")
-	bp.Printfln("// This is auto-generated. DO NOT EDIT.")
-	bp.Printfln("")
+
+	bpFile := &bpFile{
+		modules: make(map[string]*bpModule),
+	}
 
 	builder := &snapshotBuilder{
-		ctx:           ctx,
-		version:       "current",
-		snapshotDir:   snapshotDir.OutputPath,
-		filesToZip:    []android.Path{bp.path},
-		androidBpFile: bp,
+		ctx:             ctx,
+		sdk:             s,
+		version:         "current",
+		snapshotDir:     snapshotDir.OutputPath,
+		filesToZip:      []android.Path{bp.path},
+		bpFile:          bpFile,
+		prebuiltModules: make(map[string]*bpModule),
 	}
 	s.builderForTests = builder
 
@@ -269,41 +277,38 @@
 		buildSharedNativeLibSnapshot(ctx, info, builder)
 	}
 
-	// generate Android.bp
+	for _, unversioned := range builder.prebuiltOrder {
+		// Copy the unversioned module so it can be modified to make it versioned.
+		versioned := unversioned.copy()
+		name := versioned.properties["name"].(string)
+		versioned.setProperty("name", builder.versionedSdkMemberName(name))
+		versioned.insertAfter("name", "sdk_member_name", name)
+		bpFile.AddModule(versioned)
 
-	bp.Printfln("sdk_snapshot {")
-	bp.Indent()
-	bp.Printfln("name: %q,", ctx.ModuleName()+string(android.SdkVersionSeparator)+builder.version)
+		// Set prefer: false - this is not strictly required as that is the default.
+		unversioned.insertAfter("name", "prefer", false)
+		bpFile.AddModule(unversioned)
+	}
+
+	// Create the snapshot module.
+	snapshotName := ctx.ModuleName() + string(android.SdkVersionSeparator) + builder.version
+	snapshotModule := bpFile.newModule("sdk_snapshot")
+	snapshotModule.AddProperty("name", snapshotName)
+	addHostDeviceSupportedProperties(&s.ModuleBase, snapshotModule)
 	if len(s.properties.Java_libs) > 0 {
-		bp.Printfln("java_libs: [")
-		bp.Indent()
-		for _, m := range s.properties.Java_libs {
-			bp.Printfln("%q,", builder.VersionedSdkMemberName(m))
-		}
-		bp.Dedent()
-		bp.Printfln("],") // java_libs
+		snapshotModule.AddProperty("java_libs", builder.versionedSdkMemberNames(s.properties.Java_libs))
 	}
 	if len(s.properties.Stubs_sources) > 0 {
-		bp.Printfln("stubs_sources: [")
-		bp.Indent()
-		for _, m := range s.properties.Stubs_sources {
-			bp.Printfln("%q,", builder.VersionedSdkMemberName(m))
-		}
-		bp.Dedent()
-		bp.Printfln("],") // stubs_sources
+		snapshotModule.AddProperty("stubs_sources", builder.versionedSdkMemberNames(s.properties.Stubs_sources))
 	}
 	if len(s.properties.Native_shared_libs) > 0 {
-		bp.Printfln("native_shared_libs: [")
-		bp.Indent()
-		for _, m := range s.properties.Native_shared_libs {
-			bp.Printfln("%q,", builder.VersionedSdkMemberName(m))
-		}
-		bp.Dedent()
-		bp.Printfln("],") // native_shared_libs
+		snapshotModule.AddProperty("native_shared_libs", builder.versionedSdkMemberNames(s.properties.Native_shared_libs))
 	}
-	bp.Dedent()
-	bp.Printfln("}") // sdk_snapshot
-	bp.Printfln("")
+	bpFile.AddModule(snapshotModule)
+
+	// generate Android.bp
+	bp = newGeneratedFile(ctx, "snapshot", "Android.bp")
+	generateBpContents(&bp.generatedContents, bpFile)
 
 	bp.build(pctx, ctx, nil)
 
@@ -351,8 +356,61 @@
 	return outputZipFile
 }
 
+func generateBpContents(contents *generatedContents, bpFile *bpFile) {
+	contents.Printfln("// This is auto-generated. DO NOT EDIT.")
+	for _, bpModule := range bpFile.order {
+		contents.Printfln("")
+		contents.Printfln("%s {", bpModule.moduleType)
+		outputPropertySet(contents, &bpModule.bpPropertySet)
+		contents.Printfln("}")
+	}
+	contents.Printfln("")
+}
+
+func outputPropertySet(contents *generatedContents, set *bpPropertySet) {
+	contents.Indent()
+	for _, name := range set.order {
+		value := set.properties[name]
+
+		reflectedValue := reflect.ValueOf(value)
+		t := reflectedValue.Type()
+
+		kind := t.Kind()
+		switch kind {
+		case reflect.Slice:
+			length := reflectedValue.Len()
+			if length > 1 {
+				contents.Printfln("%s: [", name)
+				contents.Indent()
+				for i := 0; i < length; i = i + 1 {
+					contents.Printfln("%q,", reflectedValue.Index(i).Interface())
+				}
+				contents.Dedent()
+				contents.Printfln("],")
+			} else if length == 0 {
+				contents.Printfln("%s: [],", name)
+			} else {
+				contents.Printfln("%s: [%q],", name, reflectedValue.Index(0).Interface())
+			}
+		case reflect.Bool:
+			contents.Printfln("%s: %t,", name, reflectedValue.Bool())
+
+		case reflect.Ptr:
+			contents.Printfln("%s: {", name)
+			outputPropertySet(contents, reflectedValue.Interface().(*bpPropertySet))
+			contents.Printfln("},")
+
+		default:
+			contents.Printfln("%s: %q,", name, value)
+		}
+	}
+	contents.Dedent()
+}
+
 func (s *sdk) GetAndroidBpContentsForTests() string {
-	return s.builderForTests.androidBpFile.content.String()
+	contents := &generatedContents{}
+	generateBpContents(contents, s.builderForTests.bpFile)
+	return contents.content.String()
 }
 
 func buildSharedNativeLibSnapshot(ctx android.ModuleContext, info *nativeLibInfo, builder android.SnapshotBuilder) {
@@ -406,81 +464,58 @@
 		}
 	}
 
-	info.generatePrebuiltLibrary(ctx, builder, true)
-
-	// This module is for the case when the source tree for the unversioned module
-	// doesn't exist (i.e. building in an unbundled tree). "prefer:" is set to false
-	// so that this module does not eclipse the unversioned module if it exists.
-	info.generatePrebuiltLibrary(ctx, builder, false)
+	info.generatePrebuiltLibrary(ctx, builder)
 }
 
-func (info *nativeLibInfo) generatePrebuiltLibrary(ctx android.ModuleContext, builder android.SnapshotBuilder, versioned bool) {
-	bp := builder.AndroidBpFile()
-	bp.Printfln("cc_prebuilt_library_shared {")
-	bp.Indent()
-	name := info.name
-	if versioned {
-		bp.Printfln("name: %q,", builder.VersionedSdkMemberName(name))
-		bp.Printfln("sdk_member_name: %q,", name)
-	} else {
-		bp.Printfln("name: %q,", name)
-		bp.Printfln("prefer: false,")
-	}
+func (info *nativeLibInfo) generatePrebuiltLibrary(ctx android.ModuleContext, builder android.SnapshotBuilder) {
 
 	// a function for emitting include dirs
-	printExportedDirsForNativeLibs := func(lib archSpecificNativeLibInfo, systemInclude bool) {
+	addExportedDirsForNativeLibs := func(lib archSpecificNativeLibInfo, properties android.BpPropertySet, systemInclude bool) {
 		includeDirs := nativeIncludeDirPathsFor(ctx, lib, systemInclude, info.hasArchSpecificFlags)
 		if len(includeDirs) == 0 {
 			return
 		}
+		var propertyName string
 		if !systemInclude {
-			bp.Printfln("export_include_dirs: [")
+			propertyName = "export_include_dirs"
 		} else {
-			bp.Printfln("export_system_include_dirs: [")
+			propertyName = "export_system_include_dirs"
 		}
-		bp.Indent()
-		for _, dir := range includeDirs {
-			bp.Printfln("%q,", dir)
-		}
-		bp.Dedent()
-		bp.Printfln("],")
+		properties.AddProperty(propertyName, includeDirs)
 	}
 
+	pbm := builder.AddPrebuiltModule(info.name, "cc_prebuilt_library_shared")
+
 	if !info.hasArchSpecificFlags {
-		printExportedDirsForNativeLibs(info.archVariants[0], false /*systemInclude*/)
-		printExportedDirsForNativeLibs(info.archVariants[0], true /*systemInclude*/)
+		addExportedDirsForNativeLibs(info.archVariants[0], pbm, false /*systemInclude*/)
+		addExportedDirsForNativeLibs(info.archVariants[0], pbm, true /*systemInclude*/)
 	}
 
-	bp.Printfln("arch: {")
-	bp.Indent()
+	archProperties := pbm.AddPropertySet("arch")
 	for _, av := range info.archVariants {
-		bp.Printfln("%s: {", av.archType)
-		bp.Indent()
-		bp.Printfln("srcs: [%q],", nativeStubFilePathFor(av))
+		archTypeProperties := archProperties.AddPropertySet(av.archType)
+		archTypeProperties.AddProperty("srcs", []string{nativeStubFilePathFor(av)})
 		if info.hasArchSpecificFlags {
 			// export_* properties are added inside the arch: {<arch>: {...}} block
-			printExportedDirsForNativeLibs(av, false /*systemInclude*/)
-			printExportedDirsForNativeLibs(av, true /*systemInclude*/)
+			addExportedDirsForNativeLibs(av, archTypeProperties, false /*systemInclude*/)
+			addExportedDirsForNativeLibs(av, archTypeProperties, true /*systemInclude*/)
 		}
-		bp.Dedent()
-		bp.Printfln("},") // <arch>
 	}
-	bp.Dedent()
-	bp.Printfln("},") // arch
-	bp.Printfln("stl: \"none\",")
-	bp.Printfln("system_shared_libs: [],")
-	bp.Dedent()
-	bp.Printfln("}") // cc_prebuilt_library_shared
-	bp.Printfln("")
+	pbm.AddProperty("stl", "none")
+	pbm.AddProperty("system_shared_libs", []string{})
 }
 
 type snapshotBuilder struct {
-	ctx           android.ModuleContext
-	version       string
-	snapshotDir   android.OutputPath
-	androidBpFile *generatedFile
-	filesToZip    android.Paths
-	zipsToMerge   android.Paths
+	ctx         android.ModuleContext
+	sdk         *sdk
+	version     string
+	snapshotDir android.OutputPath
+	bpFile      *bpFile
+	filesToZip  android.Paths
+	zipsToMerge android.Paths
+
+	prebuiltModules map[string]*bpModule
+	prebuiltOrder   []*bpModule
 }
 
 func (s *snapshotBuilder) CopyToSnapshot(src android.Path, dest string) {
@@ -512,10 +547,38 @@
 	s.zipsToMerge = append(s.zipsToMerge, tmpZipPath)
 }
 
-func (s *snapshotBuilder) AndroidBpFile() android.GeneratedSnapshotFile {
-	return s.androidBpFile
+func (s *snapshotBuilder) AddPrebuiltModule(name string, moduleType string) android.BpModule {
+	if s.prebuiltModules[name] != nil {
+		panic(fmt.Sprintf("Duplicate module detected, module %s has already been added", name))
+	}
+
+	m := s.bpFile.newModule(moduleType)
+	m.AddProperty("name", name)
+	addHostDeviceSupportedProperties(&s.sdk.ModuleBase, m)
+
+	s.prebuiltModules[name] = m
+	s.prebuiltOrder = append(s.prebuiltOrder, m)
+	return m
 }
 
-func (s *snapshotBuilder) VersionedSdkMemberName(unversionedName string) interface{} {
+func addHostDeviceSupportedProperties(module *android.ModuleBase, bpModule *bpModule) {
+	if !module.DeviceSupported() {
+		bpModule.AddProperty("device_supported", false)
+	}
+	if module.HostSupported() {
+		bpModule.AddProperty("host_supported", true)
+	}
+}
+
+// Get a versioned name appropriate for the SDK snapshot version being taken.
+func (s *snapshotBuilder) versionedSdkMemberName(unversionedName string) string {
 	return versionedSdkMemberName(s.ctx, unversionedName, s.version)
 }
+
+func (s *snapshotBuilder) versionedSdkMemberNames(members []string) []string {
+	var references []string = nil
+	for _, m := range members {
+		references = append(references, s.versionedSdkMemberName(m))
+	}
+	return references
+}