Merge "Fix boot jar handling when both source and prebuilt APEXes and modules are present."
diff --git a/android/apex.go b/android/apex.go
index 4f1d41b..b87ff09 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -843,9 +843,8 @@
 		return
 	}
 
-	// do not enforce deps.min_sdk_version if APEX/APK doesn't set min_sdk_version or
-	// min_sdk_version is not finalized (e.g. current or codenames)
-	if minSdkVersion.IsCurrent() {
+	// do not enforce deps.min_sdk_version if APEX/APK doesn't set min_sdk_version
+	if minSdkVersion.IsNone() {
 		return
 	}
 
diff --git a/android/api_levels.go b/android/api_levels.go
index 08328e1..1b53f3f 100644
--- a/android/api_levels.go
+++ b/android/api_levels.go
@@ -81,6 +81,10 @@
 	return this.value == "current"
 }
 
+func (this ApiLevel) IsNone() bool {
+	return this.number == -1
+}
+
 // Returns -1 if the current API level is less than the argument, 0 if they
 // are equal, and 1 if it is greater than the argument.
 func (this ApiLevel) CompareTo(other ApiLevel) int {
diff --git a/android/filegroup.go b/android/filegroup.go
index fd4a2fe..3d1bbc5 100644
--- a/android/filegroup.go
+++ b/android/filegroup.go
@@ -23,7 +23,7 @@
 
 func init() {
 	RegisterModuleType("filegroup", FileGroupFactory)
-	RegisterBp2BuildMutator("filegroup", bp2buildMutator)
+	RegisterBp2BuildMutator("filegroup", FilegroupBp2Build)
 }
 
 // https://docs.bazel.build/versions/master/be/general.html#filegroup
@@ -51,7 +51,7 @@
 func (bfg *bazelFilegroup) GenerateAndroidBuildActions(ctx ModuleContext) {}
 
 // TODO: Create helper functions to avoid this boilerplate.
-func bp2buildMutator(ctx TopDownMutatorContext) {
+func FilegroupBp2Build(ctx TopDownMutatorContext) {
 	if m, ok := ctx.Module().(*fileGroup); ok {
 		name := "__bp2build__" + m.base().BaseModuleName()
 		ctx.CreateModule(BazelFileGroupFactory, &bazelFilegroupAttributes{
diff --git a/android/testing.go b/android/testing.go
index 76172d1..5640c29 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -89,7 +89,7 @@
 	f := func(ctx RegisterMutatorsContext) {
 		ctx.TopDown(mutatorName, m)
 	}
-	bp2buildMutators = append(bp2buildMutators, f)
+	ctx.bp2buildMutators = append(ctx.bp2buildMutators, f)
 }
 
 func (ctx *TestContext) Register() {
@@ -100,7 +100,7 @@
 
 // RegisterForBazelConversion prepares a test context for bp2build conversion.
 func (ctx *TestContext) RegisterForBazelConversion() {
-	RegisterMutatorsForBazelConversion(ctx.Context.Context, bp2buildMutators)
+	RegisterMutatorsForBazelConversion(ctx.Context.Context, ctx.bp2buildMutators)
 }
 
 func (ctx *TestContext) ParseFileList(rootDir string, filePaths []string) (deps []string, errs []error) {
diff --git a/apex/Android.bp b/apex/Android.bp
index 77dde72..b6fdcf4 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -25,6 +25,7 @@
     ],
     testSrcs: [
         "apex_test.go",
+        "boot_image_test.go",
         "vndk_test.go",
     ],
     pluginFor: ["soong_build"],
diff --git a/apex/apex.go b/apex/apex.go
index 384d6c7..ade8fa9 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -854,11 +854,17 @@
 		Contents: apexContents,
 	})
 
+	minSdkVersion := a.minSdkVersion(mctx)
+	// When min_sdk_version is not set, the apex is built against FutureApiLevel.
+	if minSdkVersion.IsNone() {
+		minSdkVersion = android.FutureApiLevel
+	}
+
 	// This is the main part of this mutator. Mark the collected dependencies that they need to
 	// be built for this apexBundle.
 	apexInfo := android.ApexInfo{
 		ApexVariationName: mctx.ModuleName(),
-		MinSdkVersionStr:  a.minSdkVersion(mctx).String(),
+		MinSdkVersionStr:  minSdkVersion.String(),
 		RequiredSdks:      a.RequiredSdks(),
 		Updatable:         a.Updatable(),
 		InApexes:          []string{mctx.ModuleName()},
@@ -2116,17 +2122,13 @@
 func (a *apexBundle) minSdkVersion(ctx android.BaseModuleContext) android.ApiLevel {
 	ver := proptools.String(a.properties.Min_sdk_version)
 	if ver == "" {
-		return android.FutureApiLevel
+		return android.NoneApiLevel
 	}
 	apiLevel, err := android.ApiLevelFromUser(ctx, ver)
 	if err != nil {
 		ctx.PropertyErrorf("min_sdk_version", "%s", err.Error())
 		return android.NoneApiLevel
 	}
-	if apiLevel.IsPreview() {
-		// All codenames should build against "current".
-		return android.FutureApiLevel
-	}
 	return apiLevel
 }
 
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 39eeeb2..7f5be7e 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -2135,6 +2135,74 @@
 	expectLink("mylib", "shared_apex30", "mylib2", "shared_apex30")
 }
 
+func TestApexMinSdkVersion_WorksWithSdkCodename(t *testing.T) {
+	withSAsActiveCodeNames := func(fs map[string][]byte, config android.Config) {
+		config.TestProductVariables.Platform_sdk_codename = proptools.StringPtr("S")
+		config.TestProductVariables.Platform_version_active_codenames = []string{"S"}
+	}
+	testApexError(t, `libbar.*: should support min_sdk_version\(S\)`, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			native_shared_libs: ["libfoo"],
+			min_sdk_version: "S",
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+		cc_library {
+			name: "libfoo",
+			shared_libs: ["libbar"],
+			apex_available: ["myapex"],
+			min_sdk_version: "29",
+		}
+		cc_library {
+			name: "libbar",
+			apex_available: ["myapex"],
+		}
+	`, withSAsActiveCodeNames)
+}
+
+func TestApexMinSdkVersion_WorksWithActiveCodenames(t *testing.T) {
+	withSAsActiveCodeNames := func(fs map[string][]byte, config android.Config) {
+		config.TestProductVariables.Platform_sdk_codename = proptools.StringPtr("S")
+		config.TestProductVariables.Platform_version_active_codenames = []string{"S", "T"}
+	}
+	ctx, _ := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			native_shared_libs: ["libfoo"],
+			min_sdk_version: "S",
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+		cc_library {
+			name: "libfoo",
+			shared_libs: ["libbar"],
+			apex_available: ["myapex"],
+			min_sdk_version: "S",
+		}
+		cc_library {
+			name: "libbar",
+			stubs: {
+				symbol_file: "libbar.map.txt",
+				versions: ["30", "S", "T"],
+			},
+		}
+	`, withSAsActiveCodeNames)
+
+	// ensure libfoo is linked with "S" version of libbar stub
+	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared_apex10000")
+	libFlags := libfoo.Rule("ld").Args["libFlags"]
+	ensureContains(t, libFlags, "android_arm64_armv8-a_shared_S/libbar.so")
+}
+
 func TestFilesInSubDir(t *testing.T) {
 	ctx, _ := testApex(t, `
 		apex {
diff --git a/apex/boot_image_test.go b/apex/boot_image_test.go
new file mode 100644
index 0000000..07feb03
--- /dev/null
+++ b/apex/boot_image_test.go
@@ -0,0 +1,128 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package apex
+
+import (
+	"testing"
+
+	"android/soong/android"
+	"android/soong/dexpreopt"
+	"android/soong/java"
+)
+
+// Contains tests for boot_image logic from java/boot_image.go as the ART boot image requires
+// modules from the ART apex.
+
+func TestBootImages(t *testing.T) {
+	ctx, _ := testApex(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["b.java"],
+			unsafe_ignore_missing_latest_api: true,
+		}
+
+		java_library {
+			name: "bar",
+			srcs: ["b.java"],
+			installable: true,
+		}
+
+		apex {
+			name: "com.android.art",
+			key: "com.android.art.key",
+ 			java_libs: [
+				"baz",
+				"quuz",
+			],
+		}
+
+		apex_key {
+			name: "com.android.art.key",
+			public_key: "com.android.art.avbpubkey",
+			private_key: "com.android.art.pem",
+		}
+
+		java_library {
+			name: "baz",
+			apex_available: [
+				"com.android.art",
+			],
+			srcs: ["b.java"],
+		}
+
+		java_library {
+			name: "quuz",
+			apex_available: [
+				"com.android.art",
+			],
+			srcs: ["b.java"],
+		}
+`,
+		// Configure some libraries in the art and framework boot images.
+		withArtBootImageJars("com.android.art:baz", "com.android.art:quuz"),
+		withFrameworkBootImageJars("platform:foo", "platform:bar"),
+		withFiles(filesForSdkLibrary),
+		// Some additional files needed for the art apex.
+		withFiles(map[string][]byte{
+			"com.android.art.avbpubkey":                          nil,
+			"com.android.art.pem":                                nil,
+			"system/sepolicy/apex/com.android.art-file_contexts": nil,
+		}),
+	)
+
+	// Make sure that the framework-boot-image is using the correct configuration.
+	checkBootImage(t, ctx, "framework-boot-image", "platform:foo,platform:bar")
+
+	// Make sure that the art-boot-image is using the correct configuration.
+	checkBootImage(t, ctx, "art-boot-image", "com.android.art:baz,com.android.art:quuz")
+}
+
+func checkBootImage(t *testing.T, ctx *android.TestContext, moduleName string, expectedConfiguredModules string) {
+	t.Helper()
+
+	bootImage := ctx.ModuleForTests(moduleName, "android_common").Module().(*java.BootImageModule)
+
+	bootImageInfo := ctx.ModuleProvider(bootImage, java.BootImageInfoProvider).(java.BootImageInfo)
+	modules := bootImageInfo.Modules()
+	if actual := modules.String(); actual != expectedConfiguredModules {
+		t.Errorf("invalid modules for %s: expected %q, actual %q", moduleName, expectedConfiguredModules, actual)
+	}
+}
+
+func modifyDexpreoptConfig(configModifier func(dexpreoptConfig *dexpreopt.GlobalConfig)) func(fs map[string][]byte, config android.Config) {
+	return func(fs map[string][]byte, config android.Config) {
+		// Initialize the dexpreopt GlobalConfig to an empty structure. This has no effect if it has
+		// already been set.
+		pathCtx := android.PathContextForTesting(config)
+		dexpreoptConfig := dexpreopt.GlobalConfigForTests(pathCtx)
+		dexpreopt.SetTestGlobalConfig(config, dexpreoptConfig)
+
+		// Retrieve the existing configuration and modify it.
+		dexpreoptConfig = dexpreopt.GetGlobalConfig(pathCtx)
+		configModifier(dexpreoptConfig)
+	}
+}
+
+func withArtBootImageJars(bootJars ...string) func(fs map[string][]byte, config android.Config) {
+	return modifyDexpreoptConfig(func(dexpreoptConfig *dexpreopt.GlobalConfig) {
+		dexpreoptConfig.ArtApexJars = android.CreateTestConfiguredJarList(bootJars)
+	})
+}
+
+func withFrameworkBootImageJars(bootJars ...string) func(fs map[string][]byte, config android.Config) {
+	return modifyDexpreoptConfig(func(dexpreoptConfig *dexpreopt.GlobalConfig) {
+		dexpreoptConfig.BootJars = android.CreateTestConfiguredJarList(bootJars)
+	})
+}
diff --git a/apex/builder.go b/apex/builder.go
index 67314d8..16ca74c 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -598,7 +598,7 @@
 
 		// bundletool doesn't understand what "current" is. We need to transform it to
 		// codename
-		if moduleMinSdkVersion.IsCurrent() {
+		if moduleMinSdkVersion.IsCurrent() || moduleMinSdkVersion.IsNone() {
 			minSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String()
 		}
 		// apex module doesn't have a concept of target_sdk_version, hence for the time
@@ -780,7 +780,7 @@
 		ctx.PropertyErrorf("test_only_force_compression", "not available")
 		return
 	}
-	compressionEnabled := ctx.Config().CompressedApex() && proptools.BoolDefault(a.properties.Compressible, true)
+	compressionEnabled := ctx.Config().CompressedApex() && proptools.BoolDefault(a.properties.Compressible, false)
 	if apexType == imageApex && (compressionEnabled || a.testOnlyShouldForceCompression()) {
 		a.isCompressed = true
 		unsignedCompressedOutputFile := android.PathForModuleOut(ctx, a.Name()+".capex.unsigned")
diff --git a/bazel/properties.go b/bazel/properties.go
index ac0047b..79956e1 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -31,4 +31,7 @@
 type BazelTargetModuleProperties struct {
 	// The Bazel rule class for this target.
 	Rule_class string
+
+	// The target label for the bzl file containing the definition of the rule class.
+	Bzl_load_location string
 }
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index a7c3adb..1af1d60 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -18,6 +18,7 @@
 	"android/soong/android"
 	"fmt"
 	"reflect"
+	"strconv"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -29,8 +30,62 @@
 }
 
 type BazelTarget struct {
-	name    string
-	content string
+	name            string
+	content         string
+	ruleClass       string
+	bzlLoadLocation string
+}
+
+// IsLoadedFromStarlark determines if the BazelTarget's rule class is loaded from a .bzl file,
+// as opposed to a native rule built into Bazel.
+func (t BazelTarget) IsLoadedFromStarlark() bool {
+	return t.bzlLoadLocation != ""
+}
+
+// BazelTargets is a typedef for a slice of BazelTarget objects.
+type BazelTargets []BazelTarget
+
+// String returns the string representation of BazelTargets, without load
+// statements (use LoadStatements for that), since the targets are usually not
+// adjacent to the load statements at the top of the BUILD file.
+func (targets BazelTargets) String() string {
+	var res string
+	for i, target := range targets {
+		res += target.content
+		if i != len(targets)-1 {
+			res += "\n\n"
+		}
+	}
+	return res
+}
+
+// LoadStatements return the string representation of the sorted and deduplicated
+// Starlark rule load statements needed by a group of BazelTargets.
+func (targets BazelTargets) LoadStatements() string {
+	bzlToLoadedSymbols := map[string][]string{}
+	for _, target := range targets {
+		if target.IsLoadedFromStarlark() {
+			bzlToLoadedSymbols[target.bzlLoadLocation] =
+				append(bzlToLoadedSymbols[target.bzlLoadLocation], target.ruleClass)
+		}
+	}
+
+	var loadStatements []string
+	for bzl, ruleClasses := range bzlToLoadedSymbols {
+		loadStatement := "load(\""
+		loadStatement += bzl
+		loadStatement += "\", "
+		ruleClasses = android.SortedUniqueStrings(ruleClasses)
+		for i, ruleClass := range ruleClasses {
+			loadStatement += "\"" + ruleClass + "\""
+			if i != len(ruleClasses)-1 {
+				loadStatement += ", "
+			}
+		}
+		loadStatement += ")"
+		loadStatements = append(loadStatements, loadStatement)
+	}
+	return strings.Join(android.SortedUniqueStrings(loadStatements), "\n")
 }
 
 type bpToBuildContext interface {
@@ -104,8 +159,8 @@
 	return attributes
 }
 
-func GenerateSoongModuleTargets(ctx bpToBuildContext, codegenMode CodegenMode) map[string][]BazelTarget {
-	buildFileToTargets := make(map[string][]BazelTarget)
+func GenerateSoongModuleTargets(ctx bpToBuildContext, codegenMode CodegenMode) map[string]BazelTargets {
+	buildFileToTargets := make(map[string]BazelTargets)
 	ctx.VisitAllModules(func(m blueprint.Module) {
 		dir := ctx.ModuleDir(m)
 		var t BazelTarget
@@ -127,22 +182,44 @@
 	return buildFileToTargets
 }
 
+// Helper method to trim quotes around strings.
+func trimQuotes(s string) string {
+	if s == "" {
+		// strconv.Unquote would error out on empty strings, but this method
+		// allows them, so return the empty string directly.
+		return ""
+	}
+	ret, err := strconv.Unquote(s)
+	if err != nil {
+		// Panic the error immediately.
+		panic(fmt.Errorf("Trying to unquote '%s', but got error: %s", s, err))
+	}
+	return ret
+}
+
 func generateBazelTarget(ctx bpToBuildContext, m blueprint.Module) BazelTarget {
 	// extract the bazel attributes from the module.
 	props := getBuildProperties(ctx, m)
 
 	// extract the rule class name from the attributes. Since the string value
 	// will be string-quoted, remove the quotes here.
-	ruleClass := strings.Replace(props.Attrs["rule_class"], "\"", "", 2)
+	ruleClass := trimQuotes(props.Attrs["rule_class"])
 	// Delete it from being generated in the BUILD file.
 	delete(props.Attrs, "rule_class")
 
+	// extract the bzl_load_location, and also remove the quotes around it here.
+	bzlLoadLocation := trimQuotes(props.Attrs["bzl_load_location"])
+	// Delete it from being generated in the BUILD file.
+	delete(props.Attrs, "bzl_load_location")
+
 	// Return the Bazel target with rule class and attributes, ready to be
 	// code-generated.
 	attributes := propsToAttributes(props.Attrs)
 	targetName := targetNameForBp2Build(ctx, m)
 	return BazelTarget{
-		name: targetName,
+		name:            targetName,
+		ruleClass:       ruleClass,
+		bzlLoadLocation: bzlLoadLocation,
 		content: fmt.Sprintf(
 			bazelTarget,
 			ruleClass,
diff --git a/bp2build/build_conversion_test.go b/bp2build/build_conversion_test.go
index 367e13f..66ed42d 100644
--- a/bp2build/build_conversion_test.go
+++ b/bp2build/build_conversion_test.go
@@ -268,18 +268,185 @@
 	}
 }
 
-func TestModuleTypeBp2Build(t *testing.T) {
+func TestLoadStatements(t *testing.T) {
 	testCases := []struct {
-		moduleTypeUnderTest        string
-		moduleTypeUnderTestFactory android.ModuleFactory
-		bp                         string
-		expectedBazelTarget        string
-		description                string
+		bazelTargets           BazelTargets
+		expectedLoadStatements string
 	}{
 		{
-			description:                "filegroup with no srcs",
-			moduleTypeUnderTest:        "filegroup",
-			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			bazelTargets: BazelTargets{
+				BazelTarget{
+					name:            "foo",
+					ruleClass:       "cc_library",
+					bzlLoadLocation: "//build/bazel/rules:cc.bzl",
+				},
+			},
+			expectedLoadStatements: `load("//build/bazel/rules:cc.bzl", "cc_library")`,
+		},
+		{
+			bazelTargets: BazelTargets{
+				BazelTarget{
+					name:            "foo",
+					ruleClass:       "cc_library",
+					bzlLoadLocation: "//build/bazel/rules:cc.bzl",
+				},
+				BazelTarget{
+					name:            "bar",
+					ruleClass:       "cc_library",
+					bzlLoadLocation: "//build/bazel/rules:cc.bzl",
+				},
+			},
+			expectedLoadStatements: `load("//build/bazel/rules:cc.bzl", "cc_library")`,
+		},
+		{
+			bazelTargets: BazelTargets{
+				BazelTarget{
+					name:            "foo",
+					ruleClass:       "cc_library",
+					bzlLoadLocation: "//build/bazel/rules:cc.bzl",
+				},
+				BazelTarget{
+					name:            "bar",
+					ruleClass:       "cc_binary",
+					bzlLoadLocation: "//build/bazel/rules:cc.bzl",
+				},
+			},
+			expectedLoadStatements: `load("//build/bazel/rules:cc.bzl", "cc_binary", "cc_library")`,
+		},
+		{
+			bazelTargets: BazelTargets{
+				BazelTarget{
+					name:            "foo",
+					ruleClass:       "cc_library",
+					bzlLoadLocation: "//build/bazel/rules:cc.bzl",
+				},
+				BazelTarget{
+					name:            "bar",
+					ruleClass:       "cc_binary",
+					bzlLoadLocation: "//build/bazel/rules:cc.bzl",
+				},
+				BazelTarget{
+					name:            "baz",
+					ruleClass:       "java_binary",
+					bzlLoadLocation: "//build/bazel/rules:java.bzl",
+				},
+			},
+			expectedLoadStatements: `load("//build/bazel/rules:cc.bzl", "cc_binary", "cc_library")
+load("//build/bazel/rules:java.bzl", "java_binary")`,
+		},
+		{
+			bazelTargets: BazelTargets{
+				BazelTarget{
+					name:            "foo",
+					ruleClass:       "cc_binary",
+					bzlLoadLocation: "//build/bazel/rules:cc.bzl",
+				},
+				BazelTarget{
+					name:            "bar",
+					ruleClass:       "java_binary",
+					bzlLoadLocation: "//build/bazel/rules:java.bzl",
+				},
+				BazelTarget{
+					name:      "baz",
+					ruleClass: "genrule",
+					// Note: no bzlLoadLocation for native rules
+				},
+			},
+			expectedLoadStatements: `load("//build/bazel/rules:cc.bzl", "cc_binary")
+load("//build/bazel/rules:java.bzl", "java_binary")`,
+		},
+	}
+
+	for _, testCase := range testCases {
+		actual := testCase.bazelTargets.LoadStatements()
+		expected := testCase.expectedLoadStatements
+		if actual != expected {
+			t.Fatalf("Expected load statements to be %s, got %s", expected, actual)
+		}
+	}
+
+}
+
+func TestGenerateBazelTargetModules_OneToMany_LoadedFromStarlark(t *testing.T) {
+	testCases := []struct {
+		bp                       string
+		expectedBazelTarget      string
+		expectedBazelTargetCount int
+		expectedLoadStatements   string
+	}{
+		{
+			bp: `custom {
+    name: "bar",
+}`,
+			expectedBazelTarget: `my_library(
+    name = "bar",
+)
+
+my_proto_library(
+    name = "bar_my_proto_library_deps",
+)
+
+proto_library(
+    name = "bar_proto_library_deps",
+)`,
+			expectedBazelTargetCount: 3,
+			expectedLoadStatements: `load("//build/bazel/rules:proto.bzl", "my_proto_library", "proto_library")
+load("//build/bazel/rules:rules.bzl", "my_library")`,
+		},
+	}
+
+	dir := "."
+	for _, testCase := range testCases {
+		config := android.TestConfig(buildDir, nil, testCase.bp, nil)
+		ctx := android.NewTestContext(config)
+		ctx.RegisterModuleType("custom", customModuleFactory)
+		ctx.RegisterBp2BuildMutator("custom_starlark", customBp2BuildMutatorFromStarlark)
+		ctx.RegisterForBazelConversion()
+
+		_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
+		android.FailIfErrored(t, errs)
+		_, errs = ctx.ResolveDependencies(config)
+		android.FailIfErrored(t, errs)
+
+		bazelTargets := GenerateSoongModuleTargets(ctx.Context.Context, Bp2Build)[dir]
+		if actualCount := len(bazelTargets); actualCount != testCase.expectedBazelTargetCount {
+			t.Fatalf("Expected %d bazel target, got %d", testCase.expectedBazelTargetCount, actualCount)
+		}
+
+		actualBazelTargets := bazelTargets.String()
+		if actualBazelTargets != testCase.expectedBazelTarget {
+			t.Errorf(
+				"Expected generated Bazel target to be '%s', got '%s'",
+				testCase.expectedBazelTarget,
+				actualBazelTargets,
+			)
+		}
+
+		actualLoadStatements := bazelTargets.LoadStatements()
+		if actualLoadStatements != testCase.expectedLoadStatements {
+			t.Errorf(
+				"Expected generated load statements to be '%s', got '%s'",
+				testCase.expectedLoadStatements,
+				actualLoadStatements,
+			)
+		}
+	}
+}
+
+func TestModuleTypeBp2Build(t *testing.T) {
+	testCases := []struct {
+		moduleTypeUnderTest                string
+		moduleTypeUnderTestFactory         android.ModuleFactory
+		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
+		bp                                 string
+		expectedBazelTarget                string
+		description                        string
+	}{
+		{
+			description:                        "filegroup with no srcs",
+			moduleTypeUnderTest:                "filegroup",
+			moduleTypeUnderTestFactory:         android.FileGroupFactory,
+			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
 			bp: `filegroup {
 	name: "foo",
 	srcs: [],
@@ -291,9 +458,10 @@
 )`,
 		},
 		{
-			description:                "filegroup with srcs",
-			moduleTypeUnderTest:        "filegroup",
-			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			description:                        "filegroup with srcs",
+			moduleTypeUnderTest:                "filegroup",
+			moduleTypeUnderTestFactory:         android.FileGroupFactory,
+			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
 			bp: `filegroup {
 	name: "foo",
 	srcs: ["a", "b"],
@@ -307,9 +475,10 @@
 )`,
 		},
 		{
-			description:                "genrule with command line variable replacements",
-			moduleTypeUnderTest:        "genrule",
-			moduleTypeUnderTestFactory: genrule.GenRuleFactory,
+			description:                        "genrule with command line variable replacements",
+			moduleTypeUnderTest:                "genrule",
+			moduleTypeUnderTestFactory:         genrule.GenRuleFactory,
+			moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
 			bp: `genrule {
     name: "foo",
     out: ["foo.out"],
@@ -332,9 +501,10 @@
 )`,
 		},
 		{
-			description:                "genrule using $(locations :label)",
-			moduleTypeUnderTest:        "genrule",
-			moduleTypeUnderTestFactory: genrule.GenRuleFactory,
+			description:                        "genrule using $(locations :label)",
+			moduleTypeUnderTest:                "genrule",
+			moduleTypeUnderTestFactory:         genrule.GenRuleFactory,
+			moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
 			bp: `genrule {
     name: "foo",
     out: ["foo.out"],
@@ -357,9 +527,10 @@
 )`,
 		},
 		{
-			description:                "genrule using $(location) label should substitute first tool label automatically",
-			moduleTypeUnderTest:        "genrule",
-			moduleTypeUnderTestFactory: genrule.GenRuleFactory,
+			description:                        "genrule using $(location) label should substitute first tool label automatically",
+			moduleTypeUnderTest:                "genrule",
+			moduleTypeUnderTestFactory:         genrule.GenRuleFactory,
+			moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
 			bp: `genrule {
     name: "foo",
     out: ["foo.out"],
@@ -383,9 +554,10 @@
 )`,
 		},
 		{
-			description:                "genrule using $(locations) label should substitute first tool label automatically",
-			moduleTypeUnderTest:        "genrule",
-			moduleTypeUnderTestFactory: genrule.GenRuleFactory,
+			description:                        "genrule using $(locations) label should substitute first tool label automatically",
+			moduleTypeUnderTest:                "genrule",
+			moduleTypeUnderTestFactory:         genrule.GenRuleFactory,
+			moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
 			bp: `genrule {
     name: "foo",
     out: ["foo.out"],
@@ -409,9 +581,10 @@
 )`,
 		},
 		{
-			description:                "genrule without tools or tool_files can convert successfully",
-			moduleTypeUnderTest:        "genrule",
-			moduleTypeUnderTestFactory: genrule.GenRuleFactory,
+			description:                        "genrule without tools or tool_files can convert successfully",
+			moduleTypeUnderTest:                "genrule",
+			moduleTypeUnderTestFactory:         genrule.GenRuleFactory,
+			moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
 			bp: `genrule {
     name: "foo",
     out: ["foo.out"],
@@ -436,6 +609,7 @@
 		config := android.TestConfig(buildDir, nil, testCase.bp, nil)
 		ctx := android.NewTestContext(config)
 		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
+		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
 		ctx.RegisterForBazelConversion()
 
 		_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
diff --git a/bp2build/bzl_conversion_test.go b/bp2build/bzl_conversion_test.go
index 8cdee65..f2a4058 100644
--- a/bp2build/bzl_conversion_test.go
+++ b/bp2build/bzl_conversion_test.go
@@ -172,7 +172,7 @@
 			content: "irrelevant",
 		},
 	}
-	files := CreateBazelFiles(ruleShims, make(map[string][]BazelTarget), QueryView)
+	files := CreateBazelFiles(ruleShims, make(map[string]BazelTargets), QueryView)
 
 	var actualSoongModuleBzl BazelFile
 	for _, f := range files {
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
index 2025ef7..62cd8d4 100644
--- a/bp2build/conversion.go
+++ b/bp2build/conversion.go
@@ -17,7 +17,7 @@
 
 func CreateBazelFiles(
 	ruleShims map[string]RuleShim,
-	buildToTargets map[string][]BazelTarget,
+	buildToTargets map[string]BazelTargets,
 	mode CodegenMode) []BazelFile {
 	files := make([]BazelFile, 0, len(ruleShims)+len(buildToTargets)+numAdditionalFiles)
 
@@ -43,20 +43,20 @@
 	return files
 }
 
-func createBuildFiles(buildToTargets map[string][]BazelTarget, mode CodegenMode) []BazelFile {
+func createBuildFiles(buildToTargets map[string]BazelTargets, mode CodegenMode) []BazelFile {
 	files := make([]BazelFile, 0, len(buildToTargets))
 	for _, dir := range android.SortedStringKeys(buildToTargets) {
-		content := soongModuleLoad
-		if mode == Bp2Build {
-			// No need to load soong_module for bp2build BUILD files.
-			content = ""
-		}
 		targets := buildToTargets[dir]
 		sort.Slice(targets, func(i, j int) bool { return targets[i].name < targets[j].name })
-		for _, t := range targets {
-			content += "\n\n"
-			content += t.content
+		content := soongModuleLoad
+		if mode == Bp2Build {
+			content = targets.LoadStatements()
 		}
+		if content != "" {
+			// If there are load statements, add a couple of newlines.
+			content += "\n\n"
+		}
+		content += targets.String()
 		files = append(files, newFile(dir, "BUILD.bazel", content))
 	}
 	return files
diff --git a/bp2build/conversion_test.go b/bp2build/conversion_test.go
index 7f75b14..ec5f27e 100644
--- a/bp2build/conversion_test.go
+++ b/bp2build/conversion_test.go
@@ -55,7 +55,7 @@
 }
 
 func TestCreateBazelFiles_QueryView_AddsTopLevelFiles(t *testing.T) {
-	files := CreateBazelFiles(map[string]RuleShim{}, map[string][]BazelTarget{}, QueryView)
+	files := CreateBazelFiles(map[string]RuleShim{}, map[string]BazelTargets{}, QueryView)
 	expectedFilePaths := []filepath{
 		{
 			dir:      "",
@@ -85,7 +85,7 @@
 }
 
 func TestCreateBazelFiles_Bp2Build_AddsTopLevelFiles(t *testing.T) {
-	files := CreateBazelFiles(map[string]RuleShim{}, map[string][]BazelTarget{}, Bp2Build)
+	files := CreateBazelFiles(map[string]RuleShim{}, map[string]BazelTargets{}, Bp2Build)
 	expectedFilePaths := []filepath{
 		{
 			dir:      "",
diff --git a/bp2build/testing.go b/bp2build/testing.go
index 4c31d2d..5e6481b 100644
--- a/bp2build/testing.go
+++ b/bp2build/testing.go
@@ -137,3 +137,29 @@
 		})
 	}
 }
+
+// A bp2build mutator that uses load statements and creates a 1:M mapping from
+// module to target.
+func customBp2BuildMutatorFromStarlark(ctx android.TopDownMutatorContext) {
+	if m, ok := ctx.Module().(*customModule); ok {
+		baseName := "__bp2build__" + m.Name()
+		ctx.CreateModule(customBazelModuleFactory, &customBazelModuleAttributes{
+			Name: proptools.StringPtr(baseName),
+		}, &bazel.BazelTargetModuleProperties{
+			Rule_class:        "my_library",
+			Bzl_load_location: "//build/bazel/rules:rules.bzl",
+		})
+		ctx.CreateModule(customBazelModuleFactory, &customBazelModuleAttributes{
+			Name: proptools.StringPtr(baseName + "_proto_library_deps"),
+		}, &bazel.BazelTargetModuleProperties{
+			Rule_class:        "proto_library",
+			Bzl_load_location: "//build/bazel/rules:proto.bzl",
+		})
+		ctx.CreateModule(customBazelModuleFactory, &customBazelModuleAttributes{
+			Name: proptools.StringPtr(baseName + "_my_proto_library_deps"),
+		}, &bazel.BazelTargetModuleProperties{
+			Rule_class:        "my_proto_library",
+			Bzl_load_location: "//build/bazel/rules:proto.bzl",
+		})
+	}
+}
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 66e5652..ddfb459 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -47,7 +47,7 @@
 		ctx.BottomUp("genrule_tool_deps", toolDepsMutator).Parallel()
 	})
 
-	android.RegisterBp2BuildMutator("genrule", bp2buildMutator)
+	android.RegisterBp2BuildMutator("genrule", GenruleBp2Build)
 }
 
 var (
@@ -794,7 +794,7 @@
 	return module
 }
 
-func bp2buildMutator(ctx android.TopDownMutatorContext) {
+func GenruleBp2Build(ctx android.TopDownMutatorContext) {
 	if m, ok := ctx.Module().(*Module); ok {
 		name := "__bp2build__" + m.Name()
 		// Bazel only has the "tools" attribute.
diff --git a/java/Android.bp b/java/Android.bp
index 9c28968..364566a 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -24,6 +24,7 @@
         "app.go",
         "app_import.go",
         "app_set.go",
+        "boot_image.go",
         "boot_jars.go",
         "builder.go",
         "device_host_converter.go",
@@ -63,6 +64,7 @@
         "app_import_test.go",
         "app_set_test.go",
         "app_test.go",
+        "boot_image_test.go",
         "device_host_converter_test.go",
         "dexpreopt_test.go",
         "dexpreopt_bootjars_test.go",
diff --git a/java/boot_image.go b/java/boot_image.go
new file mode 100644
index 0000000..07ef0d8
--- /dev/null
+++ b/java/boot_image.go
@@ -0,0 +1,85 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"strings"
+
+	"android/soong/android"
+	"github.com/google/blueprint"
+)
+
+func init() {
+	RegisterBootImageBuildComponents(android.InitRegistrationContext)
+}
+
+func RegisterBootImageBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("boot_image", bootImageFactory)
+}
+
+type bootImageProperties struct {
+	// The name of the image this represents.
+	//
+	// Must be one of "art" or "boot".
+	Image_name string
+}
+
+type BootImageModule struct {
+	android.ModuleBase
+
+	properties bootImageProperties
+}
+
+func bootImageFactory() android.Module {
+	m := &BootImageModule{}
+	m.AddProperties(&m.properties)
+	android.InitAndroidArchModule(m, android.HostAndDeviceDefault, android.MultilibCommon)
+	return m
+}
+
+var BootImageInfoProvider = blueprint.NewProvider(BootImageInfo{})
+
+type BootImageInfo struct {
+	// The image config, internal to this module (and the dex_bootjars singleton).
+	imageConfig *bootImageConfig
+}
+
+func (i BootImageInfo) Modules() android.ConfiguredJarList {
+	return i.imageConfig.modules
+}
+
+func (b *BootImageModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// Nothing to do if skipping the dexpreopt of boot image jars.
+	if SkipDexpreoptBootJars(ctx) {
+		return
+	}
+
+	// Get a map of the image configs that are supported.
+	imageConfigs := genBootImageConfigs(ctx)
+
+	// Retrieve the config for this image.
+	imageName := b.properties.Image_name
+	imageConfig := imageConfigs[imageName]
+	if imageConfig == nil {
+		ctx.PropertyErrorf("image_name", "Unknown image name %q, expected one of %s", imageName, strings.Join(android.SortedStringKeys(imageConfigs), ", "))
+		return
+	}
+
+	// Construct the boot image info from the config.
+	info := BootImageInfo{imageConfig: imageConfig}
+
+	// Make it available for other modules.
+	ctx.SetProvider(BootImageInfoProvider, info)
+}
diff --git a/java/boot_image_test.go b/java/boot_image_test.go
new file mode 100644
index 0000000..a295782
--- /dev/null
+++ b/java/boot_image_test.go
@@ -0,0 +1,31 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"testing"
+)
+
+// Contains some simple tests for boot_image logic, additional tests can be found in
+// apex/boot_image_test.go as the ART boot image requires modules from the ART apex.
+
+func TestUnknownBootImage(t *testing.T) {
+	testJavaError(t, "image_name: Unknown image name \\\"unknown\\\", expected one of art, boot", `
+		boot_image {
+			name: "unknown-boot-image",
+			image_name: "unknown",
+		}
+`)
+}
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 2dcc9e2..36d0d30 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -35,6 +35,13 @@
 //
 // 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:
diff --git a/java/prebuilt_apis.go b/java/prebuilt_apis.go
index 0ffbaaa..1e90149 100644
--- a/java/prebuilt_apis.go
+++ b/java/prebuilt_apis.go
@@ -15,12 +15,14 @@
 package java
 
 import (
+	"fmt"
 	"strconv"
 	"strings"
 
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/genrule"
 )
 
 func init() {
@@ -35,6 +37,12 @@
 	// list of api version directories
 	Api_dirs []string
 
+	// The next API directory can optionally point to a directory where
+	// files incompatibility-tracking files are stored for the current
+	// "in progress" API. Each module present in one of the api_dirs will have
+	// a <module>-incompatibilities.api.<scope>.latest module created.
+	Next_api_dir *string
+
 	// The sdk_version of java_import modules generated based on jar files.
 	// Defaults to "current"
 	Imports_sdk_version *string
@@ -98,28 +106,46 @@
 	mctx.CreateModule(ImportFactory, &props)
 }
 
-func createFilegroup(mctx android.LoadHookContext, module string, scope string, apiver string, path string) {
-	fgName := module + ".api." + scope + "." + apiver
+func createFilegroup(mctx android.LoadHookContext, name string, path string) {
 	filegroupProps := struct {
 		Name *string
 		Srcs []string
 	}{}
-	filegroupProps.Name = proptools.StringPtr(fgName)
+	filegroupProps.Name = proptools.StringPtr(name)
 	filegroupProps.Srcs = []string{path}
 	mctx.CreateModule(android.FileGroupFactory, &filegroupProps)
 }
 
+func createEmptyFile(mctx android.LoadHookContext, name string) {
+	props := struct {
+		Name *string
+		Cmd  *string
+		Out  []string
+	}{}
+	props.Name = proptools.StringPtr(name)
+	props.Out = []string{name}
+	props.Cmd = proptools.StringPtr("touch $(genDir)/" + name)
+	mctx.CreateModule(genrule.GenRuleFactory, &props)
+}
+
 func getPrebuiltFiles(mctx android.LoadHookContext, p *prebuiltApis, name string) []string {
-	mydir := mctx.ModuleDir() + "/"
 	var files []string
 	for _, apiver := range p.properties.Api_dirs {
-		for _, scope := range []string{"public", "system", "test", "core", "module-lib", "system-server"} {
-			vfiles, err := mctx.GlobWithDeps(mydir+apiver+"/"+scope+"/"+name, nil)
-			if err != nil {
-				mctx.ModuleErrorf("failed to glob %s files under %q: %s", name, mydir+apiver+"/"+scope, err)
-			}
-			files = append(files, vfiles...)
+		files = append(files, getPrebuiltFilesInSubdir(mctx, apiver, name)...)
+	}
+	return files
+}
+
+func getPrebuiltFilesInSubdir(mctx android.LoadHookContext, subdir string, name string) []string {
+	var files []string
+	dir := mctx.ModuleDir() + "/" + subdir
+	for _, scope := range []string{"public", "system", "test", "core", "module-lib", "system-server"} {
+		glob := fmt.Sprintf("%s/%s/%s", dir, scope, name)
+		vfiles, err := mctx.GlobWithDeps(glob, nil)
+		if err != nil {
+			mctx.ModuleErrorf("failed to glob %s files under %q: %s", name, dir+"/"+scope, err)
 		}
+		files = append(files, vfiles...)
 	}
 	return files
 }
@@ -181,11 +207,14 @@
 
 	// Create filegroups for all (<module>, <scope, <version>) triplets,
 	// and a "latest" filegroup variant for each (<module>, <scope>) pair
+	moduleName := func(module, scope, version string) string {
+		return module + ".api." + scope + "." + version
+	}
 	m := make(map[string]latestApiInfo)
 	for _, f := range files {
 		localPath := strings.TrimPrefix(f, mydir)
 		module, apiver, scope := parseApiFilePath(mctx, localPath)
-		createFilegroup(mctx, module, scope, apiver, localPath)
+		createFilegroup(mctx, moduleName(module, scope, apiver), localPath)
 
 		version, err := strconv.Atoi(apiver)
 		if err != nil {
@@ -193,20 +222,52 @@
 			return
 		}
 
-		key := module + "." + scope
-		info, ok := m[key]
-		if !ok {
-			m[key] = latestApiInfo{module, scope, version, localPath}
-		} else if version > info.version {
-			info.version = version
-			info.path = localPath
-			m[key] = info
+		// Track latest version of each module/scope, except for incompatibilities
+		if !strings.HasSuffix(module, "incompatibilities") {
+			key := module + "." + scope
+			info, ok := m[key]
+			if !ok {
+				m[key] = latestApiInfo{module, scope, version, localPath}
+			} else if version > info.version {
+				info.version = version
+				info.path = localPath
+				m[key] = info
+			}
 		}
 	}
+
 	// Sort the keys in order to make build.ninja stable
 	for _, k := range android.SortedStringKeys(m) {
 		info := m[k]
-		createFilegroup(mctx, info.module, info.scope, "latest", info.path)
+		name := moduleName(info.module, info.scope, "latest")
+		createFilegroup(mctx, name, info.path)
+	}
+
+	// Create incompatibilities tracking files for all modules, if we have a "next" api.
+	if nextApiDir := String(p.properties.Next_api_dir); nextApiDir != "" {
+		files := getPrebuiltFilesInSubdir(mctx, nextApiDir, "api/*incompatibilities.txt")
+		incompatibilities := make(map[string]bool)
+		for _, f := range files {
+			localPath := strings.TrimPrefix(f, mydir)
+			module, _, scope := parseApiFilePath(mctx, localPath)
+
+			// Figure out which module is referenced by this file. Special case for "android".
+			referencedModule := strings.TrimSuffix(module, "incompatibilities")
+			referencedModule = strings.TrimSuffix(referencedModule, "-")
+			if referencedModule == "" {
+				referencedModule = "android"
+			}
+
+			createFilegroup(mctx, moduleName(referencedModule+"-incompatibilities", scope, "latest"), localPath)
+
+			incompatibilities[referencedModule+"."+scope] = true
+		}
+		// Create empty incompatibilities files for remaining modules
+		for _, k := range android.SortedStringKeys(m) {
+			if _, ok := incompatibilities[k]; !ok {
+				createEmptyFile(mctx, moduleName(m[k].module+"-incompatibilities", m[k].scope, "latest"))
+			}
+		}
 	}
 }
 
diff --git a/java/testing.go b/java/testing.go
index 0b1f2d1..31ff47f 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -107,6 +107,7 @@
 	RegisterAppBuildComponents(ctx)
 	RegisterAppImportBuildComponents(ctx)
 	RegisterAppSetBuildComponents(ctx)
+	RegisterBootImageBuildComponents(ctx)
 	RegisterDexpreoptBootJarsComponents(ctx)
 	RegisterDocsBuildComponents(ctx)
 	RegisterGenRuleBuildComponents(ctx)
@@ -218,6 +219,16 @@
 		dex_bootjars {
 			name: "dex_bootjars",
 		}
+
+		boot_image {
+			name: "art-boot-image",
+			image_name: "art",
+		}
+
+		boot_image {
+			name: "framework-boot-image",
+			image_name: "boot",
+		}
 `
 
 	return bp
diff --git a/licenses/Android.bp b/licenses/Android.bp
index d7fac90..f4a76d7 100644
--- a/licenses/Android.bp
+++ b/licenses/Android.bp
@@ -125,6 +125,22 @@
 }
 
 license_kind {
+    name: "SPDX-license-identifier-APSL-1.1",
+    conditions: [
+        "reciprocal",
+    ],
+    url: "https://spdx.org/licenses/APSL-1.1.html",
+}
+
+license_kind {
+    name: "SPDX-license-identifier-APSL-2.0",
+    conditions: [
+        "reciprocal",
+    ],
+    url: "https://spdx.org/licenses/APSL-2.0.html",
+}
+
+license_kind {
     name: "SPDX-license-identifier-Apache",
     conditions: ["notice"],
 }
diff --git a/rust/config/allowed_list.go b/rust/config/allowed_list.go
index 21df024..fc11d29 100644
--- a/rust/config/allowed_list.go
+++ b/rust/config/allowed_list.go
@@ -19,6 +19,7 @@
 		"prebuilts/rust",
 		"system/bt",
 		"system/extras/profcollectd",
+		"system/extras/simpleperf",
 		"system/hardware/interfaces/keystore2",
 		"system/security",
 		"system/tools/aidl",
diff --git a/scripts/OWNERS b/scripts/OWNERS
index 8198083..c4c39d5 100644
--- a/scripts/OWNERS
+++ b/scripts/OWNERS
@@ -1,5 +1,4 @@
 per-file system-clang-format,system-clang-format-2 = enh@google.com,smoreland@google.com
 per-file build-mainline-modules.sh = ngeoffray@google.com,paulduffin@google.com,mast@google.com
-per-file build-aml-prebuilts.sh = ngeoffray@google.com,paulduffin@google.com,mast@google.com
 per-file construct_context.py = ngeoffray@google.com,calin@google.com,mathieuc@google.com,skvadrik@google.com
 per-file conv_linker_config.py = kiyoungkim@google.com, jiyong@google.com, jooyung@google.com
diff --git a/scripts/build-aml-prebuilts.sh b/scripts/build-aml-prebuilts.sh
deleted file mode 100755
index 4b08ac3..0000000
--- a/scripts/build-aml-prebuilts.sh
+++ /dev/null
@@ -1,131 +0,0 @@
-#!/bin/bash -e
-
-# This is a wrapper around "m" that builds the given modules in multi-arch mode
-# for all architectures supported by Mainline modules. The make (kati) stage is
-# skipped, so the build targets in the arguments can only be Soong modules or
-# intermediate output files - make targets and normal installed paths are not
-# supported.
-#
-# This script is typically used with "sdk" or "module_export" modules, which
-# Soong will install in $OUT_DIR/soong/mainline-sdks (cf
-# PathForMainlineSdksInstall in android/paths.go).
-
-export OUT_DIR=${OUT_DIR:-out}
-
-if [ -e ${OUT_DIR}/soong/.soong.kati_enabled ]; then
-  # If ${OUT_DIR} has been created without --skip-make, Soong will create an
-  # ${OUT_DIR}/soong/build.ninja that leaves out many targets which are
-  # expected to be supplied by the .mk files, and that might cause errors in
-  # "m --skip-make" below. We therefore default to a different out dir
-  # location in that case.
-  AML_OUT_DIR=out/aml
-  echo "Avoiding in-make OUT_DIR '${OUT_DIR}' - building in '${AML_OUT_DIR}' instead"
-  OUT_DIR=${AML_OUT_DIR}
-fi
-
-if [ ! -e "build/envsetup.sh" ]; then
-  echo "$0 must be run from the top of the tree"
-  exit 1
-fi
-
-source build/envsetup.sh
-
-my_get_build_var() {
-  # get_build_var will run Soong in normal in-make mode where it creates
-  # .soong.kati_enabled. That would clobber our real out directory, so we need
-  # to run it in a different one.
-  OUT_DIR=${OUT_DIR}/get_build_var get_build_var "$@"
-}
-
-readonly SOONG_OUT=${OUT_DIR}/soong
-mkdir -p ${SOONG_OUT}
-
-# Some Soong build rules may require this, and the failure mode if it's missing
-# is confusing (b/172548608).
-readonly BUILD_NUMBER="$(my_get_build_var BUILD_NUMBER)"
-echo -n ${BUILD_NUMBER} > ${SOONG_OUT}/build_number.txt
-
-readonly PLATFORM_SDK_VERSION="$(my_get_build_var PLATFORM_SDK_VERSION)"
-readonly PLATFORM_VERSION="$(my_get_build_var PLATFORM_VERSION)"
-PLATFORM_VERSION_ALL_CODENAMES="$(my_get_build_var PLATFORM_VERSION_ALL_CODENAMES)"
-
-# PLATFORM_VERSION_ALL_CODENAMES is a comma separated list like O,P. We need to
-# turn this into ["O","P"].
-PLATFORM_VERSION_ALL_CODENAMES="${PLATFORM_VERSION_ALL_CODENAMES/,/'","'}"
-PLATFORM_VERSION_ALL_CODENAMES="[\"${PLATFORM_VERSION_ALL_CODENAMES}\"]"
-
-# Get the list of missing <uses-library> modules and convert it to a JSON array
-# (quote module names, add comma separator and wrap in brackets).
-MISSING_USES_LIBRARIES="$(my_get_build_var INTERNAL_PLATFORM_MISSING_USES_LIBRARIES)"
-MISSING_USES_LIBRARIES="[$(echo $MISSING_USES_LIBRARIES | sed -e 's/\([^ ]\+\)/\"\1\"/g' -e 's/[ ]\+/, /g')]"
-
-# Logic from build/make/core/goma.mk
-if [ "${USE_GOMA}" = true ]; then
-  if [ -n "${GOMA_DIR}" ]; then
-    goma_dir="${GOMA_DIR}"
-  else
-    goma_dir="${HOME}/goma"
-  fi
-  GOMA_CC="${goma_dir}/gomacc"
-  export CC_WRAPPER="${CC_WRAPPER}${CC_WRAPPER:+ }${GOMA_CC}"
-  export CXX_WRAPPER="${CXX_WRAPPER}${CXX_WRAPPER:+ }${GOMA_CC}"
-  export JAVAC_WRAPPER="${JAVAC_WRAPPER}${JAVAC_WRAPPER:+ }${GOMA_CC}"
-else
-  USE_GOMA=false
-fi
-
-readonly SOONG_VARS=${SOONG_OUT}/soong.variables
-
-# Aml_abis: true
-#   -  This flag configures Soong to compile for all architectures required for
-#      Mainline modules.
-# CrossHost: linux_bionic
-# CrossHostArch: x86_64
-#   -  Enable Bionic on host as ART needs prebuilts for it.
-# VendorVars.art_mdoule.source_build
-#   -  TODO(b/172480615): Change default to false when platform uses ART Module
-#      prebuilts by default.
-cat > ${SOONG_VARS}.new << EOF
-{
-    "BuildNumberFile": "build_number.txt",
-
-    "Platform_version_name": "${PLATFORM_VERSION}",
-    "Platform_sdk_version": ${PLATFORM_SDK_VERSION},
-    "Platform_sdk_codename": "${PLATFORM_VERSION}",
-    "Platform_version_active_codenames": ${PLATFORM_VERSION_ALL_CODENAMES},
-
-    "DeviceName": "generic_arm64",
-    "HostArch": "x86_64",
-    "HostSecondaryArch": "x86",
-    "CrossHost": "linux_bionic",
-    "CrossHostArch": "x86_64",
-    "Aml_abis": true,
-
-    "Allow_missing_dependencies": ${SOONG_ALLOW_MISSING_DEPENDENCIES:-false},
-    "Unbundled_build": ${TARGET_BUILD_UNBUNDLED:-false},
-    "UseGoma": ${USE_GOMA},
-
-    "VendorVars": {
-        "art_module": {
-            "source_build": "${ENABLE_ART_SOURCE_BUILD:-true}"
-        }
-    },
-
-    "MissingUsesLibraries": ${MISSING_USES_LIBRARIES}
-}
-EOF
-
-if [ -f ${SOONG_VARS} ] && cmp -s ${SOONG_VARS} ${SOONG_VARS}.new; then
-  # Don't touch soong.variables if we don't have to, to avoid Soong rebuilding
-  # the ninja file when it isn't necessary.
-  rm ${SOONG_VARS}.new
-else
-  mv ${SOONG_VARS}.new ${SOONG_VARS}
-fi
-
-# We use force building LLVM components flag (even though we actually don't
-# compile them) because we don't have bionic host prebuilts
-# for them.
-export FORCE_BUILD_LLVM_COMPONENTS=true
-
-m --skip-make "$@"
diff --git a/scripts/build-mainline-modules.sh b/scripts/build-mainline-modules.sh
index ea62af4..d6c6f94 100755
--- a/scripts/build-mainline-modules.sh
+++ b/scripts/build-mainline-modules.sh
@@ -84,10 +84,16 @@
 done
 
 # Create multi-archs SDKs in a different out directory. The multi-arch script
-# uses Soong in --skip-make mode which cannot use the same directory as normal
+# uses Soong in --skip-kati mode which cannot use the same directory as normal
 # mode with make.
 export OUT_DIR=${OUT_DIR}/aml
-echo_and_run build/soong/scripts/build-aml-prebuilts.sh ${MODULES_SDK_AND_EXPORTS[@]}
+# We use force building LLVM components flag (even though we actually don't
+# compile them) because we don't have bionic host prebuilts
+# for them.
+export FORCE_BUILD_LLVM_COMPONENTS=true
+
+echo_and_run build/soong/soong_ui.bash --make-mode --skip-kati \
+  TARGET_PRODUCT=mainline_sdk "$@" ${MODULES_SDK_AND_EXPORTS[@]}
 
 rm -rf ${DIST_DIR}/mainline-sdks
 echo_and_run cp -R ${OUT_DIR}/soong/mainline-sdks ${DIST_DIR}