Merge "Switch to clang r416183b 12.0.5."
diff --git a/android/androidmk.go b/android/androidmk.go
index 66a1036..618e4be 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -505,6 +505,8 @@
 	// TODO(b/151177513): Does this code need to set LOCAL_MODULE_IS_CONTAINER ?
 	if amod.commonProperties.Effective_package_name != nil {
 		a.SetString("LOCAL_LICENSE_PACKAGE_NAME", *amod.commonProperties.Effective_package_name)
+	} else if len(amod.commonProperties.Effective_licenses) > 0 {
+		a.SetString("LOCAL_LICENSE_PACKAGE_NAME", strings.Join(amod.commonProperties.Effective_licenses, " "))
 	}
 	a.SetString("LOCAL_MODULE_CLASS", a.Class)
 	a.SetString("LOCAL_PREBUILT_MODULE_FILE", a.OutputFile.String())
diff --git a/android/apex.go b/android/apex.go
index cfda2aa..25a07b8 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -43,10 +43,8 @@
 	// mergeApexVariations.
 	ApexVariationName string
 
-	// Serialized ApiLevel that this module has to support at minimum. Should be accessed via
-	// MinSdkVersion() method. Cannot be stored in its struct form because this is cloned into
-	// properties structs, and ApiLevel has private members.
-	MinSdkVersionStr string
+	// ApiLevel that this module has to support at minimum.
+	MinSdkVersion ApiLevel
 
 	// True if this module comes from an updatable apexBundle.
 	Updatable bool
@@ -82,19 +80,13 @@
 // have to be built twice, but only once. In that case, the two apex variations apex.a and apex.b
 // are configured to have the same alias variation named apex29.
 func (i ApexInfo) mergedName(ctx PathContext) string {
-	name := "apex" + strconv.Itoa(i.MinSdkVersion(ctx).FinalOrFutureInt())
+	name := "apex" + strconv.Itoa(i.MinSdkVersion.FinalOrFutureInt())
 	for _, sdk := range i.RequiredSdks {
 		name += "_" + sdk.Name + "_" + sdk.Version
 	}
 	return name
 }
 
-// MinSdkVersion gives the api level that this module has to support at minimum. This is from the
-// min_sdk_version property of the containing apexBundle.
-func (i ApexInfo) MinSdkVersion(ctx PathContext) ApiLevel {
-	return ApiLevelOrPanic(ctx, i.MinSdkVersionStr)
-}
-
 // IsForPlatform tells whether this module is for the platform or not. If false is returned, it
 // means that this apex variant of the module is built for an APEX.
 func (i ApexInfo) IsForPlatform() bool {
diff --git a/android/apex_test.go b/android/apex_test.go
index b5323e8..109b1c8 100644
--- a/android/apex_test.go
+++ b/android/apex_test.go
@@ -33,10 +33,10 @@
 		{
 			name: "single",
 			in: []ApexInfo{
-				{"foo", "current", false, nil, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"foo", FutureApiLevel, false, nil, []string{"foo"}, nil, NotForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex10000", "current", false, nil, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"apex10000", FutureApiLevel, false, nil, []string{"foo"}, nil, NotForPrebuiltApex},
 			},
 			wantAliases: [][2]string{
 				{"foo", "apex10000"},
@@ -45,11 +45,11 @@
 		{
 			name: "merge",
 			in: []ApexInfo{
-				{"foo", "current", false, SdkRefs{{"baz", "1"}}, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", "current", false, SdkRefs{{"baz", "1"}}, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"foo", FutureApiLevel, false, SdkRefs{{"baz", "1"}}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"bar", FutureApiLevel, false, SdkRefs{{"baz", "1"}}, []string{"bar"}, nil, NotForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex10000_baz_1", "current", false, SdkRefs{{"baz", "1"}}, []string{"bar", "foo"}, nil, false}},
+				{"apex10000_baz_1", FutureApiLevel, false, SdkRefs{{"baz", "1"}}, []string{"bar", "foo"}, nil, false}},
 			wantAliases: [][2]string{
 				{"bar", "apex10000_baz_1"},
 				{"foo", "apex10000_baz_1"},
@@ -58,12 +58,12 @@
 		{
 			name: "don't merge version",
 			in: []ApexInfo{
-				{"foo", "current", false, nil, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", "30", false, nil, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"foo", FutureApiLevel, false, nil, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"bar", uncheckedFinalApiLevel(30), false, nil, []string{"bar"}, nil, NotForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex30", "30", false, nil, []string{"bar"}, nil, NotForPrebuiltApex},
-				{"apex10000", "current", false, nil, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"apex30", uncheckedFinalApiLevel(30), false, nil, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"apex10000", FutureApiLevel, false, nil, []string{"foo"}, nil, NotForPrebuiltApex},
 			},
 			wantAliases: [][2]string{
 				{"bar", "apex30"},
@@ -73,11 +73,11 @@
 		{
 			name: "merge updatable",
 			in: []ApexInfo{
-				{"foo", "current", false, nil, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", "current", true, nil, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"foo", FutureApiLevel, false, nil, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"bar", FutureApiLevel, true, nil, []string{"bar"}, nil, NotForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex10000", "current", true, nil, []string{"bar", "foo"}, nil, NotForPrebuiltApex},
+				{"apex10000", FutureApiLevel, true, nil, []string{"bar", "foo"}, nil, NotForPrebuiltApex},
 			},
 			wantAliases: [][2]string{
 				{"bar", "apex10000"},
@@ -87,12 +87,12 @@
 		{
 			name: "don't merge sdks",
 			in: []ApexInfo{
-				{"foo", "current", false, SdkRefs{{"baz", "1"}}, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", "current", false, SdkRefs{{"baz", "2"}}, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"foo", FutureApiLevel, false, SdkRefs{{"baz", "1"}}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"bar", FutureApiLevel, false, SdkRefs{{"baz", "2"}}, []string{"bar"}, nil, NotForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex10000_baz_2", "current", false, SdkRefs{{"baz", "2"}}, []string{"bar"}, nil, NotForPrebuiltApex},
-				{"apex10000_baz_1", "current", false, SdkRefs{{"baz", "1"}}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"apex10000_baz_2", FutureApiLevel, false, SdkRefs{{"baz", "2"}}, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"apex10000_baz_1", FutureApiLevel, false, SdkRefs{{"baz", "1"}}, []string{"foo"}, nil, NotForPrebuiltApex},
 			},
 			wantAliases: [][2]string{
 				{"bar", "apex10000_baz_2"},
@@ -102,15 +102,15 @@
 		{
 			name: "don't merge when for prebuilt_apex",
 			in: []ApexInfo{
-				{"foo", "current", false, nil, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", "current", true, nil, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"foo", FutureApiLevel, false, nil, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"bar", FutureApiLevel, true, nil, []string{"bar"}, nil, NotForPrebuiltApex},
 				// This one should not be merged in with the others because it is for
 				// a prebuilt_apex.
-				{"baz", "current", true, nil, []string{"baz"}, nil, ForPrebuiltApex},
+				{"baz", FutureApiLevel, true, nil, []string{"baz"}, nil, ForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex10000", "current", true, nil, []string{"bar", "foo"}, nil, NotForPrebuiltApex},
-				{"baz", "current", true, nil, []string{"baz"}, nil, ForPrebuiltApex},
+				{"apex10000", FutureApiLevel, true, nil, []string{"bar", "foo"}, nil, NotForPrebuiltApex},
+				{"baz", FutureApiLevel, true, nil, []string{"baz"}, nil, ForPrebuiltApex},
 			},
 			wantAliases: [][2]string{
 				{"bar", "apex10000"},
diff --git a/android/arch.go b/android/arch.go
index e40b6f5..9f93752 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -19,7 +19,6 @@
 	"fmt"
 	"reflect"
 	"runtime"
-	"strconv"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -176,7 +175,7 @@
 // MarshalText allows an ArchType to be serialized through any encoder that supports
 // encoding.TextMarshaler.
 func (a ArchType) MarshalText() ([]byte, error) {
-	return []byte(strconv.Quote(a.String())), nil
+	return []byte(a.String()), nil
 }
 
 var _ encoding.TextMarshaler = ArchType{}
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 2697007..8d561d2 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -737,7 +737,7 @@
 
 	// Add ninja file dependencies for files which all bazel invocations require.
 	bazelBuildList := absolutePath(filepath.Join(
-		filepath.Dir(bootstrap.CmdlineModuleListFile()), "bazel.list"))
+		filepath.Dir(bootstrap.CmdlineArgs.ModuleListFile), "bazel.list"))
 	ctx.AddNinjaFileDeps(bazelBuildList)
 
 	data, err := ioutil.ReadFile(bazelBuildList)
diff --git a/android/config.go b/android/config.go
index c170f1e..3db7980 100644
--- a/android/config.go
+++ b/android/config.go
@@ -1295,10 +1295,6 @@
 	return Bool(c.productVariables.Aml_abis)
 }
 
-func (c *config) ExcludeDraftNdkApis() bool {
-	return Bool(c.productVariables.Exclude_draft_ndk_apis)
-}
-
 func (c *config) FlattenApex() bool {
 	return Bool(c.productVariables.Flatten_apex)
 }
diff --git a/android/variable.go b/android/variable.go
index f25143d..e830845 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -331,8 +331,7 @@
 
 	VendorVars map[string]map[string]string `json:",omitempty"`
 
-	Ndk_abis               *bool `json:",omitempty"`
-	Exclude_draft_ndk_apis *bool `json:",omitempty"`
+	Ndk_abis *bool `json:",omitempty"`
 
 	Flatten_apex                 *bool `json:",omitempty"`
 	ForceApexSymlinkOptimization *bool `json:",omitempty"`
diff --git a/apex/apex.go b/apex/apex.go
index 0eacf6d..f5e6fa9 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -903,7 +903,7 @@
 	// be built for this apexBundle.
 	apexInfo := android.ApexInfo{
 		ApexVariationName: mctx.ModuleName(),
-		MinSdkVersionStr:  minSdkVersion.String(),
+		MinSdkVersion:     minSdkVersion,
 		RequiredSdks:      a.RequiredSdks(),
 		Updatable:         a.Updatable(),
 		InApexes:          []string{mctx.ModuleName()},
diff --git a/apex/builder.go b/apex/builder.go
index bbb4666..e59dc96 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -517,6 +517,9 @@
 	outHostBinDir := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "bin").String()
 	prebuiltSdkToolsBinDir := filepath.Join("prebuilts", "sdk", "tools", runtime.GOOS, "bin")
 
+	// Figure out if need to compress apex.
+	compressionEnabled := ctx.Config().CompressedApex() && proptools.BoolDefault(a.properties.Compressible, false) && !a.testApex
+
 	if apexType == imageApex {
 		////////////////////////////////////////////////////////////////////////////////////
 		// Step 2: create canned_fs_config which encodes filemode,uid,gid of each files
@@ -631,7 +634,7 @@
 			ctx.PropertyErrorf("test_only_no_hashtree", "not available")
 			return
 		}
-		if moduleMinSdkVersion.GreaterThan(android.SdkVersion_Android10) || a.testOnlyShouldSkipHashtreeGeneration() {
+		if (moduleMinSdkVersion.GreaterThan(android.SdkVersion_Android10) || a.testOnlyShouldSkipHashtreeGeneration()) && !compressionEnabled {
 			// Apexes which are supposed to be installed in builtin dirs(/system, etc)
 			// don't need hashtree for activation. Therefore, by removing hashtree from
 			// apex bundle (filesystem image in it, to be specific), we can save storage.
@@ -780,12 +783,11 @@
 	})
 	a.outputFile = signedOutputFile
 
-	// Process APEX compression if enabled or forced
 	if ctx.ModuleDir() != "system/apex/apexd/apexd_testdata" && a.testOnlyShouldForceCompression() {
 		ctx.PropertyErrorf("test_only_force_compression", "not available")
 		return
 	}
-	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/cquery/request_type_test.go b/bazel/cquery/request_type_test.go
index 56e03e2..48edb90 100644
--- a/bazel/cquery/request_type_test.go
+++ b/bazel/cquery/request_type_test.go
@@ -76,6 +76,12 @@
 			expectedOutput:       CcInfo{},
 			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", 3, []string{"", ""}),
 		},
+		{
+			description:          "too many result splits",
+			input:                "|||",
+			expectedOutput:       CcInfo{},
+			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", 3, []string{"", "", "", ""}),
+		},
 	}
 	for _, tc := range testCases {
 		actualOutput, err := GetCcInfo.ParseResult(tc.input)
diff --git a/bloaty/Android.bp b/bloaty/Android.bp
index 96cc1a5..7e722a7 100644
--- a/bloaty/Android.bp
+++ b/bloaty/Android.bp
@@ -11,6 +11,7 @@
     ],
     srcs: [
         "bloaty.go",
+        "testing.go",
     ],
     pluginFor: ["soong_build"],
 }
diff --git a/bloaty/bloaty.go b/bloaty/bloaty.go
index 764cded..50f241f 100644
--- a/bloaty/bloaty.go
+++ b/bloaty/bloaty.go
@@ -52,12 +52,28 @@
 	pctx.SourcePathVariable("bloaty", "prebuilts/build-tools/${hostPrebuiltTag}/bin/bloaty")
 	pctx.HostBinToolVariable("bloatyMerger", "bloaty_merger")
 	android.RegisterSingletonType("file_metrics", fileSizesSingleton)
-	fileSizeMeasurerKey = blueprint.NewProvider(android.ModuleOutPath{})
+	fileSizeMeasurerKey = blueprint.NewProvider(measuredFiles{})
 }
 
-// MeasureSizeForPath should be called by binary producers (e.g. in builder.go).
-func MeasureSizeForPath(ctx android.ModuleContext, filePath android.WritablePath) {
-	ctx.SetProvider(fileSizeMeasurerKey, filePath)
+// measuredFiles contains the paths of the files measured by a module.
+type measuredFiles struct {
+	paths []android.WritablePath
+}
+
+// MeasureSizeForPaths should be called by binary producers to measure the
+// sizes of artifacts. It must only be called once per module; it will panic
+// otherwise.
+func MeasureSizeForPaths(ctx android.ModuleContext, paths ...android.OptionalPath) {
+	mf := measuredFiles{}
+	for _, p := range paths {
+		if !p.Valid() {
+			continue
+		}
+		if p, ok := p.Path().(android.WritablePath); ok {
+			mf.paths = append(mf.paths, p)
+		}
+	}
+	ctx.SetProvider(fileSizeMeasurerKey, mf)
 }
 
 type sizesSingleton struct{}
@@ -68,21 +84,22 @@
 
 func (singleton *sizesSingleton) GenerateBuildActions(ctx android.SingletonContext) {
 	var deps android.Paths
-	// Visit all modules. If the size provider give us a binary path to measure,
-	// create the rule to measure it.
 	ctx.VisitAllModules(func(m android.Module) {
 		if !ctx.ModuleHasProvider(m, fileSizeMeasurerKey) {
 			return
 		}
-		filePath := ctx.ModuleProvider(m, fileSizeMeasurerKey).(android.ModuleOutPath)
-		sizeFile := filePath.InSameDir(ctx, filePath.Base()+bloatyDescriptorExt)
-		ctx.Build(pctx, android.BuildParams{
-			Rule:        bloaty,
-			Description: "bloaty " + filePath.Rel(),
-			Input:       filePath,
-			Output:      sizeFile,
-		})
-		deps = append(deps, sizeFile)
+		filePaths := ctx.ModuleProvider(m, fileSizeMeasurerKey).(measuredFiles)
+		for _, path := range filePaths.paths {
+			filePath := path.(android.ModuleOutPath)
+			sizeFile := filePath.InSameDir(ctx, filePath.Base()+bloatyDescriptorExt)
+			ctx.Build(pctx, android.BuildParams{
+				Rule:        bloaty,
+				Description: "bloaty " + filePath.Rel(),
+				Input:       filePath,
+				Output:      sizeFile,
+			})
+			deps = append(deps, sizeFile)
+		}
 	})
 
 	ctx.Build(pctx, android.BuildParams{
diff --git a/bloaty/testing.go b/bloaty/testing.go
new file mode 100644
index 0000000..5f5ec84
--- /dev/null
+++ b/bloaty/testing.go
@@ -0,0 +1,25 @@
+// Copyright 2021 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bloaty
+
+import (
+	"android/soong/android"
+)
+
+// Preparer that will define the default bloaty singleton.
+var PrepareForTestWithBloatyDefaultModules = android.GroupFixturePreparers(
+	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+		ctx.RegisterSingletonType("file_metrics", fileSizesSingleton)
+	}))
diff --git a/cc/api_level.go b/cc/api_level.go
index c93d6ed..fd145a9 100644
--- a/cc/api_level.go
+++ b/cc/api_level.go
@@ -53,14 +53,6 @@
 	return value, nil
 }
 
-func nativeApiLevelFromUserWithDefault(ctx android.BaseModuleContext,
-	raw string, defaultValue string) (android.ApiLevel, error) {
-	if raw == "" {
-		raw = defaultValue
-	}
-	return nativeApiLevelFromUser(ctx, raw)
-}
-
 func nativeApiLevelOrPanic(ctx android.BaseModuleContext,
 	raw string) android.ApiLevel {
 	value, err := nativeApiLevelFromUser(ctx, raw)
diff --git a/cc/cc.go b/cc/cc.go
index bef49b8..7f69d56 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -2499,7 +2499,7 @@
 	c.apexSdkVersion = android.FutureApiLevel
 	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
 	if !apexInfo.IsForPlatform() {
-		c.apexSdkVersion = apexInfo.MinSdkVersion(ctx)
+		c.apexSdkVersion = apexInfo.MinSdkVersion
 	}
 
 	if android.InList("hwaddress", ctx.Config().SanitizeDevice()) {
diff --git a/cc/library.go b/cc/library.go
index 53be3a5..42115cc 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -60,6 +60,7 @@
 
 	Static_ndk_lib *bool
 
+	// Generate stubs to make this library accessible to APEXes.
 	Stubs struct {
 		// Relative path to the symbol map. The symbol map provides the list of
 		// symbols that are exported for stubs variant of this library.
diff --git a/cc/ndk_headers.go b/cc/ndk_headers.go
index 60f931d..56fd5fc 100644
--- a/cc/ndk_headers.go
+++ b/cc/ndk_headers.go
@@ -75,11 +75,6 @@
 
 	// Path to the NOTICE file associated with the headers.
 	License *string `android:"path"`
-
-	// True if this API is not yet ready to be shipped in the NDK. It will be
-	// available in the platform for testing, but will be excluded from the
-	// sysroot provided to the NDK proper.
-	Draft bool
 }
 
 type headerModule struct {
@@ -184,11 +179,6 @@
 
 	// Path to the NOTICE file associated with the headers.
 	License *string
-
-	// True if this API is not yet ready to be shipped in the NDK. It will be
-	// available in the platform for testing, but will be excluded from the
-	// sysroot provided to the NDK proper.
-	Draft bool
 }
 
 // Like ndk_headers, but preprocesses the headers with the bionic versioner:
@@ -311,11 +301,6 @@
 
 	// Path to the NOTICE file associated with the headers.
 	License *string
-
-	// True if this API is not yet ready to be shipped in the NDK. It will be
-	// available in the platform for testing, but will be excluded from the
-	// sysroot provided to the NDK proper.
-	Draft bool
 }
 
 type preprocessedHeadersModule struct {
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index 10de889..95d8477 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -20,6 +20,7 @@
 	"sync"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
 )
@@ -78,11 +79,6 @@
 	// used. This is only needed to work around platform bugs like
 	// https://github.com/android-ndk/ndk/issues/265.
 	Unversioned_until *string
-
-	// True if this API is not yet ready to be shipped in the NDK. It will be
-	// available in the platform for testing, but will be excluded from the
-	// sysroot provided to the NDK proper.
-	Draft bool
 }
 
 type stubDecorator struct {
@@ -147,8 +143,8 @@
 		return false
 	}
 
-	this.unversionedUntil, err = nativeApiLevelFromUserWithDefault(ctx,
-		String(this.properties.Unversioned_until), "minimum")
+	str := proptools.StringDefault(this.properties.Unversioned_until, "minimum")
+	this.unversionedUntil, err = nativeApiLevelFromUser(ctx, str)
 	if err != nil {
 		ctx.PropertyErrorf("unversioned_until", err.Error())
 		return false
diff --git a/cc/ndk_sysroot.go b/cc/ndk_sysroot.go
index 70b15c1..d8c500e 100644
--- a/cc/ndk_sysroot.go
+++ b/cc/ndk_sysroot.go
@@ -104,38 +104,22 @@
 		}
 
 		if m, ok := module.(*headerModule); ok {
-			if ctx.Config().ExcludeDraftNdkApis() && m.properties.Draft {
-				return
-			}
-
 			installPaths = append(installPaths, m.installPaths...)
 			licensePaths = append(licensePaths, m.licensePath)
 		}
 
 		if m, ok := module.(*versionedHeaderModule); ok {
-			if ctx.Config().ExcludeDraftNdkApis() && m.properties.Draft {
-				return
-			}
-
 			installPaths = append(installPaths, m.installPaths...)
 			licensePaths = append(licensePaths, m.licensePath)
 		}
 
 		if m, ok := module.(*preprocessedHeadersModule); ok {
-			if ctx.Config().ExcludeDraftNdkApis() && m.properties.Draft {
-				return
-			}
-
 			installPaths = append(installPaths, m.installPaths...)
 			licensePaths = append(licensePaths, m.licensePath)
 		}
 
 		if m, ok := module.(*Module); ok {
 			if installer, ok := m.installer.(*stubDecorator); ok && m.library.buildStubs() {
-				if ctx.Config().ExcludeDraftNdkApis() &&
-					installer.properties.Draft {
-					return
-				}
 				installPaths = append(installPaths, installer.installPath)
 			}
 
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 7a4cb29..1e796ec 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -26,6 +26,7 @@
 	"android/soong/bp2build"
 	"android/soong/shared"
 	"github.com/google/blueprint/bootstrap"
+	"github.com/google/blueprint/deptools"
 
 	"android/soong/android"
 )
@@ -89,7 +90,7 @@
 }
 
 func newConfig(srcDir, outDir string, availableEnv map[string]string) android.Config {
-	configuration, err := android.NewConfig(srcDir, outDir, bootstrap.CmdlineModuleListFile(), availableEnv)
+	configuration, err := android.NewConfig(srcDir, outDir, bootstrap.CmdlineArgs.ModuleListFile, availableEnv)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "%s", err)
 		os.Exit(1)
@@ -103,21 +104,31 @@
 // TODO(cparsons): Don't output any ninja file, as the second pass will overwrite
 // the incorrect results from the first pass, and file I/O is expensive.
 func runMixedModeBuild(configuration android.Config, firstCtx *android.Context, extraNinjaDeps []string) {
+	var firstArgs, secondArgs bootstrap.Args
+
+	firstArgs = bootstrap.CmdlineArgs
 	configuration.SetStopBefore(bootstrap.StopBeforeWriteNinja)
-	bootstrap.Main(firstCtx.Context, configuration, false, extraNinjaDeps...)
+	bootstrap.RunBlueprint(firstArgs, firstCtx.Context, configuration, extraNinjaDeps...)
+
 	// Invoke bazel commands and save results for second pass.
 	if err := configuration.BazelContext.InvokeBazel(); err != nil {
 		fmt.Fprintf(os.Stderr, "%s", err)
 		os.Exit(1)
 	}
 	// Second pass: Full analysis, using the bazel command results. Output ninja file.
-	secondPassConfig, err := android.ConfigForAdditionalRun(configuration)
+	secondConfig, err := android.ConfigForAdditionalRun(configuration)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "%s", err)
 		os.Exit(1)
 	}
-	secondCtx := newContext(secondPassConfig, true)
-	bootstrap.Main(secondCtx.Context, secondPassConfig, false, extraNinjaDeps...)
+	secondCtx := newContext(secondConfig, true)
+	secondArgs = bootstrap.CmdlineArgs
+	ninjaDeps := bootstrap.RunBlueprint(secondArgs, secondCtx.Context, secondConfig, extraNinjaDeps...)
+	err = deptools.WriteDepFile(shared.JoinPath(topDir, secondArgs.DepFile), secondArgs.OutFile, ninjaDeps)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Error writing depfile '%s': %s\n", secondArgs.DepFile, err)
+		os.Exit(1)
+	}
 }
 
 // Run the code-generation phase to convert BazelTargetModules to BUILD files.
@@ -132,7 +143,8 @@
 
 func runSoongDocs(configuration android.Config, extraNinjaDeps []string) {
 	ctx := newContext(configuration, false)
-	bootstrap.Main(ctx.Context, configuration, false, extraNinjaDeps...)
+	soongDocsArgs := bootstrap.CmdlineArgs
+	bootstrap.RunBlueprint(soongDocsArgs, ctx.Context, configuration, extraNinjaDeps...)
 	if err := writeDocs(ctx, configuration, docFile); err != nil {
 		fmt.Fprintf(os.Stderr, "%s", err)
 		os.Exit(1)
@@ -166,8 +178,8 @@
 	generateQueryView := bazelQueryViewDir != ""
 	jsonModuleFile := configuration.Getenv("SOONG_DUMP_JSON_MODULE_GRAPH")
 
+	blueprintArgs := bootstrap.CmdlineArgs
 	prepareBuildActions := !generateQueryView && jsonModuleFile == ""
-
 	if bazelConversionRequested {
 		// Run the alternate pipeline of bp2build mutators and singleton to convert
 		// Blueprint to BUILD files before everything else.
@@ -175,7 +187,7 @@
 		if bp2buildMarker != "" {
 			return bp2buildMarker
 		} else {
-			return bootstrap.CmdlineOutFile()
+			return bootstrap.CmdlineArgs.OutFile
 		}
 	}
 
@@ -183,22 +195,27 @@
 	if mixedModeBuild {
 		runMixedModeBuild(configuration, ctx, extraNinjaDeps)
 	} else {
-		bootstrap.Main(ctx.Context, configuration, false, extraNinjaDeps...)
+		ninjaDeps := bootstrap.RunBlueprint(blueprintArgs, ctx.Context, configuration, extraNinjaDeps...)
+		err := deptools.WriteDepFile(shared.JoinPath(topDir, blueprintArgs.DepFile), blueprintArgs.OutFile, ninjaDeps)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "Error writing depfile '%s': %s\n", blueprintArgs.DepFile, err)
+			os.Exit(1)
+		}
 	}
 
 	// Convert the Soong module graph into Bazel BUILD files.
 	if generateQueryView {
 		runQueryView(configuration, ctx)
-		return bootstrap.CmdlineOutFile() // TODO: This is a lie
+		return bootstrap.CmdlineArgs.OutFile // TODO: This is a lie
 	}
 
 	if jsonModuleFile != "" {
 		writeJsonModuleGraph(configuration, ctx, jsonModuleFile, extraNinjaDeps)
-		return bootstrap.CmdlineOutFile() // TODO: This is a lie
+		return bootstrap.CmdlineArgs.OutFile // TODO: This is a lie
 	}
 
 	writeMetrics(configuration)
-	return bootstrap.CmdlineOutFile()
+	return bootstrap.CmdlineArgs.OutFile
 }
 
 // soong_ui dumps the available environment variables to
@@ -242,7 +259,7 @@
 	configuration := newConfig(srcDir, outDir, availableEnv)
 	extraNinjaDeps := []string{
 		configuration.ProductVariablesFileName,
-		shared.JoinPath(outDir, "soong.environment.used"),
+		usedEnvFile,
 	}
 
 	if configuration.Getenv("ALLOW_MISSING_DEPENDENCIES") == "true" {
@@ -344,18 +361,18 @@
 	// Register an alternate set of singletons and mutators for bazel
 	// conversion for Bazel conversion.
 	bp2buildCtx := android.NewContext(configuration)
-	bp2buildCtx.SetAllowMissingDependencies(configuration.AllowMissingDependencies())
-	bp2buildCtx.RegisterForBazelConversion()
 
-	// No need to generate Ninja build rules/statements from Modules and Singletons.
-	configuration.SetStopBefore(bootstrap.StopBeforePrepareBuildActions)
+	// Propagate "allow misssing dependencies" bit. This is normally set in
+	// newContext(), but we create bp2buildCtx without calling that method.
+	bp2buildCtx.SetAllowMissingDependencies(configuration.AllowMissingDependencies())
 	bp2buildCtx.SetNameInterface(newNameResolver(configuration))
+	bp2buildCtx.RegisterForBazelConversion()
 
 	// The bp2build process is a purely functional process that only depends on
 	// Android.bp files. It must not depend on the values of per-build product
 	// configurations or variables, since those will generate different BUILD
 	// files based on how the user has configured their tree.
-	bp2buildCtx.SetModuleListFile(bootstrap.CmdlineModuleListFile())
+	bp2buildCtx.SetModuleListFile(bootstrap.CmdlineArgs.ModuleListFile)
 	modulePaths, err := bp2buildCtx.ListModulePaths(configuration.SrcDir())
 	if err != nil {
 		panic(err)
@@ -363,10 +380,25 @@
 
 	extraNinjaDeps = append(extraNinjaDeps, modulePaths...)
 
+	// No need to generate Ninja build rules/statements from Modules and Singletons.
+	configuration.SetStopBefore(bootstrap.StopBeforePrepareBuildActions)
+
 	// Run the loading and analysis pipeline to prepare the graph of regular
 	// Modules parsed from Android.bp files, and the BazelTargetModules mapped
 	// from the regular Modules.
-	bootstrap.Main(bp2buildCtx.Context, configuration, false, extraNinjaDeps...)
+	blueprintArgs := bootstrap.CmdlineArgs
+	ninjaDeps := bootstrap.RunBlueprint(blueprintArgs, bp2buildCtx.Context, configuration, extraNinjaDeps...)
+
+	for _, globPath := range bp2buildCtx.Globs() {
+		ninjaDeps = append(ninjaDeps, globPath.FileListFile(configuration.BuildDir()))
+	}
+
+	depFile := bp2buildMarker + ".d"
+	err = deptools.WriteDepFile(shared.JoinPath(topDir, depFile), bp2buildMarker, ninjaDeps)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Cannot write depfile '%s': %s\n", depFile, err)
+		os.Exit(1)
+	}
 
 	// Run the code-generation phase to convert BazelTargetModules to BUILD files
 	// and print conversion metrics to the user.
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index 02f1120..26ff5ba 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -115,7 +115,7 @@
 	DexLocation     string // dex location on device
 	BuildPath       android.OutputPath
 	DexPath         android.Path
-	ManifestPath    android.Path
+	ManifestPath    android.OptionalPath
 	UncompressedDex bool
 	HasApkLibraries bool
 	PreoptFlags     []string
@@ -291,7 +291,7 @@
 	// Construct paths that require a PathContext.
 	config.ModuleConfig.BuildPath = constructPath(ctx, config.BuildPath).(android.OutputPath)
 	config.ModuleConfig.DexPath = constructPath(ctx, config.DexPath)
-	config.ModuleConfig.ManifestPath = constructPath(ctx, config.ManifestPath)
+	config.ModuleConfig.ManifestPath = android.OptionalPathForPath(constructPath(ctx, config.ManifestPath))
 	config.ModuleConfig.ProfileClassListing = android.OptionalPathForPath(constructPath(ctx, config.ProfileClassListing))
 	config.ModuleConfig.EnforceUsesLibrariesStatusFile = constructPath(ctx, config.EnforceUsesLibrariesStatusFile)
 	config.ModuleConfig.ClassLoaderContexts = fromJsonClassLoaderContext(ctx, config.ClassLoaderContexts)
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index 81a63b0..dc17c0a 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -276,9 +276,9 @@
 		// no time/space is wasted on AOT-compiling modules that will fail CLC check on device.
 
 		var manifestOrApk android.Path
-		if module.ManifestPath != nil {
+		if module.ManifestPath.Valid() {
 			// Ok, there is an XML manifest.
-			manifestOrApk = module.ManifestPath
+			manifestOrApk = module.ManifestPath.Path()
 		} else if filepath.Ext(base) == ".apk" {
 			// Ok, there is is an APK with the manifest inside.
 			manifestOrApk = module.DexPath
diff --git a/docs/map_files.md b/docs/map_files.md
index 9fc0d14..192530f 100644
--- a/docs/map_files.md
+++ b/docs/map_files.md
@@ -52,7 +52,8 @@
 symbol visibility of the library to expose only the interface named by the map
 file. Without this, APIs that you have not explicitly exposed will still be
 available to users via `dlsym`. Note: All comments are ignored in this case. Any
-symbol named in any `global:` group will be visible.
+symbol named in any `global:` group will be visible in the implementation
+library. Annotations in comments only affect what is exposed by the stubs.
 
 ## Special version names
 
@@ -76,9 +77,13 @@
 ### future
 
 Indicates that the version or symbol is first introduced in the "future" API
-level. This is an abitrarily high API level used to define APIs that have not
+level. This is an arbitrarily high API level used to define APIs that have not
 yet been added to a specific release.
 
+Warning: APIs marked `future` will be usable in any module with `sdk: "current"`
+but **will not be included in the NDK**. `future` should generally not be used,
+but is useful when developing APIs for an unknown future release.
+
 ### introduced
 
 Indicates the version in which an API was first introduced. For example,
@@ -92,13 +97,15 @@
 determine which API level an API was added in. The `first_version` property of
 `ndk_library` will dictate which API levels stubs are generated for. If the
 module sets `first_version: "21"`, no symbols were introduced before API 21.
+**Symbol names for which no other rule applies will implicitly be introduced in
+`first_version`.**
 
-Codenames can (and typically should) be used when defining new APIs. This allows
-the actual number of the API level to remain vague during development of that
-release. For example, `introduced=S` can be used to define APIs added in S. Any
-code name known to the build system can be used. For a list of versions known to
-the build system, see `out/soong/api_levels.json` (if not present, run `m
-out/soong/api_levels.json` to generate it).
+Code names can (and typically should) be used when defining new APIs. This
+allows the actual number of the API level to remain vague during development of
+that release. For example, `introduced=S` can be used to define APIs added in S.
+Any code name known to the build system can be used. For a list of versions
+known to the build system, see `out/soong/api_levels.json` (if not present, run
+`m out/soong/api_levels.json` to generate it).
 
 Architecture-specific variants of this tag exist:
 
@@ -123,6 +130,8 @@
 than the NDK. May be used in combination with `apex` if the symbol is exposed to
 both APEX and the LL-NDK.
 
+Historically this annotation was spelled `vndk`, but it has always meant LL-NDK.
+
 ### platform-only
 
 Indicates that the version or symbol is public in the implementation library but
@@ -131,9 +140,9 @@
 clear to the developer that they are up to no good.
 
 The typical use for this tag is for exposing an API to the platform that is not
-for use by the NDK, LL-NDK, or APEX. It is preferable to keep such APIs in an
-entirely separate library to protect them from access via `dlsym`, but this is
-not always possible.
+for use by the NDK, LL-NDK, or APEX (similar to Java's `@SystemAPI`). It is
+preferable to keep such APIs in an entirely separate library to protect them
+from access via `dlsym`, but this is not always possible.
 
 ### var
 
diff --git a/filesystem/Android.bp b/filesystem/Android.bp
index 791019d..3cdaa64 100644
--- a/filesystem/Android.bp
+++ b/filesystem/Android.bp
@@ -15,8 +15,10 @@
         "filesystem.go",
         "logical_partition.go",
         "vbmeta.go",
+        "testing.go",
     ],
     testSrcs: [
+        "filesystem_test.go",
     ],
     pluginFor: ["soong_build"],
 }
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index b2bd6bd..cf98717 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -26,7 +26,11 @@
 )
 
 func init() {
-	android.RegisterModuleType("android_filesystem", filesystemFactory)
+	registerBuildComponents(android.InitRegistrationContext)
+}
+
+func registerBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("android_filesystem", filesystemFactory)
 }
 
 type filesystem struct {
diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go
new file mode 100644
index 0000000..880b177
--- /dev/null
+++ b/filesystem/filesystem_test.go
@@ -0,0 +1,42 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package filesystem
+
+import (
+	"os"
+	"testing"
+
+	"android/soong/android"
+)
+
+func TestMain(m *testing.M) {
+	os.Exit(m.Run())
+}
+
+var fixture = android.GroupFixturePreparers(
+	android.PrepareForIntegrationTestWithAndroid,
+	PrepareForTestWithFilesystemBuildComponents,
+)
+
+func TestFileSystemDeps(t *testing.T) {
+	result := fixture.RunTestWithBp(t, `
+		android_filesystem {
+			name: "myfilesystem",
+		}
+	`)
+
+	// produces "myfilesystem.img"
+	result.ModuleForTests("myfilesystem", "android_common").Output("myfilesystem.img")
+}
diff --git a/filesystem/testing.go b/filesystem/testing.go
new file mode 100644
index 0000000..631f1b1
--- /dev/null
+++ b/filesystem/testing.go
@@ -0,0 +1,19 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package filesystem
+
+import "android/soong/android"
+
+var PrepareForTestWithFilesystemBuildComponents = android.FixtureRegisterWithContext(registerBuildComponents)
diff --git a/java/classpath_fragment.go b/java/classpath_fragment.go
index adbe490..d497460 100644
--- a/java/classpath_fragment.go
+++ b/java/classpath_fragment.go
@@ -17,6 +17,9 @@
 package java
 
 import (
+	"fmt"
+	"strings"
+
 	"android/soong/android"
 )
 
@@ -47,21 +50,102 @@
 type classpathFragment interface {
 	android.Module
 
-	classpathFragmentBase() *classpathFragmentBase
+	classpathFragmentBase() *ClasspathFragmentBase
 }
 
-// classpathFragmentBase is meant to be embedded in any module types that implement classpathFragment;
+// ClasspathFragmentBase is meant to be embedded in any module types that implement classpathFragment;
 // such modules are expected to call initClasspathFragment().
-type classpathFragmentBase struct {
+type ClasspathFragmentBase struct {
 	properties classpathFragmentProperties
 
-	classpathType classpathType
-
 	outputFilepath android.OutputPath
+	installDirPath android.InstallPath
 }
 
-// Initializes classpathFragmentBase struct. Must be called by all modules that include classpathFragmentBase.
+func (c *ClasspathFragmentBase) classpathFragmentBase() *ClasspathFragmentBase {
+	return c
+}
+
+// Initializes ClasspathFragmentBase struct. Must be called by all modules that include ClasspathFragmentBase.
 func initClasspathFragment(c classpathFragment) {
 	base := c.classpathFragmentBase()
 	c.AddProperties(&base.properties)
 }
+
+// Matches definition of Jar in packages/modules/SdkExtensions/proto/classpaths.proto
+type classpathJar struct {
+	path      string
+	classpath classpathType
+	// TODO(satayev): propagate min/max sdk versions for the jars
+	minSdkVersion int32
+	maxSdkVersion int32
+}
+
+func (c *ClasspathFragmentBase) generateAndroidBuildActions(ctx android.ModuleContext) {
+	outputFilename := ctx.ModuleName() + ".pb"
+	c.outputFilepath = android.PathForModuleOut(ctx, outputFilename).OutputPath
+	c.installDirPath = android.PathForModuleInstall(ctx, "etc", "classpaths")
+
+	var jars []classpathJar
+	jars = appendClasspathJar(jars, BOOTCLASSPATH, defaultBootclasspath(ctx)...)
+	jars = appendClasspathJar(jars, DEX2OATBOOTCLASSPATH, defaultBootImageConfig(ctx).getAnyAndroidVariant().dexLocationsDeps...)
+	jars = appendClasspathJar(jars, SYSTEMSERVERCLASSPATH, systemServerClasspath(ctx)...)
+
+	generatedJson := android.PathForModuleOut(ctx, outputFilename+".json")
+	writeClasspathsJson(ctx, generatedJson, jars)
+
+	rule := android.NewRuleBuilder(pctx, ctx)
+	rule.Command().
+		BuiltTool("conv_classpaths_proto").
+		Flag("encode").
+		Flag("--format=json").
+		FlagWithInput("--input=", generatedJson).
+		FlagWithOutput("--output=", c.outputFilepath)
+
+	rule.Build("classpath_fragment", "Compiling "+c.outputFilepath.String())
+}
+
+func writeClasspathsJson(ctx android.ModuleContext, output android.WritablePath, jars []classpathJar) {
+	var content strings.Builder
+	fmt.Fprintf(&content, "{\n")
+	fmt.Fprintf(&content, "\"jars\": [\n")
+	for idx, jar := range jars {
+		fmt.Fprintf(&content, "{\n")
+
+		fmt.Fprintf(&content, "\"relativePath\": \"%s\",\n", jar.path)
+		fmt.Fprintf(&content, "\"classpath\": \"%s\"\n", jar.classpath)
+
+		if idx < len(jars)-1 {
+			fmt.Fprintf(&content, "},\n")
+		} else {
+			fmt.Fprintf(&content, "}\n")
+		}
+	}
+	fmt.Fprintf(&content, "]\n")
+	fmt.Fprintf(&content, "}\n")
+	android.WriteFileRule(ctx, output, content.String())
+}
+
+func appendClasspathJar(slice []classpathJar, classpathType classpathType, paths ...string) (result []classpathJar) {
+	result = append(result, slice...)
+	for _, path := range paths {
+		result = append(result, classpathJar{
+			path:      path,
+			classpath: classpathType,
+		})
+	}
+	return
+}
+
+func (c *ClasspathFragmentBase) getAndroidMkEntries() []android.AndroidMkEntries {
+	return []android.AndroidMkEntries{android.AndroidMkEntries{
+		Class:      "ETC",
+		OutputFile: android.OptionalPathForPath(c.outputFilepath),
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+				entries.SetString("LOCAL_MODULE_PATH", c.installDirPath.ToMakePath().String())
+				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", c.outputFilepath.Base())
+			},
+		},
+	}}
+}
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index b4cf012..3571590 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -221,7 +221,7 @@
 		DexLocation:     dexLocation,
 		BuildPath:       android.PathForModuleOut(ctx, "dexpreopt", ctx.ModuleName()+".jar").OutputPath,
 		DexPath:         dexJarFile,
-		ManifestPath:    d.manifestFile,
+		ManifestPath:    android.OptionalPathForPath(d.manifestFile),
 		UncompressedDex: d.uncompressedDex,
 		HasApkLibraries: false,
 		PreoptFlags:     nil,
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 7137f33..e57c3e9 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -484,7 +484,7 @@
 
 	// Now match the apex part of the boot image configuration.
 	requiredApex := bootjars.Apex(index)
-	if requiredApex == "platform" {
+	if requiredApex == "platform" || requiredApex == "system_ext" {
 		if len(apexInfo.InApexes) != 0 {
 			// A platform variant is required but this is for an apex so ignore it.
 			return -1, nil, nil
diff --git a/java/dexpreopt_bootjars_test.go b/java/dexpreopt_bootjars_test.go
index d78651d..73f21d1 100644
--- a/java/dexpreopt_bootjars_test.go
+++ b/java/dexpreopt_bootjars_test.go
@@ -35,6 +35,7 @@
 			name: "bar",
 			srcs: ["b.java"],
 			installable: true,
+			system_ext_specific: true,
 		}
 
 		dex_import {
@@ -47,7 +48,7 @@
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("foo"),
-		dexpreopt.FixtureSetBootJars("platform:foo", "platform:bar", "platform:baz"),
+		dexpreopt.FixtureSetBootJars("platform:foo", "system_ext:bar", "platform:baz"),
 	).RunTestWithBp(t, bp)
 
 	dexpreoptBootJars := result.SingletonForTests("dex_bootjars")
diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go
index 64b2656..656f5ef 100644
--- a/java/dexpreopt_config.go
+++ b/java/dexpreopt_config.go
@@ -26,7 +26,7 @@
 // systemServerClasspath returns the on-device locations of the modules in the system server classpath.  It is computed
 // once the first time it is called for any ctx.Config(), and returns the same slice for all future calls with the same
 // ctx.Config().
-func systemServerClasspath(ctx android.MakeVarsContext) []string {
+func systemServerClasspath(ctx android.PathContext) []string {
 	return ctx.Config().OnceStringSlice(systemServerClasspathKey, func() []string {
 		global := dexpreopt.GetGlobalConfig(ctx)
 		var systemServerClasspathLocations []string
diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go
index bed11fe..7cf082b 100644
--- a/java/hiddenapi_modular.go
+++ b/java/hiddenapi_modular.go
@@ -20,9 +20,9 @@
 
 // Contains support for processing hiddenAPI in a modular fashion.
 
-// HiddenAPIAugmentationProperties contains paths to the files that can be used to augment the information
-// obtained from annotations within the source code in order to create the complete set of flags
-// that should be applied to the dex implementation jars on the bootclasspath.
+// HiddenAPIFlagFileProperties contains paths to the flag files that can be used to augment the
+// information obtained from annotations within the source code in order to create the complete set
+// of flags that should be applied to the dex implementation jars on the bootclasspath.
 //
 // Each property contains a list of paths. With the exception of the Unsupported_packages the paths
 // of each property reference a plain text file that contains a java signature per line. The flags
@@ -31,7 +31,7 @@
 // The Unsupported_packages property contains a list of paths, each of which is a plain text file
 // with one Java package per line. All members of all classes within that package (but not nested
 // packages) will be updated in a property specific way.
-type HiddenAPIAugmentationProperties struct {
+type HiddenAPIFlagFileProperties struct {
 	// Marks each signature in the referenced files as being unsupported.
 	Unsupported []string `android:"path"`
 
@@ -60,45 +60,116 @@
 	Unsupported_packages []string `android:"path"`
 }
 
-func (p *HiddenAPIAugmentationProperties) hiddenAPIAugmentationInfo(ctx android.ModuleContext) hiddenAPIAugmentationInfo {
-	paths := func(paths []string) android.Paths { return android.PathsForModuleSrc(ctx, paths) }
-	return hiddenAPIAugmentationInfo{
-		Unsupported:               paths(p.Unsupported),
-		Removed:                   paths(p.Removed),
-		Max_target_r_low_priority: paths(p.Max_target_r_low_priority),
-		Max_target_q:              paths(p.Max_target_q),
-		Max_target_p:              paths(p.Max_target_p),
-		Max_target_o_low_priority: paths(p.Max_target_o_low_priority),
-		Blocked:                   paths(p.Blocked),
-		Unsupported_packages:      paths(p.Unsupported_packages),
+func (p *HiddenAPIFlagFileProperties) hiddenAPIFlagFileInfo(ctx android.ModuleContext) hiddenAPIFlagFileInfo {
+	info := hiddenAPIFlagFileInfo{categoryToPaths: map[*hiddenAPIFlagFileCategory]android.Paths{}}
+	for _, category := range hiddenAPIFlagFileCategories {
+		paths := android.PathsForModuleSrc(ctx, category.propertyAccessor(p))
+		info.categoryToPaths[category] = paths
 	}
+	return info
 }
 
-// hiddenAPIAugmentationInfo contains paths resolved from HiddenAPIAugmentationProperties
-type hiddenAPIAugmentationInfo struct {
-	// See HiddenAPIAugmentationProperties.Unsupported
-	Unsupported android.Paths
+type hiddenAPIFlagFileCategory struct {
+	// propertyName is the name of the property for this category.
+	propertyName string
 
-	// See HiddenAPIAugmentationProperties.Removed
-	Removed android.Paths
+	// propertyAccessor retrieves the value of the property for this category from the set of
+	// properties.
+	propertyAccessor func(properties *HiddenAPIFlagFileProperties) []string
 
-	// See HiddenAPIAugmentationProperties.Max_target_r_low_priority
-	Max_target_r_low_priority android.Paths
+	// commandMutator adds the appropriate command line options for this category to the supplied
+	// command
+	commandMutator func(command *android.RuleBuilderCommand, path android.Path)
+}
 
-	// See HiddenAPIAugmentationProperties.Max_target_q
-	Max_target_q android.Paths
+var hiddenAPIFlagFileCategories = []*hiddenAPIFlagFileCategory{
+	// See HiddenAPIFlagFileProperties.Unsupported
+	{
+		propertyName: "unsupported",
+		propertyAccessor: func(properties *HiddenAPIFlagFileProperties) []string {
+			return properties.Unsupported
+		},
+		commandMutator: func(command *android.RuleBuilderCommand, path android.Path) {
+			command.FlagWithInput("--unsupported ", path)
+		},
+	},
+	// See HiddenAPIFlagFileProperties.Removed
+	{
+		propertyName: "removed",
+		propertyAccessor: func(properties *HiddenAPIFlagFileProperties) []string {
+			return properties.Removed
+		},
+		commandMutator: func(command *android.RuleBuilderCommand, path android.Path) {
+			command.FlagWithInput("--unsupported ", path).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "removed")
+		},
+	},
+	// See HiddenAPIFlagFileProperties.Max_target_r_low_priority
+	{
+		propertyName: "max_target_r_low_priority",
+		propertyAccessor: func(properties *HiddenAPIFlagFileProperties) []string {
+			return properties.Max_target_r_low_priority
+		},
+		commandMutator: func(command *android.RuleBuilderCommand, path android.Path) {
+			command.FlagWithInput("--max-target-r ", path).FlagWithArg("--tag ", "lo-prio")
+		},
+	},
+	// See HiddenAPIFlagFileProperties.Max_target_q
+	{
+		propertyName: "max_target_q",
+		propertyAccessor: func(properties *HiddenAPIFlagFileProperties) []string {
+			return properties.Max_target_q
+		},
+		commandMutator: func(command *android.RuleBuilderCommand, path android.Path) {
+			command.FlagWithInput("--max-target-q ", path)
+		},
+	},
+	// See HiddenAPIFlagFileProperties.Max_target_p
+	{
+		propertyName: "max_target_p",
+		propertyAccessor: func(properties *HiddenAPIFlagFileProperties) []string {
+			return properties.Max_target_p
+		},
+		commandMutator: func(command *android.RuleBuilderCommand, path android.Path) {
+			command.FlagWithInput("--max-target-p ", path)
+		},
+	},
+	// See HiddenAPIFlagFileProperties.Max_target_o_low_priority
+	{
+		propertyName: "max_target_o_low_priority",
+		propertyAccessor: func(properties *HiddenAPIFlagFileProperties) []string {
+			return properties.Max_target_o_low_priority
+		},
+		commandMutator: func(command *android.RuleBuilderCommand, path android.Path) {
+			command.FlagWithInput("--max-target-o ", path).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "lo-prio")
+		},
+	},
+	// See HiddenAPIFlagFileProperties.Blocked
+	{
+		propertyName: "blocked",
+		propertyAccessor: func(properties *HiddenAPIFlagFileProperties) []string {
+			return properties.Blocked
+		},
+		commandMutator: func(command *android.RuleBuilderCommand, path android.Path) {
+			command.FlagWithInput("--blocked ", path)
+		},
+	},
+	// See HiddenAPIFlagFileProperties.Unsupported_packages
+	{
+		propertyName: "unsupported_packages",
+		propertyAccessor: func(properties *HiddenAPIFlagFileProperties) []string {
+			return properties.Unsupported_packages
+		},
+		commandMutator: func(command *android.RuleBuilderCommand, path android.Path) {
+			command.FlagWithInput("--unsupported ", path).Flag("--packages ")
+		},
+	},
+}
 
-	// See HiddenAPIAugmentationProperties.Max_target_p
-	Max_target_p android.Paths
-
-	// See HiddenAPIAugmentationProperties.Max_target_o_low_priority
-	Max_target_o_low_priority android.Paths
-
-	// See HiddenAPIAugmentationProperties.Blocked
-	Blocked android.Paths
-
-	// See HiddenAPIAugmentationProperties.Unsupported_packages
-	Unsupported_packages android.Paths
+// hiddenAPIFlagFileInfo contains paths resolved from HiddenAPIFlagFileProperties
+type hiddenAPIFlagFileInfo struct {
+	// categoryToPaths maps from the flag file category to the paths containing information for that
+	// category.
+	categoryToPaths map[*hiddenAPIFlagFileCategory]android.Paths
 }
 
 // ruleToGenerateHiddenApiFlags creates a rule to create the monolithic hidden API flags from the
@@ -125,7 +196,7 @@
 //
 // augmentationInfo is a struct containing paths to files that augment the information provided by
 // the moduleSpecificFlagsPaths.
-func ruleToGenerateHiddenApiFlags(ctx android.BuilderContext, outputPath android.WritablePath, baseFlagsPath android.Path, moduleSpecificFlagsPaths android.Paths, augmentationInfo hiddenAPIAugmentationInfo) {
+func ruleToGenerateHiddenApiFlags(ctx android.BuilderContext, outputPath android.WritablePath, baseFlagsPath android.Path, moduleSpecificFlagsPaths android.Paths, augmentationInfo hiddenAPIFlagFileInfo) {
 	tempPath := android.PathForOutput(ctx, outputPath.Rel()+".tmp")
 	rule := android.NewRuleBuilder(pctx, ctx)
 	command := rule.Command().
@@ -134,36 +205,12 @@
 		Inputs(moduleSpecificFlagsPaths).
 		FlagWithOutput("--output ", tempPath)
 
-	for _, path := range augmentationInfo.Unsupported {
-		command.FlagWithInput("--unsupported ", path)
-	}
-
-	for _, path := range augmentationInfo.Removed {
-		command.FlagWithInput("--unsupported ", path).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "removed")
-	}
-
-	for _, path := range augmentationInfo.Max_target_r_low_priority {
-		command.FlagWithInput("--max-target-r ", path).FlagWithArg("--tag ", "lo-prio")
-	}
-
-	for _, path := range augmentationInfo.Max_target_q {
-		command.FlagWithInput("--max-target-q ", path)
-	}
-
-	for _, path := range augmentationInfo.Max_target_p {
-		command.FlagWithInput("--max-target-p ", path)
-	}
-
-	for _, path := range augmentationInfo.Max_target_o_low_priority {
-		command.FlagWithInput("--max-target-o ", path).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "lo-prio")
-	}
-
-	for _, path := range augmentationInfo.Blocked {
-		command.FlagWithInput("--blocked ", path)
-	}
-
-	for _, path := range augmentationInfo.Unsupported_packages {
-		command.FlagWithInput("--unsupported ", path).Flag("--packages ")
+	// Add the options for the different categories of flag files.
+	for _, category := range hiddenAPIFlagFileCategories {
+		paths := augmentationInfo.categoryToPaths[category]
+		for _, path := range paths {
+			category.commandMutator(command, path)
+		}
 	}
 
 	commitChangeForRestat(rule, tempPath, outputPath)
diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go
index 994f1be..cb8ad68 100644
--- a/java/platform_bootclasspath.go
+++ b/java/platform_bootclasspath.go
@@ -59,6 +59,7 @@
 
 type platformBootclasspathModule struct {
 	android.ModuleBase
+	ClasspathFragmentBase
 
 	properties platformBootclasspathProperties
 
@@ -99,28 +100,29 @@
 	// platform_bootclasspath.
 	Fragments []ApexVariantReference
 
-	Hidden_api HiddenAPIAugmentationProperties
+	Hidden_api HiddenAPIFlagFileProperties
 }
 
 func platformBootclasspathFactory() android.Module {
 	m := &platformBootclasspathModule{}
 	m.AddProperties(&m.properties)
+	// TODO(satayev): split systemserver and apex jars into separate configs.
+	initClasspathFragment(m)
 	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
 	return m
 }
 
 var _ android.OutputFileProducer = (*platformBootclasspathModule)(nil)
 
-// A minimal AndroidMkEntries is needed in order to support the dists property.
-func (b *platformBootclasspathModule) AndroidMkEntries() []android.AndroidMkEntries {
-	return []android.AndroidMkEntries{
-		{
-			Class: "FAKE",
-			// Need at least one output file in order for this to take effect.
-			OutputFile: android.OptionalPathForPath(b.hiddenAPIFlagsCSV),
-			Include:    "$(BUILD_PHONY_PACKAGE)",
-		},
-	}
+func (b *platformBootclasspathModule) AndroidMkEntries() (entries []android.AndroidMkEntries) {
+	entries = append(entries, android.AndroidMkEntries{
+		Class: "FAKE",
+		// Need at least one output file in order for this to take effect.
+		OutputFile: android.OptionalPathForPath(b.hiddenAPIFlagsCSV),
+		Include:    "$(BUILD_PHONY_PACKAGE)",
+	})
+	entries = append(entries, b.classpathFragmentBase().getAndroidMkEntries()...)
+	return
 }
 
 // Make the hidden API files available from the platform-bootclasspath module.
@@ -245,6 +247,8 @@
 }
 
 func (b *platformBootclasspathModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	b.classpathFragmentBase().generateAndroidBuildActions(ctx)
+
 	ctx.VisitDirectDepsIf(isActiveModule, func(module android.Module) {
 		tag := ctx.OtherModuleDependencyTag(module)
 		if tag == platformBootclasspathModuleDepTag {
@@ -334,7 +338,7 @@
 		moduleSpecificFlagsPaths = append(moduleSpecificFlagsPaths, module.flagsCSV())
 	}
 
-	augmentationInfo := b.properties.Hidden_api.hiddenAPIAugmentationInfo(ctx)
+	augmentationInfo := b.properties.Hidden_api.hiddenAPIFlagFileInfo(ctx)
 
 	outputPath := hiddenAPISingletonPaths(ctx).flags
 	baseFlagsPath := hiddenAPISingletonPaths(ctx).stubFlags
diff --git a/java/platform_bootclasspath_test.go b/java/platform_bootclasspath_test.go
index 417c6bf..e51b049 100644
--- a/java/platform_bootclasspath_test.go
+++ b/java/platform_bootclasspath_test.go
@@ -132,6 +132,89 @@
 	})
 }
 
+func TestPlatformBootclasspathVariant(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithPlatformBootclasspath,
+		android.FixtureWithRootAndroidBp(`
+			platform_bootclasspath {
+				name: "platform-bootclasspath",
+			}
+		`),
+	).RunTest(t)
+
+	variants := result.ModuleVariantsForTests("platform-bootclasspath")
+	android.AssertIntEquals(t, "expect 1 variant", 1, len(variants))
+}
+
+func TestPlatformBootclasspath_ClasspathFragmentPaths(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithPlatformBootclasspath,
+		android.FixtureWithRootAndroidBp(`
+			platform_bootclasspath {
+				name: "platform-bootclasspath",
+			}
+		`),
+	).RunTest(t)
+
+	p := result.Module("platform-bootclasspath", "android_common").(*platformBootclasspathModule)
+	android.AssertStringEquals(t, "output filepath", p.Name()+".pb", p.ClasspathFragmentBase.outputFilepath.Base())
+	android.AssertPathRelativeToTopEquals(t, "install filepath", "out/soong/target/product/test_device/system/etc/classpaths", p.ClasspathFragmentBase.installDirPath)
+}
+
+func TestPlatformBootclasspathModule_AndroidMkEntries(t *testing.T) {
+	preparer := android.GroupFixturePreparers(
+		prepareForTestWithPlatformBootclasspath,
+		android.FixtureWithRootAndroidBp(`
+			platform_bootclasspath {
+				name: "platform-bootclasspath",
+			}
+		`),
+	)
+
+	t.Run("AndroidMkEntries", func(t *testing.T) {
+		result := preparer.RunTest(t)
+
+		p := result.Module("platform-bootclasspath", "android_common").(*platformBootclasspathModule)
+
+		entries := android.AndroidMkEntriesForTest(t, result.TestContext, p)
+		android.AssertIntEquals(t, "AndroidMkEntries count", 2, len(entries))
+	})
+
+	t.Run("hiddenapi-flags-entry", func(t *testing.T) {
+		result := preparer.RunTest(t)
+
+		p := result.Module("platform-bootclasspath", "android_common").(*platformBootclasspathModule)
+
+		entries := android.AndroidMkEntriesForTest(t, result.TestContext, p)
+		got := entries[0].OutputFile
+		android.AssertBoolEquals(t, "valid output path", true, got.Valid())
+		android.AssertSame(t, "output filepath", p.hiddenAPIFlagsCSV, got.Path())
+	})
+
+	t.Run("classpath-fragment-entry", func(t *testing.T) {
+		result := preparer.RunTest(t)
+
+		want := map[string][]string{
+			"LOCAL_MODULE":                {"platform-bootclasspath"},
+			"LOCAL_MODULE_CLASS":          {"ETC"},
+			"LOCAL_INSTALLED_MODULE_STEM": {"platform-bootclasspath.pb"},
+			// Output and Install paths are tested separately in TestPlatformBootclasspath_ClasspathFragmentPaths
+		}
+
+		p := result.Module("platform-bootclasspath", "android_common").(*platformBootclasspathModule)
+
+		entries := android.AndroidMkEntriesForTest(t, result.TestContext, p)
+		got := entries[1]
+		for k, expectedValue := range want {
+			if value, ok := got.EntryMap[k]; ok {
+				android.AssertDeepEquals(t, k, expectedValue, value)
+			} else {
+				t.Errorf("No %s defined, saw %q", k, got.EntryMap)
+			}
+		}
+	})
+}
+
 func TestPlatformBootclasspath_Dist(t *testing.T) {
 	result := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
diff --git a/licenses/Android.bp b/licenses/Android.bp
index c70d6bd..a983b5b 100644
--- a/licenses/Android.bp
+++ b/licenses/Android.bp
@@ -20,6 +20,7 @@
 
 license {
     name: "Android-Apache-2.0",
+    package_name: "Android",
     license_kinds: ["SPDX-license-identifier-Apache-2.0"],
     copyright_notice: "Copyright (C) The Android Open Source Project",
     license_text: ["LICENSE"],
diff --git a/rust/builder.go b/rust/builder.go
index 197c703..208b734 100644
--- a/rust/builder.go
+++ b/rust/builder.go
@@ -21,7 +21,6 @@
 	"github.com/google/blueprint"
 
 	"android/soong/android"
-	"android/soong/bloaty"
 	"android/soong/rust/config"
 )
 
@@ -254,8 +253,6 @@
 		implicits = append(implicits, clippyFile)
 	}
 
-	bloaty.MeasureSizeForPath(ctx, outputFile)
-
 	ctx.Build(pctx, android.BuildParams{
 		Rule:            rustc,
 		Description:     "rustc " + main.Rel(),
diff --git a/rust/library.go b/rust/library.go
index ae130a3..26c104c 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -475,7 +475,7 @@
 		TransformSrctoShared(ctx, srcPath, deps, flags, outputFile, deps.linkDirs)
 	}
 
-	if !library.rlib() && library.stripper.NeedsStrip(ctx) {
+	if !library.rlib() && !library.static() && library.stripper.NeedsStrip(ctx) {
 		strippedOutputFile := android.PathForModuleOut(ctx, "stripped", fileName)
 		library.stripper.StripExecutableOrSharedLib(ctx, outputFile, strippedOutputFile)
 		library.strippedOutputFile = android.OptionalPathForPath(strippedOutputFile)
diff --git a/rust/rust.go b/rust/rust.go
index d2de1bc..9738b46 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -22,6 +22,7 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/bloaty"
 	"android/soong/cc"
 	cc_config "android/soong/cc/config"
 	"android/soong/rust/config"
@@ -751,8 +752,8 @@
 	if mod.compiler != nil && !mod.compiler.Disabled() {
 		mod.compiler.initialize(ctx)
 		unstrippedOutputFile := mod.compiler.compile(ctx, flags, deps)
-
 		mod.unstrippedOutputFile = android.OptionalPathForPath(unstrippedOutputFile)
+		bloaty.MeasureSizeForPaths(ctx, mod.compiler.strippedOutputFilePath(), mod.unstrippedOutputFile)
 
 		apexInfo := actx.Provider(android.ApexInfoProvider).(android.ApexInfo)
 		if mod.installable(apexInfo) {
diff --git a/rust/rust_test.go b/rust/rust_test.go
index 47c64a9..6ae05d9 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -402,3 +402,17 @@
 	_ = ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_rlib_dylib-std")
 	_ = ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_rlib_dylib-std")
 }
+
+// Test that library size measurements are generated.
+func TestLibrarySizes(t *testing.T) {
+	ctx := testRust(t, `
+		rust_library_dylib {
+			name: "libwaldo",
+			srcs: ["foo.rs"],
+			crate_name: "waldo",
+		}`)
+
+	m := ctx.SingletonForTests("file_metrics")
+	m.Output("libwaldo.dylib.so.bloaty.csv")
+	m.Output("stripped/libwaldo.dylib.so.bloaty.csv")
+}
diff --git a/rust/testing.go b/rust/testing.go
index 2dda9c1..a0f86b2 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -16,6 +16,7 @@
 
 import (
 	"android/soong/android"
+	"android/soong/bloaty"
 	"android/soong/cc"
 )
 
@@ -34,6 +35,7 @@
 // Preparer that will define default rust modules, e.g. standard prebuilt modules.
 var PrepareForTestWithRustDefaultModules = android.GroupFixturePreparers(
 	cc.PrepareForTestWithCcDefaultModules,
+	bloaty.PrepareForTestWithBloatyDefaultModules,
 	PrepareForTestWithRustBuildComponents,
 	android.FixtureAddTextFile(rustDefaultsDir+"Android.bp", GatherRequiredDepsForTest()),
 )
diff --git a/scripts/build-ndk-prebuilts.sh b/scripts/build-ndk-prebuilts.sh
index c27f098..a1fa48d 100755
--- a/scripts/build-ndk-prebuilts.sh
+++ b/scripts/build-ndk-prebuilts.sh
@@ -54,7 +54,6 @@
     "Safestack": false,
 
     "Ndk_abis": true,
-    "Exclude_draft_ndk_apis": true,
 
     "VendorVars": {
         "art_module": {
diff --git a/tests/bootstrap_test.sh b/tests/bootstrap_test.sh
index f85af1a..a483370 100755
--- a/tests/bootstrap_test.sh
+++ b/tests/bootstrap_test.sh
@@ -119,6 +119,58 @@
   fi
 }
 
+# Test that an incremental build with a glob doesn't rerun soong_build, and
+# only regenerates the globs on the first but not the second incremental build.
+function test_glob_noop_incremental() {
+  setup
+
+  mkdir -p a
+  cat > a/Android.bp <<'EOF'
+python_binary_host {
+  name: "my_little_binary_host",
+  srcs: ["*.py"],
+}
+EOF
+  touch a/my_little_binary_host.py
+  run_soong
+  local ninja_mtime1=$(stat -c "%y" out/soong/build.ninja)
+
+  local glob_deps_file=out/soong/.glob/a/__py.glob.d
+
+  if [ -e "$glob_deps_file" ]; then
+    fail "Glob deps file unexpectedly written on first build"
+  fi
+
+  run_soong
+  local ninja_mtime2=$(stat -c "%y" out/soong/build.ninja)
+
+  # There is an ineffiencency in glob that requires bpglob to rerun once for each glob to update
+  # the entry in the .ninja_log.  It doesn't update the output file, but we can detect the rerun
+  # by checking if the deps file was created.
+  if [ ! -e "$glob_deps_file" ]; then
+    fail "Glob deps file missing after second build"
+  fi
+
+  local glob_deps_mtime2=$(stat -c "%y" "$glob_deps_file")
+
+  if [[ "$ninja_mtime1" != "$ninja_mtime2" ]]; then
+    fail "Ninja file rewritten on null incremental build"
+  fi
+
+  run_soong
+  local ninja_mtime3=$(stat -c "%y" out/soong/build.ninja)
+  local glob_deps_mtime3=$(stat -c "%y" "$glob_deps_file")
+
+  if [[ "$ninja_mtime2" != "$ninja_mtime3" ]]; then
+    fail "Ninja file rewritten on null incremental build"
+  fi
+
+  # The bpglob commands should not rerun after the first incremental build.
+  if [[ "$glob_deps_mtime2" != "$glob_deps_mtime3" ]]; then
+    fail "Glob deps file rewritten on second null incremental build"
+  fi
+}
+
 function test_add_file_to_glob() {
   setup
 
@@ -429,12 +481,47 @@
   setup
   INTEGRATED_BP2BUILD=1 run_soong
   if [[ ! -e out/soong/.bootstrap/bp2build_workspace_marker ]]; then
-    fail "b2build marker file not created"
+    fail "bp2build marker file not created"
+  fi
+}
+
+function test_integrated_bp2build_add_android_bp {
+  setup
+
+  mkdir -p a
+  touch a/a.txt
+  cat > a/Android.bp <<'EOF'
+filegroup {
+  name: "a",
+  srcs: ["a.txt"],
+  bazel_module: { bp2build_available: true },
+}
+EOF
+
+  INTEGRATED_BP2BUILD=1 run_soong
+  if [[ ! -e out/soong/bp2build/a/BUILD ]]; then
+    fail "a/BUILD not created";
+  fi
+
+  mkdir -p b
+  touch b/b.txt
+  cat > b/Android.bp <<'EOF'
+filegroup {
+  name: "b",
+  srcs: ["b.txt"],
+  bazel_module: { bp2build_available: true },
+}
+EOF
+
+  INTEGRATED_BP2BUILD=1 run_soong
+  if [[ ! -e out/soong/bp2build/b/BUILD ]]; then
+    fail "b/BUILD not created";
   fi
 }
 
 function test_integrated_bp2build_null_build {
   setup
+
   INTEGRATED_BP2BUILD=1 run_soong
   local mtime1=$(stat -c "%y" out/soong/build.ninja)
 
@@ -446,6 +533,27 @@
   fi
 }
 
+function test_integrated_bp2build_add_to_glob {
+  setup
+
+  mkdir -p a
+  touch a/a1.txt
+  cat > a/Android.bp <<'EOF'
+filegroup {
+  name: "a",
+  srcs: ["*.txt"],
+  bazel_module: { bp2build_available: true },
+}
+EOF
+
+  INTEGRATED_BP2BUILD=1 run_soong
+  grep -q a1.txt out/soong/bp2build/a/BUILD || fail "a1.txt not in BUILD file"
+
+  touch a/a2.txt
+  INTEGRATED_BP2BUILD=1 run_soong
+  grep -q a2.txt out/soong/bp2build/a/BUILD || fail "a2.txt not in BUILD file"
+}
+
 function test_dump_json_module_graph() {
   setup
   SOONG_DUMP_JSON_MODULE_GRAPH="$MOCK_TOP/modules.json" run_soong
@@ -458,6 +566,7 @@
 test_null_build
 test_null_build_after_docs
 test_soong_build_rebuilt_if_blueprint_changes
+test_glob_noop_incremental
 test_add_file_to_glob
 test_add_android_bp
 test_change_android_bp
@@ -468,3 +577,4 @@
 test_dump_json_module_graph
 test_integrated_bp2build_smoke
 test_integrated_bp2build_null_build
+test_integrated_bp2build_add_to_glob
diff --git a/tests/lib.sh b/tests/lib.sh
index 1478e37..3795dfc 100644
--- a/tests/lib.sh
+++ b/tests/lib.sh
@@ -95,7 +95,7 @@
 
   echo
   echo ----------------------------------------------------------------------------
-  info "Running test case ${FUNCNAME[1]}"
+  info "Running test case \e[96;1m${FUNCNAME[1]}\e[0m"
   cd "$MOCK_TOP"
 
   tar xzf "$WARMED_UP_MOCK_TOP"
diff --git a/ui/build/soong.go b/ui/build/soong.go
index d77a089..7e94b25 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -21,6 +21,7 @@
 	"strconv"
 
 	"android/soong/shared"
+	"github.com/google/blueprint/deptools"
 
 	soong_metrics_proto "android/soong/ui/metrics/metrics_proto"
 	"github.com/google/blueprint"
@@ -107,6 +108,7 @@
 	mainNinjaFile := shared.JoinPath(config.SoongOutDir(), "build.ninja")
 	globFile := shared.JoinPath(config.SoongOutDir(), ".bootstrap/soong-build-globs.ninja")
 	bootstrapGlobFile := shared.JoinPath(config.SoongOutDir(), ".bootstrap/build-globs.ninja")
+	bootstrapDepFile := shared.JoinPath(config.SoongOutDir(), ".bootstrap/build.ninja.d")
 
 	args.RunGoTests = !config.skipSoongTests
 	args.UseValidations = true // Use validations to depend on tests
@@ -115,7 +117,6 @@
 	args.TopFile = "Android.bp"
 	args.ModuleListFile = filepath.Join(config.FileListDir(), "Android.bp.list")
 	args.OutFile = shared.JoinPath(config.SoongOutDir(), ".bootstrap/build.ninja")
-	args.DepFile = shared.JoinPath(config.SoongOutDir(), ".bootstrap/build.ninja.d")
 	args.GlobFile = globFile
 	args.GeneratingPrimaryBuilder = true
 
@@ -171,7 +172,11 @@
 		debugCompilation: os.Getenv("SOONG_DELVE") != "",
 	}
 
-	bootstrap.RunBlueprint(args, blueprintCtx, blueprintConfig)
+	bootstrapDeps := bootstrap.RunBlueprint(args, blueprintCtx, blueprintConfig)
+	err := deptools.WriteDepFile(bootstrapDepFile, args.OutFile, bootstrapDeps)
+	if err != nil {
+		ctx.Fatalf("Error writing depfile '%s': %s", bootstrapDepFile, err)
+	}
 }
 
 func checkEnvironmentFile(currentEnv *Environment, envFile string) {