Merge "Adding support for building AFLpp Test: Build AFL fuzzers locally and ran them"
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index f1ec55e..3be9805 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -34,6 +34,21 @@
 	"android/soong/bazel"
 )
 
+var (
+	writeBazelFile = pctx.AndroidStaticRule("bazelWriteFileRule", blueprint.RuleParams{
+		Command:        `sed "s/\\\\n/\n/g" ${out}.rsp >${out}`,
+		Rspfile:        "${out}.rsp",
+		RspfileContent: "${content}",
+	}, "content")
+	_                 = pctx.HostBinToolVariable("bazelBuildRunfilesTool", "build-runfiles")
+	buildRunfilesRule = pctx.AndroidStaticRule("bazelBuildRunfiles", blueprint.RuleParams{
+		Command:     "${bazelBuildRunfilesTool} ${in} ${outDir}",
+		Depfile:     "",
+		Description: "",
+		CommandDeps: []string{"${bazelBuildRunfilesTool}"},
+	}, "outDir")
+)
+
 func init() {
 	RegisterMixedBuildsMutator(InitRegistrationContext)
 }
@@ -173,26 +188,26 @@
 	LabelToPythonBinary map[string]string
 }
 
-func (m MockBazelContext) QueueBazelRequest(label string, requestType cqueryRequest, cfgKey configKey) {
+func (m MockBazelContext) QueueBazelRequest(_ string, _ cqueryRequest, _ configKey) {
 	panic("unimplemented")
 }
 
-func (m MockBazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, error) {
+func (m MockBazelContext) GetOutputFiles(label string, _ configKey) ([]string, error) {
 	result, _ := m.LabelToOutputFiles[label]
 	return result, nil
 }
 
-func (m MockBazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, error) {
+func (m MockBazelContext) GetCcInfo(label string, _ configKey) (cquery.CcInfo, error) {
 	result, _ := m.LabelToCcInfo[label]
 	return result, nil
 }
 
-func (m MockBazelContext) GetPythonBinary(label string, cfgKey configKey) (string, error) {
+func (m MockBazelContext) GetPythonBinary(label string, _ configKey) (string, error) {
 	result, _ := m.LabelToPythonBinary[label]
 	return result, nil
 }
 
-func (m MockBazelContext) InvokeBazel(config Config) error {
+func (m MockBazelContext) InvokeBazel(_ Config) error {
 	panic("unimplemented")
 }
 
@@ -246,23 +261,23 @@
 	return "", fmt.Errorf("no bazel response found for %v", key)
 }
 
-func (n noopBazelContext) QueueBazelRequest(label string, requestType cqueryRequest, cfgKey configKey) {
+func (n noopBazelContext) QueueBazelRequest(_ string, _ cqueryRequest, _ configKey) {
 	panic("unimplemented")
 }
 
-func (n noopBazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, error) {
+func (n noopBazelContext) GetOutputFiles(_ string, _ configKey) ([]string, error) {
 	panic("unimplemented")
 }
 
-func (n noopBazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, error) {
+func (n noopBazelContext) GetCcInfo(_ string, _ configKey) (cquery.CcInfo, error) {
 	panic("unimplemented")
 }
 
-func (n noopBazelContext) GetPythonBinary(label string, cfgKey configKey) (string, error) {
+func (n noopBazelContext) GetPythonBinary(_ string, _ configKey) (string, error) {
 	panic("unimplemented")
 }
 
-func (n noopBazelContext) InvokeBazel(config Config) error {
+func (n noopBazelContext) InvokeBazel(_ Config) error {
 	panic("unimplemented")
 }
 
@@ -304,7 +319,7 @@
 	p := bazelPaths{
 		soongOutDir: c.soongOutDir,
 	}
-	missingEnvVars := []string{}
+	var missingEnvVars []string
 	if len(c.Getenv("BAZEL_HOME")) > 1 {
 		p.homeDir = c.Getenv("BAZEL_HOME")
 	} else {
@@ -365,10 +380,8 @@
 	extraFlags          []string
 }
 
-func (r *mockBazelRunner) issueBazelCommand(paths *bazelPaths,
-	runName bazel.RunName,
-	command bazelCommand,
-	extraFlags ...string) (string, string, error) {
+func (r *mockBazelRunner) issueBazelCommand(_ *bazelPaths, _ bazel.RunName,
+	command bazelCommand, extraFlags ...string) (string, string, error) {
 	r.commands = append(r.commands, command)
 	r.extraFlags = append(r.extraFlags, strings.Join(extraFlags, " "))
 	if ret, ok := r.bazelCommandResults[command]; ok {
@@ -396,26 +409,30 @@
 		command.command,
 	}
 	cmdFlags = append(cmdFlags, command.expression)
-	cmdFlags = append(cmdFlags, "--profile="+shared.BazelMetricsFilename(paths, runName))
+	cmdFlags = append(cmdFlags,
+		// TODO(asmundak): is it needed in every build?
+		"--profile="+shared.BazelMetricsFilename(paths, runName),
 
-	// Set default platforms to canonicalized values for mixed builds requests.
-	// If these are set in the bazelrc, they will have values that are
-	// non-canonicalized to @sourceroot labels, and thus be invalid when
-	// referenced from the buildroot.
-	//
-	// The actual platform values here may be overridden by configuration
-	// transitions from the buildroot.
-	cmdFlags = append(cmdFlags,
-		fmt.Sprintf("--platforms=%s", "//build/bazel/platforms:android_target"))
-	cmdFlags = append(cmdFlags,
-		fmt.Sprintf("--extra_toolchains=%s", "//prebuilts/clang/host/linux-x86:all"))
-	// This should be parameterized on the host OS, but let's restrict to linux
-	// to keep things simple for now.
-	cmdFlags = append(cmdFlags,
-		fmt.Sprintf("--host_platform=%s", "//build/bazel/platforms:linux_x86_64"))
+		// Set default platforms to canonicalized values for mixed builds requests.
+		// If these are set in the bazelrc, they will have values that are
+		// non-canonicalized to @sourceroot labels, and thus be invalid when
+		// referenced from the buildroot.
+		//
+		// The actual platform values here may be overridden by configuration
+		// transitions from the buildroot.
+		fmt.Sprintf("--platforms=%s", "//build/bazel/platforms:android_target"),
+		fmt.Sprintf("--extra_toolchains=%s", "//prebuilts/clang/host/linux-x86:all"),
 
-	// Explicitly disable downloading rules (such as canonical C++ and Java rules) from the network.
-	cmdFlags = append(cmdFlags, "--experimental_repository_disable_download")
+		// This should be parameterized on the host OS, but let's restrict to linux
+		// to keep things simple for now.
+		fmt.Sprintf("--host_platform=%s", "//build/bazel/platforms:linux_x86_64"),
+
+		// Explicitly disable downloading rules (such as canonical C++ and Java rules) from the network.
+		"--experimental_repository_disable_download",
+
+		// Suppress noise
+		"--ui_event_filters=-INFO",
+		"--noshow_progress")
 	cmdFlags = append(cmdFlags, extraFlags...)
 
 	bazelCmd := exec.Command(paths.bazelPath, cmdFlags...)
@@ -682,8 +699,6 @@
 func (context *bazelContext) InvokeBazel(config Config) error {
 	context.results = make(map[cqueryKey]string)
 
-	var cqueryOutput string
-	var cqueryErr string
 	var err error
 
 	soongInjectionPath := absolutePath(context.paths.injectedFilesDir())
@@ -700,45 +715,27 @@
 			return err
 		}
 	}
-	err = ioutil.WriteFile(filepath.Join(soongInjectionPath, "WORKSPACE.bazel"), []byte{}, 0666)
-	if err != nil {
+	if err = ioutil.WriteFile(filepath.Join(soongInjectionPath, "WORKSPACE.bazel"), []byte{}, 0666); err != nil {
 		return err
 	}
-
-	err = ioutil.WriteFile(
-		filepath.Join(mixedBuildsPath, "main.bzl"),
-		context.mainBzlFileContents(), 0666)
-	if err != nil {
+	if err = ioutil.WriteFile(filepath.Join(mixedBuildsPath, "main.bzl"), context.mainBzlFileContents(), 0666); err != nil {
 		return err
 	}
-
-	err = ioutil.WriteFile(
-		filepath.Join(mixedBuildsPath, "BUILD.bazel"),
-		context.mainBuildFileContents(), 0666)
-	if err != nil {
+	if err = ioutil.WriteFile(filepath.Join(mixedBuildsPath, "BUILD.bazel"), context.mainBuildFileContents(), 0666); err != nil {
 		return err
 	}
 	cqueryFileRelpath := filepath.Join(context.paths.injectedFilesDir(), "buildroot.cquery")
-	err = ioutil.WriteFile(
-		absolutePath(cqueryFileRelpath),
-		context.cqueryStarlarkFileContents(), 0666)
-	if err != nil {
+	if err = ioutil.WriteFile(absolutePath(cqueryFileRelpath), context.cqueryStarlarkFileContents(), 0666); err != nil {
 		return err
 	}
 
-	buildrootLabel := "@soong_injection//mixed_builds:buildroot"
-	cqueryOutput, cqueryErr, err = context.issueBazelCommand(
-		context.paths,
-		bazel.CqueryBuildRootRunName,
-		bazelCommand{"cquery", fmt.Sprintf("deps(%s, 2)", buildrootLabel)},
-		"--output=starlark",
-		"--starlark:file="+absolutePath(cqueryFileRelpath))
-	err = ioutil.WriteFile(filepath.Join(soongInjectionPath, "cquery.out"),
-		[]byte(cqueryOutput), 0666)
+	const buildrootLabel = "@soong_injection//mixed_builds:buildroot"
+	cqueryCmd := bazelCommand{"cquery", fmt.Sprintf("deps(%s, 2)", buildrootLabel)}
+	cqueryOutput, cqueryErr, err := context.issueBazelCommand(context.paths, bazel.CqueryBuildRootRunName, cqueryCmd,
+		"--output=starlark", "--starlark:file="+absolutePath(cqueryFileRelpath))
 	if err != nil {
-		return err
+		err = ioutil.WriteFile(filepath.Join(soongInjectionPath, "cquery.out"), []byte(cqueryOutput), 0666)
 	}
-
 	if err != nil {
 		return err
 	}
@@ -750,7 +747,6 @@
 			cqueryResults[splitLine[0]] = splitLine[1]
 		}
 	}
-
 	for val := range context.requests {
 		if cqueryResult, ok := cqueryResults[getCqueryId(val)]; ok {
 			context.results[val] = cqueryResult
@@ -762,37 +758,27 @@
 
 	// Issue an aquery command to retrieve action information about the bazel build tree.
 	//
-	var aqueryOutput string
-	var coverageFlags []string
+	// Use jsonproto instead of proto; actual proto parsing would require a dependency on Bazel's
+	// proto sources, which would add a number of unnecessary dependencies.
+	extraFlags := []string{"--output=jsonproto", "--include_file_write_contents"}
 	if Bool(config.productVariables.ClangCoverage) {
-		coverageFlags = append(coverageFlags, "--collect_code_coverage")
-		if len(config.productVariables.NativeCoveragePaths) > 0 ||
-			len(config.productVariables.NativeCoverageExcludePaths) > 0 {
-			includePaths := JoinWithPrefixAndSeparator(config.productVariables.NativeCoveragePaths, "+", ",")
-			excludePaths := JoinWithPrefixAndSeparator(config.productVariables.NativeCoverageExcludePaths, "-", ",")
-			if len(includePaths) > 0 && len(excludePaths) > 0 {
-				includePaths += ","
-			}
-			coverageFlags = append(coverageFlags, fmt.Sprintf(`--instrumentation_filter=%s`,
-				includePaths+excludePaths))
+		extraFlags = append(extraFlags, "--collect_code_coverage")
+		paths := make([]string, 0, 2)
+		if p := config.productVariables.NativeCoveragePaths; len(p) > 0 {
+			paths = append(paths, JoinWithPrefixAndSeparator(p, "+", ","))
+		}
+		if p := config.productVariables.NativeCoverageExcludePaths; len(p) > 0 {
+			paths = append(paths, JoinWithPrefixAndSeparator(p, "-", ","))
+		}
+		if len(paths) > 0 {
+			extraFlags = append(extraFlags, "--instrumentation_filter="+strings.Join(paths, ","))
 		}
 	}
-
-	extraFlags := append([]string{"--output=jsonproto"}, coverageFlags...)
-
-	aqueryOutput, _, err = context.issueBazelCommand(
-		context.paths,
-		bazel.AqueryBuildRootRunName,
-		bazelCommand{"aquery", fmt.Sprintf("deps(%s)", buildrootLabel)},
-		// Use jsonproto instead of proto; actual proto parsing would require a dependency on Bazel's
-		// proto sources, which would add a number of unnecessary dependencies.
-		extraFlags...)
-
-	if err != nil {
-		return err
+	aqueryCmd := bazelCommand{"aquery", fmt.Sprintf("deps(%s)", buildrootLabel)}
+	if aqueryOutput, _, err := context.issueBazelCommand(context.paths, bazel.AqueryBuildRootRunName, aqueryCmd,
+		extraFlags...); err == nil {
+		context.buildStatements, context.depsets, err = bazel.AqueryBuildStatements([]byte(aqueryOutput))
 	}
-
-	context.buildStatements, context.depsets, err = bazel.AqueryBuildStatements([]byte(aqueryOutput))
 	if err != nil {
 		return err
 	}
@@ -800,12 +786,8 @@
 	// Issue a build command of the phony root to generate symlink forests for dependencies of the
 	// Bazel build. This is necessary because aquery invocations do not generate this symlink forest,
 	// but some of symlinks may be required to resolve source dependencies of the build.
-	_, _, err = context.issueBazelCommand(
-		context.paths,
-		bazel.BazelBuildPhonyRootRunName,
-		bazelCommand{"build", "@soong_injection//mixed_builds:phonyroot"})
-
-	if err != nil {
+	buildCmd := bazelCommand{"build", "@soong_injection//mixed_builds:phonyroot"}
+	if _, _, err = context.issueBazelCommand(context.paths, bazel.BazelBuildPhonyRootRunName, buildCmd); err != nil {
 		return err
 	}
 
@@ -874,13 +856,56 @@
 	executionRoot := path.Join(ctx.Config().BazelContext.OutputBase(), "execroot", "__main__")
 	bazelOutDir := path.Join(executionRoot, "bazel-out")
 	for index, buildStatement := range ctx.Config().BazelContext.BuildStatementsToRegister() {
-		if len(buildStatement.Command) < 1 {
+		if len(buildStatement.Command) > 0 {
+			rule := NewRuleBuilder(pctx, ctx)
+			createCommand(rule.Command(), buildStatement, executionRoot, bazelOutDir, ctx)
+			desc := fmt.Sprintf("%s: %s", buildStatement.Mnemonic, buildStatement.OutputPaths)
+			rule.Build(fmt.Sprintf("bazel %d", index), desc)
+			continue
+		}
+		// Certain actions returned by aquery (for instance FileWrite) do not contain a command
+		// and thus require special treatment. If BuildStatement were an interface implementing
+		// buildRule(ctx) function, the code here would just call it.
+		// Unfortunately, the BuildStatement is defined in
+		// the 'bazel' package, which cannot depend on 'android' package where ctx is defined,
+		// because this would cause circular dependency. So, until we move aquery processing
+		// to the 'android' package, we need to handle special cases here.
+		if buildStatement.Mnemonic == "FileWrite" || buildStatement.Mnemonic == "SourceSymlinkManifest" {
+			// Pass file contents as the value of the rule's "content" argument.
+			// Escape newlines and $ in the contents (the action "writeBazelFile" restores "\\n"
+			// back to the newline, and Ninja reads $$ as $.
+			escaped := strings.ReplaceAll(strings.ReplaceAll(buildStatement.FileContents, "\n", "\\n"),
+				"$", "$$")
+			ctx.Build(pctx, BuildParams{
+				Rule:        writeBazelFile,
+				Output:      PathForBazelOut(ctx, buildStatement.OutputPaths[0]),
+				Description: fmt.Sprintf("%s %s", buildStatement.Mnemonic, buildStatement.OutputPaths[0]),
+				Args: map[string]string{
+					"content": escaped,
+				},
+			})
+		} else if buildStatement.Mnemonic == "SymlinkTree" {
+			// build-runfiles arguments are the manifest file and the target directory
+			// where it creates the symlink tree according to this manifest (and then
+			// writes the MANIFEST file to it).
+			outManifest := PathForBazelOut(ctx, buildStatement.OutputPaths[0])
+			outManifestPath := outManifest.String()
+			if !strings.HasSuffix(outManifestPath, "MANIFEST") {
+				panic("the base name of the symlink tree action should be MANIFEST, got " + outManifestPath)
+			}
+			outDir := filepath.Dir(outManifestPath)
+			ctx.Build(pctx, BuildParams{
+				Rule:        buildRunfilesRule,
+				Output:      outManifest,
+				Inputs:      []Path{PathForBazelOut(ctx, buildStatement.InputPaths[0])},
+				Description: "symlink tree for " + outDir,
+				Args: map[string]string{
+					"outDir": outDir,
+				},
+			})
+		} else {
 			panic(fmt.Sprintf("unhandled build statement: %v", buildStatement))
 		}
-		rule := NewRuleBuilder(pctx, ctx)
-		createCommand(rule.Command(), buildStatement, executionRoot, bazelOutDir, ctx)
-		desc := fmt.Sprintf("%s: %s", buildStatement.Mnemonic, buildStatement.OutputPaths)
-		rule.Build(fmt.Sprintf("bazel %d", index), desc)
 	}
 }
 
diff --git a/android/sdk.go b/android/sdk.go
index 533f2f4..07b94b2 100644
--- a/android/sdk.go
+++ b/android/sdk.go
@@ -677,6 +677,10 @@
 	// SupportedLinkages returns the names of the linkage variants supported by this module.
 	SupportedLinkages() []string
 
+	// ArePrebuiltsRequired returns true if prebuilts are required in the sdk snapshot, false
+	// otherwise.
+	ArePrebuiltsRequired() bool
+
 	// AddDependencies adds dependencies from the SDK module to all the module variants the member
 	// type contributes to the SDK. `names` is the list of module names given in the member type
 	// property (as returned by SdkPropertyName()) in the SDK module. The exact set of variants
@@ -782,7 +786,12 @@
 	// If not specified then it is assumed to be available on all targeted build releases.
 	SupportedBuildReleaseSpecification string
 
-	SupportsSdk     bool
+	// Set to true if this must be usable with the sdk/sdk_snapshot module types. Otherwise, it will
+	// only be usable with module_exports/module_exports_snapshots module types.
+	SupportsSdk bool
+
+	// Set to true if prebuilt host artifacts of this member may be specific to the host OS. Only
+	// applicable to modules where HostSupported() is true.
 	HostOsDependent bool
 
 	// When set to true UseSourceModuleTypeInSnapshot indicates that the member type creates a source
@@ -790,6 +799,11 @@
 	// code from automatically adding a prefer: true flag.
 	UseSourceModuleTypeInSnapshot bool
 
+	// Set to proptools.BoolPtr(false) if this member does not generate prebuilts but is only provided
+	// to allow the sdk to gather members from this member's dependencies. If not specified then
+	// defaults to true.
+	PrebuiltsRequired *bool
+
 	// The list of supported traits.
 	Traits []SdkMemberTrait
 }
@@ -814,6 +828,10 @@
 	return b.HostOsDependent
 }
 
+func (b *SdkMemberTypeBase) ArePrebuiltsRequired() bool {
+	return proptools.BoolDefault(b.PrebuiltsRequired, true)
+}
+
 func (b *SdkMemberTypeBase) UsesSourceModuleTypeInSnapshot() bool {
 	return b.UseSourceModuleTypeInSnapshot
 }
diff --git a/apex/Android.bp b/apex/Android.bp
index fcdf8e6..6533c61 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -23,6 +23,7 @@
     srcs: [
         "androidmk.go",
         "apex.go",
+        "apex_sdk_member.go",
         "apex_singleton.go",
         "builder.go",
         "constants.go",
diff --git a/apex/apex.go b/apex/apex.go
index 5678b06..7b6707c 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -606,6 +606,18 @@
 	// replacement. This is needed because some prebuilt modules do not provide all the information
 	// needed by the apex.
 	sourceOnly bool
+
+	// If not-nil and an APEX is a member of an SDK then dependencies of that APEX with this tag will
+	// also be added as exported members of that SDK.
+	memberType android.SdkMemberType
+}
+
+func (d *dependencyTag) SdkMemberType(_ android.Module) android.SdkMemberType {
+	return d.memberType
+}
+
+func (d *dependencyTag) ExportMember() bool {
+	return true
 }
 
 func (d *dependencyTag) String() string {
@@ -617,6 +629,7 @@
 }
 
 var _ android.ReplaceSourceWithPrebuilt = &dependencyTag{}
+var _ android.SdkMemberDependencyTag = &dependencyTag{}
 
 var (
 	androidAppTag   = &dependencyTag{name: "androidApp", payload: true}
@@ -624,8 +637,8 @@
 	certificateTag  = &dependencyTag{name: "certificate"}
 	executableTag   = &dependencyTag{name: "executable", payload: true}
 	fsTag           = &dependencyTag{name: "filesystem", payload: true}
-	bcpfTag         = &dependencyTag{name: "bootclasspathFragment", payload: true, sourceOnly: true}
-	sscpfTag        = &dependencyTag{name: "systemserverclasspathFragment", payload: true, sourceOnly: true}
+	bcpfTag         = &dependencyTag{name: "bootclasspathFragment", payload: true, sourceOnly: true, memberType: java.BootclasspathFragmentSdkMemberType}
+	sscpfTag        = &dependencyTag{name: "systemserverclasspathFragment", payload: true, sourceOnly: true, memberType: java.SystemServerClasspathFragmentSdkMemberType}
 	compatConfigTag = &dependencyTag{name: "compatConfig", payload: true, sourceOnly: true}
 	javaLibTag      = &dependencyTag{name: "javaLib", payload: true}
 	jniLibTag       = &dependencyTag{name: "jniLib", payload: true}
diff --git a/apex/apex_sdk_member.go b/apex/apex_sdk_member.go
new file mode 100644
index 0000000..284158f
--- /dev/null
+++ b/apex/apex_sdk_member.go
@@ -0,0 +1,58 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package apex
+
+import (
+	"android/soong/android"
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+// This file contains support for using apex modules within an sdk.
+
+func init() {
+	// Register sdk member types.
+	android.RegisterSdkMemberType(&apexSdkMemberType{
+		SdkMemberTypeBase: android.SdkMemberTypeBase{
+			PropertyName: "apexes",
+			SupportsSdk:  true,
+
+			// The apexes property does not need to be included in the snapshot as adding an apex to an
+			// sdk does not produce any prebuilts of the apex.
+			PrebuiltsRequired: proptools.BoolPtr(false),
+		},
+	})
+}
+
+type apexSdkMemberType struct {
+	android.SdkMemberTypeBase
+}
+
+func (mt *apexSdkMemberType) AddDependencies(ctx android.SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string) {
+	ctx.AddVariationDependencies(nil, dependencyTag, names...)
+}
+
+func (mt *apexSdkMemberType) IsInstance(module android.Module) bool {
+	_, ok := module.(*apexBundle)
+	return ok
+}
+
+func (mt *apexSdkMemberType) AddPrebuiltModule(ctx android.SdkMemberContext, member android.SdkMember) android.BpModule {
+	panic("Sdk does not create prebuilts of the apexes in its snapshot")
+}
+
+func (mt *apexSdkMemberType) CreateVariantPropertiesStruct() android.SdkMemberProperties {
+	panic("Sdk does not create prebuilts of the apexes in its snapshot")
+}
diff --git a/apex/builder.go b/apex/builder.go
index e3c4476..4bc740f 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -76,11 +76,12 @@
 		Command: `rm -f $out && ${jsonmodify} $in ` +
 			`-a provideNativeLibs ${provideNativeLibs} ` +
 			`-a requireNativeLibs ${requireNativeLibs} ` +
+			`-se version 0 ${default_version} ` +
 			`${opt} ` +
 			`-o $out`,
 		CommandDeps: []string{"${jsonmodify}"},
 		Description: "prepare ${out}",
-	}, "provideNativeLibs", "requireNativeLibs", "opt")
+	}, "provideNativeLibs", "requireNativeLibs", "default_version", "opt")
 
 	stripCommentsApexManifestRule = pctx.StaticRule("stripCommentsApexManifestRule", blueprint.RuleParams{
 		Command:     `sed '/^\s*\/\//d' $in > $out`,
@@ -227,6 +228,7 @@
 		Args: map[string]string{
 			"provideNativeLibs": strings.Join(provideNativeLibs, " "),
 			"requireNativeLibs": strings.Join(requireNativeLibs, " "),
+			"default_version":   defaultManifestVersion,
 			"opt":               strings.Join(optCommands, " "),
 		},
 	})
diff --git a/bazel/aquery.go b/bazel/aquery.go
index 1d1f49c..ae2b107 100644
--- a/bazel/aquery.go
+++ b/bazel/aquery.go
@@ -21,7 +21,6 @@
 	"fmt"
 	"path/filepath"
 	"reflect"
-	"regexp"
 	"sort"
 	"strings"
 
@@ -83,6 +82,7 @@
 	OutputIds            []artifactId
 	TemplateContent      string
 	Substitutions        []KeyValuePair
+	FileContents         string
 }
 
 // actionGraphContainer contains relevant portions of Bazel's aquery proto, ActionGraphContainer.
@@ -110,6 +110,7 @@
 	// input path string, but not both.
 	InputDepsetHashes []string
 	InputPaths        []string
+	FileContents      string
 }
 
 // A helper type for aquery processing which facilitates retrieval of path IDs from their
@@ -139,9 +140,6 @@
 	"%python_binary%": "python3",
 }
 
-// This pattern matches the MANIFEST file created for a py_binary target.
-var manifestFilePattern = regexp.MustCompile(".*/.+\\.runfiles/MANIFEST$")
-
 // The file name of py3wrapper.sh, which is used by py_binary targets.
 const py3wrapperFileName = "/py3wrapper.sh"
 
@@ -225,20 +223,12 @@
 			// Swap middleman artifacts with their corresponding depsets and drop the middleman artifacts.
 			transitiveDepsetIds = append(transitiveDepsetIds, depsetsToUse...)
 		} else if strings.HasSuffix(path, py3wrapperFileName) ||
-			manifestFilePattern.MatchString(path) ||
 			strings.HasPrefix(path, "../bazel_tools") {
 			// Drop these artifacts.
 			// See go/python-binary-host-mixed-build for more details.
-			// 1) For py3wrapper.sh, there is no action for creating py3wrapper.sh in the aquery output of
-			// Bazel py_binary targets, so there is no Ninja build statements generated for creating it.
-			// 2) For MANIFEST file, SourceSymlinkManifest action is in aquery output of Bazel py_binary targets,
-			// but it doesn't contain sufficient information so no Ninja build statements are generated
-			// for creating it.
-			// So in mixed build mode, when these two are used as input of some Ninja build statement,
-			// since there is no build statement to create them, they should be removed from input paths.
-			// TODO(b/197135294): Clean up this custom runfiles handling logic when
-			// SourceSymlinkManifest and SymlinkTree actions are supported.
-			// 3) ../bazel_tools: they have MODIFY timestamp 10years in the future and would cause the
+			// 1) Drop py3wrapper.sh, just use python binary, the launcher script generated by the
+			// TemplateExpandAction handles everything necessary to launch a Pythin application.
+			// 2) ../bazel_tools: they have MODIFY timestamp 10years in the future and would cause the
 			// containing depset to always be considered newer than their outputs.
 		} else {
 			directArtifactPaths = append(directArtifactPaths, path)
@@ -346,12 +336,14 @@
 		}
 
 		var buildStatement BuildStatement
-		if isSymlinkAction(actionEntry) {
+		if actionEntry.isSymlinkAction() {
 			buildStatement, err = aqueryHandler.symlinkActionBuildStatement(actionEntry)
-		} else if isTemplateExpandAction(actionEntry) && len(actionEntry.Arguments) < 1 {
+		} else if actionEntry.isTemplateExpandAction() && len(actionEntry.Arguments) < 1 {
 			buildStatement, err = aqueryHandler.templateExpandActionBuildStatement(actionEntry)
-		} else if isPythonZipperAction(actionEntry) {
-			buildStatement, err = aqueryHandler.pythonZipperActionBuildStatement(actionEntry, buildStatements)
+		} else if actionEntry.isFileWriteAction() {
+			buildStatement, err = aqueryHandler.fileWriteActionBuildStatement(actionEntry)
+		} else if actionEntry.isSymlinkTreeAction() {
+			buildStatement, err = aqueryHandler.symlinkTreeActionBuildStatement(actionEntry)
 		} else if len(actionEntry.Arguments) < 1 {
 			return nil, nil, fmt.Errorf("received action with no command: [%s]", actionEntry.Mnemonic)
 		} else {
@@ -452,54 +444,6 @@
 	return buildStatement, nil
 }
 
-func (a *aqueryArtifactHandler) pythonZipperActionBuildStatement(actionEntry action, prevBuildStatements []BuildStatement) (BuildStatement, error) {
-	inputPaths, err := a.getInputPaths(actionEntry.InputDepSetIds)
-	if err != nil {
-		return BuildStatement{}, err
-	}
-	outputPaths, depfile, err := a.getOutputPaths(actionEntry)
-	if err != nil {
-		return BuildStatement{}, err
-	}
-
-	if len(inputPaths) < 1 || len(outputPaths) != 1 {
-		return BuildStatement{}, fmt.Errorf("Expect 1+ input and 1 output to python zipper action, got: input %q, output %q", inputPaths, outputPaths)
-	}
-	command := strings.Join(proptools.ShellEscapeListIncludingSpaces(actionEntry.Arguments), " ")
-	inputPaths, command = removePy3wrapperScript(inputPaths, command)
-	command = addCommandForPyBinaryRunfilesDir(command, outputPaths[0])
-	// Add the python zip file as input of the corresponding python binary stub script in Ninja build statements.
-	// In Ninja build statements, the outputs of dependents of a python binary have python binary stub script as input,
-	// which is not sufficient without the python zip file from which runfiles directory is created for py_binary.
-	//
-	// The following logic relies on that Bazel aquery output returns actions in the order that
-	// PythonZipper is after TemplateAction of creating Python binary stub script. If later Bazel doesn't return actions
-	// in that order, the following logic might not find the build statement generated for Python binary
-	// stub script and the build might fail. So the check of pyBinaryFound is added to help debug in case later Bazel might change aquery output.
-	// See go/python-binary-host-mixed-build for more details.
-	pythonZipFilePath := outputPaths[0]
-	pyBinaryFound := false
-	for i := range prevBuildStatements {
-		if len(prevBuildStatements[i].OutputPaths) == 1 && prevBuildStatements[i].OutputPaths[0]+".zip" == pythonZipFilePath {
-			prevBuildStatements[i].InputPaths = append(prevBuildStatements[i].InputPaths, pythonZipFilePath)
-			pyBinaryFound = true
-		}
-	}
-	if !pyBinaryFound {
-		return BuildStatement{}, fmt.Errorf("Could not find the correspondinging Python binary stub script of PythonZipper: %q", outputPaths)
-	}
-
-	buildStatement := BuildStatement{
-		Command:     command,
-		Depfile:     depfile,
-		OutputPaths: outputPaths,
-		InputPaths:  inputPaths,
-		Env:         actionEntry.EnvironmentVariables,
-		Mnemonic:    actionEntry.Mnemonic,
-	}
-	return buildStatement, nil
-}
-
 func (a *aqueryArtifactHandler) templateExpandActionBuildStatement(actionEntry action) (BuildStatement, error) {
 	outputPaths, depfile, err := a.getOutputPaths(actionEntry)
 	if err != nil {
@@ -532,6 +476,47 @@
 	return buildStatement, nil
 }
 
+func (a *aqueryArtifactHandler) fileWriteActionBuildStatement(actionEntry action) (BuildStatement, error) {
+	outputPaths, _, err := a.getOutputPaths(actionEntry)
+	var depsetHashes []string
+	if err == nil {
+		depsetHashes, err = a.depsetContentHashes(actionEntry.InputDepSetIds)
+	}
+	if err != nil {
+		return BuildStatement{}, err
+	}
+	return BuildStatement{
+		Depfile:           nil,
+		OutputPaths:       outputPaths,
+		Env:               actionEntry.EnvironmentVariables,
+		Mnemonic:          actionEntry.Mnemonic,
+		InputDepsetHashes: depsetHashes,
+		FileContents:      actionEntry.FileContents,
+	}, nil
+}
+
+func (a *aqueryArtifactHandler) symlinkTreeActionBuildStatement(actionEntry action) (BuildStatement, error) {
+	outputPaths, _, err := a.getOutputPaths(actionEntry)
+	if err != nil {
+		return BuildStatement{}, err
+	}
+	inputPaths, err := a.getInputPaths(actionEntry.InputDepSetIds)
+	if err != nil {
+		return BuildStatement{}, err
+	}
+	if len(inputPaths) != 1 || len(outputPaths) != 1 {
+		return BuildStatement{}, fmt.Errorf("Expect 1 input and 1 output to symlink action, got: input %q, output %q", inputPaths, outputPaths)
+	}
+	// The actual command is generated in bazelSingleton.GenerateBuildActions
+	return BuildStatement{
+		Depfile:     nil,
+		OutputPaths: outputPaths,
+		Env:         actionEntry.EnvironmentVariables,
+		Mnemonic:    actionEntry.Mnemonic,
+		InputPaths:  inputPaths,
+	}, nil
+}
+
 func (a *aqueryArtifactHandler) symlinkActionBuildStatement(actionEntry action) (BuildStatement, error) {
 	outputPaths, depfile, err := a.getOutputPaths(actionEntry)
 	if err != nil {
@@ -614,76 +599,35 @@
 	return replacer.Replace(str)
 }
 
-// removePy3wrapperScript removes py3wrapper.sh from the input paths and command of the action of
-// creating python zip file in mixed build mode. py3wrapper.sh is returned as input by aquery but
-// there is no action returned by aquery for creating it. So in mixed build "python3" is used
-// as the PYTHON_BINARY in python binary stub script, and py3wrapper.sh is not needed and should be
-// removed from input paths and command of creating python zip file.
-// See go/python-binary-host-mixed-build for more details.
-// TODO(b/205879240) remove this after py3wrapper.sh could be created in the mixed build mode.
-func removePy3wrapperScript(inputPaths []string, command string) (newInputPaths []string, newCommand string) {
-	// Remove from inputs
-	var filteredInputPaths []string
-	for _, path := range inputPaths {
-		if !strings.HasSuffix(path, py3wrapperFileName) {
-			filteredInputPaths = append(filteredInputPaths, path)
-		}
-	}
-	newInputPaths = filteredInputPaths
-
-	// Remove from command line
-	var re = regexp.MustCompile(`\S*` + py3wrapperFileName)
-	newCommand = re.ReplaceAllString(command, "")
-	return
-}
-
-// addCommandForPyBinaryRunfilesDir adds commands creating python binary runfiles directory.
-// runfiles directory is created by using MANIFEST file and MANIFEST file is the output of
-// SourceSymlinkManifest action is in aquery output of Bazel py_binary targets,
-// but since SourceSymlinkManifest doesn't contain sufficient information
-// so MANIFEST file could not be created, which also blocks the creation of runfiles directory.
-// See go/python-binary-host-mixed-build for more details.
-// TODO(b/197135294) create runfiles directory from MANIFEST file once it can be created from SourceSymlinkManifest action.
-func addCommandForPyBinaryRunfilesDir(oldCommand string, zipFilePath string) string {
-	// Unzip the zip file, zipFilePath looks like <python_binary>.zip
-	runfilesDirName := zipFilePath[0:len(zipFilePath)-4] + ".runfiles"
-	command := fmt.Sprintf("%s x %s -d %s", "../bazel_tools/tools/zip/zipper/zipper", zipFilePath, runfilesDirName)
-	// Create a symbolic link in <python_binary>.runfiles/, which is the expected structure
-	// when running the python binary stub script.
-	command += fmt.Sprintf(" && ln -sf runfiles/__main__ %s", runfilesDirName)
-	return oldCommand + " && " + command
-}
-
-func isSymlinkAction(a action) bool {
+func (a action) isSymlinkAction() bool {
 	return a.Mnemonic == "Symlink" || a.Mnemonic == "SolibSymlink" || a.Mnemonic == "ExecutableSymlink"
 }
 
-func isTemplateExpandAction(a action) bool {
+func (a action) isTemplateExpandAction() bool {
 	return a.Mnemonic == "TemplateExpand"
 }
 
-func isPythonZipperAction(a action) bool {
-	return a.Mnemonic == "PythonZipper"
+func (a action) isFileWriteAction() bool {
+	return a.Mnemonic == "FileWrite" || a.Mnemonic == "SourceSymlinkManifest"
+}
+
+func (a action) isSymlinkTreeAction() bool {
+	return a.Mnemonic == "SymlinkTree"
 }
 
 func shouldSkipAction(a action) bool {
-	// TODO(b/180945121): Handle complex symlink actions.
-	if a.Mnemonic == "SymlinkTree" || a.Mnemonic == "SourceSymlinkManifest" {
-		return true
-	}
 	// Middleman actions are not handled like other actions; they are handled separately as a
 	// preparatory step so that their inputs may be relayed to actions depending on middleman
 	// artifacts.
 	if a.Mnemonic == "Middleman" {
 		return true
 	}
-	// Skip "Fail" actions, which are placeholder actions designed to always fail.
-	if a.Mnemonic == "Fail" {
+	// PythonZipper is bogus action returned by aquery, ignore it (b/236198693)
+	if a.Mnemonic == "PythonZipper" {
 		return true
 	}
-	// TODO(b/180946980): Handle FileWrite. The aquery proto currently contains no information
-	// about the contents that are written.
-	if a.Mnemonic == "FileWrite" {
+	// Skip "Fail" actions, which are placeholder actions designed to always fail.
+	if a.Mnemonic == "Fail" {
 		return true
 	}
 	if a.Mnemonic == "BaselineCoverage" {
diff --git a/bazel/aquery_test.go b/bazel/aquery_test.go
index c759d56..3a2bf0f 100644
--- a/bazel/aquery_test.go
+++ b/bazel/aquery_test.go
@@ -460,6 +460,43 @@
 	}
 }
 
+func TestSymlinkTree(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [
+    { "id": 1, "pathFragmentId": 1 },
+    { "id": 2, "pathFragmentId": 2 }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "SymlinkTree",
+    "configurationId": 1,
+    "inputDepSetIds": [1],
+    "outputIds": [2],
+    "primaryOutputId": 2,
+    "executionPlatform": "//build/bazel/platforms:linux_x86_64"
+  }],
+  "pathFragments": [
+    { "id": 1, "label": "foo.manifest" },
+    { "id": 2, "label": "foo.runfiles/MANIFEST" }],
+  "depSetOfFiles": [
+    { "id": 1, "directArtifactIds": [1] }]
+}
+`
+	actual, _, err := AqueryBuildStatements([]byte(inputString))
+	if err != nil {
+		t.Errorf("Unexpected error %q", err)
+	}
+	assertBuildStatements(t, []BuildStatement{
+		{
+			Command:     "",
+			OutputPaths: []string{"foo.runfiles/MANIFEST"},
+			Mnemonic:    "SymlinkTree",
+			InputPaths:  []string{"foo.manifest"},
+		},
+	}, actual)
+}
+
 func TestBazelOutRemovalFromInputDepsets(t *testing.T) {
 	const inputString = `{
   "artifacts": [{
@@ -861,151 +898,67 @@
 	assertError(t, err, `Expect 1 output to template expand action, got: output []`)
 }
 
-func TestPythonZipperActionSuccess(t *testing.T) {
+func TestFileWrite(t *testing.T) {
 	const inputString = `
 {
   "artifacts": [
-    { "id": 1, "pathFragmentId": 1 },
-    { "id": 2, "pathFragmentId": 2 },
-    { "id": 3, "pathFragmentId": 3 },
-    { "id": 4, "pathFragmentId": 4 },
-    { "id": 5, "pathFragmentId": 10 },
-    { "id": 10, "pathFragmentId": 20 }],
+    { "id": 1, "pathFragmentId": 1 }],
   "actions": [{
     "targetId": 1,
     "actionKey": "x",
-    "mnemonic": "TemplateExpand",
+    "mnemonic": "FileWrite",
     "configurationId": 1,
     "outputIds": [1],
     "primaryOutputId": 1,
     "executionPlatform": "//build/bazel/platforms:linux_x86_64",
-    "templateContent": "Test template substitutions: %token1%, %python_binary%",
-    "substitutions": [{
-      "key": "%token1%",
-      "value": "abcd"
-    },{
-      "key": "%python_binary%",
-      "value": "python3"
-    }]
-  },{
-    "targetId": 1,
-    "actionKey": "x",
-    "mnemonic": "PythonZipper",
-    "configurationId": 1,
-    "arguments": ["../bazel_tools/tools/zip/zipper/zipper", "cC", "python_binary.zip", "__main__.py\u003dbazel-out/k8-fastbuild/bin/python_binary.temp", "__init__.py\u003d", "runfiles/__main__/__init__.py\u003d", "runfiles/__main__/python_binary.py\u003dpython_binary.py", "runfiles/bazel_tools/tools/python/py3wrapper.sh\u003dbazel-out/bazel_tools/k8-fastbuild/bin/tools/python/py3wrapper.sh"],
-    "outputIds": [2],
-    "inputDepSetIds": [1],
-    "primaryOutputId": 2
+    "fileContents": "file data\n"
   }],
-  "depSetOfFiles": [
-    { "id": 1, "directArtifactIds": [4, 3, 5] }],
   "pathFragments": [
-    { "id": 1, "label": "python_binary" },
-    { "id": 2, "label": "python_binary.zip" },
-    { "id": 3, "label": "python_binary.py" },
-    { "id": 9, "label": ".." },
-    { "id": 8, "label": "bazel_tools", "parentId": 9 },
-    { "id": 7, "label": "tools", "parentId": 8 },
-    { "id": 6, "label": "zip", "parentId": 7  },
-    { "id": 5, "label": "zipper", "parentId": 6 },
-    { "id": 4, "label": "zipper", "parentId": 5 },
-    { "id": 16, "label": "bazel-out" },
-    { "id": 15, "label": "bazel_tools", "parentId": 16 },
-    { "id": 14, "label": "k8-fastbuild", "parentId": 15 },
-    { "id": 13, "label": "bin", "parentId": 14 },
-    { "id": 12, "label": "tools", "parentId": 13 },
-    { "id": 11, "label": "python", "parentId": 12 },
-    { "id": 10, "label": "py3wrapper.sh", "parentId": 11 },
-    { "id": 20, "label": "python_binary" }]
-}`
+    { "id": 1, "label": "foo.manifest" }]
+}
+`
 	actual, _, err := AqueryBuildStatements([]byte(inputString))
-
 	if err != nil {
 		t.Errorf("Unexpected error %q", err)
 	}
+	assertBuildStatements(t, []BuildStatement{
+		{
+			OutputPaths:  []string{"foo.manifest"},
+			Mnemonic:     "FileWrite",
+			FileContents: "file data\n",
+		},
+	}, actual)
+}
 
-	expectedBuildStatements := []BuildStatement{
-		{
-			Command: "/bin/bash -c 'echo \"Test template substitutions: abcd, python3\" | sed \"s/\\\\\\\\n/\\\\n/g\" > python_binary && " +
-				"chmod a+x python_binary'",
-			InputPaths:  []string{"python_binary.zip"},
-			OutputPaths: []string{"python_binary"},
-			Mnemonic:    "TemplateExpand",
-		},
-		{
-			Command: "../bazel_tools/tools/zip/zipper/zipper cC python_binary.zip __main__.py=bazel-out/k8-fastbuild/bin/python_binary.temp " +
-				"__init__.py= runfiles/__main__/__init__.py= runfiles/__main__/python_binary.py=python_binary.py  && " +
-				"../bazel_tools/tools/zip/zipper/zipper x python_binary.zip -d python_binary.runfiles && ln -sf runfiles/__main__ python_binary.runfiles",
-			InputPaths:  []string{"python_binary.py"},
-			OutputPaths: []string{"python_binary.zip"},
-			Mnemonic:    "PythonZipper",
-		},
+func TestSourceSymlinkManifest(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [
+    { "id": 1, "pathFragmentId": 1 }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "SourceSymlinkManifest",
+    "configurationId": 1,
+    "outputIds": [1],
+    "primaryOutputId": 1,
+    "executionPlatform": "//build/bazel/platforms:linux_x86_64",
+    "fileContents": "symlink target\n"
+  }],
+  "pathFragments": [
+    { "id": 1, "label": "foo.manifest" }]
+}
+`
+	actual, _, err := AqueryBuildStatements([]byte(inputString))
+	if err != nil {
+		t.Errorf("Unexpected error %q", err)
 	}
-	assertBuildStatements(t, expectedBuildStatements, actual)
-}
-
-func TestPythonZipperActionNoInput(t *testing.T) {
-	const inputString = `
-{
-  "artifacts": [
-    { "id": 1, "pathFragmentId": 1 },
-    { "id": 2, "pathFragmentId": 2 }],
-  "actions": [{
-    "targetId": 1,
-    "actionKey": "x",
-    "mnemonic": "PythonZipper",
-    "configurationId": 1,
-    "arguments": ["../bazel_tools/tools/zip/zipper/zipper", "cC", "python_binary.zip", "__main__.py\u003dbazel-out/k8-fastbuild/bin/python_binary.temp", "__init__.py\u003d", "runfiles/__main__/__init__.py\u003d", "runfiles/__main__/python_binary.py\u003dpython_binary.py", "runfiles/bazel_tools/tools/python/py3wrapper.sh\u003dbazel-out/bazel_tools/k8-fastbuild/bin/tools/python/py3wrapper.sh"],
-    "outputIds": [2],
-    "primaryOutputId": 2
-  }],
-  "pathFragments": [
-    { "id": 1, "label": "python_binary" },
-    { "id": 2, "label": "python_binary.zip" }]
-}`
-	_, _, err := AqueryBuildStatements([]byte(inputString))
-	assertError(t, err, `Expect 1+ input and 1 output to python zipper action, got: input [], output ["python_binary.zip"]`)
-}
-
-func TestPythonZipperActionNoOutput(t *testing.T) {
-	const inputString = `
-{
-  "artifacts": [
-    { "id": 1, "pathFragmentId": 1 },
-    { "id": 2, "pathFragmentId": 2 },
-    { "id": 3, "pathFragmentId": 3 },
-    { "id": 4, "pathFragmentId": 4 },
-    { "id": 5, "pathFragmentId": 10 }],
-  "actions": [{
-    "targetId": 1,
-    "actionKey": "x",
-    "mnemonic": "PythonZipper",
-    "configurationId": 1,
-    "arguments": ["../bazel_tools/tools/zip/zipper/zipper", "cC", "python_binary.zip", "__main__.py\u003dbazel-out/k8-fastbuild/bin/python_binary.temp", "__init__.py\u003d", "runfiles/__main__/__init__.py\u003d", "runfiles/__main__/python_binary.py\u003dpython_binary.py", "runfiles/bazel_tools/tools/python/py3wrapper.sh\u003dbazel-out/bazel_tools/k8-fastbuild/bin/tools/python/py3wrapper.sh"],
-    "inputDepSetIds": [1]
-  }],
-  "depSetOfFiles": [
-    { "id": 1, "directArtifactIds": [4, 3, 5]}],
-  "pathFragments": [
-    { "id": 1, "label": "python_binary" },
-    { "id": 2, "label": "python_binary.zip" },
-    { "id": 3, "label": "python_binary.py" },
-    { "id": 9, "label": ".." },
-    { "id": 8, "label": "bazel_tools", "parentId": 9 },
-    { "id": 7, "label": "tools", "parentId": 8 },
-    { "id": 6, "label": "zip", "parentId": 7 },
-    { "id": 5, "label": "zipper", "parentId": 6 },
-    { "id": 4, "label": "zipper", "parentId": 5 },
-    { "id": 16, "label": "bazel-out" },
-    { "id": 15, "label": "bazel_tools", "parentId": 16 },
-    { "id": 14, "label": "k8-fastbuild", "parentId": 15 },
-    { "id": 13, "label": "bin", "parentId": 14 },
-    { "id": 12, "label": "tools", "parentId": 13 },
-    { "id": 11, "label": "python", "parentId": 12 },
-    { "id": 10, "label": "py3wrapper.sh", "parentId": 11 }]
-}`
-	_, _, err := AqueryBuildStatements([]byte(inputString))
-	assertError(t, err, `Expect 1+ input and 1 output to python zipper action, got: input ["python_binary.py"], output []`)
+	assertBuildStatements(t, []BuildStatement{
+		{
+			OutputPaths: []string{"foo.manifest"},
+			Mnemonic:    "SourceSymlinkManifest",
+		},
+	}, actual)
 }
 
 func assertError(t *testing.T, err error, expected string) {
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index de139c4..d8011d6 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -394,10 +394,14 @@
 	if !android.PrefixInList(preoptFlags, "--compiler-filter=") {
 		var compilerFilter string
 		if systemServerJars.ContainsJar(module.Name) {
-			// Jars of system server, use the product option if it is set, speed otherwise.
 			if global.SystemServerCompilerFilter != "" {
+				// Use the product option if it is set.
 				compilerFilter = global.SystemServerCompilerFilter
+			} else if profile != nil {
+				// Use "speed-profile" for system server jars that have a profile.
+				compilerFilter = "speed-profile"
 			} else {
+				// Use "speed" for system server jars that do not have a profile.
 				compilerFilter = "speed"
 			}
 		} else if contains(global.SpeedApps, module.Name) || contains(global.SystemServerApps, module.Name) {
diff --git a/fuzz/fuzz_common.go b/fuzz/fuzz_common.go
index 66d5aba..631380c 100644
--- a/fuzz/fuzz_common.go
+++ b/fuzz/fuzz_common.go
@@ -18,6 +18,7 @@
 
 import (
 	"encoding/json"
+	"fmt"
 	"sort"
 	"strings"
 
@@ -61,9 +62,65 @@
 	Dir          string
 }
 
+type PrivilegedLevel string
+
+const (
+	// Environment with the most minimal permissions.
+	Constrained PrivilegedLevel = "Constrained"
+	// Typical execution environment running unprivileged code.
+	Unprivileged = "Unprivileged"
+	// May have access to elevated permissions.
+	Privileged = "Privileged"
+	// Trusted computing base.
+	Tcb = "TCB"
+	// Bootloader chain.
+	Bootloader = "Bootloader"
+	// Tusted execution environment.
+	Tee = "Tee"
+	// Secure enclave.
+	Se = "Se"
+	// Other.
+	Other = "Other"
+)
+
+func IsValidConfig(fuzzModule FuzzPackagedModule, moduleName string) bool {
+	var config = fuzzModule.FuzzProperties.Fuzz_config
+	if config != nil {
+		var level = PrivilegedLevel(config.Privilege_level)
+		if level != "" {
+			switch level {
+			case Constrained, Unprivileged, Privileged, Tcb, Bootloader, Tee, Se, Other:
+				return true
+			}
+			panic(fmt.Errorf("Invalid privileged level in fuzz config in %s", moduleName))
+		}
+		return true
+	} else {
+		return false
+	}
+}
+
 type FuzzConfig struct {
 	// Email address of people to CC on bugs or contact about this fuzz target.
 	Cc []string `json:"cc,omitempty"`
+	// A brief description of what the fuzzed code does.
+	Description string `json:"description,omitempty"`
+	// Can this code be triggered remotely or only locally.
+	Remotely_accessible bool `json:"remotely_accessible,omitempty"`
+	// Is the fuzzed code host only, i.e. test frameworks or support utilities.
+	Host_only bool `json:"access_vector,omitempty"`
+	// Can third party/untrusted apps supply data to fuzzed code.
+	Untrusted_data bool `json:"untrusted_data,omitempty"`
+	// Is the code being fuzzed in a privileged, constrained or any other
+	// context from:
+	// https://source.android.com/security/overview/updates-resources#context_types.
+	Privilege_level PrivilegedLevel `json:"privilege_level,omitempty"`
+	// Can the fuzzed code isolated or can be called by multiple users/processes.
+	Isolated bool `json:"users_isolation,omitempty"`
+	// When code was relaeased or will be released.
+	Production_date string `json:"production_date,omitempty"`
+	// Prevents critical service functionality like phone calls, bluetooth, etc.
+	Critical bool `json:"critical,omitempty"`
 	// Specify whether to enable continuous fuzzing on devices. Defaults to true.
 	Fuzz_on_haiku_device *bool `json:"fuzz_on_haiku_device,omitempty"`
 	// Specify whether to enable continuous fuzzing on host. Defaults to true.
@@ -159,7 +216,7 @@
 	}
 
 	// Additional fuzz config.
-	if fuzzModule.Config != nil {
+	if fuzzModule.Config != nil && IsValidConfig(fuzzModule, module.Name()) {
 		files = append(files, FileToZip{fuzzModule.Config, ""})
 	}
 
diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go
index 0591012..f08b64b 100644
--- a/java/bootclasspath_fragment.go
+++ b/java/bootclasspath_fragment.go
@@ -32,12 +32,7 @@
 func init() {
 	registerBootclasspathFragmentBuildComponents(android.InitRegistrationContext)
 
-	android.RegisterSdkMemberType(&bootclasspathFragmentMemberType{
-		SdkMemberTypeBase: android.SdkMemberTypeBase{
-			PropertyName: "bootclasspath_fragments",
-			SupportsSdk:  true,
-		},
-	})
+	android.RegisterSdkMemberType(BootclasspathFragmentSdkMemberType)
 }
 
 func registerBootclasspathFragmentBuildComponents(ctx android.RegistrationContext) {
@@ -46,6 +41,15 @@
 	ctx.RegisterModuleType("prebuilt_bootclasspath_fragment", prebuiltBootclasspathFragmentFactory)
 }
 
+// BootclasspathFragmentSdkMemberType is the member type used to add bootclasspath_fragments to
+// the SDK snapshot. It is exported for use by apex.
+var BootclasspathFragmentSdkMemberType = &bootclasspathFragmentMemberType{
+	SdkMemberTypeBase: android.SdkMemberTypeBase{
+		PropertyName: "bootclasspath_fragments",
+		SupportsSdk:  true,
+	},
+}
+
 type bootclasspathFragmentContentDependencyTag struct {
 	blueprint.BaseDependencyTag
 }
diff --git a/java/dexpreopt.go_v1 b/java/dexpreopt.go_v1
deleted file mode 100644
index 0adaf99..0000000
--- a/java/dexpreopt.go_v1
+++ /dev/null
@@ -1,404 +0,0 @@
-// Copyright 2018 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package java
-
-import (
-	"path/filepath"
-	"strings"
-
-	"android/soong/android"
-	"android/soong/dexpreopt"
-)
-
-type DexpreopterInterface interface {
-	IsInstallable() bool // Structs that embed dexpreopter must implement this.
-	dexpreoptDisabled(ctx android.BaseModuleContext) bool
-	DexpreoptBuiltInstalledForApex() []dexpreopterInstall
-	AndroidMkEntriesForApex() []android.AndroidMkEntries
-}
-
-type dexpreopterInstall struct {
-	// A unique name to distinguish an output from others for the same java library module. Usually in
-	// the form of `<arch>-<encoded-path>.odex/vdex/art`.
-	name string
-
-	// The name of the input java module.
-	moduleName string
-
-	// The path to the dexpreopt output on host.
-	outputPathOnHost android.Path
-
-	// The directory on the device for the output to install to.
-	installDirOnDevice android.InstallPath
-
-	// The basename (the last segment of the path) for the output to install as.
-	installFileOnDevice string
-}
-
-// The full module name of the output in the makefile.
-func (install *dexpreopterInstall) FullModuleName() string {
-	return install.moduleName + install.SubModuleName()
-}
-
-// The sub-module name of the output in the makefile (the name excluding the java module name).
-func (install *dexpreopterInstall) SubModuleName() string {
-	return "-dexpreopt-" + install.name
-}
-
-// Returns Make entries for installing the file.
-//
-// This function uses a value receiver rather than a pointer receiver to ensure that the object is
-// safe to use in `android.AndroidMkExtraEntriesFunc`.
-func (install dexpreopterInstall) ToMakeEntries() android.AndroidMkEntries {
-	return android.AndroidMkEntries{
-		Class:      "ETC",
-		SubName:    install.SubModuleName(),
-		OutputFile: android.OptionalPathForPath(install.outputPathOnHost),
-		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
-			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", install.installDirOnDevice.String())
-				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", install.installFileOnDevice)
-				entries.SetString("LOCAL_NOT_AVAILABLE_FOR_PLATFORM", "false")
-			},
-		},
-	}
-}
-
-type dexpreopter struct {
-	dexpreoptProperties DexpreoptProperties
-
-	installPath         android.InstallPath
-	uncompressedDex     bool
-	isSDKLibrary        bool
-	isApp               bool
-	isTest              bool
-	isPresignedPrebuilt bool
-	preventInstall      bool
-
-	manifestFile        android.Path
-	statusFile          android.WritablePath
-	enforceUsesLibs     bool
-	classLoaderContexts dexpreopt.ClassLoaderContextMap
-
-	// See the `dexpreopt` function for details.
-	builtInstalled        string
-	builtInstalledForApex []dexpreopterInstall
-
-	// The config is used for two purposes:
-	// - Passing dexpreopt information about libraries from Soong to Make. This is needed when
-	//   a <uses-library> is defined in Android.bp, but used in Android.mk (see dex_preopt_config_merger.py).
-	//   Note that dexpreopt.config might be needed even if dexpreopt is disabled for the library itself.
-	// - Dexpreopt post-processing (using dexpreopt artifacts from a prebuilt system image to incrementally
-	//   dexpreopt another partition).
-	configPath android.WritablePath
-}
-
-type DexpreoptProperties struct {
-	Dex_preopt struct {
-		// If false, prevent dexpreopting.  Defaults to true.
-		Enabled *bool
-
-		// If true, generate an app image (.art file) for this module.
-		App_image *bool
-
-		// If true, use a checked-in profile to guide optimization.  Defaults to false unless
-		// a matching profile is set or a profile is found in PRODUCT_DEX_PREOPT_PROFILE_DIR
-		// that matches the name of this module, in which case it is defaulted to true.
-		Profile_guided *bool
-
-		// If set, provides the path to profile relative to the Android.bp file.  If not set,
-		// defaults to searching for a file that matches the name of this module in the default
-		// profile location set by PRODUCT_DEX_PREOPT_PROFILE_DIR, or empty if not found.
-		Profile *string `android:"path"`
-	}
-}
-
-func init() {
-	dexpreopt.DexpreoptRunningInSoong = true
-}
-
-func isApexVariant(ctx android.BaseModuleContext) bool {
-	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
-	return !apexInfo.IsForPlatform()
-}
-
-func forPrebuiltApex(ctx android.BaseModuleContext) bool {
-	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
-	return apexInfo.ForPrebuiltApex
-}
-
-func moduleName(ctx android.BaseModuleContext) string {
-	// Remove the "prebuilt_" prefix if the module is from a prebuilt because the prefix is not
-	// expected by dexpreopter.
-	return android.RemoveOptionalPrebuiltPrefix(ctx.ModuleName())
-}
-
-func (d *dexpreopter) dexpreoptDisabled(ctx android.BaseModuleContext) bool {
-	if !ctx.Device() {
-		return true
-	}
-
-	if d.isTest {
-		return true
-	}
-
-	if !BoolDefault(d.dexpreoptProperties.Dex_preopt.Enabled, true) {
-		return true
-	}
-
-	// If the module is from a prebuilt APEX, it shouldn't be installable, but it can still be
-	// dexpreopted.
-	if !ctx.Module().(DexpreopterInterface).IsInstallable() && !forPrebuiltApex(ctx) {
-		return true
-	}
-
-	if !android.IsModulePreferred(ctx.Module()) {
-		return true
-	}
-
-	global := dexpreopt.GetGlobalConfig(ctx)
-
-	if global.DisablePreopt {
-		return true
-	}
-
-	if inList(moduleName(ctx), global.DisablePreoptModules) {
-		return true
-	}
-
-	isApexSystemServerJar := global.AllApexSystemServerJars(ctx).ContainsJar(moduleName(ctx))
-	if isApexVariant(ctx) {
-		// Don't preopt APEX variant module unless the module is an APEX system server jar and we are
-		// building the entire system image.
-		if !isApexSystemServerJar || ctx.Config().UnbundledBuild() {
-			return true
-		}
-	} else {
-		// Don't preopt the platform variant of an APEX system server jar to avoid conflicts.
-		if isApexSystemServerJar {
-			return true
-		}
-	}
-
-	// TODO: contains no java code
-
-	return false
-}
-
-func dexpreoptToolDepsMutator(ctx android.BottomUpMutatorContext) {
-	if d, ok := ctx.Module().(DexpreopterInterface); !ok || d.dexpreoptDisabled(ctx) {
-		return
-	}
-	dexpreopt.RegisterToolDeps(ctx)
-}
-
-func (d *dexpreopter) odexOnSystemOther(ctx android.ModuleContext, installPath android.InstallPath) bool {
-	return dexpreopt.OdexOnSystemOtherByName(moduleName(ctx), android.InstallPathToOnDevicePath(ctx, installPath), dexpreopt.GetGlobalConfig(ctx))
-}
-
-// Returns the install path of the dex jar of a module.
-//
-// Do not rely on `ApexInfo.ApexVariationName` because it can be something like "apex1000", rather
-// than the `name` in the path `/apex/<name>` as suggested in its comment.
-//
-// This function is on a best-effort basis. It cannot handle the case where an APEX jar is not a
-// system server jar, which is fine because we currently only preopt system server jars for APEXes.
-func (d *dexpreopter) getInstallPath(
-	ctx android.ModuleContext, defaultInstallPath android.InstallPath) android.InstallPath {
-	global := dexpreopt.GetGlobalConfig(ctx)
-	if global.AllApexSystemServerJars(ctx).ContainsJar(moduleName(ctx)) {
-		dexLocation := dexpreopt.GetSystemServerDexLocation(ctx, global, moduleName(ctx))
-		return android.PathForModuleInPartitionInstall(ctx, "", strings.TrimPrefix(dexLocation, "/"))
-	}
-	if !d.dexpreoptDisabled(ctx) && isApexVariant(ctx) &&
-		filepath.Base(defaultInstallPath.PartitionDir()) != "apex" {
-		ctx.ModuleErrorf("unable to get the install path of the dex jar for dexpreopt")
-	}
-	return defaultInstallPath
-}
-
-func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.WritablePath) {
-	global := dexpreopt.GetGlobalConfig(ctx)
-
-	// TODO(b/148690468): The check on d.installPath is to bail out in cases where
-	// the dexpreopter struct hasn't been fully initialized before we're called,
-	// e.g. in aar.go. This keeps the behaviour that dexpreopting is effectively
-	// disabled, even if installable is true.
-	if d.installPath.Base() == "." {
-		return
-	}
-
-	dexLocation := android.InstallPathToOnDevicePath(ctx, d.installPath)
-
-	providesUsesLib := moduleName(ctx)
-	if ulib, ok := ctx.Module().(ProvidesUsesLib); ok {
-		name := ulib.ProvidesUsesLib()
-		if name != nil {
-			providesUsesLib = *name
-		}
-	}
-
-	// If it is test, make config files regardless of its dexpreopt setting.
-	// The config files are required for apps defined in make which depend on the lib.
-	if d.isTest && d.dexpreoptDisabled(ctx) {
-		return
-	}
-
-	isSystemServerJar := global.AllSystemServerJars(ctx).ContainsJar(moduleName(ctx))
-
-	bootImage := defaultBootImageConfig(ctx)
-	dexFiles, dexLocations := bcpForDexpreopt(ctx, global.PreoptWithUpdatableBcp)
-
-	targets := ctx.MultiTargets()
-	if len(targets) == 0 {
-		// assume this is a java library, dexpreopt for all arches for now
-		for _, target := range ctx.Config().Targets[android.Android] {
-			if target.NativeBridge == android.NativeBridgeDisabled {
-				targets = append(targets, target)
-			}
-		}
-		if isSystemServerJar && !d.isSDKLibrary {
-			// If the module is not an SDK library and it's a system server jar, only preopt the primary arch.
-			targets = targets[:1]
-		}
-	}
-
-	var archs []android.ArchType
-	var images android.Paths
-	var imagesDeps []android.OutputPaths
-	for _, target := range targets {
-		archs = append(archs, target.Arch.ArchType)
-		variant := bootImage.getVariant(target)
-		images = append(images, variant.imagePathOnHost)
-		imagesDeps = append(imagesDeps, variant.imagesDeps)
-	}
-	// The image locations for all Android variants are identical.
-	hostImageLocations, deviceImageLocations := bootImage.getAnyAndroidVariant().imageLocations()
-
-	var profileClassListing android.OptionalPath
-	var profileBootListing android.OptionalPath
-	profileIsTextListing := false
-	if BoolDefault(d.dexpreoptProperties.Dex_preopt.Profile_guided, true) {
-		// If dex_preopt.profile_guided is not set, default it based on the existence of the
-		// dexprepot.profile option or the profile class listing.
-		if String(d.dexpreoptProperties.Dex_preopt.Profile) != "" {
-			profileClassListing = android.OptionalPathForPath(
-				android.PathForModuleSrc(ctx, String(d.dexpreoptProperties.Dex_preopt.Profile)))
-			profileBootListing = android.ExistentPathForSource(ctx,
-				ctx.ModuleDir(), String(d.dexpreoptProperties.Dex_preopt.Profile)+"-boot")
-			profileIsTextListing = true
-		} else if global.ProfileDir != "" {
-			profileClassListing = android.ExistentPathForSource(ctx,
-				global.ProfileDir, moduleName(ctx)+".prof")
-		}
-	}
-
-	// Full dexpreopt config, used to create dexpreopt build rules.
-	dexpreoptConfig := &dexpreopt.ModuleConfig{
-		Name:            moduleName(ctx),
-		DexLocation:     dexLocation,
-		BuildPath:       android.PathForModuleOut(ctx, "dexpreopt", moduleName(ctx)+".jar").OutputPath,
-		DexPath:         dexJarFile,
-		ManifestPath:    android.OptionalPathForPath(d.manifestFile),
-		UncompressedDex: d.uncompressedDex,
-		HasApkLibraries: false,
-		PreoptFlags:     nil,
-
-		ProfileClassListing:  profileClassListing,
-		ProfileIsTextListing: profileIsTextListing,
-		ProfileBootListing:   profileBootListing,
-
-		EnforceUsesLibrariesStatusFile: dexpreopt.UsesLibrariesStatusFile(ctx),
-		EnforceUsesLibraries:           d.enforceUsesLibs,
-		ProvidesUsesLibrary:            providesUsesLib,
-		ClassLoaderContexts:            d.classLoaderContexts,
-
-		Archs:                           archs,
-		DexPreoptImagesDeps:             imagesDeps,
-		DexPreoptImageLocationsOnHost:   hostImageLocations,
-		DexPreoptImageLocationsOnDevice: deviceImageLocations,
-
-		PreoptBootClassPathDexFiles:     dexFiles.Paths(),
-		PreoptBootClassPathDexLocations: dexLocations,
-
-		PreoptExtractedApk: false,
-
-		NoCreateAppImage:    !BoolDefault(d.dexpreoptProperties.Dex_preopt.App_image, true),
-		ForceCreateAppImage: BoolDefault(d.dexpreoptProperties.Dex_preopt.App_image, false),
-
-		PresignedPrebuilt: d.isPresignedPrebuilt,
-	}
-
-	d.configPath = android.PathForModuleOut(ctx, "dexpreopt", "dexpreopt.config")
-	dexpreopt.WriteModuleConfig(ctx, dexpreoptConfig, d.configPath)
-
-	if d.dexpreoptDisabled(ctx) {
-		return
-	}
-
-	globalSoong := dexpreopt.GetGlobalSoongConfig(ctx)
-
-	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, globalSoong, global, dexpreoptConfig)
-	if err != nil {
-		ctx.ModuleErrorf("error generating dexpreopt rule: %s", err.Error())
-		return
-	}
-
-	dexpreoptRule.Build("dexpreopt", "dexpreopt")
-
-	isApexSystemServerJar := global.AllApexSystemServerJars(ctx).ContainsJar(moduleName(ctx))
-
-	for _, install := range dexpreoptRule.Installs() {
-		// Remove the "/" prefix because the path should be relative to $ANDROID_PRODUCT_OUT.
-		installDir := strings.TrimPrefix(filepath.Dir(install.To), "/")
-		installBase := filepath.Base(install.To)
-		arch := filepath.Base(installDir)
-		installPath := android.PathForModuleInPartitionInstall(ctx, "", installDir)
-
-		if isApexSystemServerJar {
-			// APEX variants of java libraries are hidden from Make, so their dexpreopt
-			// outputs need special handling. Currently, for APEX variants of java
-			// libraries, only those in the system server classpath are handled here.
-			// Preopting of boot classpath jars in the ART APEX are handled in
-			// java/dexpreopt_bootjars.go, and other APEX jars are not preopted.
-			// The installs will be handled by Make as sub-modules of the java library.
-			d.builtInstalledForApex = append(d.builtInstalledForApex, dexpreopterInstall{
-				name:                arch + "-" + installBase,
-				moduleName:          moduleName(ctx),
-				outputPathOnHost:    install.From,
-				installDirOnDevice:  installPath,
-				installFileOnDevice: installBase,
-			})
-		} else if !d.preventInstall {
-			ctx.InstallFile(installPath, installBase, install.From)
-		}
-	}
-
-	if !isApexSystemServerJar {
-		d.builtInstalled = dexpreoptRule.Installs().String()
-	}
-}
-
-func (d *dexpreopter) DexpreoptBuiltInstalledForApex() []dexpreopterInstall {
-	return d.builtInstalledForApex
-}
-
-func (d *dexpreopter) AndroidMkEntriesForApex() []android.AndroidMkEntries {
-	var entries []android.AndroidMkEntries
-	for _, install := range d.builtInstalledForApex {
-		entries = append(entries, install.ToMakeEntries())
-	}
-	return entries
-}
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 7c4da3e..b4cd07a 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -785,24 +785,26 @@
 	}
 
 	defaultProfile := "frameworks/base/config/boot-image-profile.txt"
+	extraProfile := "frameworks/base/config/boot-image-profile-extra.txt"
 
 	rule := android.NewRuleBuilder(pctx, ctx)
 
-	var bootImageProfile android.Path
-	if len(global.BootImageProfiles) > 1 {
-		combinedBootImageProfile := image.dir.Join(ctx, "boot-image-profile.txt")
-		rule.Command().Text("cat").Inputs(global.BootImageProfiles).Text(">").Output(combinedBootImageProfile)
-		bootImageProfile = combinedBootImageProfile
-	} else if len(global.BootImageProfiles) == 1 {
-		bootImageProfile = global.BootImageProfiles[0]
+	var profiles android.Paths
+	if len(global.BootImageProfiles) > 0 {
+		profiles = append(profiles, global.BootImageProfiles...)
 	} else if path := android.ExistentPathForSource(ctx, defaultProfile); path.Valid() {
-		bootImageProfile = path.Path()
+		profiles = append(profiles, path.Path())
 	} else {
 		// No profile (not even a default one, which is the case on some branches
 		// like master-art-host that don't have frameworks/base).
 		// Return nil and continue without profile.
 		return nil
 	}
+	if path := android.ExistentPathForSource(ctx, extraProfile); path.Valid() {
+		profiles = append(profiles, path.Path())
+	}
+	bootImageProfile := image.dir.Join(ctx, "boot-image-profile.txt")
+	rule.Command().Text("cat").Inputs(profiles).Text(">").Output(bootImageProfile)
 
 	profile := image.dir.Join(ctx, "boot.prof")
 
diff --git a/java/dexpreopt_bootjars.go_v1 b/java/dexpreopt_bootjars.go_v1
deleted file mode 100644
index 07a357b..0000000
--- a/java/dexpreopt_bootjars.go_v1
+++ /dev/null
@@ -1,952 +0,0 @@
-// Copyright 2019 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package java
-
-import (
-	"path/filepath"
-	"strings"
-
-	"android/soong/android"
-	"android/soong/dexpreopt"
-
-	"github.com/google/blueprint/proptools"
-)
-
-// =================================================================================================
-// WIP - see http://b/177892522 for details
-//
-// The build support for boot images is currently being migrated away from singleton to modules so
-// the documentation may not be strictly accurate. Rather than update the documentation at every
-// step which will create a lot of churn the changes that have been made will be listed here and the
-// documentation will be updated once it is closer to the final result.
-//
-// Changes:
-// 1) dex_bootjars is now a singleton module and not a plain singleton.
-// 2) Boot images are now represented by the boot_image module type.
-// 3) The art boot image is called "art-boot-image", the framework boot image is called
-//    "framework-boot-image".
-// 4) They are defined in art/build/boot/Android.bp and frameworks/base/boot/Android.bp
-//    respectively.
-// 5) Each boot_image retrieves the appropriate boot image configuration from the map returned by
-//    genBootImageConfigs() using the image_name specified in the boot_image module.
-// =================================================================================================
-
-// This comment describes:
-//   1. ART boot images in general (their types, structure, file layout, etc.)
-//   2. build system support for boot images
-//
-// 1. ART boot images
-// ------------------
-//
-// A boot image in ART is a set of files that contain AOT-compiled native code and a heap snapshot
-// of AOT-initialized classes for the bootclasspath Java libraries. A boot image is compiled from a
-// set of DEX jars by the dex2oat compiler. A boot image is used for two purposes: 1) it is
-// installed on device and loaded at runtime, and 2) other Java libraries and apps are compiled
-// against it (compilation may take place either on host, known as "dexpreopt", or on device, known
-// as "dexopt").
-//
-// A boot image is not a single file, but a collection of interrelated files. Each boot image has a
-// number of components that correspond to the Java libraries that constitute it. For each component
-// there are multiple files:
-//   - *.oat or *.odex file with native code (architecture-specific, one per instruction set)
-//   - *.art file with pre-initialized Java classes (architecture-specific, one per instruction set)
-//   - *.vdex file with verification metadata for the DEX bytecode (architecture independent)
-//
-// *.vdex files for the boot images do not contain the DEX bytecode itself, because the
-// bootclasspath DEX files are stored on disk in uncompressed and aligned form. Consequently a boot
-// image is not self-contained and cannot be used without its DEX files. To simplify the management
-// of boot image files, ART uses a certain naming scheme and associates the following metadata with
-// each boot image:
-//   - A stem, which is a symbolic name that is prepended to boot image file names.
-//   - A location (on-device path to the boot image files).
-//   - A list of boot image locations (on-device paths to dependency boot images).
-//   - A set of DEX locations (on-device paths to the DEX files, one location for one DEX file used
-//     to compile the boot image).
-//
-// There are two kinds of boot images:
-//   - primary boot images
-//   - boot image extensions
-//
-// 1.1. Primary boot images
-// ------------------------
-//
-// A primary boot image is compiled for a core subset of bootclasspath Java libraries. It does not
-// depend on any other images, and other boot images may depend on it.
-//
-// For example, assuming that the stem is "boot", the location is /apex/com.android.art/javalib/,
-// the set of core bootclasspath libraries is A B C, and the boot image is compiled for ARM targets
-// (32 and 64 bits), it will have three components with the following files:
-//   - /apex/com.android.art/javalib/{arm,arm64}/boot.{art,oat,vdex}
-//   - /apex/com.android.art/javalib/{arm,arm64}/boot-B.{art,oat,vdex}
-//   - /apex/com.android.art/javalib/{arm,arm64}/boot-C.{art,oat,vdex}
-//
-// The files of the first component are special: they do not have the component name appended after
-// the stem. This naming convention dates back to the times when the boot image was not split into
-// components, and there were just boot.oat and boot.art. The decision to split was motivated by
-// licensing reasons for one of the bootclasspath libraries.
-//
-// As of November 2020 the only primary boot image in Android is the image in the ART APEX
-// com.android.art. The primary ART boot image contains the Core libraries that are part of the ART
-// module. When the ART module gets updated, the primary boot image will be updated with it, and all
-// dependent images will get invalidated (the checksum of the primary image stored in dependent
-// images will not match), unless they are updated in sync with the ART module.
-//
-// 1.2. Boot image extensions
-// --------------------------
-//
-// A boot image extension is compiled for a subset of bootclasspath Java libraries (in particular,
-// this subset does not include the Core bootclasspath libraries that go into the primary boot
-// image). A boot image extension depends on the primary boot image and optionally some other boot
-// image extensions. Other images may depend on it. In other words, boot image extensions can form
-// acyclic dependency graphs.
-//
-// The motivation for boot image extensions comes from the Mainline project. Consider a situation
-// when the list of bootclasspath libraries is A B C, and both A and B are parts of the Android
-// platform, but C is part of an updatable APEX com.android.C. When the APEX is updated, the Java
-// code for C might have changed compared to the code that was used to compile the boot image.
-// Consequently, the whole boot image is obsolete and invalidated (even though the code for A and B
-// that does not depend on C is up to date). To avoid this, the original monolithic boot image is
-// split in two parts: the primary boot image that contains A B, and the boot image extension that
-// contains C and depends on the primary boot image (extends it).
-//
-// For example, assuming that the stem is "boot", the location is /system/framework, the set of
-// bootclasspath libraries is D E (where D is part of the platform and is located in
-// /system/framework, and E is part of a non-updatable APEX com.android.E and is located in
-// /apex/com.android.E/javalib), and the boot image is compiled for ARM targets (32 and 64 bits),
-// it will have two components with the following files:
-//   - /system/framework/{arm,arm64}/boot-D.{art,oat,vdex}
-//   - /system/framework/{arm,arm64}/boot-E.{art,oat,vdex}
-//
-// As of November 2020 the only boot image extension in Android is the Framework boot image
-// extension. It extends the primary ART boot image and contains Framework libraries and other
-// bootclasspath libraries from the platform and non-updatable APEXes that are not included in the
-// ART image. The Framework boot image extension is updated together with the platform. In the
-// future other boot image extensions may be added for some updatable modules.
-//
-//
-// 2. Build system support for boot images
-// ---------------------------------------
-//
-// The primary ART boot image needs to be compiled with one dex2oat invocation that depends on DEX
-// jars for the core libraries. Framework boot image extension needs to be compiled with one dex2oat
-// invocation that depends on the primary ART boot image and all bootclasspath DEX jars except the
-// core libraries as they are already part of the primary ART boot image.
-//
-// 2.1. Libraries that go in the boot images
-// -----------------------------------------
-//
-// The contents of each boot image are determined by the PRODUCT variables. The primary ART APEX
-// boot image contains libraries listed in the ART_APEX_JARS variable in the AOSP makefiles. The
-// Framework boot image extension contains libraries specified in the PRODUCT_BOOT_JARS and
-// PRODUCT_BOOT_JARS_EXTRA variables. The AOSP makefiles specify some common Framework libraries,
-// but more product-specific libraries can be added in the product makefiles.
-//
-// Each component of the PRODUCT_BOOT_JARS and PRODUCT_BOOT_JARS_EXTRA variables is a
-// colon-separated pair <apex>:<library>, where <apex> is the variant name of a non-updatable APEX,
-// "platform" if the library is a part of the platform in the system partition, or "system_ext" if
-// it's in the system_ext partition.
-//
-// In these variables APEXes are identified by their "variant names", i.e. the names they get
-// mounted as in /apex on device. In Soong modules that is the name set in the "apex_name"
-// properties, which default to the "name" values. For example, many APEXes have both
-// com.android.xxx and com.google.android.xxx modules in Soong, but take the same place
-// /apex/com.android.xxx at runtime. In these cases the variant name is always com.android.xxx,
-// regardless which APEX goes into the product. See also android.ApexInfo.ApexVariationName and
-// apex.apexBundleProperties.Apex_name.
-//
-// A related variable PRODUCT_APEX_BOOT_JARS contains bootclasspath libraries that are in APEXes.
-// They are not included in the boot image. The only exception here are ART jars and core-icu4j.jar
-// that have been historically part of the boot image and are now in apexes; they are in boot images
-// and core-icu4j.jar is generally treated as being part of PRODUCT_BOOT_JARS.
-//
-// One exception to the above rules are "coverage" builds (a special build flavor which requires
-// setting environment variable EMMA_INSTRUMENT_FRAMEWORK=true). In coverage builds the Java code in
-// boot image libraries is instrumented, which means that the instrumentation library (jacocoagent)
-// needs to be added to the list of bootclasspath DEX jars.
-//
-// In general, there is a requirement that the source code for a boot image library must be
-// available at build time (e.g. it cannot be a stub that has a separate implementation library).
-//
-// 2.2. Static configs
-// -------------------
-//
-// Because boot images are used to dexpreopt other Java modules, the paths to boot image files must
-// be known by the time dexpreopt build rules for the dependent modules are generated. Boot image
-// configs are constructed very early during the build, before build rule generation. The configs
-// provide predefined paths to boot image files (these paths depend only on static build
-// configuration, such as PRODUCT variables, and use hard-coded directory names).
-//
-// 2.3. Singleton
-// --------------
-//
-// Build rules for the boot images are generated with a Soong singleton. Because a singleton has no
-// dependencies on other modules, it has to find the modules for the DEX jars using VisitAllModules.
-// Soong loops through all modules and compares each module against a list of bootclasspath library
-// names. Then it generates build rules that copy DEX jars from their intermediate module-specific
-// locations to the hard-coded locations predefined in the boot image configs.
-//
-// It would be possible to use a module with proper dependencies instead, but that would require
-// changes in the way Soong generates variables for Make: a singleton can use one MakeVars() method
-// that writes variables to out/soong/make_vars-*.mk, which is included early by the main makefile,
-// but module(s) would have to use out/soong/Android-*.mk which has a group of LOCAL_* variables
-// for each module, and is included later.
-//
-// 2.4. Install rules
-// ------------------
-//
-// The primary boot image and the Framework extension are installed in different ways. The primary
-// boot image is part of the ART APEX: it is copied into the APEX intermediate files, packaged
-// together with other APEX contents, extracted and mounted on device. The Framework boot image
-// extension is installed by the rules defined in makefiles (make/core/dex_preopt_libart.mk). Soong
-// writes out a few DEXPREOPT_IMAGE_* variables for Make; these variables contain boot image names,
-// paths and so on.
-//
-
-var artApexNames = []string{
-	"com.android.art",
-	"com.android.art.debug",
-	"com.android.art.testing",
-	"com.google.android.art",
-	"com.google.android.art.debug",
-	"com.google.android.art.testing",
-}
-
-func init() {
-	RegisterDexpreoptBootJarsComponents(android.InitRegistrationContext)
-}
-
-// Target-independent description of a boot image.
-type bootImageConfig struct {
-	// If this image is an extension, the image that it extends.
-	extends *bootImageConfig
-
-	// Image name (used in directory names and ninja rule names).
-	name string
-
-	// Basename of the image: the resulting filenames are <stem>[-<jar>].{art,oat,vdex}.
-	stem string
-
-	// Output directory for the image files.
-	dir android.OutputPath
-
-	// Output directory for the image files with debug symbols.
-	symbolsDir android.OutputPath
-
-	// Subdirectory where the image files are installed.
-	installDirOnHost string
-
-	// Subdirectory where the image files on device are installed.
-	installDirOnDevice string
-
-	// Install path of the boot image profile if it needs to be installed in the APEX, or empty if not
-	// needed.
-	profileInstallPathInApex string
-
-	// A list of (location, jar) pairs for the Java modules in this image.
-	modules android.ConfiguredJarList
-
-	// File paths to jars.
-	dexPaths     android.WritablePaths // for this image
-	dexPathsDeps android.WritablePaths // for the dependency images and in this image
-
-	// Map from module name (without prebuilt_ prefix) to the predefined build path.
-	dexPathsByModule map[string]android.WritablePath
-
-	// File path to a zip archive with all image files (or nil, if not needed).
-	zip android.WritablePath
-
-	// Rules which should be used in make to install the outputs.
-	profileInstalls android.RuleBuilderInstalls
-
-	// Path to the license metadata file for the module that built the profile.
-	profileLicenseMetadataFile android.OptionalPath
-
-	// Path to the image profile file on host (or empty, if profile is not generated).
-	profilePathOnHost android.Path
-
-	// Target-dependent fields.
-	variants []*bootImageVariant
-
-	// Path of the preloaded classes file.
-	preloadedClassesFile string
-}
-
-// Target-dependent description of a boot image.
-type bootImageVariant struct {
-	*bootImageConfig
-
-	// Target for which the image is generated.
-	target android.Target
-
-	// The "locations" of jars.
-	dexLocations     []string // for this image
-	dexLocationsDeps []string // for the dependency images and in this image
-
-	// Paths to image files.
-	imagePathOnHost   android.OutputPath // first image file path on host
-	imagePathOnDevice string             // first image file path on device
-
-	// All the files that constitute this image variant, i.e. .art, .oat and .vdex files.
-	imagesDeps android.OutputPaths
-
-	// The path to the primary image variant's imagePathOnHost field, where primary image variant
-	// means the image variant that this extends.
-	//
-	// This is only set for a variant of an image that extends another image.
-	primaryImages android.OutputPath
-
-	// The paths to the primary image variant's imagesDeps field, where primary image variant
-	// means the image variant that this extends.
-	//
-	// This is only set for a variant of an image that extends another image.
-	primaryImagesDeps android.Paths
-
-	// Rules which should be used in make to install the outputs on host.
-	installs           android.RuleBuilderInstalls
-	vdexInstalls       android.RuleBuilderInstalls
-	unstrippedInstalls android.RuleBuilderInstalls
-
-	// Rules which should be used in make to install the outputs on device.
-	deviceInstalls android.RuleBuilderInstalls
-
-	// Path to the license metadata file for the module that built the image.
-	licenseMetadataFile android.OptionalPath
-}
-
-// Get target-specific boot image variant for the given boot image config and target.
-func (image bootImageConfig) getVariant(target android.Target) *bootImageVariant {
-	for _, variant := range image.variants {
-		if variant.target.Os == target.Os && variant.target.Arch.ArchType == target.Arch.ArchType {
-			return variant
-		}
-	}
-	return nil
-}
-
-// Return any (the first) variant which is for the device (as opposed to for the host).
-func (image bootImageConfig) getAnyAndroidVariant() *bootImageVariant {
-	for _, variant := range image.variants {
-		if variant.target.Os == android.Android {
-			return variant
-		}
-	}
-	return nil
-}
-
-// Return the name of a boot image module given a boot image config and a component (module) index.
-// A module name is a combination of the Java library name, and the boot image stem (that is stored
-// in the config).
-func (image bootImageConfig) moduleName(ctx android.PathContext, idx int) string {
-	// The first module of the primary boot image is special: its module name has only the stem, but
-	// not the library name. All other module names are of the form <stem>-<library name>
-	m := image.modules.Jar(idx)
-	name := image.stem
-	if idx != 0 || image.extends != nil {
-		name += "-" + android.ModuleStem(m)
-	}
-	return name
-}
-
-// Return the name of the first boot image module, or stem if the list of modules is empty.
-func (image bootImageConfig) firstModuleNameOrStem(ctx android.PathContext) string {
-	if image.modules.Len() > 0 {
-		return image.moduleName(ctx, 0)
-	} else {
-		return image.stem
-	}
-}
-
-// Return filenames for the given boot image component, given the output directory and a list of
-// extensions.
-func (image bootImageConfig) moduleFiles(ctx android.PathContext, dir android.OutputPath, exts ...string) android.OutputPaths {
-	ret := make(android.OutputPaths, 0, image.modules.Len()*len(exts))
-	for i := 0; i < image.modules.Len(); i++ {
-		name := image.moduleName(ctx, i)
-		for _, ext := range exts {
-			ret = append(ret, dir.Join(ctx, name+ext))
-		}
-	}
-	return ret
-}
-
-// apexVariants returns a list of all *bootImageVariant that could be included in an apex.
-func (image *bootImageConfig) apexVariants() []*bootImageVariant {
-	variants := []*bootImageVariant{}
-	for _, variant := range image.variants {
-		// We also generate boot images for host (for testing), but we don't need those in the apex.
-		// TODO(b/177892522) - consider changing this to check Os.OsClass = android.Device
-		if variant.target.Os == android.Android {
-			variants = append(variants, variant)
-		}
-	}
-	return variants
-}
-
-// Returns true if the boot image should be installed in the APEX.
-func (image *bootImageConfig) shouldInstallInApex() bool {
-	return strings.HasPrefix(image.installDirOnDevice, "apex/")
-}
-
-// Return boot image locations (as a list of symbolic paths).
-//
-// The image "location" is a symbolic path that, with multiarchitecture support, doesn't really
-// exist on the device. Typically it is /apex/com.android.art/javalib/boot.art and should be the
-// same for all supported architectures on the device. The concrete architecture specific files
-// actually end up in architecture-specific sub-directory such as arm, arm64, x86, or x86_64.
-//
-// For example a physical file /apex/com.android.art/javalib/x86/boot.art has "image location"
-// /apex/com.android.art/javalib/boot.art (which is not an actual file).
-//
-// For a primary boot image the list of locations has a single element.
-//
-// For a boot image extension the list of locations contains a location for all dependency images
-// (including the primary image) and the location of the extension itself. For example, for the
-// Framework boot image extension that depends on the primary ART boot image the list contains two
-// elements.
-//
-// The location is passed as an argument to the ART tools like dex2oat instead of the real path.
-// ART tools will then reconstruct the architecture-specific real path.
-//
-func (image *bootImageVariant) imageLocations() (imageLocationsOnHost []string, imageLocationsOnDevice []string) {
-	if image.extends != nil {
-		imageLocationsOnHost, imageLocationsOnDevice = image.extends.getVariant(image.target).imageLocations()
-	}
-	return append(imageLocationsOnHost, dexpreopt.PathToLocation(image.imagePathOnHost, image.target.Arch.ArchType)),
-		append(imageLocationsOnDevice, dexpreopt.PathStringToLocation(image.imagePathOnDevice, image.target.Arch.ArchType))
-}
-
-func dexpreoptBootJarsFactory() android.SingletonModule {
-	m := &dexpreoptBootJars{}
-	android.InitAndroidModule(m)
-	return m
-}
-
-func RegisterDexpreoptBootJarsComponents(ctx android.RegistrationContext) {
-	ctx.RegisterSingletonModuleType("dex_bootjars", dexpreoptBootJarsFactory)
-}
-
-func SkipDexpreoptBootJars(ctx android.PathContext) bool {
-	return dexpreopt.GetGlobalConfig(ctx).DisablePreoptBootImages
-}
-
-// Singleton module for generating boot image build rules.
-type dexpreoptBootJars struct {
-	android.SingletonModuleBase
-
-	// Default boot image config (currently always the Framework boot image extension). It should be
-	// noted that JIT-Zygote builds use ART APEX image instead of the Framework boot image extension,
-	// but the switch is handled not here, but in the makefiles (triggered with
-	// DEXPREOPT_USE_ART_IMAGE=true).
-	defaultBootImage *bootImageConfig
-
-	// Build path to a config file that Soong writes for Make (to be used in makefiles that install
-	// the default boot image).
-	dexpreoptConfigForMake android.WritablePath
-}
-
-// Provide paths to boot images for use by modules that depend upon them.
-//
-// The build rules are created in GenerateSingletonBuildActions().
-func (d *dexpreoptBootJars) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	// Placeholder for now.
-}
-
-// Generate build rules for boot images.
-func (d *dexpreoptBootJars) GenerateSingletonBuildActions(ctx android.SingletonContext) {
-	if SkipDexpreoptBootJars(ctx) {
-		return
-	}
-	if dexpreopt.GetCachedGlobalSoongConfig(ctx) == nil {
-		// No module has enabled dexpreopting, so we assume there will be no boot image to make.
-		return
-	}
-
-	d.dexpreoptConfigForMake = android.PathForOutput(ctx, ctx.Config().DeviceName(), "dexpreopt.config")
-	writeGlobalConfigForMake(ctx, d.dexpreoptConfigForMake)
-
-	global := dexpreopt.GetGlobalConfig(ctx)
-	if !shouldBuildBootImages(ctx.Config(), global) {
-		return
-	}
-
-	defaultImageConfig := defaultBootImageConfig(ctx)
-	d.defaultBootImage = defaultImageConfig
-}
-
-// shouldBuildBootImages determines whether boot images should be built.
-func shouldBuildBootImages(config android.Config, global *dexpreopt.GlobalConfig) bool {
-	// Skip recompiling the boot image for the second sanitization phase. We'll get separate paths
-	// and invalidate first-stage artifacts which are crucial to SANITIZE_LITE builds.
-	// Note: this is technically incorrect. Compiled code contains stack checks which may depend
-	//       on ASAN settings.
-	if len(config.SanitizeDevice()) == 1 && config.SanitizeDevice()[0] == "address" && global.SanitizeLite {
-		return false
-	}
-	return true
-}
-
-// copyBootJarsToPredefinedLocations generates commands that will copy boot jars to predefined
-// paths in the global config.
-func copyBootJarsToPredefinedLocations(ctx android.ModuleContext, srcBootDexJarsByModule bootDexJarByModule, dstBootJarsByModule map[string]android.WritablePath) {
-	// Create the super set of module names.
-	names := []string{}
-	names = append(names, android.SortedStringKeys(srcBootDexJarsByModule)...)
-	names = append(names, android.SortedStringKeys(dstBootJarsByModule)...)
-	names = android.SortedUniqueStrings(names)
-	for _, name := range names {
-		src := srcBootDexJarsByModule[name]
-		dst := dstBootJarsByModule[name]
-
-		if src == nil {
-			// A dex boot jar should be provided by the source java module. It needs to be installable or
-			// have compile_dex=true - cf. assignments to java.Module.dexJarFile.
-			//
-			// However, the source java module may be either replaced or overridden (using prefer:true) by
-			// a prebuilt java module with the same name. In that case the dex boot jar needs to be
-			// provided by the corresponding prebuilt APEX module. That APEX is the one that refers
-			// through a exported_(boot|systemserver)classpath_fragments property to a
-			// prebuilt_(boot|systemserver)classpath_fragment module, which in turn lists the prebuilt
-			// java module in the contents property. If that chain is broken then this dependency will
-			// fail.
-			if !ctx.Config().AllowMissingDependencies() {
-				ctx.ModuleErrorf("module %s does not provide a dex boot jar (see comment next to this message in Soong for details)", name)
-			} else {
-				ctx.AddMissingDependencies([]string{name})
-			}
-		} else if dst == nil {
-			ctx.ModuleErrorf("module %s is not part of the boot configuration", name)
-		} else {
-			ctx.Build(pctx, android.BuildParams{
-				Rule:   android.Cp,
-				Input:  src,
-				Output: dst,
-			})
-		}
-	}
-}
-
-// buildBootImageVariantsForAndroidOs generates rules to build the boot image variants for the
-// android.Android OsType and returns a map from the architectures to the paths of the generated
-// boot image files.
-//
-// The paths are returned because they are needed elsewhere in Soong, e.g. for populating an APEX.
-func buildBootImageVariantsForAndroidOs(ctx android.ModuleContext, image *bootImageConfig, profile android.WritablePath) bootImageFilesByArch {
-	return buildBootImageForOsType(ctx, image, profile, android.Android)
-}
-
-// buildBootImageVariantsForBuildOs generates rules to build the boot image variants for the
-// config.BuildOS OsType, i.e. the type of OS on which the build is being running.
-//
-// The files need to be generated into their predefined location because they are used from there
-// both within Soong and outside, e.g. for ART based host side testing and also for use by some
-// cloud based tools. However, they are not needed by callers of this function and so the paths do
-// not need to be returned from this func, unlike the buildBootImageVariantsForAndroidOs func.
-func buildBootImageVariantsForBuildOs(ctx android.ModuleContext, image *bootImageConfig, profile android.WritablePath) {
-	buildBootImageForOsType(ctx, image, profile, ctx.Config().BuildOS)
-}
-
-// buildBootImageForOsType takes a bootImageConfig, a profile file and an android.OsType
-// boot image files are required for and it creates rules to build the boot image
-// files for all the required architectures for them.
-//
-// It returns a map from android.ArchType to the predefined paths of the boot image files.
-func buildBootImageForOsType(ctx android.ModuleContext, image *bootImageConfig, profile android.WritablePath, requiredOsType android.OsType) bootImageFilesByArch {
-	filesByArch := bootImageFilesByArch{}
-	for _, variant := range image.variants {
-		if variant.target.Os == requiredOsType {
-			buildBootImageVariant(ctx, variant, profile)
-			filesByArch[variant.target.Arch.ArchType] = variant.imagesDeps.Paths()
-		}
-	}
-
-	return filesByArch
-}
-
-// buildBootImageZipInPredefinedLocation generates a zip file containing all the boot image files.
-//
-// The supplied filesByArch is nil when the boot image files have not been generated. Otherwise, it
-// is a map from android.ArchType to the predefined locations.
-func buildBootImageZipInPredefinedLocation(ctx android.ModuleContext, image *bootImageConfig, filesByArch bootImageFilesByArch) {
-	if filesByArch == nil {
-		return
-	}
-
-	// Compute the list of files from all the architectures.
-	zipFiles := android.Paths{}
-	for _, archType := range android.ArchTypeList() {
-		zipFiles = append(zipFiles, filesByArch[archType]...)
-	}
-
-	rule := android.NewRuleBuilder(pctx, ctx)
-	rule.Command().
-		BuiltTool("soong_zip").
-		FlagWithOutput("-o ", image.zip).
-		FlagWithArg("-C ", image.dir.Join(ctx, android.Android.String()).String()).
-		FlagWithInputList("-f ", zipFiles, " -f ")
-
-	rule.Build("zip_"+image.name, "zip "+image.name+" image")
-}
-
-// Generate boot image build rules for a specific target.
-func buildBootImageVariant(ctx android.ModuleContext, image *bootImageVariant, profile android.Path) {
-
-	globalSoong := dexpreopt.GetGlobalSoongConfig(ctx)
-	global := dexpreopt.GetGlobalConfig(ctx)
-
-	arch := image.target.Arch.ArchType
-	os := image.target.Os.String() // We need to distinguish host-x86 and device-x86.
-	symbolsDir := image.symbolsDir.Join(ctx, os, image.installDirOnHost, arch.String())
-	symbolsFile := symbolsDir.Join(ctx, image.stem+".oat")
-	outputDir := image.dir.Join(ctx, os, image.installDirOnHost, arch.String())
-	outputPath := outputDir.Join(ctx, image.stem+".oat")
-	oatLocation := dexpreopt.PathToLocation(outputPath, arch)
-	imagePath := outputPath.ReplaceExtension(ctx, "art")
-
-	rule := android.NewRuleBuilder(pctx, ctx)
-
-	rule.Command().Text("mkdir").Flag("-p").Flag(symbolsDir.String())
-	rule.Command().Text("rm").Flag("-f").
-		Flag(symbolsDir.Join(ctx, "*.art").String()).
-		Flag(symbolsDir.Join(ctx, "*.oat").String()).
-		Flag(symbolsDir.Join(ctx, "*.invocation").String())
-	rule.Command().Text("rm").Flag("-f").
-		Flag(outputDir.Join(ctx, "*.art").String()).
-		Flag(outputDir.Join(ctx, "*.oat").String()).
-		Flag(outputDir.Join(ctx, "*.invocation").String())
-
-	cmd := rule.Command()
-
-	extraFlags := ctx.Config().Getenv("ART_BOOT_IMAGE_EXTRA_ARGS")
-	if extraFlags == "" {
-		// Use ANDROID_LOG_TAGS to suppress most logging by default...
-		cmd.Text(`ANDROID_LOG_TAGS="*:e"`)
-	} else {
-		// ...unless the boot image is generated specifically for testing, then allow all logging.
-		cmd.Text(`ANDROID_LOG_TAGS="*:v"`)
-	}
-
-	invocationPath := outputPath.ReplaceExtension(ctx, "invocation")
-
-	cmd.Tool(globalSoong.Dex2oat).
-		Flag("--avoid-storing-invocation").
-		FlagWithOutput("--write-invocation-to=", invocationPath).ImplicitOutput(invocationPath).
-		Flag("--runtime-arg").FlagWithArg("-Xms", global.Dex2oatImageXms).
-		Flag("--runtime-arg").FlagWithArg("-Xmx", global.Dex2oatImageXmx)
-
-	if profile != nil {
-		cmd.FlagWithInput("--profile-file=", profile)
-	}
-
-	dirtyImageFile := "frameworks/base/config/dirty-image-objects"
-	dirtyImagePath := android.ExistentPathForSource(ctx, dirtyImageFile)
-	if dirtyImagePath.Valid() {
-		cmd.FlagWithInput("--dirty-image-objects=", dirtyImagePath.Path())
-	}
-
-	if image.extends != nil {
-		// It is a boot image extension, so it needs the boot image it depends on (in this case the
-		// primary ART APEX image).
-		artImage := image.primaryImages
-		cmd.
-			Flag("--runtime-arg").FlagWithInputList("-Xbootclasspath:", image.dexPathsDeps.Paths(), ":").
-			Flag("--runtime-arg").FlagWithList("-Xbootclasspath-locations:", image.dexLocationsDeps, ":").
-			// Add the path to the first file in the boot image with the arch specific directory removed,
-			// dex2oat will reconstruct the path to the actual file when it needs it. As the actual path
-			// to the file cannot be passed to the command make sure to add the actual path as an Implicit
-			// dependency to ensure that it is built before the command runs.
-			FlagWithArg("--boot-image=", dexpreopt.PathToLocation(artImage, arch)).Implicit(artImage).
-			// Similarly, the dex2oat tool will automatically find the paths to other files in the base
-			// boot image so make sure to add them as implicit dependencies to ensure that they are built
-			// before this command is run.
-			Implicits(image.primaryImagesDeps)
-	} else {
-		// It is a primary image, so it needs a base address.
-		cmd.FlagWithArg("--base=", ctx.Config().LibartImgDeviceBaseAddress())
-	}
-
-	// We always expect a preloaded classes file to be available. However, if we cannot find it, it's
-	// OK to not pass the flag to dex2oat.
-	preloadedClassesPath := android.ExistentPathForSource(ctx, image.preloadedClassesFile)
-	if preloadedClassesPath.Valid() {
-		cmd.FlagWithInput("--preloaded-classes=", preloadedClassesPath.Path())
-	}
-
-	cmd.
-		FlagForEachInput("--dex-file=", image.dexPaths.Paths()).
-		FlagForEachArg("--dex-location=", image.dexLocations).
-		Flag("--generate-debug-info").
-		Flag("--generate-build-id").
-		Flag("--image-format=lz4hc").
-		FlagWithArg("--oat-symbols=", symbolsFile.String()).
-		Flag("--strip").
-		FlagWithArg("--oat-file=", outputPath.String()).
-		FlagWithArg("--oat-location=", oatLocation).
-		FlagWithArg("--image=", imagePath.String()).
-		FlagWithArg("--instruction-set=", arch.String()).
-		FlagWithArg("--android-root=", global.EmptyDirectory).
-		FlagWithArg("--no-inline-from=", "core-oj.jar").
-		Flag("--force-determinism").
-		Flag("--abort-on-hard-verifier-error")
-
-	// Use the default variant/features for host builds.
-	// The map below contains only device CPU info (which might be x86 on some devices).
-	if image.target.Os == android.Android {
-		cmd.FlagWithArg("--instruction-set-variant=", global.CpuVariant[arch])
-		cmd.FlagWithArg("--instruction-set-features=", global.InstructionSetFeatures[arch])
-	}
-
-	if global.BootFlags != "" {
-		cmd.Flag(global.BootFlags)
-	}
-
-	if extraFlags != "" {
-		cmd.Flag(extraFlags)
-	}
-
-	cmd.Textf(`|| ( echo %s ; false )`, proptools.ShellEscape(failureMessage))
-
-	installDir := filepath.Join("/", image.installDirOnHost, arch.String())
-
-	var vdexInstalls android.RuleBuilderInstalls
-	var unstrippedInstalls android.RuleBuilderInstalls
-	var deviceInstalls android.RuleBuilderInstalls
-
-	for _, artOrOat := range image.moduleFiles(ctx, outputDir, ".art", ".oat") {
-		cmd.ImplicitOutput(artOrOat)
-
-		// Install the .oat and .art files
-		rule.Install(artOrOat, filepath.Join(installDir, artOrOat.Base()))
-	}
-
-	for _, vdex := range image.moduleFiles(ctx, outputDir, ".vdex") {
-		cmd.ImplicitOutput(vdex)
-
-		// Note that the vdex files are identical between architectures.
-		// Make rules will create symlinks to share them between architectures.
-		vdexInstalls = append(vdexInstalls,
-			android.RuleBuilderInstall{vdex, filepath.Join(installDir, vdex.Base())})
-	}
-
-	for _, unstrippedOat := range image.moduleFiles(ctx, symbolsDir, ".oat") {
-		cmd.ImplicitOutput(unstrippedOat)
-
-		// Install the unstripped oat files.  The Make rules will put these in $(TARGET_OUT_UNSTRIPPED)
-		unstrippedInstalls = append(unstrippedInstalls,
-			android.RuleBuilderInstall{unstrippedOat, filepath.Join(installDir, unstrippedOat.Base())})
-	}
-
-	if image.installDirOnHost != image.installDirOnDevice && !image.shouldInstallInApex() && !ctx.Config().UnbundledBuild() {
-		installDirOnDevice := filepath.Join("/", image.installDirOnDevice, arch.String())
-		for _, file := range image.moduleFiles(ctx, outputDir, ".art", ".oat", ".vdex") {
-			deviceInstalls = append(deviceInstalls,
-				android.RuleBuilderInstall{file, filepath.Join(installDirOnDevice, file.Base())})
-		}
-	}
-
-	rule.Build(image.name+"JarsDexpreopt_"+image.target.String(), "dexpreopt "+image.name+" jars "+arch.String())
-
-	// save output and installed files for makevars
-	image.installs = rule.Installs()
-	image.vdexInstalls = vdexInstalls
-	image.unstrippedInstalls = unstrippedInstalls
-	image.deviceInstalls = deviceInstalls
-	image.licenseMetadataFile = android.OptionalPathForPath(ctx.LicenseMetadataFile())
-}
-
-const failureMessage = `ERROR: Dex2oat failed to compile a boot image.
-It is likely that the boot classpath is inconsistent.
-Rebuild with ART_BOOT_IMAGE_EXTRA_ARGS="--runtime-arg -verbose:verifier" to see verification errors.`
-
-func bootImageProfileRule(ctx android.ModuleContext, image *bootImageConfig) android.WritablePath {
-	globalSoong := dexpreopt.GetGlobalSoongConfig(ctx)
-	global := dexpreopt.GetGlobalConfig(ctx)
-
-	if global.DisableGenerateProfile {
-		return nil
-	}
-
-	defaultProfile := "frameworks/base/config/boot-image-profile.txt"
-
-	rule := android.NewRuleBuilder(pctx, ctx)
-
-	var bootImageProfile android.Path
-	if len(global.BootImageProfiles) > 1 {
-		combinedBootImageProfile := image.dir.Join(ctx, "boot-image-profile.txt")
-		rule.Command().Text("cat").Inputs(global.BootImageProfiles).Text(">").Output(combinedBootImageProfile)
-		bootImageProfile = combinedBootImageProfile
-	} else if len(global.BootImageProfiles) == 1 {
-		bootImageProfile = global.BootImageProfiles[0]
-	} else if path := android.ExistentPathForSource(ctx, defaultProfile); path.Valid() {
-		bootImageProfile = path.Path()
-	} else {
-		// No profile (not even a default one, which is the case on some branches
-		// like master-art-host that don't have frameworks/base).
-		// Return nil and continue without profile.
-		return nil
-	}
-
-	profile := image.dir.Join(ctx, "boot.prof")
-
-	rule.Command().
-		Text(`ANDROID_LOG_TAGS="*:e"`).
-		Tool(globalSoong.Profman).
-		Flag("--output-profile-type=boot").
-		FlagWithInput("--create-profile-from=", bootImageProfile).
-		FlagForEachInput("--apk=", image.dexPathsDeps.Paths()).
-		FlagForEachArg("--dex-location=", image.getAnyAndroidVariant().dexLocationsDeps).
-		FlagWithOutput("--reference-profile-file=", profile)
-
-	if image == defaultBootImageConfig(ctx) {
-		rule.Install(profile, "/system/etc/boot-image.prof")
-		image.profileInstalls = append(image.profileInstalls, rule.Installs()...)
-		image.profileLicenseMetadataFile = android.OptionalPathForPath(ctx.LicenseMetadataFile())
-	}
-
-	rule.Build("bootJarsProfile", "profile boot jars")
-
-	image.profilePathOnHost = profile
-
-	return profile
-}
-
-// bootFrameworkProfileRule generates the rule to create the boot framework profile and
-// returns a path to the generated file.
-func bootFrameworkProfileRule(ctx android.ModuleContext, image *bootImageConfig) android.WritablePath {
-	globalSoong := dexpreopt.GetGlobalSoongConfig(ctx)
-	global := dexpreopt.GetGlobalConfig(ctx)
-
-	if global.DisableGenerateProfile || ctx.Config().UnbundledBuild() {
-		return nil
-	}
-
-	defaultProfile := "frameworks/base/config/boot-profile.txt"
-	bootFrameworkProfile := android.PathForSource(ctx, defaultProfile)
-
-	profile := image.dir.Join(ctx, "boot.bprof")
-
-	rule := android.NewRuleBuilder(pctx, ctx)
-	rule.Command().
-		Text(`ANDROID_LOG_TAGS="*:e"`).
-		Tool(globalSoong.Profman).
-		Flag("--output-profile-type=bprof").
-		FlagWithInput("--create-profile-from=", bootFrameworkProfile).
-		FlagForEachInput("--apk=", image.dexPathsDeps.Paths()).
-		FlagForEachArg("--dex-location=", image.getAnyAndroidVariant().dexLocationsDeps).
-		FlagWithOutput("--reference-profile-file=", profile)
-
-	rule.Install(profile, "/system/etc/boot-image.bprof")
-	rule.Build("bootFrameworkProfile", "profile boot framework jars")
-	image.profileInstalls = append(image.profileInstalls, rule.Installs()...)
-	image.profileLicenseMetadataFile = android.OptionalPathForPath(ctx.LicenseMetadataFile())
-
-	return profile
-}
-
-func dumpOatRules(ctx android.ModuleContext, image *bootImageConfig) {
-	var allPhonies android.Paths
-	for _, image := range image.variants {
-		arch := image.target.Arch.ArchType
-		suffix := arch.String()
-		// Host and target might both use x86 arch. We need to ensure the names are unique.
-		if image.target.Os.Class == android.Host {
-			suffix = "host-" + suffix
-		}
-		// Create a rule to call oatdump.
-		output := android.PathForOutput(ctx, "boot."+suffix+".oatdump.txt")
-		rule := android.NewRuleBuilder(pctx, ctx)
-		imageLocationsOnHost, _ := image.imageLocations()
-		rule.Command().
-			BuiltTool("oatdump").
-			FlagWithInputList("--runtime-arg -Xbootclasspath:", image.dexPathsDeps.Paths(), ":").
-			FlagWithList("--runtime-arg -Xbootclasspath-locations:", image.dexLocationsDeps, ":").
-			FlagWithArg("--image=", strings.Join(imageLocationsOnHost, ":")).Implicits(image.imagesDeps.Paths()).
-			FlagWithOutput("--output=", output).
-			FlagWithArg("--instruction-set=", arch.String())
-		rule.Build("dump-oat-boot-"+suffix, "dump oat boot "+arch.String())
-
-		// Create a phony rule that depends on the output file and prints the path.
-		phony := android.PathForPhony(ctx, "dump-oat-boot-"+suffix)
-		rule = android.NewRuleBuilder(pctx, ctx)
-		rule.Command().
-			Implicit(output).
-			ImplicitOutput(phony).
-			Text("echo").FlagWithArg("Output in ", output.String())
-		rule.Build("phony-dump-oat-boot-"+suffix, "dump oat boot "+arch.String())
-
-		allPhonies = append(allPhonies, phony)
-	}
-
-	phony := android.PathForPhony(ctx, "dump-oat-boot")
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        android.Phony,
-		Output:      phony,
-		Inputs:      allPhonies,
-		Description: "dump-oat-boot",
-	})
-}
-
-func writeGlobalConfigForMake(ctx android.SingletonContext, path android.WritablePath) {
-	data := dexpreopt.GetGlobalConfigRawData(ctx)
-
-	android.WriteFileRule(ctx, path, string(data))
-}
-
-// Define Make variables for boot image names, paths, etc. These variables are used in makefiles
-// (make/core/dex_preopt_libart.mk) to generate install rules that copy boot image files to the
-// correct output directories.
-func (d *dexpreoptBootJars) MakeVars(ctx android.MakeVarsContext) {
-	if d.dexpreoptConfigForMake != nil {
-		ctx.Strict("DEX_PREOPT_CONFIG_FOR_MAKE", d.dexpreoptConfigForMake.String())
-		ctx.Strict("DEX_PREOPT_SOONG_CONFIG_FOR_MAKE", android.PathForOutput(ctx, "dexpreopt_soong.config").String())
-	}
-
-	image := d.defaultBootImage
-	if image == nil {
-		return
-	}
-
-	ctx.Strict("DEXPREOPT_IMAGE_PROFILE_BUILT_INSTALLED", image.profileInstalls.String())
-	if image.profileLicenseMetadataFile.Valid() {
-		ctx.Strict("DEXPREOPT_IMAGE_PROFILE_LICENSE_METADATA", image.profileLicenseMetadataFile.String())
-	}
-
-	global := dexpreopt.GetGlobalConfig(ctx)
-	dexPaths, dexLocations := bcpForDexpreopt(ctx, global.PreoptWithUpdatableBcp)
-	ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_FILES", strings.Join(dexPaths.Strings(), " "))
-	ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS", strings.Join(dexLocations, " "))
-
-	for _, variant := range image.variants {
-		suffix := ""
-		if variant.target.Os.Class == android.Host {
-			suffix = "_host"
-		}
-		sfx := suffix + "_" + variant.target.Arch.ArchType.String()
-		ctx.Strict("DEXPREOPT_IMAGE_VDEX_BUILT_INSTALLED_"+sfx, variant.vdexInstalls.String())
-		ctx.Strict("DEXPREOPT_IMAGE_"+sfx, variant.imagePathOnHost.String())
-		ctx.Strict("DEXPREOPT_IMAGE_DEPS_"+sfx, strings.Join(variant.imagesDeps.Strings(), " "))
-		ctx.Strict("DEXPREOPT_IMAGE_BUILT_INSTALLED_"+sfx, variant.installs.String())
-		ctx.Strict("DEXPREOPT_IMAGE_UNSTRIPPED_BUILT_INSTALLED_"+sfx, variant.unstrippedInstalls.String())
-		if variant.licenseMetadataFile.Valid() {
-			ctx.Strict("DEXPREOPT_IMAGE_LICENSE_METADATA_"+sfx, variant.licenseMetadataFile.String())
-		}
-	}
-	imageLocationsOnHost, imageLocationsOnDevice := image.getAnyAndroidVariant().imageLocations()
-	ctx.Strict("DEXPREOPT_IMAGE_LOCATIONS_ON_HOST", strings.Join(imageLocationsOnHost, ":"))
-	ctx.Strict("DEXPREOPT_IMAGE_LOCATIONS_ON_DEVICE", strings.Join(imageLocationsOnDevice, ":"))
-	ctx.Strict("DEXPREOPT_IMAGE_ZIP", image.zip.String())
-
-	// There used to be multiple images for JIT-Zygote mode, not there's only one.
-	ctx.Strict("DEXPREOPT_IMAGE_NAMES", image.name)
-}
diff --git a/java/dexpreopt_config.go_v1 b/java/dexpreopt_config.go_v1
deleted file mode 100644
index d71e2bb..0000000
--- a/java/dexpreopt_config.go_v1
+++ /dev/null
@@ -1,215 +0,0 @@
-// Copyright 2019 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package java
-
-import (
-	"path/filepath"
-	"strings"
-
-	"android/soong/android"
-	"android/soong/dexpreopt"
-)
-
-// dexpreoptTargets returns the list of targets that are relevant to dexpreopting, which excludes architectures
-// supported through native bridge.
-func dexpreoptTargets(ctx android.PathContext) []android.Target {
-	var targets []android.Target
-	for _, target := range ctx.Config().Targets[android.Android] {
-		if target.NativeBridge == android.NativeBridgeDisabled {
-			targets = append(targets, target)
-		}
-	}
-	// We may also need the images on host in order to run host-based tests.
-	for _, target := range ctx.Config().Targets[ctx.Config().BuildOS] {
-		targets = append(targets, target)
-	}
-
-	return targets
-}
-
-var (
-	bootImageConfigKey     = android.NewOnceKey("bootImageConfig")
-	bootImageConfigRawKey  = android.NewOnceKey("bootImageConfigRaw")
-	artBootImageName       = "art"
-	frameworkBootImageName = "boot"
-)
-
-func genBootImageConfigRaw(ctx android.PathContext) map[string]*bootImageConfig {
-	return ctx.Config().Once(bootImageConfigRawKey, func() interface{} {
-		global := dexpreopt.GetGlobalConfig(ctx)
-
-		artModules := global.ArtApexJars
-		frameworkModules := global.BootJars.RemoveList(artModules)
-
-		// ART config for the primary boot image in the ART apex.
-		// It includes the Core Libraries.
-		artCfg := bootImageConfig{
-			name:                     artBootImageName,
-			stem:                     "boot",
-			installDirOnHost:         "apex/art_boot_images/javalib",
-			installDirOnDevice:       "system/framework",
-			profileInstallPathInApex: "etc/boot-image.prof",
-			modules:                  artModules,
-			preloadedClassesFile:     "art/build/boot/preloaded-classes",
-		}
-
-		// Framework config for the boot image extension.
-		// It includes framework libraries and depends on the ART config.
-		frameworkSubdir := "system/framework"
-		frameworkCfg := bootImageConfig{
-			extends:              &artCfg,
-			name:                 frameworkBootImageName,
-			stem:                 "boot",
-			installDirOnHost:     frameworkSubdir,
-			installDirOnDevice:   frameworkSubdir,
-			modules:              frameworkModules,
-			preloadedClassesFile: "frameworks/base/config/preloaded-classes",
-		}
-
-		return map[string]*bootImageConfig{
-			artBootImageName:       &artCfg,
-			frameworkBootImageName: &frameworkCfg,
-		}
-	}).(map[string]*bootImageConfig)
-}
-
-// Construct the global boot image configs.
-func genBootImageConfigs(ctx android.PathContext) map[string]*bootImageConfig {
-	return ctx.Config().Once(bootImageConfigKey, func() interface{} {
-		targets := dexpreoptTargets(ctx)
-		deviceDir := android.PathForOutput(ctx, ctx.Config().DeviceName())
-
-		configs := genBootImageConfigRaw(ctx)
-		artCfg := configs[artBootImageName]
-		frameworkCfg := configs[frameworkBootImageName]
-
-		// common to all configs
-		for _, c := range configs {
-			c.dir = deviceDir.Join(ctx, "dex_"+c.name+"jars")
-			c.symbolsDir = deviceDir.Join(ctx, "dex_"+c.name+"jars_unstripped")
-
-			// expands to <stem>.art for primary image and <stem>-<1st module>.art for extension
-			imageName := c.firstModuleNameOrStem(ctx) + ".art"
-
-			// The path to bootclasspath dex files needs to be known at module
-			// GenerateAndroidBuildAction time, before the bootclasspath modules have been compiled.
-			// Set up known paths for them, the singleton rules will copy them there.
-			// TODO(b/143682396): use module dependencies instead
-			inputDir := deviceDir.Join(ctx, "dex_"+c.name+"jars_input")
-			c.dexPaths = c.modules.BuildPaths(ctx, inputDir)
-			c.dexPathsByModule = c.modules.BuildPathsByModule(ctx, inputDir)
-			c.dexPathsDeps = c.dexPaths
-
-			// Create target-specific variants.
-			for _, target := range targets {
-				arch := target.Arch.ArchType
-				imageDir := c.dir.Join(ctx, target.Os.String(), c.installDirOnHost, arch.String())
-				variant := &bootImageVariant{
-					bootImageConfig:   c,
-					target:            target,
-					imagePathOnHost:   imageDir.Join(ctx, imageName),
-					imagePathOnDevice: filepath.Join("/", c.installDirOnDevice, arch.String(), imageName),
-					imagesDeps:        c.moduleFiles(ctx, imageDir, ".art", ".oat", ".vdex"),
-					dexLocations:      c.modules.DevicePaths(ctx.Config(), target.Os),
-				}
-				variant.dexLocationsDeps = variant.dexLocations
-				c.variants = append(c.variants, variant)
-			}
-
-			c.zip = c.dir.Join(ctx, c.name+".zip")
-		}
-
-		// specific to the framework config
-		frameworkCfg.dexPathsDeps = append(artCfg.dexPathsDeps, frameworkCfg.dexPathsDeps...)
-		for i := range targets {
-			frameworkCfg.variants[i].primaryImages = artCfg.variants[i].imagePathOnHost
-			frameworkCfg.variants[i].primaryImagesDeps = artCfg.variants[i].imagesDeps.Paths()
-			frameworkCfg.variants[i].dexLocationsDeps = append(artCfg.variants[i].dexLocations, frameworkCfg.variants[i].dexLocationsDeps...)
-		}
-
-		return configs
-	}).(map[string]*bootImageConfig)
-}
-
-func defaultBootImageConfig(ctx android.PathContext) *bootImageConfig {
-	return genBootImageConfigs(ctx)[frameworkBootImageName]
-}
-
-// Apex boot config allows to access build/install paths of apex boot jars without going
-// through the usual trouble of registering dependencies on those modules and extracting build paths
-// from those dependencies.
-type apexBootConfig struct {
-	// A list of apex boot jars.
-	modules android.ConfiguredJarList
-
-	// A list of predefined build paths to apex boot jars. They are configured very early,
-	// before the modules for these jars are processed and the actual paths are generated, and
-	// later on a singleton adds commands to copy actual jars to the predefined paths.
-	dexPaths android.WritablePaths
-
-	// Map from module name (without prebuilt_ prefix) to the predefined build path.
-	dexPathsByModule map[string]android.WritablePath
-
-	// A list of dex locations (a.k.a. on-device paths) to the boot jars.
-	dexLocations []string
-}
-
-var updatableBootConfigKey = android.NewOnceKey("apexBootConfig")
-
-// Returns apex boot config.
-func GetApexBootConfig(ctx android.PathContext) apexBootConfig {
-	return ctx.Config().Once(updatableBootConfigKey, func() interface{} {
-		apexBootJars := dexpreopt.GetGlobalConfig(ctx).ApexBootJars
-
-		dir := android.PathForOutput(ctx, ctx.Config().DeviceName(), "apex_bootjars")
-		dexPaths := apexBootJars.BuildPaths(ctx, dir)
-		dexPathsByModuleName := apexBootJars.BuildPathsByModule(ctx, dir)
-
-		dexLocations := apexBootJars.DevicePaths(ctx.Config(), android.Android)
-
-		return apexBootConfig{apexBootJars, dexPaths, dexPathsByModuleName, dexLocations}
-	}).(apexBootConfig)
-}
-
-// Returns a list of paths and a list of locations for the boot jars used in dexpreopt (to be
-// passed in -Xbootclasspath and -Xbootclasspath-locations arguments for dex2oat).
-func bcpForDexpreopt(ctx android.PathContext, withUpdatable bool) (android.WritablePaths, []string) {
-	// Non-updatable boot jars (they are used both in the boot image and in dexpreopt).
-	bootImage := defaultBootImageConfig(ctx)
-	dexPaths := bootImage.dexPathsDeps
-	// The dex locations for all Android variants are identical.
-	dexLocations := bootImage.getAnyAndroidVariant().dexLocationsDeps
-
-	if withUpdatable {
-		// Apex boot jars (they are used only in dexpreopt, but not in the boot image).
-		apexBootConfig := GetApexBootConfig(ctx)
-		dexPaths = append(dexPaths, apexBootConfig.dexPaths...)
-		dexLocations = append(dexLocations, apexBootConfig.dexLocations...)
-	}
-
-	return dexPaths, dexLocations
-}
-
-var defaultBootclasspathKey = android.NewOnceKey("defaultBootclasspath")
-
-var copyOf = android.CopyOf
-
-func init() {
-	android.RegisterMakeVarsProvider(pctx, dexpreoptConfigMakevars)
-}
-
-func dexpreoptConfigMakevars(ctx android.MakeVarsContext) {
-	ctx.Strict("DEXPREOPT_BOOT_JARS_MODULES", strings.Join(defaultBootImageConfig(ctx).modules.CopyOfApexJarPairs(), ":"))
-}
diff --git a/java/java.go b/java/java.go
index cbdc2bd..6e05159 100644
--- a/java/java.go
+++ b/java/java.go
@@ -513,6 +513,20 @@
 	}
 }
 
+func (v javaVersion) StringForKotlinc() string {
+	// $ ./external/kotlinc/bin/kotlinc -jvm-target foo
+	// error: unknown JVM target version: foo
+	// Supported versions: 1.6, 1.8, 9, 10, 11, 12, 13, 14, 15, 16, 17
+	switch v {
+	case JAVA_VERSION_7:
+		return "1.6"
+	case JAVA_VERSION_9:
+		return "9"
+	default:
+		return v.String()
+	}
+}
+
 // Returns true if javac targeting this version uses system modules instead of a bootclasspath.
 func (v javaVersion) usesJavaModules() bool {
 	return v >= 9
diff --git a/java/kotlin.go b/java/kotlin.go
index 903c624..9bff5ea 100644
--- a/java/kotlin.go
+++ b/java/kotlin.go
@@ -119,9 +119,8 @@
 			"srcJarDir":         android.PathForModuleOut(ctx, "kotlinc", "srcJars").String(),
 			"kotlinBuildFile":   android.PathForModuleOut(ctx, "kotlinc-build.xml").String(),
 			"emptyDir":          android.PathForModuleOut(ctx, "kotlinc", "empty").String(),
-			// http://b/69160377 kotlinc only supports -jvm-target 1.6 and 1.8
-			"kotlinJvmTarget": "1.8",
-			"name":            kotlinName,
+			"kotlinJvmTarget":   flags.javaVersion.StringForKotlinc(),
+			"name":              kotlinName,
 		},
 	})
 }
diff --git a/java/systemserver_classpath_fragment.go b/java/systemserver_classpath_fragment.go
index 79d2ee9..a2cd261 100644
--- a/java/systemserver_classpath_fragment.go
+++ b/java/systemserver_classpath_fragment.go
@@ -24,15 +24,7 @@
 func init() {
 	registerSystemserverClasspathBuildComponents(android.InitRegistrationContext)
 
-	android.RegisterSdkMemberType(&systemServerClasspathFragmentMemberType{
-		SdkMemberTypeBase: android.SdkMemberTypeBase{
-			PropertyName: "systemserverclasspath_fragments",
-			SupportsSdk:  true,
-
-			// This was only added in Tiramisu.
-			SupportedBuildReleaseSpecification: "Tiramisu+",
-		},
-	})
+	android.RegisterSdkMemberType(SystemServerClasspathFragmentSdkMemberType)
 }
 
 func registerSystemserverClasspathBuildComponents(ctx android.RegistrationContext) {
@@ -41,6 +33,17 @@
 	ctx.RegisterModuleType("prebuilt_systemserverclasspath_fragment", prebuiltSystemServerClasspathModuleFactory)
 }
 
+var SystemServerClasspathFragmentSdkMemberType = &systemServerClasspathFragmentMemberType{
+	SdkMemberTypeBase: android.SdkMemberTypeBase{
+		PropertyName: "systemserverclasspath_fragments",
+		SupportsSdk:  true,
+
+		// Support for adding systemserverclasspath_fragments to the sdk snapshot was only added in
+		// Tiramisu.
+		SupportedBuildReleaseSpecification: "Tiramisu+",
+	},
+}
+
 type platformSystemServerClasspathModule struct {
 	android.ModuleBase
 
diff --git a/rust/config/global.go b/rust/config/global.go
index 3ef0ecb..9e48344 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -24,7 +24,7 @@
 var pctx = android.NewPackageContext("android/soong/rust/config")
 
 var (
-	RustDefaultVersion = "1.61.0.p2"
+	RustDefaultVersion = "1.62.0"
 	RustDefaultBase    = "prebuilts/rust/"
 	DefaultEdition     = "2021"
 	Stdlibs            = []string{
diff --git a/scripts/Android.bp b/scripts/Android.bp
index 4773579..814bd57 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -84,6 +84,16 @@
     ],
 }
 
+python_test_host {
+    name: "jsonmodify_test",
+    main: "jsonmodify_test.py",
+    srcs: [
+        "jsonmodify_test.py",
+        "jsonmodify.py",
+    ],
+    test_suites: ["general-tests"],
+}
+
 python_binary_host {
     name: "test_config_fixer",
     main: "test_config_fixer.py",
@@ -193,3 +203,9 @@
     name: "list_image",
     src: "list_image.sh",
 }
+
+filegroup {
+    name: "rustfmt.toml",
+    srcs: ["rustfmt.toml"],
+    visibility: ["//visibility:public"],
+}
diff --git a/scripts/jsonmodify.py b/scripts/jsonmodify.py
index ba1109e..8bd8d45 100755
--- a/scripts/jsonmodify.py
+++ b/scripts/jsonmodify.py
@@ -59,6 +59,13 @@
       cur[key] = val
 
 
+class ReplaceIfEqual(str):
+  def apply(self, obj, old_val, new_val):
+    cur, key = follow_path(obj, self)
+    if cur and cur[key] == int(old_val):
+      cur[key] = new_val
+
+
 class Remove(str):
   def apply(self, obj):
     cur, key = follow_path(obj, self)
@@ -75,6 +82,14 @@
       raise ValueError(self + " should be a array.")
     cur[key].extend(args)
 
+# A JSONDecoder that supports line comments start with //
+class JSONWithCommentsDecoder(json.JSONDecoder):
+  def __init__(self, **kw):
+    super().__init__(**kw)
+
+  def decode(self, s: str):
+    s = '\n'.join(l for l in s.split('\n') if not l.lstrip(' ').startswith('//'))
+    return super().decode(s)
 
 def main():
   parser = argparse.ArgumentParser()
@@ -91,6 +106,11 @@
                       help='replace value of the key specified by path. If path doesn\'t exist, no op.',
                       metavar=('path', 'value'),
                       nargs=2, dest='patch', action='append')
+  parser.add_argument("-se", "--replace-if-equal", type=ReplaceIfEqual,
+                      help='replace value of the key specified by path to new_value if it\'s equal to old_value.' +
+                      'If path doesn\'t exist or the value is not equal to old_value, no op.',
+                      metavar=('path', 'old_value', 'new_value'),
+                      nargs=3, dest='patch', action='append')
   parser.add_argument("-r", "--remove", type=Remove,
                       help='remove the key specified by path. If path doesn\'t exist, no op.',
                       metavar='path',
@@ -103,9 +123,9 @@
 
   if args.input:
     with open(args.input) as f:
-      obj = json.load(f, object_pairs_hook=collections.OrderedDict)
+      obj = json.load(f, object_pairs_hook=collections.OrderedDict, cls=JSONWithCommentsDecoder)
   else:
-    obj = json.load(sys.stdin, object_pairs_hook=collections.OrderedDict)
+    obj = json.load(sys.stdin, object_pairs_hook=collections.OrderedDict, cls=JSONWithCommentsDecoder)
 
   for p in args.patch:
     p[0].apply(obj, *p[1:])
diff --git a/scripts/jsonmodify_test.py b/scripts/jsonmodify_test.py
new file mode 100644
index 0000000..6f0291d
--- /dev/null
+++ b/scripts/jsonmodify_test.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Tests for jsonmodify."""
+
+import json
+import jsonmodify
+import unittest
+
+
+class JsonmodifyTest(unittest.TestCase):
+
+  def test_set_value(self):
+    obj = json.loads('{"field1": 111}')
+    field1 = jsonmodify.SetValue("field1")
+    field1.apply(obj, 222)
+    field2 = jsonmodify.SetValue("field2")
+    field2.apply(obj, 333)
+    expected = json.loads('{"field1": 222, "field2": 333}')
+    self.assertEqual(obj, expected)
+
+  def test_replace(self):
+    obj = json.loads('{"field1": 111}')
+    field1 = jsonmodify.Replace("field1")
+    field1.apply(obj, 222)
+    field2 = jsonmodify.Replace("field2")
+    field2.apply(obj, 333)
+    expected = json.loads('{"field1": 222}')
+    self.assertEqual(obj, expected)
+
+  def test_replace_if_equal(self):
+    obj = json.loads('{"field1": 111, "field2": 222}')
+    field1 = jsonmodify.ReplaceIfEqual("field1")
+    field1.apply(obj, 111, 333)
+    field2 = jsonmodify.ReplaceIfEqual("field2")
+    field2.apply(obj, 444, 555)
+    field3 = jsonmodify.ReplaceIfEqual("field3")
+    field3.apply(obj, 666, 777)
+    expected = json.loads('{"field1": 333, "field2": 222}')
+    self.assertEqual(obj, expected)
+
+  def test_remove(self):
+    obj = json.loads('{"field1": 111, "field2": 222}')
+    field2 = jsonmodify.Remove("field2")
+    field2.apply(obj)
+    field3 = jsonmodify.Remove("field3")
+    field3.apply(obj)
+    expected = json.loads('{"field1": 111}')
+    self.assertEqual(obj, expected)
+
+  def test_append_list(self):
+    obj = json.loads('{"field1": [111]}')
+    field1 = jsonmodify.AppendList("field1")
+    field1.apply(obj, 222, 333)
+    field2 = jsonmodify.AppendList("field2")
+    field2.apply(obj, 444, 555, 666)
+    expected = json.loads('{"field1": [111, 222, 333], "field2": [444, 555, 666]}')
+    self.assertEqual(obj, expected)
+
+
+if __name__ == '__main__':
+    unittest.main(verbosity=2)
diff --git a/sdk/bootclasspath_fragment_sdk_test.go b/sdk/bootclasspath_fragment_sdk_test.go
index 93ad172..13ddbe7 100644
--- a/sdk/bootclasspath_fragment_sdk_test.go
+++ b/sdk/bootclasspath_fragment_sdk_test.go
@@ -190,7 +190,7 @@
 	android.AssertStringDoesContain(t, "boot jars package check", command, expectedCommandArgs)
 }
 
-func TestSnapshotWithBootClasspathFragment_Contents(t *testing.T) {
+func testSnapshotWithBootClasspathFragment_Contents(t *testing.T, sdk string, copyRules string) {
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
 		java.PrepareForTestWithJavaDefaultModules,
@@ -202,19 +202,7 @@
 		// Add a platform_bootclasspath that depends on the fragment.
 		fixtureAddPlatformBootclasspathForBootclasspathFragment("myapex", "mybootclasspathfragment"),
 
-		android.FixtureWithRootAndroidBp(`
-			sdk {
-				name: "mysdk",
-				bootclasspath_fragments: ["mybootclasspathfragment"],
-				java_sdk_libs: [
-					// This is not strictly needed as it should be automatically added to the sdk_snapshot as
-					// a java_sdk_libs module because it is used in the mybootclasspathfragment's
-					// api.stub_libs property. However, it is specified here to ensure that duplicates are
-					// correctly deduped.
-					"mysdklibrary",
-				],
-			}
-
+		android.FixtureWithRootAndroidBp(sdk+`
 			apex {
 				name: "myapex",
 				key: "myapex.key",
@@ -373,24 +361,7 @@
     },
 }
 		`),
-		checkAllCopyRules(`
-.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/annotation-flags.csv -> hiddenapi/annotation-flags.csv
-.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/metadata.csv -> hiddenapi/metadata.csv
-.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/index.csv -> hiddenapi/index.csv
-.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/signature-patterns.csv -> hiddenapi/signature-patterns.csv
-.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/filtered-stub-flags.csv -> hiddenapi/filtered-stub-flags.csv
-.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/filtered-flags.csv -> hiddenapi/filtered-flags.csv
-.intermediates/mysdk/common_os/empty -> java_boot_libs/snapshot/jars/are/invalid/mybootlib.jar
-.intermediates/myothersdklibrary.stubs/android_common/javac/myothersdklibrary.stubs.jar -> sdk_library/public/myothersdklibrary-stubs.jar
-.intermediates/myothersdklibrary.stubs.source/android_common/metalava/myothersdklibrary.stubs.source_api.txt -> sdk_library/public/myothersdklibrary.txt
-.intermediates/myothersdklibrary.stubs.source/android_common/metalava/myothersdklibrary.stubs.source_removed.txt -> sdk_library/public/myothersdklibrary-removed.txt
-.intermediates/mysdklibrary.stubs/android_common/javac/mysdklibrary.stubs.jar -> sdk_library/public/mysdklibrary-stubs.jar
-.intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_api.txt -> sdk_library/public/mysdklibrary.txt
-.intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_removed.txt -> sdk_library/public/mysdklibrary-removed.txt
-.intermediates/mycoreplatform.stubs/android_common/javac/mycoreplatform.stubs.jar -> sdk_library/public/mycoreplatform-stubs.jar
-.intermediates/mycoreplatform.stubs.source/android_common/metalava/mycoreplatform.stubs.source_api.txt -> sdk_library/public/mycoreplatform.txt
-.intermediates/mycoreplatform.stubs.source/android_common/metalava/mycoreplatform.stubs.source_removed.txt -> sdk_library/public/mycoreplatform-removed.txt
-`),
+		checkAllCopyRules(copyRules),
 		snapshotTestPreparer(checkSnapshotWithoutSource, preparerForSnapshot),
 		snapshotTestChecker(checkSnapshotWithoutSource, func(t *testing.T, result *android.TestResult) {
 			module := result.ModuleForTests("platform-bootclasspath", "android_common")
@@ -427,6 +398,89 @@
 	)
 }
 
+func TestSnapshotWithBootClasspathFragment_Contents(t *testing.T) {
+	t.Run("added-directly", func(t *testing.T) {
+		testSnapshotWithBootClasspathFragment_Contents(t, `
+			sdk {
+				name: "mysdk",
+				bootclasspath_fragments: ["mybootclasspathfragment"],
+				java_sdk_libs: [
+					// This is not strictly needed as it should be automatically added to the sdk_snapshot as
+					// a java_sdk_libs module because it is used in the mybootclasspathfragment's
+					// api.stub_libs property. However, it is specified here to ensure that duplicates are
+					// correctly deduped.
+					"mysdklibrary",
+				],
+			}
+		`, `
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/annotation-flags.csv -> hiddenapi/annotation-flags.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/metadata.csv -> hiddenapi/metadata.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/index.csv -> hiddenapi/index.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/signature-patterns.csv -> hiddenapi/signature-patterns.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/filtered-stub-flags.csv -> hiddenapi/filtered-stub-flags.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/filtered-flags.csv -> hiddenapi/filtered-flags.csv
+.intermediates/mysdk/common_os/empty -> java_boot_libs/snapshot/jars/are/invalid/mybootlib.jar
+.intermediates/myothersdklibrary.stubs/android_common/javac/myothersdklibrary.stubs.jar -> sdk_library/public/myothersdklibrary-stubs.jar
+.intermediates/myothersdklibrary.stubs.source/android_common/metalava/myothersdklibrary.stubs.source_api.txt -> sdk_library/public/myothersdklibrary.txt
+.intermediates/myothersdklibrary.stubs.source/android_common/metalava/myothersdklibrary.stubs.source_removed.txt -> sdk_library/public/myothersdklibrary-removed.txt
+.intermediates/mysdklibrary.stubs/android_common/javac/mysdklibrary.stubs.jar -> sdk_library/public/mysdklibrary-stubs.jar
+.intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_api.txt -> sdk_library/public/mysdklibrary.txt
+.intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_removed.txt -> sdk_library/public/mysdklibrary-removed.txt
+.intermediates/mycoreplatform.stubs/android_common/javac/mycoreplatform.stubs.jar -> sdk_library/public/mycoreplatform-stubs.jar
+.intermediates/mycoreplatform.stubs.source/android_common/metalava/mycoreplatform.stubs.source_api.txt -> sdk_library/public/mycoreplatform.txt
+.intermediates/mycoreplatform.stubs.source/android_common/metalava/mycoreplatform.stubs.source_removed.txt -> sdk_library/public/mycoreplatform-removed.txt
+`)
+	})
+
+	copyBootclasspathFragmentFromApexVariantRules := `
+.intermediates/mybootclasspathfragment/android_common_myapex/modular-hiddenapi/annotation-flags.csv -> hiddenapi/annotation-flags.csv
+.intermediates/mybootclasspathfragment/android_common_myapex/modular-hiddenapi/metadata.csv -> hiddenapi/metadata.csv
+.intermediates/mybootclasspathfragment/android_common_myapex/modular-hiddenapi/index.csv -> hiddenapi/index.csv
+.intermediates/mybootclasspathfragment/android_common_myapex/modular-hiddenapi/signature-patterns.csv -> hiddenapi/signature-patterns.csv
+.intermediates/mybootclasspathfragment/android_common_myapex/modular-hiddenapi/filtered-stub-flags.csv -> hiddenapi/filtered-stub-flags.csv
+.intermediates/mybootclasspathfragment/android_common_myapex/modular-hiddenapi/filtered-flags.csv -> hiddenapi/filtered-flags.csv
+.intermediates/mysdk/common_os/empty -> java_boot_libs/snapshot/jars/are/invalid/mybootlib.jar
+.intermediates/myothersdklibrary.stubs/android_common/javac/myothersdklibrary.stubs.jar -> sdk_library/public/myothersdklibrary-stubs.jar
+.intermediates/myothersdklibrary.stubs.source/android_common/metalava/myothersdklibrary.stubs.source_api.txt -> sdk_library/public/myothersdklibrary.txt
+.intermediates/myothersdklibrary.stubs.source/android_common/metalava/myothersdklibrary.stubs.source_removed.txt -> sdk_library/public/myothersdklibrary-removed.txt
+.intermediates/mysdklibrary.stubs/android_common/javac/mysdklibrary.stubs.jar -> sdk_library/public/mysdklibrary-stubs.jar
+.intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_api.txt -> sdk_library/public/mysdklibrary.txt
+.intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_removed.txt -> sdk_library/public/mysdklibrary-removed.txt
+.intermediates/mycoreplatform.stubs/android_common/javac/mycoreplatform.stubs.jar -> sdk_library/public/mycoreplatform-stubs.jar
+.intermediates/mycoreplatform.stubs.source/android_common/metalava/mycoreplatform.stubs.source_api.txt -> sdk_library/public/mycoreplatform.txt
+.intermediates/mycoreplatform.stubs.source/android_common/metalava/mycoreplatform.stubs.source_removed.txt -> sdk_library/public/mycoreplatform-removed.txt
+`
+	t.Run("added-via-apex", func(t *testing.T) {
+		testSnapshotWithBootClasspathFragment_Contents(t, `
+			sdk {
+				name: "mysdk",
+				apexes: ["myapex"],
+			}
+		`, copyBootclasspathFragmentFromApexVariantRules)
+	})
+
+	t.Run("added-directly-and-indirectly", func(t *testing.T) {
+		testSnapshotWithBootClasspathFragment_Contents(t, `
+			sdk {
+				name: "mysdk",
+				apexes: ["myapex"],
+				// This is not strictly needed as it should be automatically added to the sdk_snapshot as
+				// a bootclasspath_fragments module because it is used in the myapex's
+				// bootclasspath_fragments property. However, it is specified here to ensure that duplicates
+				// are correctly deduped.
+				bootclasspath_fragments: ["mybootclasspathfragment"],
+				java_sdk_libs: [
+					// This is not strictly needed as it should be automatically added to the sdk_snapshot as
+					// a java_sdk_libs module because it is used in the mybootclasspathfragment's
+					// api.stub_libs property. However, it is specified here to ensure that duplicates are
+					// correctly deduped.
+					"mysdklibrary",
+				],
+			}
+		`, copyBootclasspathFragmentFromApexVariantRules)
+	})
+}
+
 // TestSnapshotWithBootClasspathFragment_Fragments makes sure that the fragments property of a
 // bootclasspath_fragment is correctly output to the sdk snapshot.
 func TestSnapshotWithBootClasspathFragment_Fragments(t *testing.T) {
diff --git a/sdk/systemserverclasspath_fragment_sdk_test.go b/sdk/systemserverclasspath_fragment_sdk_test.go
index 1c84a7b..1ac405d 100644
--- a/sdk/systemserverclasspath_fragment_sdk_test.go
+++ b/sdk/systemserverclasspath_fragment_sdk_test.go
@@ -22,7 +22,7 @@
 	"android/soong/java"
 )
 
-func testSnapshotWithSystemServerClasspathFragment(t *testing.T, targetBuildRelease string, expectedSdkSnapshot string) {
+func testSnapshotWithSystemServerClasspathFragment(t *testing.T, sdk string, targetBuildRelease string, expectedSdkSnapshot string) {
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
 		java.PrepareForTestWithJavaDefaultModules,
@@ -30,23 +30,13 @@
 		java.FixtureWithLastReleaseApis("mysdklibrary"),
 		dexpreopt.FixtureSetApexSystemServerJars("myapex:mylib", "myapex:mysdklibrary"),
 		android.FixtureModifyEnv(func(env map[string]string) {
-			env["SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE"] = targetBuildRelease
+			if targetBuildRelease != "latest" {
+				env["SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE"] = targetBuildRelease
+			}
 		}),
 		prepareForSdkTestWithApex,
 
-		android.FixtureWithRootAndroidBp(`
-			sdk {
-				name: "mysdk",
-				systemserverclasspath_fragments: ["mysystemserverclasspathfragment"],
-				java_sdk_libs: [
-					// This is not strictly needed as it should be automatically added to the sdk_snapshot as
-					// a java_sdk_libs module because it is used in the mysystemserverclasspathfragment's
-					// contents property. However, it is specified here to ensure that duplicates are
-					// correctly deduped.
-					"mysdklibrary",
-				],
-			}
-
+		android.FixtureWithRootAndroidBp(sdk+`
 			apex {
 				name: "myapex",
 				key: "myapex.key",
@@ -91,8 +81,62 @@
 }
 
 func TestSnapshotWithSystemServerClasspathFragment(t *testing.T) {
+
+	commonSdk := `
+sdk {
+	name: "mysdk",
+	systemserverclasspath_fragments: ["mysystemserverclasspathfragment"],
+	java_sdk_libs: [
+		// This is not strictly needed as it should be automatically added to the sdk_snapshot as
+		// a java_sdk_libs module because it is used in the mysystemserverclasspathfragment's
+		// contents property. However, it is specified here to ensure that duplicates are
+		// correctly deduped.
+		"mysdklibrary",
+	],
+}
+	`
+
+	expectedLatestSnapshot := `
+// This is auto-generated. DO NOT EDIT.
+
+java_sdk_library_import {
+    name: "mysdklibrary",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    shared_library: false,
+    public: {
+        jars: ["sdk_library/public/mysdklibrary-stubs.jar"],
+        stub_srcs: ["sdk_library/public/mysdklibrary_stub_sources"],
+        current_api: "sdk_library/public/mysdklibrary.txt",
+        removed_api: "sdk_library/public/mysdklibrary-removed.txt",
+        sdk_version: "current",
+    },
+}
+
+java_import {
+    name: "mylib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    jars: ["java_systemserver_libs/snapshot/jars/are/invalid/mylib.jar"],
+    permitted_packages: ["mylib"],
+}
+
+prebuilt_systemserverclasspath_fragment {
+    name: "mysystemserverclasspathfragment",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    contents: [
+        "mylib",
+        "mysdklibrary",
+    ],
+}
+`
+
 	t.Run("target-s", func(t *testing.T) {
-		testSnapshotWithSystemServerClasspathFragment(t, "S", `
+		testSnapshotWithSystemServerClasspathFragment(t, commonSdk, "S", `
 // This is auto-generated. DO NOT EDIT.
 
 java_sdk_library_import {
@@ -113,7 +157,7 @@
 	})
 
 	t.Run("target-t", func(t *testing.T) {
-		testSnapshotWithSystemServerClasspathFragment(t, "Tiramisu", `
+		testSnapshotWithSystemServerClasspathFragment(t, commonSdk, "Tiramisu", `
 // This is auto-generated. DO NOT EDIT.
 
 java_sdk_library_import {
@@ -152,4 +196,17 @@
 }
 `)
 	})
+
+	t.Run("added-directly", func(t *testing.T) {
+		testSnapshotWithSystemServerClasspathFragment(t, commonSdk, `latest`, expectedLatestSnapshot)
+	})
+
+	t.Run("added-via-apex", func(t *testing.T) {
+		testSnapshotWithSystemServerClasspathFragment(t, `
+			sdk {
+				name: "mysdk",
+				apexes: ["myapex"],
+			}
+		`, `latest`, expectedLatestSnapshot)
+	})
 }
diff --git a/sdk/update.go b/sdk/update.go
index 8e4f9d4..b9ef3d0 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -433,6 +433,9 @@
 	traits := s.gatherTraits()
 	for _, member := range members {
 		memberType := member.memberType
+		if !memberType.ArePrebuiltsRequired() {
+			continue
+		}
 
 		name := member.name
 		requiredTraits := traits[name]
@@ -1319,6 +1322,119 @@
 	}
 }
 
+// TODO(187910671): BEGIN - Remove once modules do not have an APEX and default variant.
+// variantCoordinate contains the coordinates used to identify a variant of an SDK member.
+type variantCoordinate struct {
+	// osType identifies the OS target of a variant.
+	osType android.OsType
+	// archId identifies the architecture and whether it is for the native bridge.
+	archId archId
+	// image is the image variant name.
+	image string
+	// linkType is the link type name.
+	linkType string
+}
+
+func getVariantCoordinate(ctx *memberContext, variant android.Module) variantCoordinate {
+	linkType := ""
+	if len(ctx.MemberType().SupportedLinkages()) > 0 {
+		linkType = getLinkType(variant)
+	}
+	return variantCoordinate{
+		osType:   variant.Target().Os,
+		archId:   archIdFromTarget(variant.Target()),
+		image:    variant.ImageVariation().Variation,
+		linkType: linkType,
+	}
+}
+
+// selectApexVariantsWhereAvailable filters the input list of variants by selecting the APEX
+// specific variant for a specific variantCoordinate when there is both an APEX and default variant.
+//
+// There is a long-standing issue where a module that is added to an APEX has both an APEX and
+// default/platform variant created even when the module does not require a platform variant. As a
+// result an indirect dependency onto a module via the APEX will use the APEX variant, whereas a
+// direct dependency onto the module will use the default/platform variant. That would result in a
+// failure while attempting to optimize the properties for a member as it would have two variants
+// when only one was expected.
+//
+// This function mitigates that problem by detecting when there are two variants that differ only
+// by apex variant, where one is the default/platform variant and one is the APEX variant. In that
+// case it picks the APEX variant. It picks the APEX variant because that is the behavior that would
+// be expected
+func selectApexVariantsWhereAvailable(ctx *memberContext, variants []android.SdkAware) []android.SdkAware {
+	moduleCtx := ctx.sdkMemberContext
+
+	// Group the variants by coordinates.
+	variantsByCoord := make(map[variantCoordinate][]android.SdkAware)
+	for _, variant := range variants {
+		coord := getVariantCoordinate(ctx, variant)
+		variantsByCoord[coord] = append(variantsByCoord[coord], variant)
+	}
+
+	toDiscard := make(map[android.SdkAware]struct{})
+	for coord, list := range variantsByCoord {
+		count := len(list)
+		if count == 1 {
+			continue
+		}
+
+		variantsByApex := make(map[string]android.SdkAware)
+		conflictDetected := false
+		for _, variant := range list {
+			apexInfo := moduleCtx.OtherModuleProvider(variant, android.ApexInfoProvider).(android.ApexInfo)
+			apexVariationName := apexInfo.ApexVariationName
+			// If there are two variants for a specific APEX variation then there is conflict.
+			if _, ok := variantsByApex[apexVariationName]; ok {
+				conflictDetected = true
+				break
+			}
+			variantsByApex[apexVariationName] = variant
+		}
+
+		// If there are more than 2 apex variations or one of the apex variations is not the
+		// default/platform variation then there is a conflict.
+		if len(variantsByApex) != 2 {
+			conflictDetected = true
+		} else if _, ok := variantsByApex[""]; !ok {
+			conflictDetected = true
+		}
+
+		// If there are no conflicts then add the default/platform variation to the list to remove.
+		if !conflictDetected {
+			toDiscard[variantsByApex[""]] = struct{}{}
+			continue
+		}
+
+		// There are duplicate variants at this coordinate and they are not the default and APEX variant
+		// so fail.
+		variantDescriptions := []string{}
+		for _, m := range list {
+			variantDescriptions = append(variantDescriptions, fmt.Sprintf("    %s", m.String()))
+		}
+
+		moduleCtx.ModuleErrorf("multiple conflicting variants detected for OsType{%s}, %s, Image{%s}, Link{%s}\n%s",
+			coord.osType, coord.archId.String(), coord.image, coord.linkType,
+			strings.Join(variantDescriptions, "\n"))
+	}
+
+	// If there are any variants to discard then remove them from the list of variants, while
+	// preserving the order.
+	if len(toDiscard) > 0 {
+		filtered := []android.SdkAware{}
+		for _, variant := range variants {
+			if _, ok := toDiscard[variant]; !ok {
+				filtered = append(filtered, variant)
+			}
+		}
+		variants = filtered
+	}
+
+	return variants
+}
+
+// TODO(187910671): END - Remove once modules do not have an APEX and default variant.
+
 type baseInfo struct {
 	Properties android.SdkMemberProperties
 }
@@ -1374,7 +1490,14 @@
 
 	if commonVariants, ok := variantsByArchId[commonArchId]; ok {
 		if len(osTypeVariants) != 1 {
-			panic(fmt.Errorf("Expected to only have 1 variant when arch type is common but found %d", len(osTypeVariants)))
+			variants := []string{}
+			for _, m := range osTypeVariants {
+				variants = append(variants, fmt.Sprintf("    %s", m.String()))
+			}
+			panic(fmt.Errorf("expected to only have 1 variant of %q when arch type is common but found %d\n%s",
+				ctx.Name(),
+				len(osTypeVariants),
+				strings.Join(variants, "\n")))
 		}
 
 		// A common arch type only has one variant and its properties should be treated
@@ -1854,7 +1977,8 @@
 	memberType := member.memberType
 
 	// Do not add the prefer property if the member snapshot module is a source module type.
-	config := ctx.sdkMemberContext.Config()
+	moduleCtx := ctx.sdkMemberContext
+	config := moduleCtx.Config()
 	if !memberType.UsesSourceModuleTypeInSnapshot() {
 		// Set the prefer based on the environment variable. This is a temporary work around to allow a
 		// snapshot to be created that sets prefer: true.
@@ -1879,9 +2003,10 @@
 		}
 	}
 
+	variants := selectApexVariantsWhereAvailable(ctx, member.variants)
+
 	// Group the variants by os type.
 	variantsByOsType := make(map[android.OsType][]android.Module)
-	variants := member.Variants()
 	for _, variant := range variants {
 		osType := variant.Target().Os
 		variantsByOsType[osType] = append(variantsByOsType[osType], variant)
@@ -1927,7 +2052,7 @@
 	}
 
 	// Extract properties which are common across all architectures and os types.
-	extractCommonProperties(ctx.sdkMemberContext, commonValueExtractor, commonProperties, osSpecificPropertiesContainers)
+	extractCommonProperties(moduleCtx, commonValueExtractor, commonProperties, osSpecificPropertiesContainers)
 
 	// Add the common properties to the module.
 	addSdkMemberPropertiesToSet(ctx, commonProperties, bpModule)
diff --git a/ui/build/config.go b/ui/build/config.go
index 8874209..cbf1986 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -156,10 +156,15 @@
 // experiments system to control Soong features dynamically.
 func fetchEnvConfig(ctx Context, config *configImpl, envConfigName string) error {
 	configName := envConfigName + "." + jsonSuffix
-	expConfigFetcher := &smpb.ExpConfigFetcher{}
+	expConfigFetcher := &smpb.ExpConfigFetcher{Filename: &configName}
 	defer func() {
 		ctx.Metrics.ExpConfigFetcher(expConfigFetcher)
 	}()
+	if !config.GoogleProdCredsExist() {
+		status := smpb.ExpConfigFetcher_MISSING_GCERT
+		expConfigFetcher.Status = &status
+		return nil
+	}
 
 	s, err := os.Stat(configFetcher)
 	if err != nil {
diff --git a/ui/metrics/metrics_proto/metrics.pb.go b/ui/metrics/metrics_proto/metrics.pb.go
index 4bc713b..2dd8299 100644
--- a/ui/metrics/metrics_proto/metrics.pb.go
+++ b/ui/metrics/metrics_proto/metrics.pb.go
@@ -220,9 +220,10 @@
 type ExpConfigFetcher_ConfigStatus int32
 
 const (
-	ExpConfigFetcher_NO_CONFIG ExpConfigFetcher_ConfigStatus = 0
-	ExpConfigFetcher_CONFIG    ExpConfigFetcher_ConfigStatus = 1
-	ExpConfigFetcher_ERROR     ExpConfigFetcher_ConfigStatus = 2
+	ExpConfigFetcher_NO_CONFIG     ExpConfigFetcher_ConfigStatus = 0
+	ExpConfigFetcher_CONFIG        ExpConfigFetcher_ConfigStatus = 1
+	ExpConfigFetcher_ERROR         ExpConfigFetcher_ConfigStatus = 2
+	ExpConfigFetcher_MISSING_GCERT ExpConfigFetcher_ConfigStatus = 3
 )
 
 // Enum value maps for ExpConfigFetcher_ConfigStatus.
@@ -231,11 +232,13 @@
 		0: "NO_CONFIG",
 		1: "CONFIG",
 		2: "ERROR",
+		3: "MISSING_GCERT",
 	}
 	ExpConfigFetcher_ConfigStatus_value = map[string]int32{
-		"NO_CONFIG": 0,
-		"CONFIG":    1,
-		"ERROR":     2,
+		"NO_CONFIG":     0,
+		"CONFIG":        1,
+		"ERROR":         2,
+		"MISSING_GCERT": 3,
 	}
 )
 
@@ -1578,7 +1581,7 @@
 	0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72,
 	0x69, 0x63, 0x73, 0x2e, 0x4d, 0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x73, 0x49,
 	0x6e, 0x66, 0x6f, 0x52, 0x0f, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x73,
-	0x49, 0x6e, 0x66, 0x6f, 0x22, 0xc8, 0x01, 0x0a, 0x10, 0x45, 0x78, 0x70, 0x43, 0x6f, 0x6e, 0x66,
+	0x49, 0x6e, 0x66, 0x6f, 0x22, 0xdb, 0x01, 0x0a, 0x10, 0x45, 0x78, 0x70, 0x43, 0x6f, 0x6e, 0x66,
 	0x69, 0x67, 0x46, 0x65, 0x74, 0x63, 0x68, 0x65, 0x72, 0x12, 0x4a, 0x0a, 0x06, 0x73, 0x74, 0x61,
 	0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x32, 0x2e, 0x73, 0x6f, 0x6f, 0x6e,
 	0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e,
@@ -1587,22 +1590,23 @@
 	0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d,
 	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d,
 	0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x04, 0x52, 0x06, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x22, 0x34, 0x0a, 0x0c, 0x43, 0x6f, 0x6e,
+	0x04, 0x52, 0x06, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x22, 0x47, 0x0a, 0x0c, 0x43, 0x6f, 0x6e,
 	0x66, 0x69, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x5f,
 	0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4e, 0x46,
-	0x49, 0x47, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x22,
-	0x91, 0x01, 0x0a, 0x0f, 0x4d, 0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x73, 0x49,
-	0x6e, 0x66, 0x6f, 0x12, 0x3d, 0x0a, 0x1b, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x62, 0x75, 0x69,
-	0x6c, 0x64, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c,
-	0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x18, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x42,
-	0x75, 0x69, 0x6c, 0x64, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c,
-	0x65, 0x73, 0x12, 0x3f, 0x0a, 0x1c, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x62, 0x75, 0x69, 0x6c,
-	0x64, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c,
-	0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x19, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x42,
-	0x75, 0x69, 0x6c, 0x64, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75,
-	0x6c, 0x65, 0x73, 0x42, 0x28, 0x5a, 0x26, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73,
-	0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x75, 0x69, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f,
-	0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x49, 0x47, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12,
+	0x11, 0x0a, 0x0d, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, 0x5f, 0x47, 0x43, 0x45, 0x52, 0x54,
+	0x10, 0x03, 0x22, 0x91, 0x01, 0x0a, 0x0f, 0x4d, 0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c,
+	0x64, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x3d, 0x0a, 0x1b, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f,
+	0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5f, 0x6d, 0x6f,
+	0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x18, 0x6d, 0x69, 0x78,
+	0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x4d, 0x6f,
+	0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x3f, 0x0a, 0x1c, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x62,
+	0x75, 0x69, 0x6c, 0x64, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5f, 0x6d, 0x6f,
+	0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x19, 0x6d, 0x69, 0x78,
+	0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x4d,
+	0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x42, 0x28, 0x5a, 0x26, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69,
+	0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x75, 0x69, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69,
+	0x63, 0x73, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 }
 
 var (
diff --git a/ui/metrics/metrics_proto/metrics.proto b/ui/metrics/metrics_proto/metrics.proto
index 51dd523..4f8fe7f 100644
--- a/ui/metrics/metrics_proto/metrics.proto
+++ b/ui/metrics/metrics_proto/metrics.proto
@@ -251,6 +251,7 @@
     NO_CONFIG = 0;
     CONFIG = 1;
     ERROR = 2;
+    MISSING_GCERT = 3;
   }
   // The result of the call to expconfigfetcher
   // NO_CONFIG - Not part of experiment