Merge "rust: Refactor staticStd to stdLinkage"
diff --git a/android/androidmk.go b/android/androidmk.go
index e90e5f0..ddd51ff 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -639,3 +639,21 @@
 		// Make does not understand LinuxBionic
 		module.Os() == LinuxBionic
 }
+
+func AndroidMkDataPaths(data []DataPath) []string {
+	var testFiles []string
+	for _, d := range data {
+		rel := d.SrcPath.Rel()
+		path := d.SrcPath.String()
+		if !strings.HasSuffix(path, rel) {
+			panic(fmt.Errorf("path %q does not end with %q", path, rel))
+		}
+		path = strings.TrimSuffix(path, rel)
+		testFileString := path + ":" + rel
+		if len(d.RelativeInstallPath) > 0 {
+			testFileString += ":" + d.RelativeInstallPath
+		}
+		testFiles = append(testFiles, testFileString)
+	}
+	return testFiles
+}
diff --git a/android/apex.go b/android/apex.go
index 3cc663b..7ae46d4 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -136,7 +136,7 @@
 	// Returns the highest version which is <= maxSdkVersion.
 	// For example, with maxSdkVersion is 10 and versionList is [9,11]
 	// it returns 9 as string
-	ChooseSdkVersion(versionList []string, maxSdkVersion int) (string, error)
+	ChooseSdkVersion(ctx BaseModuleContext, versionList []string, maxSdkVersion ApiLevel) (string, error)
 
 	// Tests if the module comes from an updatable APEX.
 	Updatable() bool
@@ -320,14 +320,18 @@
 	return true
 }
 
-func (m *ApexModuleBase) ChooseSdkVersion(versionList []string, maxSdkVersion int) (string, error) {
+func (m *ApexModuleBase) ChooseSdkVersion(ctx BaseModuleContext, versionList []string, maxSdkVersion ApiLevel) (string, error) {
 	for i := range versionList {
-		ver, _ := strconv.Atoi(versionList[len(versionList)-i-1])
-		if ver <= maxSdkVersion {
-			return versionList[len(versionList)-i-1], nil
+		version := versionList[len(versionList)-i-1]
+		ver, err := ApiLevelFromUser(ctx, version)
+		if err != nil {
+			return "", err
+		}
+		if ver.LessThanOrEqualTo(maxSdkVersion) {
+			return version, nil
 		}
 	}
-	return "", fmt.Errorf("not found a version(<=%d) in versionList: %v", maxSdkVersion, versionList)
+	return "", fmt.Errorf("not found a version(<=%s) in versionList: %v", maxSdkVersion, versionList)
 }
 
 func (m *ApexModuleBase) checkApexAvailableProperty(mctx BaseModuleContext) {
diff --git a/android/module.go b/android/module.go
index f5adc54..c4e43c2 100644
--- a/android/module.go
+++ b/android/module.go
@@ -625,7 +625,7 @@
 	Native_bridge_supported *bool `android:"arch_variant"`
 
 	// init.rc files to be installed if this module is installed
-	Init_rc []string `android:"path"`
+	Init_rc []string `android:"arch_variant,path"`
 
 	// VINTF manifest fragments to be installed if this module is installed
 	Vintf_fragments []string `android:"path"`
diff --git a/android/sdk.go b/android/sdk.go
index 9ea7ff4..f2cdc88 100644
--- a/android/sdk.go
+++ b/android/sdk.go
@@ -237,9 +237,25 @@
 	// * string
 	// * array of the above
 	// * bool
+	// For these types it is an error if multiple properties with the same name
+	// are added.
+	//
+	// * pointer to a struct
 	// * BpPropertySet
 	//
-	// It is an error if multiple properties with the same name are added.
+	// A pointer to a Blueprint-style property struct is first converted into a
+	// BpPropertySet by traversing the fields and adding their values as
+	// properties in a BpPropertySet. A field with a struct value is itself
+	// converted into a BpPropertySet before adding.
+	//
+	// Adding a BpPropertySet is done as follows:
+	// * If no property with the name exists then the BpPropertySet is added
+	//   directly to this property. Care must be taken to ensure that it does not
+	//   introduce a cycle.
+	// * If a property exists with the name and the current value is a
+	//   BpPropertySet then every property of the new BpPropertySet is added to
+	//   the existing BpPropertySet.
+	// * Otherwise, if a property exists with the name then it is an error.
 	AddProperty(name string, value interface{})
 
 	// Add a property with an associated tag
diff --git a/android/variable.go b/android/variable.go
index 53f081e..ce523ed 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -365,12 +365,12 @@
 	*v = productVariables{
 		BuildNumberFile: stringPtr("build_number.txt"),
 
-		Platform_version_name:             stringPtr("Q"),
-		Platform_sdk_version:              intPtr(28),
-		Platform_sdk_codename:             stringPtr("Q"),
+		Platform_version_name:             stringPtr("S"),
+		Platform_sdk_version:              intPtr(30),
+		Platform_sdk_codename:             stringPtr("S"),
 		Platform_sdk_final:                boolPtr(false),
-		Platform_version_active_codenames: []string{"Q"},
-		Platform_vndk_version:             stringPtr("Q"),
+		Platform_version_active_codenames: []string{"S"},
+		Platform_vndk_version:             stringPtr("S"),
 
 		HostArch:                   stringPtr("x86_64"),
 		HostSecondaryArch:          stringPtr("x86"),
diff --git a/apex/androidmk.go b/apex/androidmk.go
index c4fe3a3..f76181d 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -135,7 +135,7 @@
 				}
 			}
 			if len(newDataPaths) > 0 {
-				fmt.Fprintln(w, "LOCAL_TEST_DATA :=", strings.Join(cc.AndroidMkDataPaths(newDataPaths), " "))
+				fmt.Fprintln(w, "LOCAL_TEST_DATA :=", strings.Join(android.AndroidMkDataPaths(newDataPaths), " "))
 			}
 
 			if fi.module != nil && len(fi.module.NoticeFiles()) > 0 {
diff --git a/apex/apex.go b/apex/apex.go
index e8294a8..0b69111 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -1499,6 +1499,12 @@
 		}
 	}
 	for i, target := range targets {
+		if target.HostCross {
+			// Don't include artifats for the host cross targets because there is no way
+			// for us to run those artifacts natively on host
+			continue
+		}
+
 		// When multilib.* is omitted for native_shared_libs/jni_libs/tests, it implies
 		// multilib.both
 		addDependenciesForNativeModules(ctx,
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 02689a0..2099109 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -836,6 +836,105 @@
 	})
 }
 
+func TestApexWithStubsWithMinSdkVersion(t *testing.T) {
+	t.Parallel()
+	ctx, _ := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			native_shared_libs: ["mylib", "mylib3"],
+			min_sdk_version: "29",
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			shared_libs: ["mylib2", "mylib3"],
+			system_shared_libs: [],
+			stl: "none",
+			apex_available: [ "myapex" ],
+			min_sdk_version: "28",
+		}
+
+		cc_library {
+			name: "mylib2",
+			srcs: ["mylib.cpp"],
+			cflags: ["-include mylib.h"],
+			system_shared_libs: [],
+			stl: "none",
+			stubs: {
+				versions: ["28", "29", "30", "current"],
+			},
+			min_sdk_version: "28",
+		}
+
+		cc_library {
+			name: "mylib3",
+			srcs: ["mylib.cpp"],
+			shared_libs: ["mylib4"],
+			system_shared_libs: [],
+			stl: "none",
+			stubs: {
+				versions: ["28", "29", "30", "current"],
+			},
+			apex_available: [ "myapex" ],
+			min_sdk_version: "28",
+		}
+
+		cc_library {
+			name: "mylib4",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+			apex_available: [ "myapex" ],
+			min_sdk_version: "28",
+		}
+	`)
+
+	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("apexRule")
+	copyCmds := apexRule.Args["copy_commands"]
+
+	// Ensure that direct non-stubs dep is always included
+	ensureContains(t, copyCmds, "image.apex/lib64/mylib.so")
+
+	// Ensure that indirect stubs dep is not included
+	ensureNotContains(t, copyCmds, "image.apex/lib64/mylib2.so")
+
+	// Ensure that direct stubs dep is included
+	ensureContains(t, copyCmds, "image.apex/lib64/mylib3.so")
+
+	mylibLdFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_apex29").Rule("ld").Args["libFlags"]
+
+	// Ensure that mylib is linking with the version 29 stubs for mylib2
+	ensureContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared_29/mylib2.so")
+	// ... and not linking to the non-stub (impl) variant of mylib2
+	ensureNotContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared/mylib2.so")
+
+	// Ensure that mylib is linking with the non-stub (impl) of mylib3 (because mylib3 is in the same apex)
+	ensureContains(t, mylibLdFlags, "mylib3/android_arm64_armv8-a_shared_apex29/mylib3.so")
+	// .. and not linking to the stubs variant of mylib3
+	ensureNotContains(t, mylibLdFlags, "mylib3/android_arm64_armv8-a_shared_29/mylib3.so")
+
+	// Ensure that stubs libs are built without -include flags
+	mylib2Cflags := ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
+	ensureNotContains(t, mylib2Cflags, "-include ")
+
+	// Ensure that genstub is invoked with --apex
+	ensureContains(t, "--apex", ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_static_29").Rule("genStubSrc").Args["flags"])
+
+	ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{
+		"lib64/mylib.so",
+		"lib64/mylib3.so",
+		"lib64/mylib4.so",
+	})
+}
+
 func TestApexWithExplicitStubsDependency(t *testing.T) {
 	ctx, _ := testApex(t, `
 		apex {
diff --git a/cc/androidmk.go b/cc/androidmk.go
index 5bdbac6..4f149af 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -83,6 +83,13 @@
 				if len(c.Properties.Logtags) > 0 {
 					entries.AddStrings("LOCAL_LOGTAGS_FILES", c.Properties.Logtags...)
 				}
+				// Note: Pass the exact value of AndroidMkSystemSharedLibs to the Make
+				// world, even if it is an empty list. In the Make world,
+				// LOCAL_SYSTEM_SHARED_LIBRARIES defaults to "none", which is expanded
+				// to the default list of system shared libs by the build system.
+				// Soong computes the exact list of system shared libs, so we have to
+				// override the default value when the list of libs is actually empty.
+				entries.SetString("LOCAL_SYSTEM_SHARED_LIBRARIES", strings.Join(c.Properties.AndroidMkSystemSharedLibs, " "))
 				if len(c.Properties.AndroidMkSharedLibs) > 0 {
 					entries.AddStrings("LOCAL_SHARED_LIBRARIES", c.Properties.AndroidMkSharedLibs...)
 				}
@@ -149,24 +156,6 @@
 	return []android.AndroidMkEntries{entries}
 }
 
-func AndroidMkDataPaths(data []android.DataPath) []string {
-	var testFiles []string
-	for _, d := range data {
-		rel := d.SrcPath.Rel()
-		path := d.SrcPath.String()
-		if !strings.HasSuffix(path, rel) {
-			panic(fmt.Errorf("path %q does not end with %q", path, rel))
-		}
-		path = strings.TrimSuffix(path, rel)
-		testFileString := path + ":" + rel
-		if len(d.RelativeInstallPath) > 0 {
-			testFileString += ":" + d.RelativeInstallPath
-		}
-		testFiles = append(testFiles, testFileString)
-	}
-	return testFiles
-}
-
 func androidMkWriteExtraTestConfigs(extraTestConfigs android.Paths, entries *android.AndroidMkEntries) {
 	if len(extraTestConfigs) > 0 {
 		entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
@@ -176,7 +165,7 @@
 }
 
 func androidMkWriteTestData(data []android.DataPath, ctx AndroidMkContext, entries *android.AndroidMkEntries) {
-	testFiles := AndroidMkDataPaths(data)
+	testFiles := android.AndroidMkDataPaths(data)
 	if len(testFiles) > 0 {
 		entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
 			entries.AddStrings("LOCAL_TEST_DATA", testFiles...)
diff --git a/cc/builder.go b/cc/builder.go
index ef65348..81c09b1 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -221,7 +221,6 @@
 			ExecStrategy: "${config.REAbiDumperExecStrategy}",
 			Platform: map[string]string{
 				remoteexec.PoolKey:      "${config.RECXXPool}",
-				"InputRootAbsolutePath": android.AbsSrcDirForExistingUseCases(),
 			},
 		}, []string{"cFlags", "exportDirs"}, nil)
 
diff --git a/cc/cc.go b/cc/cc.go
index e71f859..a813428 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -100,6 +100,9 @@
 	// Used for data dependencies adjacent to tests
 	DataLibs []string
 
+	// Used by DepsMutator to pass system_shared_libs information to check_elf_file.py.
+	SystemSharedLibs []string
+
 	StaticUnwinderIfLegacy bool
 
 	ReexportSharedLibHeaders, ReexportStaticLibHeaders, ReexportHeaderLibHeaders []string
@@ -238,6 +241,9 @@
 	PreventInstall            bool     `blueprint:"mutated"`
 	ApexesProvidingSharedLibs []string `blueprint:"mutated"`
 
+	// Set by DepsMutator.
+	AndroidMkSystemSharedLibs []string `blueprint:"mutated"`
+
 	ImageVariationPrefix string `blueprint:"mutated"`
 	VndkVersion          string `blueprint:"mutated"`
 	SubName              string `blueprint:"mutated"`
@@ -1839,6 +1845,8 @@
 
 	deps := c.deps(ctx)
 
+	c.Properties.AndroidMkSystemSharedLibs = deps.SystemSharedLibs
+
 	variantNdkLibs := []string{}
 	variantLateNdkLibs := []string{}
 	if ctx.Os() == android.Android {
@@ -2465,7 +2473,7 @@
 
 				// when to use (unspecified) stubs, check min_sdk_version and choose the right one
 				if useThisDep && depIsStubs && !libDepTag.explicitlyVersioned {
-					versionToUse, err := c.ChooseSdkVersion(ccDep.StubsVersions(), c.apexSdkVersion.FinalOrFutureInt())
+					versionToUse, err := c.ChooseSdkVersion(ctx, ccDep.StubsVersions(), c.apexSdkVersion)
 					if err != nil {
 						ctx.OtherModuleErrorf(dep, err.Error())
 						return
@@ -2488,7 +2496,7 @@
 						// if this is for use_vendor apex && dep has stubsVersions
 						// apply the same rule of apex sdk enforcement to choose right version
 						var err error
-						versionToUse, err = c.ChooseSdkVersion(versions, c.apexSdkVersion.FinalOrFutureInt())
+						versionToUse, err = c.ChooseSdkVersion(ctx, versions, c.apexSdkVersion)
 						if err != nil {
 							ctx.OtherModuleErrorf(dep, err.Error())
 							return
@@ -2559,17 +2567,20 @@
 					// in the context of proper cc.Modules.
 					if ccWholeStaticLib, ok := ccDep.(*Module); ok {
 						staticLib := ccWholeStaticLib.linker.(libraryInterface)
-						if missingDeps := staticLib.getWholeStaticMissingDeps(); missingDeps != nil {
-							postfix := " (required by " + ctx.OtherModuleName(dep) + ")"
-							for i := range missingDeps {
-								missingDeps[i] += postfix
-							}
-							ctx.AddMissingDependencies(missingDeps)
-						}
-						if _, ok := ccWholeStaticLib.linker.(prebuiltLinkerInterface); ok {
-							depPaths.WholeStaticLibsFromPrebuilts = append(depPaths.WholeStaticLibsFromPrebuilts, linkFile.Path())
+						if objs := staticLib.objs(); len(objs.objFiles) > 0 {
+							depPaths.WholeStaticLibObjs = depPaths.WholeStaticLibObjs.Append(objs)
 						} else {
-							depPaths.WholeStaticLibObjs = depPaths.WholeStaticLibObjs.Append(staticLib.objs())
+							// This case normally catches prebuilt static
+							// libraries, but it can also occur when
+							// AllowMissingDependencies is on and the
+							// dependencies has no sources of its own
+							// but has a whole_static_libs dependency
+							// on a missing library.  We want to depend
+							// on the .a file so that there is something
+							// in the dependency tree that contains the
+							// error rule for the missing transitive
+							// dependency.
+							depPaths.WholeStaticLibsFromPrebuilts = append(depPaths.WholeStaticLibsFromPrebuilts, linkFile.Path())
 						}
 					} else {
 						ctx.ModuleErrorf(
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 132d091..e0d4640 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -3780,3 +3780,46 @@
 		t.Errorf("expected -DBAR in cppflags, got %q", libfoo.flags.Local.CppFlags)
 	}
 }
+
+func TestEmptyWholeStaticLibsAllowMissingDependencies(t *testing.T) {
+	t.Parallel()
+	bp := `
+		cc_library_static {
+			name: "libfoo",
+			srcs: ["foo.c"],
+			whole_static_libs: ["libbar"],
+		}
+
+		cc_library_static {
+			name: "libbar",
+			whole_static_libs: ["libmissing"],
+		}
+	`
+
+	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config.TestProductVariables.Allow_missing_dependencies = BoolPtr(true)
+
+	ctx := CreateTestContext()
+	ctx.SetAllowMissingDependencies(true)
+	ctx.Register(config)
+
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	android.FailIfErrored(t, errs)
+	_, errs = ctx.PrepareBuildActions(config)
+	android.FailIfErrored(t, errs)
+
+	libbar := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_static").Output("libbar.a")
+	if g, w := libbar.Rule, android.ErrorRule; g != w {
+		t.Fatalf("Expected libbar rule to be %q, got %q", w, g)
+	}
+
+	if g, w := libbar.Args["error"], "missing dependencies: libmissing"; !strings.Contains(g, w) {
+		t.Errorf("Expected libbar error to contain %q, was %q", w, g)
+	}
+
+	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_static").Output("libfoo.a")
+	if g, w := libfoo.Inputs.Strings(), libbar.Output.String(); !android.InList(w, g) {
+		t.Errorf("Expected libfoo.a to depend on %q, got %q", w, g)
+	}
+
+}
diff --git a/cc/check.go b/cc/check.go
index 46328e9..0058b8c 100644
--- a/cc/check.go
+++ b/cc/check.go
@@ -38,6 +38,8 @@
 			ctx.PropertyErrorf(prop, "Illegal flag `%s`", flag)
 		} else if flag == "--coverage" {
 			ctx.PropertyErrorf(prop, "Bad flag: `%s`, use native_coverage instead", flag)
+		} else if flag == "-fwhole-program-vtables" {
+			ctx.PropertyErrorf(prop, "Bad flag: `%s`, use whole_program_vtables instead", flag)
 		} else if flag == "-Weverything" {
 			if !ctx.Config().IsEnvTrue("ANDROID_TEMPORARILY_ALLOW_WEVERYTHING") {
 				ctx.PropertyErrorf(prop, "-Weverything is not allowed in Android.bp files.  "+
diff --git a/cc/library.go b/cc/library.go
index 8048f00..bf7868f 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -338,10 +338,6 @@
 	flagExporter
 	stripper Stripper
 
-	// If we're used as a whole_static_lib, our missing dependencies need
-	// to be given
-	wholeStaticMissingDeps []string
-
 	// For whole_static_libs
 	objects Objects
 
@@ -682,7 +678,6 @@
 }
 
 type libraryInterface interface {
-	getWholeStaticMissingDeps() []string
 	static() bool
 	shared() bool
 	objs() Objects
@@ -889,8 +884,6 @@
 
 	library.coverageOutputFile = TransformCoverageFilesToZip(ctx, library.objects, ctx.ModuleName())
 
-	library.wholeStaticMissingDeps = ctx.GetMissingDependencies()
-
 	ctx.CheckbuildFile(outputFile)
 
 	return outputFile
@@ -1182,10 +1175,6 @@
 		BoolDefault(library.SharedProperties.Shared.Enabled, true)
 }
 
-func (library *libraryDecorator) getWholeStaticMissingDeps() []string {
-	return append([]string(nil), library.wholeStaticMissingDeps...)
-}
-
 func (library *libraryDecorator) objs() Objects {
 	return library.objects
 }
diff --git a/cc/library_sdk_member.go b/cc/library_sdk_member.go
index 2f15544..41ce294 100644
--- a/cc/library_sdk_member.go
+++ b/cc/library_sdk_member.go
@@ -221,10 +221,7 @@
 // Add properties that may, or may not, be arch specific.
 func addPossiblyArchSpecificProperties(sdkModuleContext android.ModuleContext, builder android.SnapshotBuilder, libInfo *nativeLibInfoProperties, outputProperties android.BpPropertySet) {
 
-	if libInfo.SanitizeNever {
-		sanitizeSet := outputProperties.AddPropertySet("sanitize")
-		sanitizeSet.AddProperty("never", true)
-	}
+	outputProperties.AddProperty("sanitize", &libInfo.Sanitize)
 
 	// Copy the generated library to the snapshot and add a reference to it in the .bp module.
 	if libInfo.outputFile != nil {
@@ -295,9 +292,9 @@
 		outputProperties.AddProperty(property, includeDirs[property])
 	}
 
-	if len(libInfo.StubsVersion) > 0 {
+	if len(libInfo.StubsVersions) > 0 {
 		stubsSet := outputProperties.AddPropertySet("stubs")
-		stubsSet.AddProperty("versions", []string{libInfo.StubsVersion})
+		stubsSet.AddProperty("versions", libInfo.StubsVersions)
 	}
 }
 
@@ -368,13 +365,15 @@
 	// The specific stubs version for the lib variant, or empty string if stubs
 	// are not in use.
 	//
-	// Marked 'ignored-on-host' as the StubsVersion() from which this is initialized is
-	// not set on host and the stubs.versions property which this is written to is does
-	// not vary by arch so cannot be android specific.
-	StubsVersion string `sdk:"ignored-on-host"`
+	// Marked 'ignored-on-host' as the AllStubsVersions() from which this is
+	// initialized is not set on host and the stubs.versions property which this
+	// is written to does not vary by arch so cannot be android specific.
+	StubsVersions []string `sdk:"ignored-on-host"`
 
-	// Value of SanitizeProperties.Sanitize.Never. Needs to be propagated for CRT objects.
-	SanitizeNever bool `android:"arch_variant"`
+	// Value of SanitizeProperties.Sanitize. Several - but not all - of these
+	// affect the expanded variants. All are propagated to avoid entangling the
+	// sanitizer logic with the snapshot generation.
+	Sanitize SanitizeUserProps `android:"arch_variant"`
 
 	// outputFile is not exported as it is always arch specific.
 	outputFile android.Path
@@ -420,11 +419,15 @@
 	p.exportedGeneratedHeaders = ccModule.ExportedGeneratedHeaders()
 
 	if ccModule.HasStubsVariants() {
-		p.StubsVersion = ccModule.StubsVersion()
+		// TODO(b/169373910): 1. Only output the specific version (from
+		// ccModule.StubsVersion()) if the module is versioned. 2. Ensure that all
+		// the versioned stub libs are retained in the prebuilt tree; currently only
+		// the stub corresponding to ccModule.StubsVersion() is.
+		p.StubsVersions = ccModule.AllStubsVersions()
 	}
 
-	if ccModule.sanitize != nil && proptools.Bool(ccModule.sanitize.Properties.Sanitize.Never) {
-		p.SanitizeNever = true
+	if ccModule.sanitize != nil {
+		p.Sanitize = ccModule.sanitize.Properties.Sanitize
 	}
 }
 
diff --git a/cc/linker.go b/cc/linker.go
index 58f8a29..12c8b2c 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -278,19 +278,19 @@
 			deps.LateStaticLibs = append(deps.LateStaticLibs, "libatomic")
 		}
 
-		systemSharedLibs := linker.Properties.System_shared_libs
-		if systemSharedLibs == nil {
+		deps.SystemSharedLibs = linker.Properties.System_shared_libs
+		if deps.SystemSharedLibs == nil {
 			// 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.
-			systemSharedLibs = []string{"libc", "libm", "libdl"}
+			deps.SystemSharedLibs = []string{"libc", "libm", "libdl"}
 		}
 
 		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.
-			if inList("libc", systemSharedLibs) {
-				if !inList("libdl", systemSharedLibs) {
+			if inList("libc", deps.SystemSharedLibs) {
+				if !inList("libdl", deps.SystemSharedLibs) {
 					ctx.PropertyErrorf("shared_libs",
 						"libdl must be in system_shared_libs, not shared_libs")
 				}
@@ -300,12 +300,12 @@
 
 		// If libc and libdl are both in system_shared_libs make sure libdl comes after libc
 		// to avoid loading libdl before libc.
-		if inList("libdl", systemSharedLibs) && inList("libc", systemSharedLibs) &&
-			indexList("libdl", systemSharedLibs) < indexList("libc", systemSharedLibs) {
+		if inList("libdl", deps.SystemSharedLibs) && inList("libc", deps.SystemSharedLibs) &&
+			indexList("libdl", deps.SystemSharedLibs) < indexList("libc", deps.SystemSharedLibs) {
 			ctx.PropertyErrorf("system_shared_libs", "libdl must be after libc")
 		}
 
-		deps.LateSharedLibs = append(deps.LateSharedLibs, systemSharedLibs...)
+		deps.LateSharedLibs = append(deps.LateSharedLibs, deps.SystemSharedLibs...)
 	}
 
 	if ctx.Fuchsia() {
diff --git a/cc/lto.go b/cc/lto.go
index 3e1acdf..abd8dfb 100644
--- a/cc/lto.go
+++ b/cc/lto.go
@@ -45,8 +45,6 @@
 		Thin  *bool `android:"arch_variant"`
 	} `android:"arch_variant"`
 
-	GlobalThin *bool `blueprint:"mutated"`
-
 	// Dep properties indicate that this module needs to be built with LTO
 	// since it is an object dependency of an LTO module.
 	FullDep bool `blueprint:"mutated"`
@@ -71,7 +69,13 @@
 	if ctx.Config().IsEnvTrue("DISABLE_LTO") {
 		lto.Properties.Lto.Never = boolPtr(true)
 	} else if ctx.Config().IsEnvTrue("GLOBAL_THINLTO") {
-		lto.Properties.GlobalThin = boolPtr(true)
+		staticLib := ctx.static() && !ctx.staticBinary()
+		hostBin := ctx.Host()
+		if !staticLib && !hostBin {
+			if !lto.Never() && !lto.FullLTO() {
+				lto.Properties.Lto.Thin = boolPtr(true)
+			}
+		}
 	}
 }
 
@@ -133,7 +137,7 @@
 
 // Can be called with a null receiver
 func (lto *lto) LTO() bool {
-	if lto == nil || lto.Disabled() {
+	if lto == nil || lto.Never() {
 		return false
 	}
 
@@ -145,18 +149,12 @@
 }
 
 func (lto *lto) ThinLTO() bool {
-	if Bool(lto.Properties.GlobalThin) {
-		if !lto.Disabled() && !lto.FullLTO() {
-			return true
-		}
-	}
-
 	return Bool(lto.Properties.Lto.Thin)
 }
 
 // Is lto.never explicitly set to true?
-func (lto *lto) Disabled() bool {
-	return lto.Properties.Lto.Never != nil && *lto.Properties.Lto.Never
+func (lto *lto) Never() bool {
+	return Bool(lto.Properties.Lto.Never)
 }
 
 // Propagate lto requirements down from binaries
@@ -184,7 +182,7 @@
 			}
 
 			if dep, ok := dep.(*Module); ok && dep.lto != nil &&
-				!dep.lto.Disabled() {
+				!dep.lto.Never() {
 				if full && !dep.lto.FullLTO() {
 					dep.lto.Properties.FullDep = true
 				}
diff --git a/cc/ndkstubgen/__init__.py b/cc/ndkstubgen/__init__.py
index 2f4326a..86bf6ff 100755
--- a/cc/ndkstubgen/__init__.py
+++ b/cc/ndkstubgen/__init__.py
@@ -20,13 +20,16 @@
 import logging
 import os
 import sys
+from typing import Iterable, TextIO
 
 import symbolfile
+from symbolfile import Arch, Version
 
 
 class Generator:
     """Output generator that writes stub source files and version scripts."""
-    def __init__(self, src_file, version_script, arch, api, llndk, apex):
+    def __init__(self, src_file: TextIO, version_script: TextIO, arch: Arch,
+                 api: int, llndk: bool, apex: bool) -> None:
         self.src_file = src_file
         self.version_script = version_script
         self.arch = arch
@@ -34,12 +37,12 @@
         self.llndk = llndk
         self.apex = apex
 
-    def write(self, versions):
+    def write(self, versions: Iterable[Version]) -> None:
         """Writes all symbol data to the output files."""
         for version in versions:
             self.write_version(version)
 
-    def write_version(self, version):
+    def write_version(self, version: Version) -> None:
         """Writes a single version block's data to the output files."""
         if symbolfile.should_omit_version(version, self.arch, self.api,
                                           self.llndk, self.apex):
@@ -84,7 +87,7 @@
                 self.version_script.write('}' + base + ';\n')
 
 
-def parse_args():
+def parse_args() -> argparse.Namespace:
     """Parses and returns command line arguments."""
     parser = argparse.ArgumentParser()
 
@@ -100,23 +103,31 @@
     parser.add_argument(
         '--apex', action='store_true', help='Use the APEX variant.')
 
+    # https://github.com/python/mypy/issues/1317
+    # mypy has issues with using os.path.realpath as an argument here.
     parser.add_argument(
-        '--api-map', type=os.path.realpath, required=True,
+        '--api-map',
+        type=os.path.realpath,  # type: ignore
+        required=True,
         help='Path to the API level map JSON file.')
 
     parser.add_argument(
-        'symbol_file', type=os.path.realpath, help='Path to symbol file.')
+        'symbol_file',
+        type=os.path.realpath,  # type: ignore
+        help='Path to symbol file.')
     parser.add_argument(
-        'stub_src', type=os.path.realpath,
+        'stub_src',
+        type=os.path.realpath,  # type: ignore
         help='Path to output stub source file.')
     parser.add_argument(
-        'version_script', type=os.path.realpath,
+        'version_script',
+        type=os.path.realpath,  # type: ignore
         help='Path to output version script.')
 
     return parser.parse_args()
 
 
-def main():
+def main() -> None:
     """Program entry point."""
     args = parse_args()
 
diff --git a/cc/ndkstubgen/mypy.ini b/cc/ndkstubgen/mypy.ini
new file mode 100644
index 0000000..82aa7eb
--- /dev/null
+++ b/cc/ndkstubgen/mypy.ini
@@ -0,0 +1,2 @@
+[mypy]
+disallow_untyped_defs = True
diff --git a/cc/ndkstubgen/test_ndkstubgen.py b/cc/ndkstubgen/test_ndkstubgen.py
index 70bcf78..6d2c9d6 100755
--- a/cc/ndkstubgen/test_ndkstubgen.py
+++ b/cc/ndkstubgen/test_ndkstubgen.py
@@ -21,19 +21,20 @@
 
 import ndkstubgen
 import symbolfile
+from symbolfile import Arch, Tag
 
 
 # pylint: disable=missing-docstring
 
 
 class GeneratorTest(unittest.TestCase):
-    def test_omit_version(self):
+    def test_omit_version(self) -> None:
         # Thorough testing of the cases involved here is handled by
         # OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest.
         src_file = io.StringIO()
         version_file = io.StringIO()
-        generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9,
-                                         False, False)
+        generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
+                                         9, False, False)
 
         version = symbolfile.Version('VERSION_PRIVATE', None, [], [
             symbolfile.Symbol('foo', []),
@@ -42,74 +43,75 @@
         self.assertEqual('', src_file.getvalue())
         self.assertEqual('', version_file.getvalue())
 
-        version = symbolfile.Version('VERSION', None, ['x86'], [
+        version = symbolfile.Version('VERSION', None, [Tag('x86')], [
             symbolfile.Symbol('foo', []),
         ])
         generator.write_version(version)
         self.assertEqual('', src_file.getvalue())
         self.assertEqual('', version_file.getvalue())
 
-        version = symbolfile.Version('VERSION', None, ['introduced=14'], [
+        version = symbolfile.Version('VERSION', None, [Tag('introduced=14')], [
             symbolfile.Symbol('foo', []),
         ])
         generator.write_version(version)
         self.assertEqual('', src_file.getvalue())
         self.assertEqual('', version_file.getvalue())
 
-    def test_omit_symbol(self):
+    def test_omit_symbol(self) -> None:
         # Thorough testing of the cases involved here is handled by
         # SymbolPresenceTest.
         src_file = io.StringIO()
         version_file = io.StringIO()
-        generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9,
-                                         False, False)
+        generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
+                                         9, False, False)
 
         version = symbolfile.Version('VERSION_1', None, [], [
-            symbolfile.Symbol('foo', ['x86']),
+            symbolfile.Symbol('foo', [Tag('x86')]),
         ])
         generator.write_version(version)
         self.assertEqual('', src_file.getvalue())
         self.assertEqual('', version_file.getvalue())
 
         version = symbolfile.Version('VERSION_1', None, [], [
-            symbolfile.Symbol('foo', ['introduced=14']),
+            symbolfile.Symbol('foo', [Tag('introduced=14')]),
         ])
         generator.write_version(version)
         self.assertEqual('', src_file.getvalue())
         self.assertEqual('', version_file.getvalue())
 
         version = symbolfile.Version('VERSION_1', None, [], [
-            symbolfile.Symbol('foo', ['llndk']),
+            symbolfile.Symbol('foo', [Tag('llndk')]),
         ])
         generator.write_version(version)
         self.assertEqual('', src_file.getvalue())
         self.assertEqual('', version_file.getvalue())
 
         version = symbolfile.Version('VERSION_1', None, [], [
-            symbolfile.Symbol('foo', ['apex']),
+            symbolfile.Symbol('foo', [Tag('apex')]),
         ])
         generator.write_version(version)
         self.assertEqual('', src_file.getvalue())
         self.assertEqual('', version_file.getvalue())
 
-    def test_write(self):
+    def test_write(self) -> None:
         src_file = io.StringIO()
         version_file = io.StringIO()
-        generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9,
-                                         False, False)
+        generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
+                                         9, False, False)
 
         versions = [
             symbolfile.Version('VERSION_1', None, [], [
                 symbolfile.Symbol('foo', []),
-                symbolfile.Symbol('bar', ['var']),
-                symbolfile.Symbol('woodly', ['weak']),
-                symbolfile.Symbol('doodly', ['weak', 'var']),
+                symbolfile.Symbol('bar', [Tag('var')]),
+                symbolfile.Symbol('woodly', [Tag('weak')]),
+                symbolfile.Symbol('doodly',
+                                  [Tag('weak'), Tag('var')]),
             ]),
             symbolfile.Version('VERSION_2', 'VERSION_1', [], [
                 symbolfile.Symbol('baz', []),
             ]),
             symbolfile.Version('VERSION_3', 'VERSION_1', [], [
-                symbolfile.Symbol('qux', ['versioned=14']),
+                symbolfile.Symbol('qux', [Tag('versioned=14')]),
             ]),
         ]
 
@@ -141,7 +143,7 @@
 
 
 class IntegrationTest(unittest.TestCase):
-    def test_integration(self):
+    def test_integration(self) -> None:
         api_map = {
             'O': 9000,
             'P': 9001,
@@ -178,14 +180,14 @@
                 wobble;
             } VERSION_4;
         """))
-        parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9,
-                                             False, False)
+        parser = symbolfile.SymbolFileParser(input_file, api_map, Arch('arm'),
+                                             9, False, False)
         versions = parser.parse()
 
         src_file = io.StringIO()
         version_file = io.StringIO()
-        generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9,
-                                         False, False)
+        generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
+                                         9, False, False)
         generator.write(versions)
 
         expected_src = textwrap.dedent("""\
@@ -213,7 +215,7 @@
         """)
         self.assertEqual(expected_version, version_file.getvalue())
 
-    def test_integration_future_api(self):
+    def test_integration_future_api(self) -> None:
         api_map = {
             'O': 9000,
             'P': 9001,
@@ -230,14 +232,14 @@
                     *;
             };
         """))
-        parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9001,
-                                             False, False)
+        parser = symbolfile.SymbolFileParser(input_file, api_map, Arch('arm'),
+                                             9001, False, False)
         versions = parser.parse()
 
         src_file = io.StringIO()
         version_file = io.StringIO()
-        generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9001,
-                                         False, False)
+        generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
+                                         9001, False, False)
         generator.write(versions)
 
         expected_src = textwrap.dedent("""\
@@ -255,7 +257,7 @@
         """)
         self.assertEqual(expected_version, version_file.getvalue())
 
-    def test_multiple_definition(self):
+    def test_multiple_definition(self) -> None:
         input_file = io.StringIO(textwrap.dedent("""\
             VERSION_1 {
                 global:
@@ -280,8 +282,8 @@
             } VERSION_2;
 
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False,
-                                             False)
+        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
+                                             False, False)
 
         with self.assertRaises(
                 symbolfile.MultiplyDefinedSymbolError) as ex_context:
@@ -289,7 +291,7 @@
         self.assertEqual(['bar', 'foo'],
                          ex_context.exception.multiply_defined_symbols)
 
-    def test_integration_with_apex(self):
+    def test_integration_with_apex(self) -> None:
         api_map = {
             'O': 9000,
             'P': 9001,
@@ -328,14 +330,14 @@
                 wobble;
             } VERSION_4;
         """))
-        parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9,
-                                             False, True)
+        parser = symbolfile.SymbolFileParser(input_file, api_map, Arch('arm'),
+                                             9, False, True)
         versions = parser.parse()
 
         src_file = io.StringIO()
         version_file = io.StringIO()
-        generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9,
-                                         False, True)
+        generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
+                                         9, False, True)
         generator.write(versions)
 
         expected_src = textwrap.dedent("""\
@@ -369,7 +371,8 @@
         """)
         self.assertEqual(expected_version, version_file.getvalue())
 
-def main():
+
+def main() -> None:
     suite = unittest.TestLoader().loadTestsFromName(__name__)
     unittest.TextTestRunner(verbosity=3).run(suite)
 
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 174dcfe..8c3e97a 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -139,56 +139,59 @@
 	return t == asan || t == fuzzer || t == hwasan
 }
 
-type SanitizeProperties struct {
-	// enable AddressSanitizer, ThreadSanitizer, or UndefinedBehaviorSanitizer
-	Sanitize struct {
-		Never *bool `android:"arch_variant"`
+type SanitizeUserProps struct {
+	Never *bool `android:"arch_variant"`
 
-		// main sanitizers
-		Address   *bool `android:"arch_variant"`
-		Thread    *bool `android:"arch_variant"`
-		Hwaddress *bool `android:"arch_variant"`
+	// main sanitizers
+	Address   *bool `android:"arch_variant"`
+	Thread    *bool `android:"arch_variant"`
+	Hwaddress *bool `android:"arch_variant"`
 
-		// local sanitizers
+	// local sanitizers
+	Undefined        *bool    `android:"arch_variant"`
+	All_undefined    *bool    `android:"arch_variant"`
+	Misc_undefined   []string `android:"arch_variant"`
+	Fuzzer           *bool    `android:"arch_variant"`
+	Safestack        *bool    `android:"arch_variant"`
+	Cfi              *bool    `android:"arch_variant"`
+	Integer_overflow *bool    `android:"arch_variant"`
+	Scudo            *bool    `android:"arch_variant"`
+	Scs              *bool    `android:"arch_variant"`
+
+	// A modifier for ASAN and HWASAN for write only instrumentation
+	Writeonly *bool `android:"arch_variant"`
+
+	// Sanitizers to run in the diagnostic mode (as opposed to the release mode).
+	// Replaces abort() on error with a human-readable error message.
+	// Address and Thread sanitizers always run in diagnostic mode.
+	Diag struct {
 		Undefined        *bool    `android:"arch_variant"`
-		All_undefined    *bool    `android:"arch_variant"`
-		Misc_undefined   []string `android:"arch_variant"`
-		Fuzzer           *bool    `android:"arch_variant"`
-		Safestack        *bool    `android:"arch_variant"`
 		Cfi              *bool    `android:"arch_variant"`
 		Integer_overflow *bool    `android:"arch_variant"`
-		Scudo            *bool    `android:"arch_variant"`
-		Scs              *bool    `android:"arch_variant"`
+		Misc_undefined   []string `android:"arch_variant"`
+		No_recover       []string
+	}
 
-		// A modifier for ASAN and HWASAN for write only instrumentation
-		Writeonly *bool `android:"arch_variant"`
+	// value to pass to -fsanitize-recover=
+	Recover []string
 
-		// Sanitizers to run in the diagnostic mode (as opposed to the release mode).
-		// Replaces abort() on error with a human-readable error message.
-		// Address and Thread sanitizers always run in diagnostic mode.
-		Diag struct {
-			Undefined        *bool    `android:"arch_variant"`
-			Cfi              *bool    `android:"arch_variant"`
-			Integer_overflow *bool    `android:"arch_variant"`
-			Misc_undefined   []string `android:"arch_variant"`
-			No_recover       []string
-		}
+	// value to pass to -fsanitize-blacklist
+	Blocklist *string
+}
 
-		// value to pass to -fsanitize-recover=
-		Recover []string
-
-		// value to pass to -fsanitize-blacklist
-		Blocklist *string
-	} `android:"arch_variant"`
-
-	SanitizerEnabled  bool     `blueprint:"mutated"`
-	SanitizeDep       bool     `blueprint:"mutated"`
-	MinimalRuntimeDep bool     `blueprint:"mutated"`
-	BuiltinsDep       bool     `blueprint:"mutated"`
-	UbsanRuntimeDep   bool     `blueprint:"mutated"`
-	InSanitizerDir    bool     `blueprint:"mutated"`
-	Sanitizers        []string `blueprint:"mutated"`
-	DiagSanitizers    []string `blueprint:"mutated"`
+type SanitizeProperties struct {
+	// Enable AddressSanitizer, ThreadSanitizer, UndefinedBehaviorSanitizer, and
+	// others. Please see SanitizerUserProps in build/soong/cc/sanitize.go for
+	// details.
+	Sanitize          SanitizeUserProps `android:"arch_variant"`
+	SanitizerEnabled  bool              `blueprint:"mutated"`
+	SanitizeDep       bool              `blueprint:"mutated"`
+	MinimalRuntimeDep bool              `blueprint:"mutated"`
+	BuiltinsDep       bool              `blueprint:"mutated"`
+	UbsanRuntimeDep   bool              `blueprint:"mutated"`
+	InSanitizerDir    bool              `blueprint:"mutated"`
+	Sanitizers        []string          `blueprint:"mutated"`
+	DiagSanitizers    []string          `blueprint:"mutated"`
 }
 
 type sanitize struct {
diff --git a/cc/symbolfile/__init__.py b/cc/symbolfile/__init__.py
index faa3823..5678e7d 100644
--- a/cc/symbolfile/__init__.py
+++ b/cc/symbolfile/__init__.py
@@ -14,15 +14,31 @@
 # limitations under the License.
 #
 """Parser for Android's version script information."""
+from dataclasses import dataclass
 import logging
 import re
+from typing import (
+    Dict,
+    Iterable,
+    List,
+    Mapping,
+    NewType,
+    Optional,
+    TextIO,
+    Tuple,
+)
+
+
+ApiMap = Mapping[str, int]
+Arch = NewType('Arch', str)
+Tag = NewType('Tag', str)
 
 
 ALL_ARCHITECTURES = (
-    'arm',
-    'arm64',
-    'x86',
-    'x86_64',
+    Arch('arm'),
+    Arch('arm64'),
+    Arch('x86'),
+    Arch('x86_64'),
 )
 
 
@@ -30,18 +46,36 @@
 FUTURE_API_LEVEL = 10000
 
 
-def logger():
+def logger() -> logging.Logger:
     """Return the main logger for this module."""
     return logging.getLogger(__name__)
 
 
-def get_tags(line):
+@dataclass
+class Symbol:
+    """A symbol definition from a symbol file."""
+
+    name: str
+    tags: List[Tag]
+
+
+@dataclass
+class Version:
+    """A version block of a symbol file."""
+
+    name: str
+    base: Optional[str]
+    tags: List[Tag]
+    symbols: List[Symbol]
+
+
+def get_tags(line: str) -> List[Tag]:
     """Returns a list of all tags on this line."""
     _, _, all_tags = line.strip().partition('#')
-    return [e for e in re.split(r'\s+', all_tags) if e.strip()]
+    return [Tag(e) for e in re.split(r'\s+', all_tags) if e.strip()]
 
 
-def is_api_level_tag(tag):
+def is_api_level_tag(tag: Tag) -> bool:
     """Returns true if this tag has an API level that may need decoding."""
     if tag.startswith('introduced='):
         return True
@@ -52,7 +86,7 @@
     return False
 
 
-def decode_api_level(api, api_map):
+def decode_api_level(api: str, api_map: ApiMap) -> int:
     """Decodes the API level argument into the API level number.
 
     For the average case, this just decodes the integer value from the string,
@@ -70,12 +104,13 @@
     return api_map[api]
 
 
-def decode_api_level_tags(tags, api_map):
+def decode_api_level_tags(tags: Iterable[Tag], api_map: ApiMap) -> List[Tag]:
     """Decodes API level code names in a list of tags.
 
     Raises:
         ParseError: An unknown version name was found in a tag.
     """
+    decoded_tags = list(tags)
     for idx, tag in enumerate(tags):
         if not is_api_level_tag(tag):
             continue
@@ -83,13 +118,13 @@
 
         try:
             decoded = str(decode_api_level(value, api_map))
-            tags[idx] = '='.join([name, decoded])
+            decoded_tags[idx] = Tag('='.join([name, decoded]))
         except KeyError:
-            raise ParseError('Unknown version name in tag: {}'.format(tag))
-    return tags
+            raise ParseError(f'Unknown version name in tag: {tag}')
+    return decoded_tags
 
 
-def split_tag(tag):
+def split_tag(tag: Tag) -> Tuple[str, str]:
     """Returns a key/value tuple of the tag.
 
     Raises:
@@ -103,7 +138,7 @@
     return key, value
 
 
-def get_tag_value(tag):
+def get_tag_value(tag: Tag) -> str:
     """Returns the value of a key/value tag.
 
     Raises:
@@ -114,12 +149,13 @@
     return split_tag(tag)[1]
 
 
-def version_is_private(version):
+def version_is_private(version: str) -> bool:
     """Returns True if the version name should be treated as private."""
     return version.endswith('_PRIVATE') or version.endswith('_PLATFORM')
 
 
-def should_omit_version(version, arch, api, llndk, apex):
+def should_omit_version(version: Version, arch: Arch, api: int, llndk: bool,
+                        apex: bool) -> bool:
     """Returns True if the version section should be ommitted.
 
     We want to omit any sections that do not have any symbols we'll have in the
@@ -145,7 +181,8 @@
     return False
 
 
-def should_omit_symbol(symbol, arch, api, llndk, apex):
+def should_omit_symbol(symbol: Symbol, arch: Arch, api: int, llndk: bool,
+                       apex: bool) -> bool:
     """Returns True if the symbol should be omitted."""
     no_llndk_no_apex = 'llndk' not in symbol.tags and 'apex' not in symbol.tags
     keep = no_llndk_no_apex or \
@@ -160,7 +197,7 @@
     return False
 
 
-def symbol_in_arch(tags, arch):
+def symbol_in_arch(tags: Iterable[Tag], arch: Arch) -> bool:
     """Returns true if the symbol is present for the given architecture."""
     has_arch_tags = False
     for tag in tags:
@@ -175,7 +212,7 @@
     return not has_arch_tags
 
 
-def symbol_in_api(tags, arch, api):
+def symbol_in_api(tags: Iterable[Tag], arch: Arch, api: int) -> bool:
     """Returns true if the symbol is present for the given API level."""
     introduced_tag = None
     arch_specific = False
@@ -197,7 +234,7 @@
     return api >= int(get_tag_value(introduced_tag))
 
 
-def symbol_versioned_in_api(tags, api):
+def symbol_versioned_in_api(tags: Iterable[Tag], api: int) -> bool:
     """Returns true if the symbol should be versioned for the given API.
 
     This models the `versioned=API` tag. This should be a very uncommonly
@@ -223,68 +260,40 @@
 
 class MultiplyDefinedSymbolError(RuntimeError):
     """A symbol name was multiply defined."""
-    def __init__(self, multiply_defined_symbols):
-        super(MultiplyDefinedSymbolError, self).__init__(
+    def __init__(self, multiply_defined_symbols: Iterable[str]) -> None:
+        super().__init__(
             'Version script contains multiple definitions for: {}'.format(
                 ', '.join(multiply_defined_symbols)))
         self.multiply_defined_symbols = multiply_defined_symbols
 
 
-class Version:
-    """A version block of a symbol file."""
-    def __init__(self, name, base, tags, symbols):
-        self.name = name
-        self.base = base
-        self.tags = tags
-        self.symbols = symbols
-
-    def __eq__(self, other):
-        if self.name != other.name:
-            return False
-        if self.base != other.base:
-            return False
-        if self.tags != other.tags:
-            return False
-        if self.symbols != other.symbols:
-            return False
-        return True
-
-
-class Symbol:
-    """A symbol definition from a symbol file."""
-    def __init__(self, name, tags):
-        self.name = name
-        self.tags = tags
-
-    def __eq__(self, other):
-        return self.name == other.name and set(self.tags) == set(other.tags)
-
-
 class SymbolFileParser:
     """Parses NDK symbol files."""
-    def __init__(self, input_file, api_map, arch, api, llndk, apex):
+    def __init__(self, input_file: TextIO, api_map: ApiMap, arch: Arch,
+                 api: int, llndk: bool, apex: bool) -> None:
         self.input_file = input_file
         self.api_map = api_map
         self.arch = arch
         self.api = api
         self.llndk = llndk
         self.apex = apex
-        self.current_line = None
+        self.current_line: Optional[str] = None
 
-    def parse(self):
+    def parse(self) -> List[Version]:
         """Parses the symbol file and returns a list of Version objects."""
         versions = []
         while self.next_line() != '':
+            assert self.current_line is not None
             if '{' in self.current_line:
                 versions.append(self.parse_version())
             else:
                 raise ParseError(
-                    'Unexpected contents at top level: ' + self.current_line)
+                    f'Unexpected contents at top level: {self.current_line}')
 
         self.check_no_duplicate_symbols(versions)
         return versions
 
-    def check_no_duplicate_symbols(self, versions):
+    def check_no_duplicate_symbols(self, versions: Iterable[Version]) -> None:
         """Raises errors for multiply defined symbols.
 
         This situation is the normal case when symbol versioning is actually
@@ -312,12 +321,13 @@
             raise MultiplyDefinedSymbolError(
                 sorted(list(multiply_defined_symbols)))
 
-    def parse_version(self):
+    def parse_version(self) -> Version:
         """Parses a single version section and returns a Version object."""
+        assert self.current_line is not None
         name = self.current_line.split('{')[0].strip()
         tags = get_tags(self.current_line)
         tags = decode_api_level_tags(tags, self.api_map)
-        symbols = []
+        symbols: List[Symbol] = []
         global_scope = True
         cpp_symbols = False
         while self.next_line() != '':
@@ -333,9 +343,7 @@
                     cpp_symbols = False
                 else:
                     base = base.rstrip(';').rstrip()
-                    if base == '':
-                        base = None
-                    return Version(name, base, tags, symbols)
+                    return Version(name, base or None, tags, symbols)
             elif 'extern "C++" {' in self.current_line:
                 cpp_symbols = True
             elif not cpp_symbols and ':' in self.current_line:
@@ -354,8 +362,9 @@
                 pass
         raise ParseError('Unexpected EOF in version block.')
 
-    def parse_symbol(self):
+    def parse_symbol(self) -> Symbol:
         """Parses a single symbol line and returns a Symbol object."""
+        assert self.current_line is not None
         if ';' not in self.current_line:
             raise ParseError(
                 'Expected ; to terminate symbol: ' + self.current_line)
@@ -368,7 +377,7 @@
         tags = decode_api_level_tags(tags, self.api_map)
         return Symbol(name, tags)
 
-    def next_line(self):
+    def next_line(self) -> str:
         """Returns the next non-empty non-comment line.
 
         A return value of '' indicates EOF.
diff --git a/cc/symbolfile/mypy.ini b/cc/symbolfile/mypy.ini
new file mode 100644
index 0000000..82aa7eb
--- /dev/null
+++ b/cc/symbolfile/mypy.ini
@@ -0,0 +1,2 @@
+[mypy]
+disallow_untyped_defs = True
diff --git a/cc/symbolfile/test_symbolfile.py b/cc/symbolfile/test_symbolfile.py
index c91131f..92b1399 100644
--- a/cc/symbolfile/test_symbolfile.py
+++ b/cc/symbolfile/test_symbolfile.py
@@ -19,12 +19,13 @@
 import unittest
 
 import symbolfile
+from symbolfile import Arch, Tag
 
 # pylint: disable=missing-docstring
 
 
 class DecodeApiLevelTest(unittest.TestCase):
-    def test_decode_api_level(self):
+    def test_decode_api_level(self) -> None:
         self.assertEqual(9, symbolfile.decode_api_level('9', {}))
         self.assertEqual(9000, symbolfile.decode_api_level('O', {'O': 9000}))
 
@@ -33,70 +34,73 @@
 
 
 class TagsTest(unittest.TestCase):
-    def test_get_tags_no_tags(self):
+    def test_get_tags_no_tags(self) -> None:
         self.assertEqual([], symbolfile.get_tags(''))
         self.assertEqual([], symbolfile.get_tags('foo bar baz'))
 
-    def test_get_tags(self):
+    def test_get_tags(self) -> None:
         self.assertEqual(['foo', 'bar'], symbolfile.get_tags('# foo bar'))
         self.assertEqual(['bar', 'baz'], symbolfile.get_tags('foo # bar baz'))
 
-    def test_split_tag(self):
-        self.assertTupleEqual(('foo', 'bar'), symbolfile.split_tag('foo=bar'))
-        self.assertTupleEqual(('foo', 'bar=baz'), symbolfile.split_tag('foo=bar=baz'))
+    def test_split_tag(self) -> None:
+        self.assertTupleEqual(('foo', 'bar'),
+                              symbolfile.split_tag(Tag('foo=bar')))
+        self.assertTupleEqual(('foo', 'bar=baz'),
+                              symbolfile.split_tag(Tag('foo=bar=baz')))
         with self.assertRaises(ValueError):
-            symbolfile.split_tag('foo')
+            symbolfile.split_tag(Tag('foo'))
 
-    def test_get_tag_value(self):
-        self.assertEqual('bar', symbolfile.get_tag_value('foo=bar'))
-        self.assertEqual('bar=baz', symbolfile.get_tag_value('foo=bar=baz'))
+    def test_get_tag_value(self) -> None:
+        self.assertEqual('bar', symbolfile.get_tag_value(Tag('foo=bar')))
+        self.assertEqual('bar=baz',
+                         symbolfile.get_tag_value(Tag('foo=bar=baz')))
         with self.assertRaises(ValueError):
-            symbolfile.get_tag_value('foo')
+            symbolfile.get_tag_value(Tag('foo'))
 
-    def test_is_api_level_tag(self):
-        self.assertTrue(symbolfile.is_api_level_tag('introduced=24'))
-        self.assertTrue(symbolfile.is_api_level_tag('introduced-arm=24'))
-        self.assertTrue(symbolfile.is_api_level_tag('versioned=24'))
+    def test_is_api_level_tag(self) -> None:
+        self.assertTrue(symbolfile.is_api_level_tag(Tag('introduced=24')))
+        self.assertTrue(symbolfile.is_api_level_tag(Tag('introduced-arm=24')))
+        self.assertTrue(symbolfile.is_api_level_tag(Tag('versioned=24')))
 
         # Shouldn't try to process things that aren't a key/value tag.
-        self.assertFalse(symbolfile.is_api_level_tag('arm'))
-        self.assertFalse(symbolfile.is_api_level_tag('introduced'))
-        self.assertFalse(symbolfile.is_api_level_tag('versioned'))
+        self.assertFalse(symbolfile.is_api_level_tag(Tag('arm')))
+        self.assertFalse(symbolfile.is_api_level_tag(Tag('introduced')))
+        self.assertFalse(symbolfile.is_api_level_tag(Tag('versioned')))
 
         # We don't support arch specific `versioned` tags.
-        self.assertFalse(symbolfile.is_api_level_tag('versioned-arm=24'))
+        self.assertFalse(symbolfile.is_api_level_tag(Tag('versioned-arm=24')))
 
-    def test_decode_api_level_tags(self):
+    def test_decode_api_level_tags(self) -> None:
         api_map = {
             'O': 9000,
             'P': 9001,
         }
 
         tags = [
-            'introduced=9',
-            'introduced-arm=14',
-            'versioned=16',
-            'arm',
-            'introduced=O',
-            'introduced=P',
+            Tag('introduced=9'),
+            Tag('introduced-arm=14'),
+            Tag('versioned=16'),
+            Tag('arm'),
+            Tag('introduced=O'),
+            Tag('introduced=P'),
         ]
         expected_tags = [
-            'introduced=9',
-            'introduced-arm=14',
-            'versioned=16',
-            'arm',
-            'introduced=9000',
-            'introduced=9001',
+            Tag('introduced=9'),
+            Tag('introduced-arm=14'),
+            Tag('versioned=16'),
+            Tag('arm'),
+            Tag('introduced=9000'),
+            Tag('introduced=9001'),
         ]
         self.assertListEqual(
             expected_tags, symbolfile.decode_api_level_tags(tags, api_map))
 
         with self.assertRaises(symbolfile.ParseError):
-            symbolfile.decode_api_level_tags(['introduced=O'], {})
+            symbolfile.decode_api_level_tags([Tag('introduced=O')], {})
 
 
 class PrivateVersionTest(unittest.TestCase):
-    def test_version_is_private(self):
+    def test_version_is_private(self) -> None:
         self.assertFalse(symbolfile.version_is_private('foo'))
         self.assertFalse(symbolfile.version_is_private('PRIVATE'))
         self.assertFalse(symbolfile.version_is_private('PLATFORM'))
@@ -110,191 +114,227 @@
 
 
 class SymbolPresenceTest(unittest.TestCase):
-    def test_symbol_in_arch(self):
-        self.assertTrue(symbolfile.symbol_in_arch([], 'arm'))
-        self.assertTrue(symbolfile.symbol_in_arch(['arm'], 'arm'))
+    def test_symbol_in_arch(self) -> None:
+        self.assertTrue(symbolfile.symbol_in_arch([], Arch('arm')))
+        self.assertTrue(symbolfile.symbol_in_arch([Tag('arm')], Arch('arm')))
 
-        self.assertFalse(symbolfile.symbol_in_arch(['x86'], 'arm'))
+        self.assertFalse(symbolfile.symbol_in_arch([Tag('x86')], Arch('arm')))
 
-    def test_symbol_in_api(self):
-        self.assertTrue(symbolfile.symbol_in_api([], 'arm', 9))
-        self.assertTrue(symbolfile.symbol_in_api(['introduced=9'], 'arm', 9))
-        self.assertTrue(symbolfile.symbol_in_api(['introduced=9'], 'arm', 14))
-        self.assertTrue(symbolfile.symbol_in_api(['introduced-arm=9'], 'arm', 14))
-        self.assertTrue(symbolfile.symbol_in_api(['introduced-arm=9'], 'arm', 14))
-        self.assertTrue(symbolfile.symbol_in_api(['introduced-x86=14'], 'arm', 9))
-        self.assertTrue(symbolfile.symbol_in_api(
-            ['introduced-arm=9', 'introduced-x86=21'], 'arm', 14))
-        self.assertTrue(symbolfile.symbol_in_api(
-            ['introduced=9', 'introduced-x86=21'], 'arm', 14))
-        self.assertTrue(symbolfile.symbol_in_api(
-            ['introduced=21', 'introduced-arm=9'], 'arm', 14))
-        self.assertTrue(symbolfile.symbol_in_api(
-            ['future'], 'arm', symbolfile.FUTURE_API_LEVEL))
+    def test_symbol_in_api(self) -> None:
+        self.assertTrue(symbolfile.symbol_in_api([], Arch('arm'), 9))
+        self.assertTrue(
+            symbolfile.symbol_in_api([Tag('introduced=9')], Arch('arm'), 9))
+        self.assertTrue(
+            symbolfile.symbol_in_api([Tag('introduced=9')], Arch('arm'), 14))
+        self.assertTrue(
+            symbolfile.symbol_in_api([Tag('introduced-arm=9')], Arch('arm'),
+                                     14))
+        self.assertTrue(
+            symbolfile.symbol_in_api([Tag('introduced-arm=9')], Arch('arm'),
+                                     14))
+        self.assertTrue(
+            symbolfile.symbol_in_api([Tag('introduced-x86=14')], Arch('arm'),
+                                     9))
+        self.assertTrue(
+            symbolfile.symbol_in_api(
+                [Tag('introduced-arm=9'),
+                 Tag('introduced-x86=21')], Arch('arm'), 14))
+        self.assertTrue(
+            symbolfile.symbol_in_api(
+                [Tag('introduced=9'),
+                 Tag('introduced-x86=21')], Arch('arm'), 14))
+        self.assertTrue(
+            symbolfile.symbol_in_api(
+                [Tag('introduced=21'),
+                 Tag('introduced-arm=9')], Arch('arm'), 14))
+        self.assertTrue(
+            symbolfile.symbol_in_api([Tag('future')], Arch('arm'),
+                                     symbolfile.FUTURE_API_LEVEL))
 
-        self.assertFalse(symbolfile.symbol_in_api(['introduced=14'], 'arm', 9))
-        self.assertFalse(symbolfile.symbol_in_api(['introduced-arm=14'], 'arm', 9))
-        self.assertFalse(symbolfile.symbol_in_api(['future'], 'arm', 9))
-        self.assertFalse(symbolfile.symbol_in_api(
-            ['introduced=9', 'future'], 'arm', 14))
-        self.assertFalse(symbolfile.symbol_in_api(
-            ['introduced-arm=9', 'future'], 'arm', 14))
-        self.assertFalse(symbolfile.symbol_in_api(
-            ['introduced-arm=21', 'introduced-x86=9'], 'arm', 14))
-        self.assertFalse(symbolfile.symbol_in_api(
-            ['introduced=9', 'introduced-arm=21'], 'arm', 14))
-        self.assertFalse(symbolfile.symbol_in_api(
-            ['introduced=21', 'introduced-x86=9'], 'arm', 14))
+        self.assertFalse(
+            symbolfile.symbol_in_api([Tag('introduced=14')], Arch('arm'), 9))
+        self.assertFalse(
+            symbolfile.symbol_in_api([Tag('introduced-arm=14')], Arch('arm'),
+                                     9))
+        self.assertFalse(
+            symbolfile.symbol_in_api([Tag('future')], Arch('arm'), 9))
+        self.assertFalse(
+            symbolfile.symbol_in_api(
+                [Tag('introduced=9'), Tag('future')], Arch('arm'), 14))
+        self.assertFalse(
+            symbolfile.symbol_in_api([Tag('introduced-arm=9'),
+                                      Tag('future')], Arch('arm'), 14))
+        self.assertFalse(
+            symbolfile.symbol_in_api(
+                [Tag('introduced-arm=21'),
+                 Tag('introduced-x86=9')], Arch('arm'), 14))
+        self.assertFalse(
+            symbolfile.symbol_in_api(
+                [Tag('introduced=9'),
+                 Tag('introduced-arm=21')], Arch('arm'), 14))
+        self.assertFalse(
+            symbolfile.symbol_in_api(
+                [Tag('introduced=21'),
+                 Tag('introduced-x86=9')], Arch('arm'), 14))
 
         # Interesting edge case: this symbol should be omitted from the
         # library, but this call should still return true because none of the
         # tags indiciate that it's not present in this API level.
-        self.assertTrue(symbolfile.symbol_in_api(['x86'], 'arm', 9))
+        self.assertTrue(symbolfile.symbol_in_api([Tag('x86')], Arch('arm'), 9))
 
-    def test_verioned_in_api(self):
+    def test_verioned_in_api(self) -> None:
         self.assertTrue(symbolfile.symbol_versioned_in_api([], 9))
-        self.assertTrue(symbolfile.symbol_versioned_in_api(['versioned=9'], 9))
-        self.assertTrue(symbolfile.symbol_versioned_in_api(['versioned=9'], 14))
+        self.assertTrue(
+            symbolfile.symbol_versioned_in_api([Tag('versioned=9')], 9))
+        self.assertTrue(
+            symbolfile.symbol_versioned_in_api([Tag('versioned=9')], 14))
 
-        self.assertFalse(symbolfile.symbol_versioned_in_api(['versioned=14'], 9))
+        self.assertFalse(
+            symbolfile.symbol_versioned_in_api([Tag('versioned=14')], 9))
 
 
 class OmitVersionTest(unittest.TestCase):
-    def test_omit_private(self):
+    def test_omit_private(self) -> None:
         self.assertFalse(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [], []), 'arm', 9, False,
+                symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False,
                 False))
 
         self.assertTrue(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo_PRIVATE', None, [], []), 'arm', 9,
-                False, False))
+                symbolfile.Version('foo_PRIVATE', None, [], []), Arch('arm'),
+                9, False, False))
         self.assertTrue(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo_PLATFORM', None, [], []), 'arm', 9,
-                False, False))
-
-        self.assertTrue(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, ['platform-only'], []), 'arm',
+                symbolfile.Version('foo_PLATFORM', None, [], []), Arch('arm'),
                 9, False, False))
 
-    def test_omit_llndk(self):
         self.assertTrue(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, ['llndk'], []), 'arm', 9,
-                False, False))
+                symbolfile.Version('foo', None, [Tag('platform-only')], []),
+                Arch('arm'), 9, False, False))
 
-        self.assertFalse(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [], []), 'arm', 9, True,
-                False))
-        self.assertFalse(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, ['llndk'], []), 'arm', 9, True,
-                False))
-
-    def test_omit_apex(self):
+    def test_omit_llndk(self) -> None:
         self.assertTrue(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, ['apex'], []), 'arm', 9, False,
-                False))
+                symbolfile.Version('foo', None, [Tag('llndk')], []),
+                Arch('arm'), 9, False, False))
 
         self.assertFalse(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [], []), 'arm', 9, False,
+                symbolfile.Version('foo', None, [], []), Arch('arm'), 9, True,
+                False))
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, [Tag('llndk')], []),
+                Arch('arm'), 9, True, False))
+
+    def test_omit_apex(self) -> None:
+        self.assertTrue(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, [Tag('apex')], []),
+                Arch('arm'), 9, False, False))
+
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False,
                 True))
         self.assertFalse(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, ['apex'], []), 'arm', 9, False,
-                True))
+                symbolfile.Version('foo', None, [Tag('apex')], []),
+                Arch('arm'), 9, False, True))
 
-    def test_omit_arch(self):
+    def test_omit_arch(self) -> None:
         self.assertFalse(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [], []), 'arm', 9, False,
+                symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False,
                 False))
         self.assertFalse(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, ['arm'], []), 'arm', 9, False,
-                False))
-
-        self.assertTrue(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, ['x86'], []), 'arm', 9, False,
-                False))
-
-    def test_omit_api(self):
-        self.assertFalse(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [], []), 'arm', 9, False,
-                False))
-        self.assertFalse(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, ['introduced=9'], []), 'arm',
+                symbolfile.Version('foo', None, [Tag('arm')], []), Arch('arm'),
                 9, False, False))
 
         self.assertTrue(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, ['introduced=14'], []), 'arm',
+                symbolfile.Version('foo', None, [Tag('x86')], []), Arch('arm'),
                 9, False, False))
 
+    def test_omit_api(self) -> None:
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False,
+                False))
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, [Tag('introduced=9')], []),
+                Arch('arm'), 9, False, False))
+
+        self.assertTrue(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, [Tag('introduced=14')], []),
+                Arch('arm'), 9, False, False))
+
 
 class OmitSymbolTest(unittest.TestCase):
-    def test_omit_llndk(self):
+    def test_omit_llndk(self) -> None:
         self.assertTrue(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['llndk']),
-                                          'arm', 9, False, False))
+            symbolfile.should_omit_symbol(
+                symbolfile.Symbol('foo', [Tag('llndk')]), Arch('arm'), 9,
+                False, False))
 
         self.assertFalse(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm',
-                                          9, True, False))
-        self.assertFalse(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['llndk']),
-                                          'arm', 9, True, False))
-
-    def test_omit_apex(self):
-        self.assertTrue(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['apex']),
-                                          'arm', 9, False, False))
-
-        self.assertFalse(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm',
-                                          9, False, True))
-        self.assertFalse(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['apex']),
-                                          'arm', 9, False, True))
-
-    def test_omit_arch(self):
-        self.assertFalse(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm',
-                                          9, False, False))
-        self.assertFalse(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['arm']),
-                                          'arm', 9, False, False))
-
-        self.assertTrue(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['x86']),
-                                          'arm', 9, False, False))
-
-    def test_omit_api(self):
-        self.assertFalse(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm',
-                                          9, False, False))
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []),
+                                          Arch('arm'), 9, True, False))
         self.assertFalse(
             symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', ['introduced=9']), 'arm', 9, False,
+                symbolfile.Symbol('foo', [Tag('llndk')]), Arch('arm'), 9, True,
+                False))
+
+    def test_omit_apex(self) -> None:
+        self.assertTrue(
+            symbolfile.should_omit_symbol(
+                symbolfile.Symbol('foo', [Tag('apex')]), Arch('arm'), 9, False,
+                False))
+
+        self.assertFalse(
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []),
+                                          Arch('arm'), 9, False, True))
+        self.assertFalse(
+            symbolfile.should_omit_symbol(
+                symbolfile.Symbol('foo', [Tag('apex')]), Arch('arm'), 9, False,
+                True))
+
+    def test_omit_arch(self) -> None:
+        self.assertFalse(
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []),
+                                          Arch('arm'), 9, False, False))
+        self.assertFalse(
+            symbolfile.should_omit_symbol(
+                symbolfile.Symbol('foo', [Tag('arm')]), Arch('arm'), 9, False,
                 False))
 
         self.assertTrue(
             symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', ['introduced=14']), 'arm', 9, False,
+                symbolfile.Symbol('foo', [Tag('x86')]), Arch('arm'), 9, False,
                 False))
 
+    def test_omit_api(self) -> None:
+        self.assertFalse(
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []),
+                                          Arch('arm'), 9, False, False))
+        self.assertFalse(
+            symbolfile.should_omit_symbol(
+                symbolfile.Symbol('foo', [Tag('introduced=9')]), Arch('arm'),
+                9, False, False))
+
+        self.assertTrue(
+            symbolfile.should_omit_symbol(
+                symbolfile.Symbol('foo', [Tag('introduced=14')]), Arch('arm'),
+                9, False, False))
+
 
 class SymbolFileParseTest(unittest.TestCase):
-    def test_next_line(self):
+    def test_next_line(self) -> None:
         input_file = io.StringIO(textwrap.dedent("""\
             foo
 
@@ -302,10 +342,12 @@
             # baz
             qux
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
+        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
+                                             False, False)
         self.assertIsNone(parser.current_line)
 
         self.assertEqual('foo', parser.next_line().strip())
+        assert parser.current_line is not None
         self.assertEqual('foo', parser.current_line.strip())
 
         self.assertEqual('bar', parser.next_line().strip())
@@ -317,7 +359,7 @@
         self.assertEqual('', parser.next_line())
         self.assertEqual('', parser.current_line)
 
-    def test_parse_version(self):
+    def test_parse_version(self) -> None:
         input_file = io.StringIO(textwrap.dedent("""\
             VERSION_1 { # foo bar
                 baz;
@@ -327,7 +369,8 @@
             VERSION_2 {
             } VERSION_1; # asdf
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
+        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
+                                             False, False)
 
         parser.next_line()
         version = parser.parse_version()
@@ -337,7 +380,7 @@
 
         expected_symbols = [
             symbolfile.Symbol('baz', []),
-            symbolfile.Symbol('qux', ['woodly', 'doodly']),
+            symbolfile.Symbol('qux', [Tag('woodly'), Tag('doodly')]),
         ]
         self.assertEqual(expected_symbols, version.symbols)
 
@@ -347,32 +390,35 @@
         self.assertEqual('VERSION_1', version.base)
         self.assertEqual([], version.tags)
 
-    def test_parse_version_eof(self):
+    def test_parse_version_eof(self) -> None:
         input_file = io.StringIO(textwrap.dedent("""\
             VERSION_1 {
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
+        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
+                                             False, False)
         parser.next_line()
         with self.assertRaises(symbolfile.ParseError):
             parser.parse_version()
 
-    def test_unknown_scope_label(self):
+    def test_unknown_scope_label(self) -> None:
         input_file = io.StringIO(textwrap.dedent("""\
             VERSION_1 {
                 foo:
             }
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
+        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
+                                             False, False)
         parser.next_line()
         with self.assertRaises(symbolfile.ParseError):
             parser.parse_version()
 
-    def test_parse_symbol(self):
+    def test_parse_symbol(self) -> None:
         input_file = io.StringIO(textwrap.dedent("""\
             foo;
             bar; # baz qux
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
+        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
+                                             False, False)
 
         parser.next_line()
         symbol = parser.parse_symbol()
@@ -384,48 +430,51 @@
         self.assertEqual('bar', symbol.name)
         self.assertEqual(['baz', 'qux'], symbol.tags)
 
-    def test_wildcard_symbol_global(self):
+    def test_wildcard_symbol_global(self) -> None:
         input_file = io.StringIO(textwrap.dedent("""\
             VERSION_1 {
                 *;
             };
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
+        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
+                                             False, False)
         parser.next_line()
         with self.assertRaises(symbolfile.ParseError):
             parser.parse_version()
 
-    def test_wildcard_symbol_local(self):
+    def test_wildcard_symbol_local(self) -> None:
         input_file = io.StringIO(textwrap.dedent("""\
             VERSION_1 {
                 local:
                     *;
             };
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
+        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
+                                             False, False)
         parser.next_line()
         version = parser.parse_version()
         self.assertEqual([], version.symbols)
 
-    def test_missing_semicolon(self):
+    def test_missing_semicolon(self) -> None:
         input_file = io.StringIO(textwrap.dedent("""\
             VERSION_1 {
                 foo
             };
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
+        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
+                                             False, False)
         parser.next_line()
         with self.assertRaises(symbolfile.ParseError):
             parser.parse_version()
 
-    def test_parse_fails_invalid_input(self):
+    def test_parse_fails_invalid_input(self) -> None:
         with self.assertRaises(symbolfile.ParseError):
             input_file = io.StringIO('foo')
-            parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16,
-                                                 False, False)
+            parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'),
+                                                 16, False, False)
             parser.parse()
 
-    def test_parse(self):
+    def test_parse(self) -> None:
         input_file = io.StringIO(textwrap.dedent("""\
             VERSION_1 {
                 local:
@@ -443,23 +492,24 @@
                     qwerty;
             } VERSION_1;
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False)
+        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
+                                             False, False)
         versions = parser.parse()
 
         expected = [
             symbolfile.Version('VERSION_1', None, [], [
                 symbolfile.Symbol('foo', []),
-                symbolfile.Symbol('bar', ['baz']),
+                symbolfile.Symbol('bar', [Tag('baz')]),
             ]),
-            symbolfile.Version('VERSION_2', 'VERSION_1', ['wasd'], [
+            symbolfile.Version('VERSION_2', 'VERSION_1', [Tag('wasd')], [
                 symbolfile.Symbol('woodly', []),
-                symbolfile.Symbol('doodly', ['asdf']),
+                symbolfile.Symbol('doodly', [Tag('asdf')]),
             ]),
         ]
 
         self.assertEqual(expected, versions)
 
-    def test_parse_llndk_apex_symbol(self):
+    def test_parse_llndk_apex_symbol(self) -> None:
         input_file = io.StringIO(textwrap.dedent("""\
             VERSION_1 {
                 foo;
@@ -468,7 +518,8 @@
                 qux; # apex
             };
         """))
-        parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, True)
+        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
+                                             False, True)
 
         parser.next_line()
         version = parser.parse_version()
@@ -477,14 +528,14 @@
 
         expected_symbols = [
             symbolfile.Symbol('foo', []),
-            symbolfile.Symbol('bar', ['llndk']),
-            symbolfile.Symbol('baz', ['llndk', 'apex']),
-            symbolfile.Symbol('qux', ['apex']),
+            symbolfile.Symbol('bar', [Tag('llndk')]),
+            symbolfile.Symbol('baz', [Tag('llndk'), Tag('apex')]),
+            symbolfile.Symbol('qux', [Tag('apex')]),
         ]
         self.assertEqual(expected_symbols, version.symbols)
 
 
-def main():
+def main() -> None:
     suite = unittest.TestLoader().loadTestsFromName(__name__)
     unittest.TextTestRunner(verbosity=3).run(suite)
 
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 8b66dbf..33f422d 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -253,6 +253,10 @@
 	// if set to true, allow Metalava to generate doc_stubs source files. Defaults to false.
 	Create_doc_stubs *bool
 
+	// if set to true, cause Metalava to output Javadoc comments in the stubs source files. Defaults to false.
+	// Has no effect if create_doc_stubs: true.
+	Output_javadoc_comments *bool
+
 	// if set to false then do not write out stubs. Defaults to true.
 	//
 	// TODO(b/146727827): Remove capability when we do not need to generate stubs and API separately.
@@ -1150,7 +1154,9 @@
 			cmd.FlagWithArg("--doc-stubs ", stubsDir.String())
 		} else {
 			cmd.FlagWithArg("--stubs ", stubsDir.String())
-			cmd.Flag("--exclude-documentation-from-stubs")
+			if !Bool(d.properties.Output_javadoc_comments) {
+				cmd.Flag("--exclude-documentation-from-stubs")
+			}
 		}
 	}
 }
diff --git a/java/java_test.go b/java/java_test.go
index 9e63577..f16639a 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -1487,6 +1487,12 @@
 			libs: ["foo"],
 			sdk_version: "system_29",
 		}
+		java_library {
+			name: "baz-module-30",
+			srcs: ["c.java"],
+			libs: ["foo"],
+			sdk_version: "module_30",
+		}
 		`)
 
 	// check the existence of the internal modules
@@ -1533,6 +1539,13 @@
 			"prebuilts/sdk/29/system/foo.jar")
 	}
 
+	bazModule30Javac := ctx.ModuleForTests("baz-module-30", "android_common").Rule("javac")
+	// tests if "baz-module-30" is actually linked to the module 30 stubs lib
+	if !strings.Contains(bazModule30Javac.Args["classpath"], "prebuilts/sdk/30/module-lib/foo.jar") {
+		t.Errorf("baz-module-30 javac classpath %v does not contain %q", bazModule30Javac.Args["classpath"],
+			"prebuilts/sdk/30/module-lib/foo.jar")
+	}
+
 	// test if baz has exported SDK lib names foo and bar to qux
 	qux := ctx.ModuleForTests("qux", "android_common")
 	if quxLib, ok := qux.Module().(*Library); ok {
diff --git a/java/sdk.go b/java/sdk.go
index f599265..971791f 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -222,7 +222,7 @@
 		return ctx.Config().AlwaysUsePrebuiltSdks()
 	} else if s.version.isNumbered() {
 		// validation check
-		if s.kind != sdkPublic && s.kind != sdkSystem && s.kind != sdkTest {
+		if s.kind != sdkPublic && s.kind != sdkSystem && s.kind != sdkTest && s.kind != sdkModule {
 			panic(fmt.Errorf("prebuilt SDK is not not available for sdkKind=%q", s.kind))
 			return false
 		}
diff --git a/java/sdk_library.go b/java/sdk_library.go
index a7b92b3..60924a6 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -589,6 +589,9 @@
 	// An Android shared library is one that can be referenced in a <uses-library> element
 	// in an AndroidManifest.xml.
 	Shared_library *bool
+
+	// Files containing information about supported java doc tags.
+	Doctag_files []string `android:"path"`
 }
 
 // Common code between sdk library and sdk library import
@@ -601,6 +604,9 @@
 
 	commonSdkLibraryProperties commonToSdkLibraryAndImportProperties
 
+	// Paths to commonSdkLibraryProperties.Doctag_files
+	doctagPaths android.Paths
+
 	// Functionality related to this being used as a component of a java_sdk_library.
 	EmbeddableSdkLibraryComponent
 }
@@ -633,6 +639,10 @@
 	return true
 }
 
+func (c *commonToSdkLibraryAndImport) generateCommonBuildActions(ctx android.ModuleContext) {
+	c.doctagPaths = android.PathsForModuleSrc(ctx, c.commonSdkLibraryProperties.Doctag_files)
+}
+
 // Module name of the runtime implementation library
 func (c *commonToSdkLibraryAndImport) implLibraryModuleName() string {
 	return c.moduleBase.BaseModuleName() + ".impl"
@@ -732,6 +742,14 @@
 		}
 
 	} else {
+		switch tag {
+		case ".doctags":
+			if c.doctagPaths != nil {
+				return c.doctagPaths, nil
+			} else {
+				return nil, fmt.Errorf("no doctag_files specified on %s", c.moduleBase.BaseModuleName())
+			}
+		}
 		return nil, nil
 	}
 }
@@ -1014,6 +1032,8 @@
 }
 
 func (module *SdkLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	module.generateCommonBuildActions(ctx)
+
 	// Only build an implementation library if required.
 	if module.requiresRuntimeImplementationLibrary() {
 		module.Library.GenerateAndroidBuildActions(ctx)
@@ -1203,6 +1223,7 @@
 		Sdk_version                      *string
 		System_modules                   *string
 		Libs                             []string
+		Output_javadoc_comments          *bool
 		Arg_files                        []string
 		Args                             *string
 		Java_version                     *string
@@ -1278,6 +1299,11 @@
 	}
 	droidstubsArgs = append(droidstubsArgs, android.JoinWithPrefix(disabledWarnings, "--hide "))
 
+	// Output Javadoc comments for public scope.
+	if apiScope == apiScopePublic {
+		props.Output_javadoc_comments = proptools.BoolPtr(true)
+	}
+
 	// Add in scope specific arguments.
 	droidstubsArgs = append(droidstubsArgs, scopeSpecificDroidstubsArgs...)
 	props.Arg_files = module.sdkLibraryProperties.Droiddoc_option_files
@@ -1889,6 +1915,8 @@
 }
 
 func (module *SdkLibraryImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	module.generateCommonBuildActions(ctx)
+
 	// Record the paths to the prebuilt stubs library and stubs source.
 	ctx.VisitDirectDeps(func(to android.Module) {
 		tag := ctx.OtherModuleDependencyTag(to)
@@ -2181,6 +2209,9 @@
 	// True if the java_sdk_library_import is for a shared library, false
 	// otherwise.
 	Shared_library *bool
+
+	// The paths to the doctag files to add to the prebuilt.
+	Doctag_paths android.Paths
 }
 
 type scopeProperties struct {
@@ -2220,6 +2251,7 @@
 	s.Libs = sdk.properties.Libs
 	s.Naming_scheme = sdk.commonSdkLibraryProperties.Naming_scheme
 	s.Shared_library = proptools.BoolPtr(sdk.sharedLibrary())
+	s.Doctag_paths = sdk.doctagPaths
 }
 
 func (s *sdkLibrarySdkMemberProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) {
@@ -2268,6 +2300,16 @@
 		}
 	}
 
+	if len(s.Doctag_paths) > 0 {
+		dests := []string{}
+		for _, p := range s.Doctag_paths {
+			dest := filepath.Join("doctags", p.Rel())
+			ctx.SnapshotBuilder().CopyToSnapshot(p, dest)
+			dests = append(dests, dest)
+		}
+		propertySet.AddProperty("doctag_files", dests)
+	}
+
 	if len(s.Libs) > 0 {
 		propertySet.AddPropertyWithTag("libs", s.Libs, ctx.SnapshotBuilder().SdkMemberReferencePropertyTag(false))
 	}
diff --git a/java/testing.go b/java/testing.go
index a472413..461fd3f 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -55,6 +55,8 @@
 		"prebuilts/sdk/30/public/framework.aidl":                   nil,
 		"prebuilts/sdk/30/system/android.jar":                      nil,
 		"prebuilts/sdk/30/system/foo.jar":                          nil,
+		"prebuilts/sdk/30/module-lib/android.jar":                  nil,
+		"prebuilts/sdk/30/module-lib/foo.jar":                      nil,
 		"prebuilts/sdk/30/public/core-for-system-modules.jar":      nil,
 		"prebuilts/sdk/current/core/android.jar":                   nil,
 		"prebuilts/sdk/current/public/android.jar":                 nil,
diff --git a/python/androidmk.go b/python/androidmk.go
index 247b80d..8ad5889 100644
--- a/python/androidmk.go
+++ b/python/androidmk.go
@@ -15,11 +15,12 @@
 package python
 
 import (
-	"android/soong/android"
 	"fmt"
 	"io"
 	"path/filepath"
 	"strings"
+
+	"android/soong/android"
 )
 
 type subAndroidMkProvider interface {
@@ -74,6 +75,11 @@
 		if !BoolDefault(p.binaryProperties.Auto_gen_config, true) {
 			fmt.Fprintln(w, "LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG := true")
 		}
+
+		if len(p.data) > 0 {
+			fmt.Fprintln(w, "LOCAL_TEST_DATA :=",
+				strings.Join(android.AndroidMkDataPaths(p.data), " "))
+		}
 	})
 	base.subAndroidMk(ret, p.binaryDecorator.pythonInstaller)
 }
diff --git a/python/test.go b/python/test.go
index a669c73..434e71a 100644
--- a/python/test.go
+++ b/python/test.go
@@ -34,6 +34,10 @@
 	// the name of the test configuration template (for example "AndroidTestTemplate.xml") that
 	// should be installed with the module.
 	Test_config_template *string `android:"path,arch_variant"`
+
+	// list of files or filegroup modules that provide data that should be installed alongside
+	// the test
+	Data []string `android:"path,arch_variant"`
 }
 
 type testDecorator struct {
@@ -42,6 +46,8 @@
 	testProperties TestProperties
 
 	testConfig android.Path
+
+	data []android.DataPath
 }
 
 func (test *testDecorator) bootstrapperProps() []interface{} {
@@ -59,6 +65,12 @@
 	test.binaryDecorator.pythonInstaller.relative = ctx.ModuleName()
 
 	test.binaryDecorator.pythonInstaller.install(ctx, file)
+
+	dataSrcPaths := android.PathsForModuleSrc(ctx, test.testProperties.Data)
+
+	for _, dataSrcPath := range dataSrcPaths {
+		test.data = append(test.data, android.DataPath{SrcPath: dataSrcPath})
+	}
 }
 
 func NewTest(hod android.HostOrDeviceSupported) *Module {
diff --git a/rust/config/allowed_list.go b/rust/config/allowed_list.go
index 62d469e..483dddb 100644
--- a/rust/config/allowed_list.go
+++ b/rust/config/allowed_list.go
@@ -1,6 +1,10 @@
 package config
 
 var (
+	// When adding a new path below, add a rustfmt.toml file at the root of
+	// the repository and enable the rustfmt repo hook. See aosp/1347562
+	// for an example.
+	// TODO(b/160223496): enable rustfmt globally.
 	RustAllowedPaths = []string{
 		"external/minijail",
 		"external/rust",
diff --git a/rust/project_json.go b/rust/project_json.go
index 8310479..8d161b2 100644
--- a/rust/project_json.go
+++ b/rust/project_json.go
@@ -30,16 +30,6 @@
 //
 //   $ SOONG_GEN_RUST_PROJECT=1 m nothing
 
-func init() {
-	android.RegisterSingletonType("rust_project_generator", rustProjectGeneratorSingleton)
-}
-
-func rustProjectGeneratorSingleton() android.Singleton {
-	return &projectGeneratorSingleton{}
-}
-
-type projectGeneratorSingleton struct{}
-
 const (
 	// Environment variables used to control the behavior of this singleton.
 	envVariableCollectRustDeps = "SOONG_GEN_RUST_PROJECT"
@@ -49,6 +39,7 @@
 // The format of rust-project.json is not yet finalized. A current description is available at:
 // https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/user/manual.adoc#non-cargo-based-projects
 type rustProjectDep struct {
+	// The Crate attribute is the index of the dependency in the Crates array in rustProjectJson.
 	Crate int    `json:"crate"`
 	Name  string `json:"name"`
 }
@@ -71,12 +62,50 @@
 	Deps map[string]int
 }
 
-func mergeDependencies(ctx android.SingletonContext, project *rustProjectJson,
-	knownCrates map[string]crateInfo, module android.Module,
-	crate *rustProjectCrate, deps map[string]int) {
+type projectGeneratorSingleton struct {
+	project     rustProjectJson
+	knownCrates map[string]crateInfo
+}
+
+func rustProjectGeneratorSingleton() android.Singleton {
+	return &projectGeneratorSingleton{}
+}
+
+func init() {
+	android.RegisterSingletonType("rust_project_generator", rustProjectGeneratorSingleton)
+}
+
+// librarySource finds the main source file (.rs) for a crate.
+func librarySource(ctx android.SingletonContext, rModule *Module, rustLib *libraryDecorator) (string, bool) {
+	srcs := rustLib.baseCompiler.Properties.Srcs
+	if len(srcs) != 0 {
+		return path.Join(ctx.ModuleDir(rModule), srcs[0]), true
+	}
+	if !rustLib.source() {
+		return "", false
+	}
+	// It is a SourceProvider module. If this module is host only, uses the variation for the host.
+	// Otherwise, use the variation for the primary target.
+	switch rModule.hod {
+	case android.HostSupported:
+	case android.HostSupportedNoCross:
+		if rModule.Target().String() != ctx.Config().BuildOSTarget.String() {
+			return "", false
+		}
+	default:
+		if rModule.Target().String() != ctx.Config().Targets[android.Android][0].String() {
+			return "", false
+		}
+	}
+	src := rustLib.sourceProvider.Srcs()[0]
+	return src.String(), true
+}
+
+func (singleton *projectGeneratorSingleton) mergeDependencies(ctx android.SingletonContext,
+	module android.Module, crate *rustProjectCrate, deps map[string]int) {
 
 	ctx.VisitDirectDeps(module, func(child android.Module) {
-		childId, childCrateName, ok := appendLibraryAndDeps(ctx, project, knownCrates, child)
+		childId, childCrateName, ok := singleton.appendLibraryAndDeps(ctx, child)
 		if !ok {
 			return
 		}
@@ -88,12 +117,10 @@
 	})
 }
 
-// appendLibraryAndDeps creates a rustProjectCrate for the module argument and
-// appends it to the rustProjectJson struct.  It visits the dependencies of the
-// module depth-first. If the current module is already in knownCrates, its
-// dependencies are merged. Returns a tuple (id, crate_name, ok).
-func appendLibraryAndDeps(ctx android.SingletonContext, project *rustProjectJson,
-	knownCrates map[string]crateInfo, module android.Module) (int, string, bool) {
+// appendLibraryAndDeps creates a rustProjectCrate for the module argument and appends it to singleton.project.
+// It visits the dependencies of the module depth-first so the dependency ID can be added to the current module. If the
+// current module is already in singleton.knownCrates, its dependencies are merged. Returns a tuple (id, crate_name, ok).
+func (singleton *projectGeneratorSingleton) appendLibraryAndDeps(ctx android.SingletonContext, module android.Module) (int, string, bool) {
 	rModule, ok := module.(*Module)
 	if !ok {
 		return 0, "", false
@@ -107,46 +134,45 @@
 	}
 	moduleName := ctx.ModuleName(module)
 	crateName := rModule.CrateName()
-	if cInfo, ok := knownCrates[moduleName]; ok {
+	if cInfo, ok := singleton.knownCrates[moduleName]; ok {
 		// We have seen this crate already; merge any new dependencies.
-		crate := project.Crates[cInfo.ID]
-		mergeDependencies(ctx, project, knownCrates, module, &crate, cInfo.Deps)
-		project.Crates[cInfo.ID] = crate
+		crate := singleton.project.Crates[cInfo.ID]
+		singleton.mergeDependencies(ctx, module, &crate, cInfo.Deps)
+		singleton.project.Crates[cInfo.ID] = crate
 		return cInfo.ID, crateName, true
 	}
 	crate := rustProjectCrate{Deps: make([]rustProjectDep, 0), Cfgs: make([]string, 0)}
-	srcs := rustLib.baseCompiler.Properties.Srcs
-	if len(srcs) == 0 {
+	rootModule, ok := librarySource(ctx, rModule, rustLib)
+	if !ok {
 		return 0, "", false
 	}
-	crate.RootModule = path.Join(ctx.ModuleDir(rModule), srcs[0])
+	crate.RootModule = rootModule
 	crate.Edition = rustLib.baseCompiler.edition()
 
 	deps := make(map[string]int)
-	mergeDependencies(ctx, project, knownCrates, module, &crate, deps)
+	singleton.mergeDependencies(ctx, module, &crate, deps)
 
-	id := len(project.Crates)
-	knownCrates[moduleName] = crateInfo{ID: id, Deps: deps}
-	project.Crates = append(project.Crates, crate)
+	id := len(singleton.project.Crates)
+	singleton.knownCrates[moduleName] = crateInfo{ID: id, Deps: deps}
+	singleton.project.Crates = append(singleton.project.Crates, crate)
 	// rust-analyzer requires that all crates belong to at least one root:
 	// https://github.com/rust-analyzer/rust-analyzer/issues/4735.
-	project.Roots = append(project.Roots, path.Dir(crate.RootModule))
+	singleton.project.Roots = append(singleton.project.Roots, path.Dir(crate.RootModule))
 	return id, crateName, true
 }
 
-func (r *projectGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+func (singleton *projectGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) {
 	if !ctx.Config().IsEnvTrue(envVariableCollectRustDeps) {
 		return
 	}
 
-	project := rustProjectJson{}
-	knownCrates := make(map[string]crateInfo)
+	singleton.knownCrates = make(map[string]crateInfo)
 	ctx.VisitAllModules(func(module android.Module) {
-		appendLibraryAndDeps(ctx, &project, knownCrates, module)
+		singleton.appendLibraryAndDeps(ctx, module)
 	})
 
 	path := android.PathForOutput(ctx, rustProjectJsonFileName)
-	err := createJsonFile(project, path)
+	err := createJsonFile(singleton.project, path)
 	if err != nil {
 		ctx.Errorf(err.Error())
 	}
diff --git a/rust/project_json_test.go b/rust/project_json_test.go
index 8521940..11964f3 100644
--- a/rust/project_json_test.go
+++ b/rust/project_json_test.go
@@ -18,6 +18,7 @@
 	"encoding/json"
 	"io/ioutil"
 	"path/filepath"
+	"strings"
 	"testing"
 
 	"android/soong/android"
@@ -100,22 +101,51 @@
 	rust_library {
 		name: "liba",
 		srcs: ["src/lib.rs"],
-		rlibs: ["libbindings"],
+		rlibs: ["libbindings1"],
 		crate_name: "a"
 	}
 	rust_bindgen {
-		name: "libbindings",
-		crate_name: "bindings",
-		source_stem: "bindings",
+		name: "libbindings1",
+		crate_name: "bindings1",
+		source_stem: "bindings1",
 		host_supported: true,
 		wrapper_src: "src/any.h",
 	}
+	rust_library_host {
+		name: "libb",
+		srcs: ["src/lib.rs"],
+		rustlibs: ["libbindings2"],
+		crate_name: "b"
+	}
+	rust_bindgen_host {
+		name: "libbindings2",
+		crate_name: "bindings2",
+		source_stem: "bindings2",
+		wrapper_src: "src/any.h",
+	}
 	` + GatherRequiredDepsForTest()
 	fs := map[string][]byte{
 		"src/lib.rs": nil,
 	}
 	jsonContent := testProjectJson(t, bp, fs)
-	validateJsonCrates(t, jsonContent)
+	crates := validateJsonCrates(t, jsonContent)
+	for _, c := range crates {
+		crate, ok := c.(map[string]interface{})
+		if !ok {
+			t.Fatalf("Unexpected type for crate: %v", c)
+		}
+		rootModule, ok := crate["root_module"].(string)
+		if !ok {
+			t.Fatalf("Unexpected type for root_module: %v", crate["root_module"])
+		}
+		if strings.Contains(rootModule, "libbindings1") && !strings.Contains(rootModule, "android_arm64") {
+			t.Errorf("The source path for libbindings1 does not contain android_arm64, got %v", rootModule)
+		}
+		if strings.Contains(rootModule, "libbindings2") && !strings.Contains(rootModule, android.BuildOs.String()) {
+			t.Errorf("The source path for libbindings2 does not contain the BuildOs, got %v; want %v",
+				rootModule, android.BuildOs.String())
+		}
+	}
 }
 
 func TestProjectJsonMultiVersion(t *testing.T) {
diff --git a/rust/rust.go b/rust/rust.go
index f88b310..68e5a5b 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -695,6 +695,11 @@
 		if mod.compiler.(libraryInterface).source() {
 			mod.sourceProvider.GenerateSource(ctx, deps)
 			mod.sourceProvider.setSubName(ctx.ModuleSubDir())
+			if lib, ok := mod.compiler.(*libraryDecorator); ok {
+				lib.flagExporter.linkDirs = nil
+				lib.flagExporter.linkObjects = nil
+				lib.flagExporter.depFlags = nil
+			}
 		} else {
 			sourceMod := actx.GetDirectDepWithTag(mod.Name(), sourceDepTag)
 			sourceLib := sourceMod.(*Module).compiler.(*libraryDecorator)
diff --git a/rust/testing.go b/rust/testing.go
index ee303ed..42b0da1 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -117,6 +117,7 @@
 	ctx.RegisterModuleType("rust_binary", RustBinaryFactory)
 	ctx.RegisterModuleType("rust_binary_host", RustBinaryHostFactory)
 	ctx.RegisterModuleType("rust_bindgen", RustBindgenFactory)
+	ctx.RegisterModuleType("rust_bindgen_host", RustBindgenHostFactory)
 	ctx.RegisterModuleType("rust_test", RustTestFactory)
 	ctx.RegisterModuleType("rust_test_host", RustTestHostFactory)
 	ctx.RegisterModuleType("rust_library", RustLibraryFactory)
diff --git a/sdk/bp.go b/sdk/bp.go
index 68fe7ab..11ec8c6 100644
--- a/sdk/bp.go
+++ b/sdk/bp.go
@@ -16,6 +16,8 @@
 
 import (
 	"fmt"
+	"reflect"
+	"strings"
 
 	"android/soong/android"
 )
@@ -33,7 +35,82 @@
 	s.tags = make(map[string]android.BpPropertyTag)
 }
 
+// Converts the given value, which is assumed to be a struct, to a
+// bpPropertySet.
+func convertToPropertySet(value reflect.Value) *bpPropertySet {
+	res := newPropertySet()
+	structType := value.Type()
+
+	for i := 0; i < structType.NumField(); i++ {
+		field := structType.Field(i)
+		fieldVal := value.Field(i)
+
+		switch fieldVal.Type().Kind() {
+		case reflect.Ptr:
+			if fieldVal.IsNil() {
+				continue // nil pointer means the property isn't set.
+			}
+			fieldVal = fieldVal.Elem()
+		case reflect.Slice:
+			if fieldVal.IsNil() {
+				continue // Ignore a nil slice (but not one with length zero).
+			}
+		}
+
+		if fieldVal.Type().Kind() == reflect.Struct {
+			fieldVal = fieldVal.Addr() // Avoid struct copy below.
+		}
+		res.AddProperty(strings.ToLower(field.Name), fieldVal.Interface())
+	}
+
+	return res
+}
+
+// Converts the given value to something that can be set in a property.
+func coercePropertyValue(value interface{}) interface{} {
+	val := reflect.ValueOf(value)
+	switch val.Kind() {
+	case reflect.Struct:
+		// convertToPropertySet requires an addressable struct, and this is probably
+		// a mistake.
+		panic(fmt.Sprintf("Value is a struct, not a pointer to one: %v", value))
+	case reflect.Ptr:
+		if _, ok := value.(*bpPropertySet); !ok {
+			derefValue := reflect.Indirect(val)
+			if derefValue.Kind() != reflect.Struct {
+				panic(fmt.Sprintf("A pointer must be to a struct, got: %v", value))
+			}
+			return convertToPropertySet(derefValue)
+		}
+	}
+	return value
+}
+
+// Merges the fields of the given property set into s.
+func (s *bpPropertySet) mergePropertySet(propSet *bpPropertySet) {
+	for _, name := range propSet.order {
+		if tag, ok := propSet.tags[name]; ok {
+			s.AddPropertyWithTag(name, propSet.properties[name], tag)
+		} else {
+			s.AddProperty(name, propSet.properties[name])
+		}
+	}
+}
+
 func (s *bpPropertySet) AddProperty(name string, value interface{}) {
+	value = coercePropertyValue(value)
+
+	if propSetValue, ok := value.(*bpPropertySet); ok {
+		if curValue, ok := s.properties[name]; ok {
+			if curSet, ok := curValue.(*bpPropertySet); ok {
+				curSet.mergePropertySet(propSetValue)
+				return
+			}
+			// If the current value isn't a property set we got conflicting types.
+			// Continue down to the check below to complain about it.
+		}
+	}
+
 	if s.properties[name] != nil {
 		panic(fmt.Sprintf("Property %q already exists in property set", name))
 	}
@@ -48,9 +125,8 @@
 }
 
 func (s *bpPropertySet) AddPropertySet(name string) android.BpPropertySet {
-	set := newPropertySet()
-	s.AddProperty(name, set)
-	return set
+	s.AddProperty(name, newPropertySet())
+	return s.properties[name].(android.BpPropertySet)
 }
 
 func (s *bpPropertySet) getValue(name string) interface{} {
diff --git a/sdk/bp_test.go b/sdk/bp_test.go
index c630c25..e1edc51 100644
--- a/sdk/bp_test.go
+++ b/sdk/bp_test.go
@@ -18,8 +18,142 @@
 	"testing"
 
 	"android/soong/android"
+
+	"github.com/google/blueprint/proptools"
 )
 
+func propertySetFixture() interface{} {
+	set := newPropertySet()
+	set.AddProperty("x", "taxi")
+	set.AddPropertyWithTag("y", 1729, "tag_y")
+	subset := set.AddPropertySet("sub")
+	subset.AddPropertyWithTag("x", "taxi", "tag_x")
+	subset.AddProperty("y", 1729)
+	return set
+}
+
+func intPtr(i int) *int { return &i }
+
+type propertyStruct struct {
+	X     *string
+	Y     *int
+	Unset *bool
+	Sub   struct {
+		X     *string
+		Y     *int
+		Unset *bool
+	}
+}
+
+func propertyStructFixture() interface{} {
+	str := &propertyStruct{}
+	str.X = proptools.StringPtr("taxi")
+	str.Y = intPtr(1729)
+	str.Sub.X = proptools.StringPtr("taxi")
+	str.Sub.Y = intPtr(1729)
+	return str
+}
+
+func checkPropertySetFixture(h *TestHelper, val interface{}, hasTags bool) {
+	set := val.(*bpPropertySet)
+	h.AssertDeepEquals("wrong x value", "taxi", set.getValue("x"))
+	h.AssertDeepEquals("wrong y value", 1729, set.getValue("y"))
+
+	subset := set.getValue("sub").(*bpPropertySet)
+	h.AssertDeepEquals("wrong sub.x value", "taxi", subset.getValue("x"))
+	h.AssertDeepEquals("wrong sub.y value", 1729, subset.getValue("y"))
+
+	if hasTags {
+		h.AssertDeepEquals("wrong y tag", "tag_y", set.getTag("y"))
+		h.AssertDeepEquals("wrong sub.x tag", "tag_x", subset.getTag("x"))
+	} else {
+		h.AssertDeepEquals("wrong y tag", nil, set.getTag("y"))
+		h.AssertDeepEquals("wrong sub.x tag", nil, subset.getTag("x"))
+	}
+}
+
+func TestAddPropertySimple(t *testing.T) {
+	h := &TestHelper{t}
+	set := newPropertySet()
+	for name, val := range map[string]interface{}{
+		"x":   "taxi",
+		"y":   1729,
+		"t":   true,
+		"f":   false,
+		"arr": []string{"a", "b", "c"},
+	} {
+		set.AddProperty(name, val)
+		h.AssertDeepEquals("wrong value", val, set.getValue(name))
+	}
+	h.AssertPanic("adding x again should panic",
+		func() { set.AddProperty("x", "taxi") })
+	h.AssertPanic("adding arr again should panic",
+		func() { set.AddProperty("arr", []string{"d"}) })
+}
+
+func TestAddPropertySubset(t *testing.T) {
+	h := &TestHelper{t}
+	getFixtureMap := map[string]func() interface{}{
+		"property set":    propertySetFixture,
+		"property struct": propertyStructFixture,
+	}
+
+	t.Run("add new subset", func(t *testing.T) {
+		for name, getFixture := range getFixtureMap {
+			t.Run(name, func(t *testing.T) {
+				set := propertySetFixture().(*bpPropertySet)
+				set.AddProperty("new", getFixture())
+				checkPropertySetFixture(h, set, true)
+				checkPropertySetFixture(h, set.getValue("new"), name == "property set")
+			})
+		}
+	})
+
+	t.Run("merge existing subset", func(t *testing.T) {
+		for name, getFixture := range getFixtureMap {
+			t.Run(name, func(t *testing.T) {
+				set := newPropertySet()
+				subset := set.AddPropertySet("sub")
+				subset.AddProperty("flag", false)
+				subset.AddPropertySet("sub")
+				set.AddProperty("sub", getFixture())
+				merged := set.getValue("sub").(*bpPropertySet)
+				h.AssertDeepEquals("wrong flag value", false, merged.getValue("flag"))
+				checkPropertySetFixture(h, merged, name == "property set")
+			})
+		}
+	})
+
+	t.Run("add conflicting subset", func(t *testing.T) {
+		set := propertySetFixture().(*bpPropertySet)
+		h.AssertPanic("adding x again should panic",
+			func() { set.AddProperty("x", propertySetFixture()) })
+	})
+
+	t.Run("add non-pointer struct", func(t *testing.T) {
+		set := propertySetFixture().(*bpPropertySet)
+		str := propertyStructFixture().(*propertyStruct)
+		h.AssertPanic("adding a non-pointer struct should panic",
+			func() { set.AddProperty("new", *str) })
+	})
+}
+
+func TestAddPropertySetNew(t *testing.T) {
+	h := &TestHelper{t}
+	set := newPropertySet()
+	subset := set.AddPropertySet("sub")
+	subset.AddProperty("new", "d^^b")
+	h.AssertDeepEquals("wrong sub.new value", "d^^b", set.getValue("sub").(*bpPropertySet).getValue("new"))
+}
+
+func TestAddPropertySetExisting(t *testing.T) {
+	h := &TestHelper{t}
+	set := propertySetFixture().(*bpPropertySet)
+	subset := set.AddPropertySet("sub")
+	subset.AddProperty("new", "d^^b")
+	h.AssertDeepEquals("wrong sub.new value", "d^^b", set.getValue("sub").(*bpPropertySet).getValue("new"))
+}
+
 type removeFredTransformation struct {
 	identityTransformation
 }
diff --git a/sdk/cc_sdk_test.go b/sdk/cc_sdk_test.go
index 8c9e228..17a6d86 100644
--- a/sdk/cc_sdk_test.go
+++ b/sdk/cc_sdk_test.go
@@ -435,8 +435,10 @@
 	)
 }
 
-// Verify that when the shared library has some common and some arch specific properties that the generated
-// snapshot is optimized properly.
+// Verify that when the shared library has some common and some arch specific
+// properties that the generated snapshot is optimized properly. Substruct
+// handling is tested with the sanitize clauses (but note there's a lot of
+// built-in logic in sanitize.go that can affect those flags).
 func TestSnapshotWithCcSharedLibraryCommonProperties(t *testing.T) {
 	result := testSdkWithCc(t, `
 		sdk {
@@ -451,9 +453,18 @@
 				"aidl/foo/bar/Test.aidl",
 			],
 			export_include_dirs: ["include"],
+			sanitize: {
+				fuzzer: false,
+				integer_overflow: true,
+				diag: { undefined: false },
+			},
 			arch: {
 				arm64: {
 					export_system_include_dirs: ["arm64/include"],
+					sanitize: {
+						hwaddress: true,
+						integer_overflow: false,
+					},
 				},
 			},
 			stl: "none",
@@ -471,13 +482,26 @@
     stl: "none",
     compile_multilib: "both",
     export_include_dirs: ["include/include"],
+    sanitize: {
+        fuzzer: false,
+        diag: {
+            undefined: false,
+        },
+    },
     arch: {
         arm64: {
             srcs: ["arm64/lib/mynativelib.so"],
             export_system_include_dirs: ["arm64/include/arm64/include"],
+            sanitize: {
+                hwaddress: true,
+                integer_overflow: false,
+            },
         },
         arm: {
             srcs: ["arm/lib/mynativelib.so"],
+            sanitize: {
+                integer_overflow: true,
+            },
         },
     },
 }
@@ -488,13 +512,26 @@
     stl: "none",
     compile_multilib: "both",
     export_include_dirs: ["include/include"],
+    sanitize: {
+        fuzzer: false,
+        diag: {
+            undefined: false,
+        },
+    },
     arch: {
         arm64: {
             srcs: ["arm64/lib/mynativelib.so"],
             export_system_include_dirs: ["arm64/include/arm64/include"],
+            sanitize: {
+                hwaddress: true,
+                integer_overflow: false,
+            },
         },
         arm: {
             srcs: ["arm/lib/mynativelib.so"],
+            sanitize: {
+                integer_overflow: true,
+            },
         },
     },
 }
@@ -506,7 +543,7 @@
 `),
 		checkAllCopyRules(`
 include/Test.h -> include/include/Test.h
-.intermediates/mynativelib/android_arm64_armv8-a_shared/mynativelib.so -> arm64/lib/mynativelib.so
+.intermediates/mynativelib/android_arm64_armv8-a_shared_hwasan/mynativelib.so -> arm64/lib/mynativelib.so
 arm64/include/Arm64Test.h -> arm64/include/arm64/include/Arm64Test.h
 .intermediates/mynativelib/android_arm_armv7-a-neon_shared/mynativelib.so -> arm/lib/mynativelib.so`),
 	)
@@ -2330,7 +2367,11 @@
     installable: false,
     compile_multilib: "both",
     stubs: {
-        versions: ["3"],
+        versions: [
+            "1",
+            "2",
+            "3",
+        ],
     },
     arch: {
         arm64: {
@@ -2347,7 +2388,11 @@
     prefer: false,
     compile_multilib: "both",
     stubs: {
-        versions: ["3"],
+        versions: [
+            "1",
+            "2",
+            "3",
+        ],
     },
     arch: {
         arm64: {
@@ -2401,7 +2446,11 @@
     installable: false,
     compile_multilib: "both",
     stubs: {
-        versions: ["3"],
+        versions: [
+            "1",
+            "2",
+            "3",
+        ],
     },
     target: {
         host: {
@@ -2431,7 +2480,11 @@
     host_supported: true,
     compile_multilib: "both",
     stubs: {
-        versions: ["3"],
+        versions: [
+            "1",
+            "2",
+            "3",
+        ],
     },
     target: {
         host: {
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index a7ee8d1..23ecb6d 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -43,6 +43,7 @@
 		"api/system-server-current.txt":                     nil,
 		"api/system-server-removed.txt":                     nil,
 		"build/soong/scripts/gen-java-current-api-files.sh": nil,
+		"docs/known_doctags":                                nil,
 	}
 
 	// for java_sdk_library tests
@@ -1590,3 +1591,72 @@
 		),
 	)
 }
+
+func TestSnapshotWithJavaSdkLibrary_DoctagFiles(t *testing.T) {
+	result := testSdkWithJava(t, `
+		sdk {
+			name: "mysdk",
+			java_sdk_libs: ["myjavalib"],
+		}
+
+		java_sdk_library {
+			name: "myjavalib",
+			srcs: ["Test.java"],
+			sdk_version: "current",
+			public: {
+				enabled: true,
+			},
+			doctag_files: ["docs/known_doctags"],
+		}
+
+		filegroup {
+			name: "mygroup",
+			srcs: [":myjavalib{.doctags}"],
+		}
+	`)
+
+	result.CheckSnapshot("mysdk", "",
+		checkAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_sdk_library_import {
+    name: "mysdk_myjavalib@current",
+    sdk_member_name: "myjavalib",
+    shared_library: true,
+    doctag_files: ["doctags/docs/known_doctags"],
+    public: {
+        jars: ["sdk_library/public/myjavalib-stubs.jar"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
+        current_api: "sdk_library/public/myjavalib.txt",
+        removed_api: "sdk_library/public/myjavalib-removed.txt",
+        sdk_version: "current",
+    },
+}
+
+java_sdk_library_import {
+    name: "myjavalib",
+    prefer: false,
+    shared_library: true,
+    doctag_files: ["doctags/docs/known_doctags"],
+    public: {
+        jars: ["sdk_library/public/myjavalib-stubs.jar"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
+        current_api: "sdk_library/public/myjavalib.txt",
+        removed_api: "sdk_library/public/myjavalib-removed.txt",
+        sdk_version: "current",
+    },
+}
+
+sdk_snapshot {
+    name: "mysdk@current",
+    java_sdk_libs: ["mysdk_myjavalib@current"],
+}
+`),
+		checkAllCopyRules(`
+.intermediates/myjavalib.stubs/android_common/javac/myjavalib.stubs.jar -> sdk_library/public/myjavalib-stubs.jar
+.intermediates/myjavalib.stubs.source/android_common/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
+.intermediates/myjavalib.stubs.source/android_common/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
+docs/known_doctags -> doctags/docs/known_doctags
+`),
+	)
+}
diff --git a/sdk/testing.go b/sdk/testing.go
index e57f1f7..0b280ef 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -94,6 +94,7 @@
 
 	// from android package
 	android.RegisterPackageBuildComponents(ctx)
+	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
 	ctx.PreArchMutators(android.RegisterVisibilityRuleChecker)
 	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
 	ctx.PreArchMutators(android.RegisterComponentsMutator)
@@ -217,6 +218,22 @@
 	}
 }
 
+func (h *TestHelper) AssertPanic(message string, funcThatShouldPanic func()) {
+	h.t.Helper()
+	panicked := false
+	func() {
+		defer func() {
+			if x := recover(); x != nil {
+				panicked = true
+			}
+		}()
+		funcThatShouldPanic()
+	}()
+	if !panicked {
+		h.t.Error(message)
+	}
+}
+
 // Encapsulates result of processing an SDK definition. Provides support for
 // checking the state of the build structures.
 type testSdkResult struct {
diff --git a/sdk/update.go b/sdk/update.go
index 3c4309c..a10e852 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -1346,7 +1346,8 @@
 
 // A property that can be optimized by the commonValueExtractor.
 type extractorProperty struct {
-	// The name of the field for this property.
+	// The name of the field for this property. It is a "."-separated path for
+	// fields in non-anonymous substructs.
 	name string
 
 	// Filter that can use metadata associated with the properties being optimized
@@ -1383,18 +1384,18 @@
 func newCommonValueExtractor(propertiesStruct interface{}) *commonValueExtractor {
 	structType := getStructValue(reflect.ValueOf(propertiesStruct)).Type()
 	extractor := &commonValueExtractor{}
-	extractor.gatherFields(structType, nil)
+	extractor.gatherFields(structType, nil, "")
 	return extractor
 }
 
 // Gather the fields from the supplied structure type from which common values will
 // be extracted.
 //
-// This is recursive function. If it encounters an embedded field (no field name)
-// that is a struct then it will recurse into that struct passing in the accessor
-// for the field. That will then be used in the accessors for the fields in the
-// embedded struct.
-func (e *commonValueExtractor) gatherFields(structType reflect.Type, containingStructAccessor fieldAccessorFunc) {
+// This is recursive function. If it encounters a struct then it will recurse
+// into it, passing in the accessor for the field and the struct name as prefix
+// for the nested fields. That will then be used in the accessors for the fields
+// in the embedded struct.
+func (e *commonValueExtractor) gatherFields(structType reflect.Type, containingStructAccessor fieldAccessorFunc, namePrefix string) {
 	for f := 0; f < structType.NumField(); f++ {
 		field := structType.Field(f)
 		if field.PkgPath != "" {
@@ -1424,7 +1425,7 @@
 		// Save a copy of the field index for use in the function.
 		fieldIndex := f
 
-		name := field.Name
+		name := namePrefix + field.Name
 
 		fieldGetter := func(value reflect.Value) reflect.Value {
 			if containingStructAccessor != nil {
@@ -1446,9 +1447,15 @@
 			return value.Field(fieldIndex)
 		}
 
-		if field.Type.Kind() == reflect.Struct && field.Anonymous {
-			// Gather fields from the embedded structure.
-			e.gatherFields(field.Type, fieldGetter)
+		if field.Type.Kind() == reflect.Struct {
+			// Gather fields from the nested or embedded structure.
+			var subNamePrefix string
+			if field.Anonymous {
+				subNamePrefix = namePrefix
+			} else {
+				subNamePrefix = name + "."
+			}
+			e.gatherFields(field.Type, fieldGetter, subNamePrefix)
 		} else {
 			property := extractorProperty{
 				name,
@@ -1512,7 +1519,8 @@
 // Iterates over each exported field (capitalized name) and checks to see whether they
 // have the same value (using DeepEquals) across all the input properties. If it does not then no
 // change is made. Otherwise, the common value is stored in the field in the commonProperties
-// and the field in each of the input properties structure is set to its default value.
+// and the field in each of the input properties structure is set to its default value. Nested
+// structs are visited recursively and their non-struct fields are compared.
 func (e *commonValueExtractor) extractCommonProperties(commonProperties interface{}, inputPropertiesSlice interface{}) error {
 	commonPropertiesValue := reflect.ValueOf(commonProperties)
 	commonStructValue := commonPropertiesValue.Elem()
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index f3f4a4a..7c3cdbd 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -313,6 +313,15 @@
 		options := []tradefed.Option{{Name: "force-root", Value: "false"}}
 		configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.RootTargetPreparer", options})
 	}
+	if len(s.testProperties.Data_device_bins) > 0 {
+		moduleName := s.Name()
+		remoteDir := "/data/local/tests/unrestricted/" + moduleName + "/"
+		options := []tradefed.Option{{Name: "cleanup", Value: "true"}}
+		for _, bin := range s.testProperties.Data_device_bins {
+			options = append(options, tradefed.Option{Name: "push-file", Key: bin, Value: remoteDir + bin})
+		}
+		configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.PushFilePreparer", options})
+	}
 	s.testConfig = tradefed.AutoGenShellTestConfig(ctx, s.testProperties.Test_config,
 		s.testProperties.Test_config_template, s.testProperties.Test_suites, configs, s.testProperties.Auto_gen_config, s.outputFilePath.Base())
 
diff --git a/ui/build/config.go b/ui/build/config.go
index 27f3be3..fe74ace 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -298,8 +298,9 @@
 	}
 
 	b := &smpb.BuildConfig{
-		UseGoma: proto.Bool(config.UseGoma()),
-		UseRbe:  proto.Bool(config.UseRBE()),
+		ForceUseGoma: proto.Bool(config.ForceUseGoma()),
+		UseGoma:      proto.Bool(config.UseGoma()),
+		UseRbe:       proto.Bool(config.UseRBE()),
 	}
 	ctx.Metrics.BuildConfig(b)
 }
diff --git a/ui/metrics/metrics_proto/metrics.pb.go b/ui/metrics/metrics_proto/metrics.pb.go
index d7c53ec..05efe13 100644
--- a/ui/metrics/metrics_proto/metrics.pb.go
+++ b/ui/metrics/metrics_proto/metrics.pb.go
@@ -399,6 +399,7 @@
 type BuildConfig struct {
 	UseGoma              *bool    `protobuf:"varint,1,opt,name=use_goma,json=useGoma" json:"use_goma,omitempty"`
 	UseRbe               *bool    `protobuf:"varint,2,opt,name=use_rbe,json=useRbe" json:"use_rbe,omitempty"`
+	ForceUseGoma         *bool    `protobuf:"varint,3,opt,name=force_use_goma,json=forceUseGoma" json:"force_use_goma,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
 	XXX_sizecache        int32    `json:"-"`
@@ -443,6 +444,13 @@
 	return false
 }
 
+func (m *BuildConfig) GetForceUseGoma() bool {
+	if m != nil && m.ForceUseGoma != nil {
+		return *m.ForceUseGoma
+	}
+	return false
+}
+
 type PerfInfo struct {
 	// The description for the phase/action/part while the tool running.
 	Desc *string `protobuf:"bytes,1,opt,name=desc" json:"desc,omitempty"`
@@ -764,69 +772,70 @@
 }
 
 var fileDescriptor_6039342a2ba47b72 = []byte{
-	// 1021 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0xef, 0x6e, 0xdb, 0x36,
-	0x10, 0xaf, 0x12, 0x27, 0xb6, 0x4e, 0xb1, 0xab, 0x30, 0xed, 0xa2, 0xb6, 0x08, 0x66, 0x18, 0x6b,
-	0x11, 0x0c, 0x6b, 0x5a, 0x64, 0x45, 0x50, 0x04, 0xc5, 0x00, 0xc7, 0x09, 0xb2, 0x2e, 0x48, 0x5c,
-	0x30, 0x7f, 0x56, 0x6c, 0x1f, 0x04, 0x5a, 0xa2, 0x13, 0x75, 0x96, 0x28, 0x90, 0x54, 0x91, 0xf4,
-	0x1d, 0xf6, 0x54, 0x7b, 0x96, 0xbd, 0xc6, 0x30, 0xf0, 0x28, 0xd9, 0xca, 0xe6, 0xad, 0x41, 0xbf,
-	0x89, 0xf7, 0xfb, 0xc3, 0x3b, 0xf2, 0x78, 0x36, 0xb4, 0x53, 0xae, 0x65, 0x12, 0xa9, 0xad, 0x5c,
-	0x0a, 0x2d, 0xc8, 0x9a, 0x12, 0x22, 0xbb, 0x0c, 0x47, 0x45, 0x32, 0x89, 0xc3, 0x12, 0xea, 0xfd,
-	0x05, 0xe0, 0x1d, 0xdb, 0xef, 0x3d, 0xa6, 0x38, 0x79, 0x09, 0x0f, 0x2c, 0x21, 0x66, 0x9a, 0x87,
-	0x3a, 0x49, 0xb9, 0xd2, 0x2c, 0xcd, 0x03, 0xa7, 0xeb, 0x6c, 0x2e, 0x52, 0x82, 0xd8, 0x3e, 0xd3,
-	0xfc, 0xac, 0x42, 0xc8, 0x23, 0x68, 0x59, 0x45, 0x12, 0x07, 0x0b, 0x5d, 0x67, 0xd3, 0xa5, 0x4d,
-	0x5c, 0xbf, 0x8d, 0xc9, 0x2e, 0x3c, 0xca, 0x27, 0x4c, 0x8f, 0x85, 0x4c, 0xc3, 0x8f, 0x5c, 0xaa,
-	0x44, 0x64, 0x61, 0x24, 0x62, 0x9e, 0xb1, 0x94, 0x07, 0x8b, 0xc8, 0x5d, 0xaf, 0x08, 0x17, 0x16,
-	0x1f, 0x94, 0x30, 0x79, 0x0a, 0x1d, 0xcd, 0xe4, 0x25, 0xd7, 0x61, 0x2e, 0x45, 0x5c, 0x44, 0x3a,
-	0x68, 0xa0, 0xa0, 0x6d, 0xa3, 0xef, 0x6c, 0x90, 0xc4, 0xf0, 0xa0, 0xa4, 0xd9, 0x24, 0x3e, 0x32,
-	0x99, 0xb0, 0x4c, 0x07, 0x4b, 0x5d, 0x67, 0xb3, 0xb3, 0xfd, 0x7c, 0x6b, 0x4e, 0xcd, 0x5b, 0xb5,
-	0x7a, 0xb7, 0xf6, 0x0c, 0x72, 0x61, 0x45, 0xbb, 0x8b, 0x07, 0x27, 0x87, 0x94, 0x58, 0xbf, 0x3a,
-	0x40, 0x86, 0xe0, 0x95, 0xbb, 0x30, 0x19, 0x5d, 0x05, 0xcb, 0x68, 0xfe, 0xf4, 0xb3, 0xe6, 0x7d,
-	0x19, 0x5d, 0xed, 0x36, 0xcf, 0x4f, 0x8e, 0x4e, 0x86, 0x3f, 0x9f, 0x50, 0xb0, 0x16, 0x26, 0x48,
-	0xb6, 0x60, 0xad, 0x66, 0x38, 0xcd, 0xba, 0x89, 0x25, 0xae, 0xce, 0x88, 0x55, 0x02, 0xdf, 0x41,
-	0x99, 0x56, 0x18, 0xe5, 0xc5, 0x94, 0xde, 0x42, 0xba, 0x6f, 0x91, 0x41, 0x5e, 0x54, 0xec, 0x23,
-	0x70, 0xaf, 0x84, 0x2a, 0x93, 0x75, 0xbf, 0x28, 0xd9, 0x96, 0x31, 0xc0, 0x54, 0x29, 0xb4, 0xd1,
-	0x6c, 0x3b, 0x8b, 0xad, 0x21, 0x7c, 0x91, 0xa1, 0x67, 0x4c, 0xb6, 0xb3, 0x18, 0x3d, 0xd7, 0xa1,
-	0x89, 0x9e, 0x42, 0x05, 0x1e, 0xd6, 0xb0, 0x6c, 0x96, 0x43, 0x45, 0x7a, 0xe5, 0x66, 0x42, 0x85,
-	0xfc, 0x5a, 0x4b, 0x16, 0xac, 0x20, 0xec, 0x59, 0xf8, 0xc0, 0x84, 0xa6, 0x9c, 0x48, 0x0a, 0xa5,
-	0x8c, 0x45, 0x7b, 0xc6, 0x19, 0x98, 0xd8, 0x50, 0x91, 0x67, 0x70, 0xbf, 0xc6, 0xc1, 0xb4, 0x3b,
-	0xb6, 0x7d, 0xa6, 0x2c, 0x4c, 0xe4, 0x39, 0xac, 0xd5, 0x78, 0xd3, 0x12, 0xef, 0xdb, 0x83, 0x9d,
-	0x72, 0x6b, 0x79, 0x8b, 0x42, 0x87, 0x71, 0x22, 0x03, 0xdf, 0xe6, 0x2d, 0x0a, 0xbd, 0x9f, 0x48,
-	0xf2, 0x03, 0x78, 0x8a, 0xeb, 0x22, 0x0f, 0xb5, 0x10, 0x13, 0x15, 0xac, 0x76, 0x17, 0x37, 0xbd,
-	0xed, 0x8d, 0xb9, 0x47, 0xf4, 0x8e, 0xcb, 0xf1, 0xdb, 0x6c, 0x2c, 0x28, 0xa0, 0xe2, 0xcc, 0x08,
-	0xc8, 0x2e, 0xb8, 0xbf, 0x31, 0x9d, 0x84, 0xb2, 0xc8, 0x54, 0x40, 0xee, 0xa2, 0x6e, 0x19, 0x3e,
-	0x2d, 0x32, 0x45, 0xde, 0x00, 0x58, 0x26, 0x8a, 0xd7, 0xee, 0x22, 0x76, 0x11, 0xad, 0xd4, 0x59,
-	0x92, 0x7d, 0x60, 0x56, 0xfd, 0xe0, 0x4e, 0x6a, 0x14, 0xa0, 0xfa, 0x7b, 0x58, 0xd2, 0x42, 0xb3,
-	0x49, 0xf0, 0xb0, 0xeb, 0x7c, 0x5e, 0x68, 0xb9, 0xe4, 0x02, 0xe6, 0x8d, 0xa2, 0xe0, 0x2b, 0xb4,
-	0x78, 0x36, 0xd7, 0xe2, 0xd4, 0xc4, 0xf0, 0x49, 0x96, 0x1d, 0x46, 0x57, 0xd5, 0x3f, 0x43, 0x64,
-	0x00, 0x2b, 0x56, 0x15, 0x89, 0x6c, 0x9c, 0x5c, 0x06, 0xeb, 0x68, 0xd8, 0x9d, 0x6b, 0x88, 0xc2,
-	0x01, 0xf2, 0xa8, 0x37, 0x9a, 0x2d, 0x7a, 0x2f, 0x61, 0xe5, 0xd6, 0xd3, 0x6f, 0x41, 0xe3, 0xfc,
-	0xf4, 0x80, 0xfa, 0xf7, 0x48, 0x1b, 0x5c, 0xf3, 0xb5, 0x7f, 0xb0, 0x77, 0x7e, 0xe8, 0x3b, 0xa4,
-	0x09, 0x66, 0x5c, 0xf8, 0x0b, 0xbd, 0x37, 0xd0, 0xc0, 0xe6, 0xf0, 0xa0, 0x6a, 0x76, 0xff, 0x9e,
-	0x41, 0xfb, 0xf4, 0xd8, 0x77, 0x88, 0x0b, 0x4b, 0x7d, 0x7a, 0xbc, 0xf3, 0xca, 0x5f, 0x30, 0xb1,
-	0xf7, 0xaf, 0x77, 0xfc, 0x45, 0x02, 0xb0, 0xfc, 0xfe, 0xf5, 0x4e, 0xb8, 0xf3, 0xca, 0x6f, 0xf4,
-	0xfa, 0xe0, 0xd5, 0x72, 0x31, 0xd3, 0xb4, 0x50, 0x3c, 0xbc, 0x14, 0x29, 0xc3, 0x99, 0xdb, 0xa2,
-	0xcd, 0x42, 0xf1, 0x43, 0x91, 0x32, 0xd3, 0x7c, 0x06, 0x92, 0x23, 0x8e, 0x73, 0xb6, 0x45, 0x97,
-	0x0b, 0xc5, 0xe9, 0x88, 0xf7, 0x7e, 0x77, 0xa0, 0x55, 0x9d, 0x31, 0x21, 0xd0, 0x88, 0xb9, 0x8a,
-	0x50, 0xec, 0x52, 0xfc, 0x36, 0x31, 0x1c, 0xb9, 0x76, 0x3c, 0xe3, 0x37, 0xd9, 0x00, 0x50, 0x9a,
-	0x49, 0x8d, 0x33, 0x1e, 0x87, 0x71, 0x83, 0xba, 0x18, 0x31, 0xa3, 0x9d, 0x3c, 0x01, 0x57, 0x72,
-	0x36, 0xb1, 0x68, 0x03, 0xd1, 0x96, 0x09, 0x20, 0xb8, 0x01, 0x90, 0xf2, 0x54, 0xc8, 0x9b, 0xb0,
-	0x50, 0x1c, 0x47, 0x6d, 0x83, 0xba, 0x36, 0x72, 0xae, 0x78, 0xef, 0x4f, 0x07, 0x3a, 0xc7, 0x22,
-	0x2e, 0x26, 0xfc, 0xec, 0x26, 0xe7, 0x98, 0xd5, 0xaf, 0xd5, 0xd5, 0xa8, 0x1b, 0xa5, 0x79, 0x8a,
-	0xd9, 0x75, 0xb6, 0x5f, 0xcc, 0x9f, 0x21, 0xb7, 0xa4, 0xf6, 0xa6, 0x4e, 0x51, 0x56, 0x9b, 0x26,
-	0xa3, 0x59, 0x94, 0x7c, 0x0d, 0x5e, 0x8a, 0x9a, 0x50, 0xdf, 0xe4, 0x55, 0x95, 0x90, 0x4e, 0x6d,
-	0xc8, 0x37, 0xd0, 0xc9, 0x8a, 0x34, 0x14, 0xe3, 0xd0, 0x06, 0x15, 0xd6, 0xdb, 0xa6, 0x2b, 0x59,
-	0x91, 0x0e, 0xc7, 0x76, 0x3f, 0xd5, 0x7b, 0x51, 0xde, 0x44, 0xe9, 0x7a, 0xeb, 0x3a, 0x5d, 0x58,
-	0x3a, 0x1d, 0x0e, 0x4f, 0xcc, 0xbd, 0xb7, 0xa0, 0x71, 0xdc, 0x3f, 0x3a, 0xf0, 0x17, 0x7a, 0x13,
-	0x78, 0x3c, 0x90, 0x89, 0x4e, 0x22, 0x36, 0x39, 0x57, 0x5c, 0xfe, 0x24, 0x0a, 0x99, 0xf1, 0x9b,
-	0xaa, 0x1b, 0xab, 0x43, 0x77, 0x6a, 0x87, 0xbe, 0x0b, 0xcd, 0xaa, 0xdb, 0x17, 0xfe, 0xa7, 0x39,
-	0x6b, 0x53, 0x94, 0x56, 0x82, 0xde, 0x08, 0x9e, 0xcc, 0xd9, 0x4d, 0xcd, 0x9a, 0xbf, 0x11, 0x15,
-	0x1f, 0x54, 0xe0, 0xe0, 0x0b, 0x9e, 0x7f, 0xb2, 0xff, 0x9d, 0x2d, 0x45, 0x71, 0xef, 0x0f, 0x07,
-	0x56, 0xff, 0xf5, 0xd4, 0x48, 0x00, 0xcd, 0xea, 0xdc, 0x1c, 0x3c, 0xb7, 0x6a, 0x49, 0x1e, 0x43,
-	0xab, 0xfc, 0x2d, 0xb2, 0x05, 0xb5, 0xe9, 0x74, 0x4d, 0xbe, 0x85, 0x55, 0x7c, 0xee, 0x21, 0x9b,
-	0x4c, 0x44, 0x14, 0x46, 0xa2, 0xc8, 0x74, 0xd9, 0x67, 0xf7, 0x11, 0xe8, 0x9b, 0xf8, 0xc0, 0x84,
-	0xc9, 0x26, 0xf8, 0x75, 0xae, 0x4a, 0x3e, 0x55, 0x4d, 0xd7, 0x99, 0x51, 0x4f, 0x93, 0x4f, 0xdc,
-	0x0c, 0xff, 0x94, 0x5d, 0x87, 0x57, 0x9c, 0xe5, 0x96, 0x66, 0xbb, 0xcf, 0x4b, 0xd9, 0xf5, 0x8f,
-	0x9c, 0xe5, 0x86, 0xb3, 0xf7, 0xf0, 0x97, 0x72, 0xbe, 0x94, 0x75, 0x87, 0xf8, 0xff, 0xe7, 0xef,
-	0x00, 0x00, 0x00, 0xff, 0xff, 0x0a, 0x03, 0x26, 0x59, 0x0f, 0x09, 0x00, 0x00,
+	// 1036 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0xef, 0x4e, 0x1b, 0x47,
+	0x10, 0xcf, 0x61, 0x83, 0x7d, 0x73, 0xd8, 0x1c, 0x0b, 0x29, 0x97, 0x44, 0xa8, 0x96, 0xd5, 0x44,
+	0xa8, 0x6a, 0x48, 0x44, 0x23, 0x14, 0xa1, 0xa8, 0x12, 0x18, 0x44, 0x53, 0x04, 0x8e, 0x16, 0x4c,
+	0xa3, 0xf6, 0xc3, 0x69, 0x7d, 0xb7, 0x86, 0x4b, 0x7d, 0xb7, 0xd6, 0xee, 0x5e, 0x04, 0x79, 0x87,
+	0x3e, 0x55, 0x9f, 0xa5, 0xaf, 0x51, 0x55, 0x3b, 0x7b, 0x67, 0x1f, 0xad, 0xdb, 0xa0, 0x7c, 0xf3,
+	0xce, 0xef, 0xcf, 0xce, 0xec, 0xce, 0xce, 0x19, 0x5a, 0x29, 0xd7, 0x32, 0x89, 0xd4, 0xf6, 0x44,
+	0x0a, 0x2d, 0xc8, 0x9a, 0x12, 0x22, 0xbb, 0x0a, 0x87, 0x79, 0x32, 0x8e, 0xc3, 0x02, 0xea, 0xfe,
+	0x05, 0xe0, 0x9d, 0xda, 0xdf, 0x07, 0x4c, 0x71, 0xf2, 0x12, 0xd6, 0x2d, 0x21, 0x66, 0x9a, 0x87,
+	0x3a, 0x49, 0xb9, 0xd2, 0x2c, 0x9d, 0x04, 0x4e, 0xc7, 0xd9, 0xaa, 0x51, 0x82, 0xd8, 0x21, 0xd3,
+	0xfc, 0xa2, 0x44, 0xc8, 0x23, 0x68, 0x5a, 0x45, 0x12, 0x07, 0x0b, 0x1d, 0x67, 0xcb, 0xa5, 0x0d,
+	0x5c, 0xbf, 0x8d, 0xc9, 0x1e, 0x3c, 0x9a, 0x8c, 0x99, 0x1e, 0x09, 0x99, 0x86, 0x1f, 0xb9, 0x54,
+	0x89, 0xc8, 0xc2, 0x48, 0xc4, 0x3c, 0x63, 0x29, 0x0f, 0x6a, 0xc8, 0xdd, 0x28, 0x09, 0x97, 0x16,
+	0xef, 0x15, 0x30, 0x79, 0x0a, 0x6d, 0xcd, 0xe4, 0x15, 0xd7, 0xe1, 0x44, 0x8a, 0x38, 0x8f, 0x74,
+	0x50, 0x47, 0x41, 0xcb, 0x46, 0xdf, 0xd9, 0x20, 0x89, 0x61, 0xbd, 0xa0, 0xd9, 0x24, 0x3e, 0x32,
+	0x99, 0xb0, 0x4c, 0x07, 0x8b, 0x1d, 0x67, 0xab, 0xbd, 0xf3, 0x7c, 0x7b, 0x4e, 0xcd, 0xdb, 0x95,
+	0x7a, 0xb7, 0x0f, 0x0c, 0x72, 0x69, 0x45, 0x7b, 0xb5, 0xa3, 0xb3, 0x63, 0x4a, 0xac, 0x5f, 0x15,
+	0x20, 0x7d, 0xf0, 0x8a, 0x5d, 0x98, 0x8c, 0xae, 0x83, 0x25, 0x34, 0x7f, 0xfa, 0x59, 0xf3, 0x7d,
+	0x19, 0x5d, 0xef, 0x35, 0x06, 0x67, 0x27, 0x67, 0xfd, 0x9f, 0xcf, 0x28, 0x58, 0x0b, 0x13, 0x24,
+	0xdb, 0xb0, 0x56, 0x31, 0x9c, 0x66, 0xdd, 0xc0, 0x12, 0x57, 0x67, 0xc4, 0x32, 0x81, 0xef, 0xa0,
+	0x48, 0x2b, 0x8c, 0x26, 0xf9, 0x94, 0xde, 0x44, 0xba, 0x6f, 0x91, 0xde, 0x24, 0x2f, 0xd9, 0x27,
+	0xe0, 0x5e, 0x0b, 0x55, 0x24, 0xeb, 0x7e, 0x51, 0xb2, 0x4d, 0x63, 0x80, 0xa9, 0x52, 0x68, 0xa1,
+	0xd9, 0x4e, 0x16, 0x5b, 0x43, 0xf8, 0x22, 0x43, 0xcf, 0x98, 0xec, 0x64, 0x31, 0x7a, 0x6e, 0x40,
+	0x03, 0x3d, 0x85, 0x0a, 0x3c, 0xac, 0x61, 0xc9, 0x2c, 0xfb, 0x8a, 0x74, 0x8b, 0xcd, 0x84, 0x0a,
+	0xf9, 0x8d, 0x96, 0x2c, 0x58, 0x46, 0xd8, 0xb3, 0xf0, 0x91, 0x09, 0x4d, 0x39, 0x91, 0x14, 0x4a,
+	0x19, 0x8b, 0xd6, 0x8c, 0xd3, 0x33, 0xb1, 0xbe, 0x22, 0xcf, 0x60, 0xa5, 0xc2, 0xc1, 0xb4, 0xdb,
+	0xb6, 0x7d, 0xa6, 0x2c, 0x4c, 0xe4, 0x39, 0xac, 0x55, 0x78, 0xd3, 0x12, 0x57, 0xec, 0xc1, 0x4e,
+	0xb9, 0x95, 0xbc, 0x45, 0xae, 0xc3, 0x38, 0x91, 0x81, 0x6f, 0xf3, 0x16, 0xb9, 0x3e, 0x4c, 0x24,
+	0xf9, 0x01, 0x3c, 0xc5, 0x75, 0x3e, 0x09, 0xb5, 0x10, 0x63, 0x15, 0xac, 0x76, 0x6a, 0x5b, 0xde,
+	0xce, 0xe6, 0xdc, 0x23, 0x7a, 0xc7, 0xe5, 0xe8, 0x6d, 0x36, 0x12, 0x14, 0x50, 0x71, 0x61, 0x04,
+	0x64, 0x0f, 0xdc, 0xdf, 0x98, 0x4e, 0x42, 0x99, 0x67, 0x2a, 0x20, 0xf7, 0x51, 0x37, 0x0d, 0x9f,
+	0xe6, 0x99, 0x22, 0x6f, 0x00, 0x2c, 0x13, 0xc5, 0x6b, 0xf7, 0x11, 0xbb, 0x88, 0x96, 0xea, 0x2c,
+	0xc9, 0x3e, 0x30, 0xab, 0x5e, 0xbf, 0x97, 0x1a, 0x05, 0xa8, 0xfe, 0x1e, 0x16, 0xb5, 0xd0, 0x6c,
+	0x1c, 0x3c, 0xec, 0x38, 0x9f, 0x17, 0x5a, 0x2e, 0xb9, 0x84, 0x79, 0xa3, 0x28, 0xf8, 0x0a, 0x2d,
+	0x9e, 0xcd, 0xb5, 0x38, 0x37, 0x31, 0x7c, 0x92, 0x45, 0x87, 0xd1, 0x55, 0xf5, 0xcf, 0x10, 0xe9,
+	0xc1, 0xb2, 0x55, 0x45, 0x22, 0x1b, 0x25, 0x57, 0xc1, 0x06, 0x1a, 0x76, 0xe6, 0x1a, 0xa2, 0xb0,
+	0x87, 0x3c, 0xea, 0x0d, 0x67, 0x8b, 0xee, 0x4b, 0x58, 0xbe, 0xf3, 0xf4, 0x9b, 0x50, 0x1f, 0x9c,
+	0x1f, 0x51, 0xff, 0x01, 0x69, 0x81, 0x6b, 0x7e, 0x1d, 0x1e, 0x1d, 0x0c, 0x8e, 0x7d, 0x87, 0x34,
+	0xc0, 0x8c, 0x0b, 0x7f, 0xa1, 0xfb, 0x06, 0xea, 0xd8, 0x1c, 0x1e, 0x94, 0xcd, 0xee, 0x3f, 0x30,
+	0xe8, 0x3e, 0x3d, 0xf5, 0x1d, 0xe2, 0xc2, 0xe2, 0x3e, 0x3d, 0xdd, 0x7d, 0xe5, 0x2f, 0x98, 0xd8,
+	0xfb, 0xd7, 0xbb, 0x7e, 0x8d, 0x00, 0x2c, 0xbd, 0x7f, 0xbd, 0x1b, 0xee, 0xbe, 0xf2, 0xeb, 0xdd,
+	0x2b, 0xf0, 0x2a, 0xb9, 0x98, 0x69, 0x9a, 0x2b, 0x1e, 0x5e, 0x89, 0x94, 0xe1, 0xcc, 0x6d, 0xd2,
+	0x46, 0xae, 0xf8, 0xb1, 0x48, 0x99, 0x69, 0x3e, 0x03, 0xc9, 0x21, 0xc7, 0x39, 0xdb, 0xa4, 0x4b,
+	0xb9, 0xe2, 0x74, 0xc8, 0xc9, 0x37, 0xd0, 0x1e, 0x09, 0x19, 0xf1, 0x70, 0xaa, 0xac, 0x21, 0xbe,
+	0x8c, 0xd1, 0x81, 0x95, 0x77, 0x7f, 0x77, 0xa0, 0x59, 0xde, 0x04, 0x21, 0x50, 0x8f, 0xb9, 0x8a,
+	0x70, 0x0b, 0x97, 0xe2, 0x6f, 0x13, 0xc3, 0xc1, 0x6c, 0x87, 0x38, 0xfe, 0x26, 0x9b, 0x00, 0x4a,
+	0x33, 0xa9, 0xf1, 0x4b, 0x80, 0xb6, 0x75, 0xea, 0x62, 0xc4, 0x7c, 0x00, 0xc8, 0x13, 0x70, 0x25,
+	0x67, 0x63, 0x8b, 0xd6, 0x11, 0x6d, 0x9a, 0x00, 0x82, 0x9b, 0x00, 0x29, 0x4f, 0x85, 0xbc, 0x35,
+	0x79, 0xe1, 0x40, 0xae, 0x53, 0xd7, 0x46, 0x06, 0x8a, 0x77, 0xff, 0x74, 0xa0, 0x7d, 0x2a, 0xe2,
+	0x7c, 0xcc, 0x2f, 0x6e, 0x27, 0x1c, 0xb3, 0xfa, 0xb5, 0xbc, 0x40, 0x75, 0xab, 0x34, 0x4f, 0x31,
+	0xbb, 0xf6, 0xce, 0x8b, 0xf9, 0x93, 0xe6, 0x8e, 0xd4, 0xde, 0xe7, 0x39, 0xca, 0x2a, 0x33, 0x67,
+	0x38, 0x8b, 0x92, 0xaf, 0xc1, 0x4b, 0x51, 0x13, 0xea, 0xdb, 0x49, 0x59, 0x25, 0xa4, 0x53, 0x1b,
+	0x73, 0x8c, 0x59, 0x9e, 0x86, 0x62, 0x14, 0xda, 0xa0, 0xc2, 0x7a, 0x5b, 0x74, 0x39, 0xcb, 0xd3,
+	0xfe, 0xc8, 0xee, 0xa7, 0xba, 0x2f, 0x8a, 0xfb, 0x2a, 0x5c, 0xef, 0x5c, 0xba, 0x0b, 0x8b, 0xe7,
+	0xfd, 0xfe, 0x99, 0xe9, 0x8e, 0x26, 0xd4, 0x4f, 0xf7, 0x4f, 0x8e, 0xfc, 0x85, 0xee, 0x18, 0x1e,
+	0xf7, 0x64, 0xa2, 0x93, 0x88, 0x8d, 0x07, 0x8a, 0xcb, 0x9f, 0x44, 0x2e, 0x33, 0x7e, 0x5b, 0xf6,
+	0x6c, 0x79, 0xe8, 0x4e, 0xe5, 0xd0, 0xf7, 0xa0, 0x51, 0xbe, 0x89, 0x85, 0xff, 0x69, 0xe1, 0xca,
+	0xac, 0xa5, 0xa5, 0xa0, 0x3b, 0x84, 0x27, 0x73, 0x76, 0x53, 0xb3, 0x27, 0x52, 0x8f, 0xf2, 0x0f,
+	0x2a, 0x70, 0xf0, 0x9d, 0xcf, 0x3f, 0xd9, 0xff, 0xce, 0x96, 0xa2, 0xb8, 0xfb, 0x87, 0x03, 0xab,
+	0xff, 0x7a, 0x90, 0x24, 0x80, 0x46, 0x79, 0x6e, 0x0e, 0x9e, 0x5b, 0xb9, 0x24, 0x8f, 0xa1, 0x59,
+	0x7c, 0xb1, 0x6c, 0x41, 0x2d, 0x3a, 0x5d, 0x93, 0x6f, 0x61, 0x15, 0x87, 0x42, 0xc8, 0xc6, 0x63,
+	0x11, 0x85, 0x91, 0xc8, 0x33, 0x5d, 0xf4, 0xd9, 0x0a, 0x02, 0xfb, 0x26, 0xde, 0x33, 0x61, 0xb2,
+	0x05, 0x7e, 0x95, 0xab, 0x92, 0x4f, 0x65, 0xd3, 0xb5, 0x67, 0xd4, 0xf3, 0xe4, 0x13, 0x37, 0x9f,
+	0x88, 0x94, 0xdd, 0x84, 0xd7, 0x9c, 0x4d, 0x2c, 0xcd, 0x76, 0x9f, 0x97, 0xb2, 0x9b, 0x1f, 0x39,
+	0x9b, 0x18, 0xce, 0xc1, 0xc3, 0x5f, 0x8a, 0x29, 0x54, 0xd4, 0x1d, 0xe2, 0xbf, 0xa4, 0xbf, 0x03,
+	0x00, 0x00, 0xff, 0xff, 0x85, 0xc5, 0xe0, 0x4b, 0x35, 0x09, 0x00, 0x00,
 }
diff --git a/ui/metrics/metrics_proto/metrics.proto b/ui/metrics/metrics_proto/metrics.proto
index 6559ba3..4d6118b 100644
--- a/ui/metrics/metrics_proto/metrics.proto
+++ b/ui/metrics/metrics_proto/metrics.proto
@@ -102,6 +102,8 @@
   optional bool use_goma = 1;
 
   optional bool use_rbe = 2;
+
+  optional bool force_use_goma = 3;
 }
 
 message PerfInfo {