Merge "Remove flags rejected by RBE input processor"
diff --git a/android/allowlists/allowlists.go b/android/allowlists/allowlists.go
index 9bba3c3..cdef329 100644
--- a/android/allowlists/allowlists.go
+++ b/android/allowlists/allowlists.go
@@ -180,8 +180,10 @@
 		"frameworks/base/tools/aapt2":                        Bp2BuildDefaultTrue,
 		"frameworks/native/libs/adbd_auth":                   Bp2BuildDefaultTrueRecursively,
 		"frameworks/native/libs/arect":                       Bp2BuildDefaultTrueRecursively,
+		"frameworks/native/libs/gui":                         Bp2BuildDefaultTrue,
 		"frameworks/native/libs/math":                        Bp2BuildDefaultTrueRecursively,
 		"frameworks/native/libs/nativebase":                  Bp2BuildDefaultTrueRecursively,
+		"frameworks/native/libs/vr":                          Bp2BuildDefaultTrueRecursively,
 		"frameworks/native/opengl/tests/gl2_cameraeye":       Bp2BuildDefaultTrue,
 		"frameworks/native/opengl/tests/gl2_java":            Bp2BuildDefaultTrue,
 		"frameworks/native/opengl/tests/testLatency":         Bp2BuildDefaultTrue,
@@ -397,14 +399,11 @@
 		"libbinder_headers_platform_shared",
 		"libbinderthreadstateutils",
 		"libbluetooth-types-header",
-		"libbufferhub_headers",
 		"libcodec2",
 		"libcodec2_headers",
 		"libcodec2_internal",
 		"libdmabufheap",
-		"libdvr_headers",
 		"libgsm",
-		"libgui_bufferqueue_sources",
 		"libgrallocusage",
 		"libgralloctypes",
 		"libnativewindow",
@@ -417,7 +416,6 @@
 		"libneuralnetworks_headers",
 		"libneuralnetworks_packageinfo",
 		"libopus",
-		"libpdx_headers",
 		"libprocpartition",
 		"libruy_static",
 		"libandroidio",
@@ -443,7 +441,6 @@
 		"libtextclassifier_hash_static",
 		"libtflite_kernel_utils",
 		"libtinyxml2",
-		"libgui_aidl",
 		"libui",
 		"libui-types",
 		"libui_headers",
@@ -470,7 +467,6 @@
 		"statslog_neuralnetworks.h",
 		"tensorflow_headers",
 
-		"libgui_headers",
 		"libstagefright_bufferpool@2.0",
 		"libstagefright_bufferpool@2.0.1",
 		"libSurfaceFlingerProp",
@@ -584,7 +580,6 @@
 		"libcodec2_hidl_plugin_stub",
 		"libcodec2_hidl_plugin",
 		"libstagefright_bufferqueue_helper_novndk",
-		"libgui_bufferqueue_static",
 		"libGLESv2",
 		"libEGL",
 		"libcodec2_vndk",
@@ -653,6 +648,23 @@
 	// the "prebuilt_" prefix to the name, so that it's differentiable from
 	// the source versions within Soong's module graph.
 	Bp2buildModuleDoNotConvertList = []string{
+		// TODO(b/250876486): Created cc_aidl_library doesn't have static libs from parent cc module
+		"libgui_window_info_static",
+		"libgui",     // Depends on unconverted libgui_window_info_static
+		"libdisplay", // Depends on uncovnerted libgui
+		// Depends on unconverted libdisplay
+		"libdvr_static.google",
+		"libdvr.google",
+		"libvrsensor",
+		"dvr_api-test",
+		// Depends on unconverted libandroid, libgui
+		"dvr_buffer_queue-test",
+		"dvr_display-test",
+		// Depends on unconverted libchrome
+		"pdx_benchmarks",
+		"buffer_hub_queue-test",
+		"buffer_hub_queue_producer-test",
+
 		// cc bugs
 		"libactivitymanager_aidl", // TODO(b/207426160): Unsupported use of aidl sources (via Dactivity_manager_procstate_aidl) in a cc_library
 
@@ -935,6 +947,7 @@
 		"libdlext_test_norelro",
 		"libdlext_test_recursive",
 		"libdlext_test_zip",
+		"libdvrcommon_test",
 		"libfortify1-new-tests-clang",
 		"libfortify1-new-tests-clang",
 		"libfortify1-tests-clang",
diff --git a/android/bazel.go b/android/bazel.go
index dd1de7b..7b227bd 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -134,6 +134,11 @@
 	SetBaseModuleType(baseModuleType string)
 }
 
+// ApiProvider is implemented by modules that contribute to an API surface
+type ApiProvider interface {
+	ConvertWithApiBp2build(ctx TopDownMutatorContext)
+}
+
 // MixedBuildBuildable is an interface that module types should implement in order
 // to be "handled by Bazel" in a mixed build.
 type MixedBuildBuildable interface {
@@ -415,6 +420,13 @@
 		return false
 	}
 
+	// In api_bp2build mode, all soong modules that can provide API contributions should be converted
+	// This is irrespective of its presence/absence in bp2build allowlists
+	if ctx.Config().BuildMode == ApiBp2build {
+		_, providesApis := module.(ApiProvider)
+		return providesApis
+	}
+
 	propValue := b.bazelProperties.Bazel_module.Bp2build_available
 	packagePath := ctx.OtherModuleDir(module)
 
@@ -510,6 +522,17 @@
 	bModule.ConvertWithBp2build(ctx)
 }
 
+func registerApiBp2buildConversionMutator(ctx RegisterMutatorsContext) {
+	ctx.TopDown("apiBp2build_conversion", convertWithApiBp2build).Parallel()
+}
+
+// Generate API contribution targets if the Soong module provides APIs
+func convertWithApiBp2build(ctx TopDownMutatorContext) {
+	if m, ok := ctx.Module().(ApiProvider); ok {
+		m.ConvertWithApiBp2build(ctx)
+	}
+}
+
 // GetMainClassInManifest scans the manifest file specified in filepath and returns
 // the value of attribute Main-Class in the manifest file if it exists, or returns error.
 // WARNING: this is for bp2build converters of java_* modules only.
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 6409292..e81086d 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -167,7 +167,8 @@
 }
 
 type bazelRunner interface {
-	issueBazelCommand(paths *bazelPaths, runName bazel.RunName, command bazelCommand, extraFlags ...string) (string, string, error)
+	createBazelCommand(paths *bazelPaths, runName bazel.RunName, command bazelCommand, extraFlags ...string) *exec.Cmd
+	issueBazelCommand(bazelCmd *exec.Cmd) (output string, errorMessage string, error error)
 }
 
 type bazelPaths struct {
@@ -460,16 +461,30 @@
 
 type mockBazelRunner struct {
 	bazelCommandResults map[bazelCommand]string
-	commands            []bazelCommand
-	extraFlags          []string
+	// use *exec.Cmd as a key to get the bazelCommand, the map will be used in issueBazelCommand()
+	// Register createBazelCommand() invocations. Later, an
+	// issueBazelCommand() invocation can be mapped to the *exec.Cmd instance
+	// and then to the expected result via bazelCommandResults
+	tokens     map[*exec.Cmd]bazelCommand
+	commands   []bazelCommand
+	extraFlags []string
 }
 
-func (r *mockBazelRunner) issueBazelCommand(_ *bazelPaths, _ bazel.RunName,
-	command bazelCommand, extraFlags ...string) (string, string, error) {
+func (r *mockBazelRunner) createBazelCommand(paths *bazelPaths, runName bazel.RunName,
+	command bazelCommand, extraFlags ...string) *exec.Cmd {
 	r.commands = append(r.commands, command)
 	r.extraFlags = append(r.extraFlags, strings.Join(extraFlags, " "))
-	if ret, ok := r.bazelCommandResults[command]; ok {
-		return ret, "", nil
+	cmd := &exec.Cmd{}
+	if r.tokens == nil {
+		r.tokens = make(map[*exec.Cmd]bazelCommand)
+	}
+	r.tokens[cmd] = command
+	return cmd
+}
+
+func (r *mockBazelRunner) issueBazelCommand(bazelCmd *exec.Cmd) (string, string, error) {
+	if command, ok := r.tokens[bazelCmd]; ok {
+		return r.bazelCommandResults[command], "", nil
 	}
 	return "", "", nil
 }
@@ -480,8 +495,20 @@
 // Returns (stdout, stderr, error). The first and second return values are strings
 // containing the stdout and stderr of the run command, and an error is returned if
 // the invocation returned an error code.
-func (r *builtinBazelRunner) issueBazelCommand(paths *bazelPaths, runName bazel.RunName, command bazelCommand,
-	extraFlags ...string) (string, string, error) {
+
+func (r *builtinBazelRunner) issueBazelCommand(bazelCmd *exec.Cmd) (string, string, error) {
+	stderr := &bytes.Buffer{}
+	bazelCmd.Stderr = stderr
+	if output, err := bazelCmd.Output(); err != nil {
+		return "", string(stderr.Bytes()),
+			fmt.Errorf("bazel command failed. command: [%s], env: [%s], error [%s]", bazelCmd, bazelCmd.Env, stderr)
+	} else {
+		return string(output), string(stderr.Bytes()), nil
+	}
+}
+
+func (r *builtinBazelRunner) createBazelCommand(paths *bazelPaths, runName bazel.RunName, command bazelCommand,
+	extraFlags ...string) *exec.Cmd {
 	cmdFlags := []string{
 		"--output_base=" + absolutePath(paths.outputBase),
 		command.command,
@@ -525,15 +552,14 @@
 		"BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1",
 	}
 	bazelCmd.Env = append(os.Environ(), extraEnv...)
-	stderr := &bytes.Buffer{}
-	bazelCmd.Stderr = stderr
 
-	if output, err := bazelCmd.Output(); err != nil {
-		return "", string(stderr.Bytes()),
-			fmt.Errorf("bazel command failed. command: [%s], env: [%s], error [%s]", bazelCmd, bazelCmd.Env, stderr)
-	} else {
-		return string(output), string(stderr.Bytes()), nil
-	}
+	return bazelCmd
+}
+
+func printableCqueryCommand(bazelCmd *exec.Cmd) string {
+	outputString := strings.Join(bazelCmd.Env, " ") + " \"" + strings.Join(bazelCmd.Args, "\" \"") + "\""
+	return outputString
+
 }
 
 func (context *bazelContext) mainBzlFileContents() []byte {
@@ -838,15 +864,16 @@
 
 	const buildrootLabel = "@soong_injection//mixed_builds:buildroot"
 	cqueryCmd := bazelCommand{"cquery", fmt.Sprintf("deps(%s, 2)", buildrootLabel)}
-	cqueryOutput, cqueryErr, err := context.issueBazelCommand(context.paths, bazel.CqueryBuildRootRunName, cqueryCmd,
+	cqueryCommandWithFlag := context.createBazelCommand(context.paths, bazel.CqueryBuildRootRunName, cqueryCmd,
 		"--output=starlark", "--starlark:file="+absolutePath(cqueryFileRelpath))
+	cqueryOutput, cqueryErr, err := context.issueBazelCommand(cqueryCommandWithFlag)
 	if err != nil {
 		return err
 	}
-	if err = ioutil.WriteFile(filepath.Join(soongInjectionPath, "cquery.out"), []byte(cqueryOutput), 0666); err != nil {
+	cqueryCommandPrint := fmt.Sprintf("cquery command line:\n  %s \n\n\n", printableCqueryCommand(cqueryCommandWithFlag))
+	if err = ioutil.WriteFile(filepath.Join(soongInjectionPath, "cquery.out"), []byte(cqueryCommandPrint+cqueryOutput), 0666); err != nil {
 		return err
 	}
-
 	cqueryResults := map[string]string{}
 	for _, outputLine := range strings.Split(cqueryOutput, "\n") {
 		if strings.Contains(outputLine, ">>") {
@@ -882,8 +909,8 @@
 		}
 	}
 	aqueryCmd := bazelCommand{"aquery", fmt.Sprintf("deps(%s)", buildrootLabel)}
-	if aqueryOutput, _, err := context.issueBazelCommand(context.paths, bazel.AqueryBuildRootRunName, aqueryCmd,
-		extraFlags...); err == nil {
+	if aqueryOutput, _, err := context.issueBazelCommand(context.createBazelCommand(context.paths, bazel.AqueryBuildRootRunName, aqueryCmd,
+		extraFlags...)); err == nil {
 		context.buildStatements, context.depsets, err = bazel.AqueryBuildStatements([]byte(aqueryOutput))
 	}
 	if err != nil {
@@ -894,7 +921,7 @@
 	// Bazel build. This is necessary because aquery invocations do not generate this symlink forest,
 	// but some of symlinks may be required to resolve source dependencies of the build.
 	buildCmd := bazelCommand{"build", "@soong_injection//mixed_builds:phonyroot"}
-	if _, _, err = context.issueBazelCommand(context.paths, bazel.BazelBuildPhonyRootRunName, buildCmd); err != nil {
+	if _, _, err = context.issueBazelCommand(context.createBazelCommand(context.paths, bazel.BazelBuildPhonyRootRunName, buildCmd)); err != nil {
 		return err
 	}
 
diff --git a/android/config.go b/android/config.go
index ee432a2..e86fc27 100644
--- a/android/config.go
+++ b/android/config.go
@@ -83,6 +83,9 @@
 	// express build semantics.
 	GenerateQueryView
 
+	// Generate BUILD files for API contributions to API surfaces
+	ApiBp2build
+
 	// Create a JSON representation of the module graph and exit.
 	GenerateModuleGraph
 
diff --git a/android/mutator.go b/android/mutator.go
index 9e4aa59..83d4e66 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -31,22 +31,33 @@
 
 // RegisterMutatorsForBazelConversion is a alternate registration pipeline for bp2build. Exported for testing.
 func RegisterMutatorsForBazelConversion(ctx *Context, preArchMutators []RegisterMutatorFunc) {
+	bp2buildMutators := append(preArchMutators, registerBp2buildConversionMutator)
+	registerMutatorsForBazelConversion(ctx, bp2buildMutators)
+}
+
+// RegisterMutatorsForApiBazelConversion is an alternate registration pipeline for api_bp2build
+// This pipeline restricts generation of Bazel targets to Soong modules that contribute APIs
+func RegisterMutatorsForApiBazelConversion(ctx *Context, preArchMutators []RegisterMutatorFunc) {
+	bp2buildMutators := append(preArchMutators, registerApiBp2buildConversionMutator)
+	registerMutatorsForBazelConversion(ctx, bp2buildMutators)
+}
+
+func registerMutatorsForBazelConversion(ctx *Context, bp2buildMutators []RegisterMutatorFunc) {
 	mctx := &registerMutatorsContext{
 		bazelConversionMode: true,
 	}
 
-	bp2buildMutators := append([]RegisterMutatorFunc{
+	allMutators := append([]RegisterMutatorFunc{
 		RegisterNamespaceMutator,
 		RegisterDefaultsPreArchMutators,
 		// TODO(b/165114590): this is required to resolve deps that are only prebuilts, but we should
 		// evaluate the impact on conversion.
 		RegisterPrebuiltsPreArchMutators,
 	},
-		preArchMutators...)
-	bp2buildMutators = append(bp2buildMutators, registerBp2buildConversionMutator)
+		bp2buildMutators...)
 
 	// Register bp2build mutators
-	for _, f := range bp2buildMutators {
+	for _, f := range allMutators {
 		f(mctx)
 	}
 
diff --git a/android/paths.go b/android/paths.go
index 27f4bf5..dbcdb23 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -1638,6 +1638,10 @@
 	}
 }
 
+func (p InstallPath) Partition() string {
+	return p.partition
+}
+
 // Join creates a new InstallPath with paths... joined with the current path. The
 // provided paths... may not use '..' to escape from the current path.
 func (p InstallPath) Join(ctx PathContext, paths ...string) InstallPath {
diff --git a/android/register.go b/android/register.go
index d4ce5f1..6c69cc5 100644
--- a/android/register.go
+++ b/android/register.go
@@ -180,6 +180,16 @@
 	RegisterMutatorsForBazelConversion(ctx, bp2buildPreArchMutators)
 }
 
+// RegisterForApiBazelConversion is similar to RegisterForBazelConversion except that
+// it only generates API targets in the generated  workspace
+func (ctx *Context) RegisterForApiBazelConversion() {
+	for _, t := range moduleTypes {
+		t.register(ctx)
+	}
+
+	RegisterMutatorsForApiBazelConversion(ctx, bp2buildPreArchMutators)
+}
+
 // Register the pipeline of singletons, module types, and mutators for
 // generating build.ninja and other files for Kati, from Android.bp files.
 func (ctx *Context) Register() {
diff --git a/android/testing.go b/android/testing.go
index 7b74c89..4018659 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -461,6 +461,12 @@
 	RegisterMutatorsForBazelConversion(ctx.Context, ctx.bp2buildPreArch)
 }
 
+// RegisterForApiBazelConversion prepares a test context for API bp2build conversion.
+func (ctx *TestContext) RegisterForApiBazelConversion() {
+	ctx.config.BuildMode = ApiBp2build
+	RegisterMutatorsForApiBazelConversion(ctx.Context, ctx.bp2buildPreArch)
+}
+
 func (ctx *TestContext) ParseFileList(rootDir string, filePaths []string) (deps []string, errs []error) {
 	// This function adapts the old style ParseFileList calls that are spread throughout the tests
 	// to the new style that takes a config.
diff --git a/apex/apex.go b/apex/apex.go
index 2e54e7e..498c8c0 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -2659,9 +2659,13 @@
 		}
 
 		// Certificate
-		if overridableProperties.Certificate != nil {
-			attrs.Certificate = bazel.LabelAttribute{}
-			attrs.Certificate.SetValue(android.BazelLabelForModuleDepSingle(ctx, *overridableProperties.Certificate))
+		if overridableProperties.Certificate == nil {
+			// delegated to the rule attr default
+			attrs.Certificate = nil
+		} else {
+			certificateName, certificate := java.ParseCertificateToAttribute(ctx, overridableProperties.Certificate)
+			attrs.Certificate_name = certificateName
+			attrs.Certificate = certificate
 		}
 
 		// Prebuilts
@@ -3335,7 +3339,8 @@
 	Android_manifest      bazel.LabelAttribute
 	File_contexts         bazel.LabelAttribute
 	Key                   bazel.LabelAttribute
-	Certificate           bazel.LabelAttribute
+	Certificate           *bazel.Label // used when the certificate prop is a module
+	Certificate_name      *string      // used when the certificate prop is a string
 	Min_sdk_version       *string
 	Updatable             bazel.BoolAttribute
 	Installable           bazel.BoolAttribute
@@ -3397,10 +3402,7 @@
 		keyLabelAttribute.SetValue(android.BazelLabelForModuleDepSingle(ctx, *a.overridableProperties.Key))
 	}
 
-	var certificateLabelAttribute bazel.LabelAttribute
-	if a.overridableProperties.Certificate != nil {
-		certificateLabelAttribute.SetValue(android.BazelLabelForModuleDepSingle(ctx, *a.overridableProperties.Certificate))
-	}
+	certificateName, certificate := java.ParseCertificateToAttribute(ctx, a.overridableProperties.Certificate)
 
 	nativeSharedLibs := &convertedNativeSharedLibs{
 		Native_shared_libs_32: bazel.LabelListAttribute{},
@@ -3456,7 +3458,8 @@
 		File_contexts:         fileContextsLabelAttribute,
 		Min_sdk_version:       minSdkVersion,
 		Key:                   keyLabelAttribute,
-		Certificate:           certificateLabelAttribute,
+		Certificate:           certificate,
+		Certificate_name:      certificateName,
 		Updatable:             updatableAttribute,
 		Installable:           installableAttribute,
 		Native_shared_libs_32: nativeSharedLibs.Native_shared_libs_32,
diff --git a/bp2build/apex_conversion_test.go b/bp2build/apex_conversion_test.go
index b0a2966..233fce4 100644
--- a/bp2build/apex_conversion_test.go
+++ b/bp2build/apex_conversion_test.go
@@ -120,7 +120,7 @@
 	file_contexts: ":com.android.apogee-file_contexts",
 	min_sdk_version: "29",
 	key: "com.android.apogee.key",
-	certificate: "com.android.apogee.certificate",
+	certificate: ":com.android.apogee.certificate",
 	updatable: false,
 	installable: false,
 	compressible: false,
@@ -582,7 +582,7 @@
 	file_contexts: ":com.android.apogee-file_contexts",
 	min_sdk_version: "29",
 	key: "com.android.apogee.key",
-	certificate: "com.android.apogee.certificate",
+	certificate: ":com.android.apogee.certificate",
 	updatable: false,
 	installable: false,
 	compressible: false,
@@ -618,7 +618,7 @@
 	name: "com.google.android.apogee",
 	base: ":com.android.apogee",
 	key: "com.google.android.apogee.key",
-	certificate: "com.google.android.apogee.certificate",
+	certificate: ":com.google.android.apogee.certificate",
 	prebuilts: [],
 	compressible: true,
 }
@@ -1016,3 +1016,193 @@
 			}),
 		}})
 }
+
+func TestBp2BuildOverrideApex_CertificateNil(t *testing.T) {
+	runOverrideApexTestCase(t, Bp2buildTestCase{
+		Description:                "override_apex - don't set default certificate",
+		ModuleTypeUnderTest:        "override_apex",
+		ModuleTypeUnderTestFactory: apex.OverrideApexFactory,
+		Filesystem:                 map[string]string{},
+		Blueprint: `
+android_app_certificate {
+	name: "com.android.apogee.certificate",
+	certificate: "com.android.apogee",
+	bazel_module: { bp2build_available: false },
+}
+
+filegroup {
+	name: "com.android.apogee-file_contexts",
+	srcs: [
+		"com.android.apogee-file_contexts",
+	],
+	bazel_module: { bp2build_available: false },
+}
+
+apex {
+	name: "com.android.apogee",
+	manifest: "apogee_manifest.json",
+	file_contexts: ":com.android.apogee-file_contexts",
+	certificate: ":com.android.apogee.certificate",
+	bazel_module: { bp2build_available: false },
+}
+
+override_apex {
+	name: "com.google.android.apogee",
+	base: ":com.android.apogee",
+	// certificate is deliberately omitted, and not converted to bazel,
+	// because the overridden apex shouldn't be using the base apex's cert.
+}
+`,
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget("apex", "com.google.android.apogee", AttrNameToString{
+				"file_contexts": `":com.android.apogee-file_contexts"`,
+				"manifest":      `"apogee_manifest.json"`,
+			}),
+		}})
+}
+
+func TestApexCertificateIsModule(t *testing.T) {
+	runApexTestCase(t, Bp2buildTestCase{
+		Description:                "apex - certificate is module",
+		ModuleTypeUnderTest:        "apex",
+		ModuleTypeUnderTestFactory: apex.BundleFactory,
+		Filesystem:                 map[string]string{},
+		Blueprint: `
+android_app_certificate {
+	name: "com.android.apogee.certificate",
+	certificate: "com.android.apogee",
+	bazel_module: { bp2build_available: false },
+}
+
+apex {
+	name: "com.android.apogee",
+	manifest: "apogee_manifest.json",
+	file_contexts: ":com.android.apogee-file_contexts",
+	certificate: ":com.android.apogee.certificate",
+}
+` + simpleModuleDoNotConvertBp2build("filegroup", "com.android.apogee-file_contexts"),
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget("apex", "com.android.apogee", AttrNameToString{
+				"certificate":   `":com.android.apogee.certificate"`,
+				"file_contexts": `":com.android.apogee-file_contexts"`,
+				"manifest":      `"apogee_manifest.json"`,
+			}),
+		}})
+}
+
+func TestApexCertificateIsSrc(t *testing.T) {
+	runApexTestCase(t, Bp2buildTestCase{
+		Description:                "apex - certificate is src",
+		ModuleTypeUnderTest:        "apex",
+		ModuleTypeUnderTestFactory: apex.BundleFactory,
+		Filesystem:                 map[string]string{},
+		Blueprint: `
+apex {
+	name: "com.android.apogee",
+	manifest: "apogee_manifest.json",
+	file_contexts: ":com.android.apogee-file_contexts",
+	certificate: "com.android.apogee.certificate",
+}
+` + simpleModuleDoNotConvertBp2build("filegroup", "com.android.apogee-file_contexts"),
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget("apex", "com.android.apogee", AttrNameToString{
+				"certificate_name": `"com.android.apogee.certificate"`,
+				"file_contexts":    `":com.android.apogee-file_contexts"`,
+				"manifest":         `"apogee_manifest.json"`,
+			}),
+		}})
+}
+
+func TestBp2BuildOverrideApex_CertificateIsModule(t *testing.T) {
+	runOverrideApexTestCase(t, Bp2buildTestCase{
+		Description:                "override_apex - certificate is module",
+		ModuleTypeUnderTest:        "override_apex",
+		ModuleTypeUnderTestFactory: apex.OverrideApexFactory,
+		Filesystem:                 map[string]string{},
+		Blueprint: `
+android_app_certificate {
+	name: "com.android.apogee.certificate",
+	certificate: "com.android.apogee",
+	bazel_module: { bp2build_available: false },
+}
+
+filegroup {
+	name: "com.android.apogee-file_contexts",
+	srcs: [
+		"com.android.apogee-file_contexts",
+	],
+	bazel_module: { bp2build_available: false },
+}
+
+apex {
+	name: "com.android.apogee",
+	manifest: "apogee_manifest.json",
+	file_contexts: ":com.android.apogee-file_contexts",
+	certificate: ":com.android.apogee.certificate",
+	bazel_module: { bp2build_available: false },
+}
+
+android_app_certificate {
+	name: "com.google.android.apogee.certificate",
+	certificate: "com.google.android.apogee",
+	bazel_module: { bp2build_available: false },
+}
+
+override_apex {
+	name: "com.google.android.apogee",
+	base: ":com.android.apogee",
+	certificate: ":com.google.android.apogee.certificate",
+}
+`,
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget("apex", "com.google.android.apogee", AttrNameToString{
+				"file_contexts": `":com.android.apogee-file_contexts"`,
+				"certificate":   `":com.google.android.apogee.certificate"`,
+				"manifest":      `"apogee_manifest.json"`,
+			}),
+		}})
+}
+
+func TestBp2BuildOverrideApex_CertificateIsSrc(t *testing.T) {
+	runOverrideApexTestCase(t, Bp2buildTestCase{
+		Description:                "override_apex - certificate is src",
+		ModuleTypeUnderTest:        "override_apex",
+		ModuleTypeUnderTestFactory: apex.OverrideApexFactory,
+		Filesystem:                 map[string]string{},
+		Blueprint: `
+android_app_certificate {
+	name: "com.android.apogee.certificate",
+	certificate: "com.android.apogee",
+	bazel_module: { bp2build_available: false },
+}
+
+filegroup {
+	name: "com.android.apogee-file_contexts",
+	srcs: [
+		"com.android.apogee-file_contexts",
+	],
+	bazel_module: { bp2build_available: false },
+}
+
+apex {
+	name: "com.android.apogee",
+	manifest: "apogee_manifest.json",
+	file_contexts: ":com.android.apogee-file_contexts",
+	certificate: ":com.android.apogee.certificate",
+	bazel_module: { bp2build_available: false },
+}
+
+override_apex {
+	name: "com.google.android.apogee",
+	base: ":com.android.apogee",
+	certificate: "com.google.android.apogee.certificate",
+}
+`,
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget("apex", "com.google.android.apogee", AttrNameToString{
+				"file_contexts":    `":com.android.apogee-file_contexts"`,
+				"certificate_name": `"com.google.android.apogee.certificate"`,
+				"manifest":         `"apogee_manifest.json"`,
+			}),
+		}})
+}
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index 36c3a48..82ce115 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -163,6 +163,9 @@
 	// This mode is used for discovering and introspecting the existing Soong
 	// module graph.
 	QueryView
+
+	// ApiBp2build - generate BUILD files for API contribution targets
+	ApiBp2build
 )
 
 type unconvertedDepsMode int
@@ -181,6 +184,8 @@
 		return "Bp2Build"
 	case QueryView:
 		return "QueryView"
+	case ApiBp2build:
+		return "ApiBp2build"
 	default:
 		return fmt.Sprintf("%d", mode)
 	}
@@ -327,6 +332,10 @@
 				errs = append(errs, err)
 			}
 			targets = append(targets, t)
+		case ApiBp2build:
+			if aModule, ok := m.(android.Module); ok && aModule.IsConvertedByBp2build() {
+				targets, errs = generateBazelTargets(bpCtx, aModule)
+			}
 		default:
 			errs = append(errs, fmt.Errorf("Unknown code-generation mode: %s", ctx.Mode()))
 			return
diff --git a/bp2build/build_conversion_test.go b/bp2build/build_conversion_test.go
index c1fb800..7c24a94 100644
--- a/bp2build/build_conversion_test.go
+++ b/bp2build/build_conversion_test.go
@@ -1853,3 +1853,27 @@
 			},
 		})
 }
+
+func TestGenerateApiBazelTargets(t *testing.T) {
+	bp := `
+	custom {
+		name: "foo",
+		api: "foo.txt",
+	}
+	`
+	expectedBazelTarget := MakeBazelTarget(
+		"custom_api_contribution",
+		"foo",
+		AttrNameToString{
+			"api": `"foo.txt"`,
+		},
+	)
+	registerCustomModule := func(ctx android.RegistrationContext) {
+		ctx.RegisterModuleType("custom", customModuleFactoryHostAndDevice)
+	}
+	RunApiBp2BuildTestCase(t, registerCustomModule, Bp2buildTestCase{
+		Blueprint:            bp,
+		ExpectedBazelTargets: []string{expectedBazelTarget},
+		Description:          "Generating API contribution Bazel targets for custom module",
+	})
+}
diff --git a/bp2build/bzl_conversion_test.go b/bp2build/bzl_conversion_test.go
index 28d2c75..a8e557d 100644
--- a/bp2build/bzl_conversion_test.go
+++ b/bp2build/bzl_conversion_test.go
@@ -85,6 +85,7 @@
         "soong_module_name": attr.string(mandatory = True),
         "soong_module_variant": attr.string(),
         "soong_module_deps": attr.label_list(providers = [SoongModuleInfo]),
+        "api": attr.string(),
         "arch_paths": attr.string_list(),
         "arch_paths_exclude": attr.string_list(),
         # bazel_module start
@@ -119,6 +120,7 @@
         "soong_module_name": attr.string(mandatory = True),
         "soong_module_variant": attr.string(),
         "soong_module_deps": attr.label_list(providers = [SoongModuleInfo]),
+        "api": attr.string(),
         "arch_paths": attr.string_list(),
         "arch_paths_exclude": attr.string_list(),
         "bool_prop": attr.bool(),
@@ -149,6 +151,7 @@
         "soong_module_name": attr.string(mandatory = True),
         "soong_module_variant": attr.string(),
         "soong_module_deps": attr.label_list(providers = [SoongModuleInfo]),
+        "api": attr.string(),
         "arch_paths": attr.string_list(),
         "arch_paths_exclude": attr.string_list(),
         "bool_prop": attr.bool(),
diff --git a/bp2build/cc_binary_conversion_test.go b/bp2build/cc_binary_conversion_test.go
index c23779e..72d3940 100644
--- a/bp2build/cc_binary_conversion_test.go
+++ b/bp2build/cc_binary_conversion_test.go
@@ -201,20 +201,27 @@
 	})
 }
 
-func TestCcBinaryVersionScript(t *testing.T) {
+func TestCcBinaryVersionScriptAndDynamicList(t *testing.T) {
 	runCcBinaryTests(t, ccBinaryBp2buildTestCase{
-		description: `version script`,
+		description: `version script and dynamic list`,
 		blueprint: `
 {rule_name} {
     name: "foo",
     include_build_directory: false,
     version_script: "vs",
+    dynamic_list: "dynamic.list",
 }
 `,
 		targets: []testBazelTarget{
 			{"cc_binary", "foo", AttrNameToString{
-				"additional_linker_inputs": `["vs"]`,
-				"linkopts":                 `["-Wl,--version-script,$(location vs)"]`,
+				"additional_linker_inputs": `[
+        "vs",
+        "dynamic.list",
+    ]`,
+				"linkopts": `[
+        "-Wl,--version-script,$(location vs)",
+        "-Wl,--dynamic-list,$(location dynamic.list)",
+    ]`,
 			},
 			},
 		},
diff --git a/bp2build/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go
index 1b8e9b4..728a4dc 100644
--- a/bp2build/cc_library_conversion_test.go
+++ b/bp2build/cc_library_conversion_test.go
@@ -870,9 +870,9 @@
 			})}})
 }
 
-func TestCcLibraryNonConfiguredVersionScript(t *testing.T) {
+func TestCcLibraryNonConfiguredVersionScriptAndDynamicList(t *testing.T) {
 	runCcLibraryTestCase(t, Bp2buildTestCase{
-		Description:                "cc_library non-configured version script",
+		Description:                "cc_library non-configured version script and dynamic list",
 		ModuleTypeUnderTest:        "cc_library",
 		ModuleTypeUnderTestFactory: cc.LibraryFactory,
 		Dir:                        "foo/bar",
@@ -882,6 +882,7 @@
     name: "a",
     srcs: ["a.cpp"],
     version_script: "v.map",
+    dynamic_list: "dynamic.list",
     bazel_module: { bp2build_available: true },
     include_build_directory: false,
 }
@@ -889,17 +890,23 @@
 		},
 		Blueprint: soongCcLibraryPreamble,
 		ExpectedBazelTargets: makeCcLibraryTargets("a", AttrNameToString{
-			"additional_linker_inputs": `["v.map"]`,
-			"linkopts":                 `["-Wl,--version-script,$(location v.map)"]`,
-			"srcs":                     `["a.cpp"]`,
+			"additional_linker_inputs": `[
+        "v.map",
+        "dynamic.list",
+    ]`,
+			"linkopts": `[
+        "-Wl,--version-script,$(location v.map)",
+        "-Wl,--dynamic-list,$(location dynamic.list)",
+    ]`,
+			"srcs": `["a.cpp"]`,
 		}),
 	},
 	)
 }
 
-func TestCcLibraryConfiguredVersionScript(t *testing.T) {
+func TestCcLibraryConfiguredVersionScriptAndDynamicList(t *testing.T) {
 	runCcLibraryTestCase(t, Bp2buildTestCase{
-		Description:                "cc_library configured version script",
+		Description:                "cc_library configured version script and dynamic list",
 		ModuleTypeUnderTest:        "cc_library",
 		ModuleTypeUnderTestFactory: cc.LibraryFactory,
 		Dir:                        "foo/bar",
@@ -911,9 +918,11 @@
    arch: {
      arm: {
        version_script: "arm.map",
+       dynamic_list: "dynamic_arm.list",
      },
      arm64: {
        version_script: "arm64.map",
+       dynamic_list: "dynamic_arm64.list",
      },
    },
 
@@ -925,13 +934,25 @@
 		Blueprint: soongCcLibraryPreamble,
 		ExpectedBazelTargets: makeCcLibraryTargets("a", AttrNameToString{
 			"additional_linker_inputs": `select({
-        "//build/bazel/platforms/arch:arm": ["arm.map"],
-        "//build/bazel/platforms/arch:arm64": ["arm64.map"],
+        "//build/bazel/platforms/arch:arm": [
+            "arm.map",
+            "dynamic_arm.list",
+        ],
+        "//build/bazel/platforms/arch:arm64": [
+            "arm64.map",
+            "dynamic_arm64.list",
+        ],
         "//conditions:default": [],
     })`,
 			"linkopts": `select({
-        "//build/bazel/platforms/arch:arm": ["-Wl,--version-script,$(location arm.map)"],
-        "//build/bazel/platforms/arch:arm64": ["-Wl,--version-script,$(location arm64.map)"],
+        "//build/bazel/platforms/arch:arm": [
+            "-Wl,--version-script,$(location arm.map)",
+            "-Wl,--dynamic-list,$(location dynamic_arm.list)",
+        ],
+        "//build/bazel/platforms/arch:arm64": [
+            "-Wl,--version-script,$(location arm64.map)",
+            "-Wl,--dynamic-list,$(location dynamic_arm64.list)",
+        ],
         "//conditions:default": [],
     })`,
 			"srcs": `["a.cpp"]`,
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
index 522c10e..4d8b8a4 100644
--- a/bp2build/conversion.go
+++ b/bp2build/conversion.go
@@ -97,7 +97,7 @@
 		targets.sort()
 
 		var content string
-		if mode == Bp2Build {
+		if mode == Bp2Build || mode == ApiBp2build {
 			content = `# READ THIS FIRST:
 # This file was automatically generated by bp2build for the Bazel migration project.
 # Feel free to edit or test it, but do *not* check it into your version control system.
diff --git a/bp2build/testing.go b/bp2build/testing.go
index c2c1b19..31aa830 100644
--- a/bp2build/testing.go
+++ b/bp2build/testing.go
@@ -24,6 +24,8 @@
 	"strings"
 	"testing"
 
+	"github.com/google/blueprint/proptools"
+
 	"android/soong/android"
 	"android/soong/android/allowlists"
 	"android/soong/bazel"
@@ -88,6 +90,22 @@
 }
 
 func RunBp2BuildTestCase(t *testing.T, registerModuleTypes func(ctx android.RegistrationContext), tc Bp2buildTestCase) {
+	bp2buildSetup := func(ctx *android.TestContext) {
+		registerModuleTypes(ctx)
+		ctx.RegisterForBazelConversion()
+	}
+	runBp2BuildTestCaseWithSetup(t, bp2buildSetup, tc)
+}
+
+func RunApiBp2BuildTestCase(t *testing.T, registerModuleTypes func(ctx android.RegistrationContext), tc Bp2buildTestCase) {
+	apiBp2BuildSetup := func(ctx *android.TestContext) {
+		registerModuleTypes(ctx)
+		ctx.RegisterForApiBazelConversion()
+	}
+	runBp2BuildTestCaseWithSetup(t, apiBp2BuildSetup, tc)
+}
+
+func runBp2BuildTestCaseWithSetup(t *testing.T, setup func(ctx *android.TestContext), tc Bp2buildTestCase) {
 	t.Helper()
 	dir := "."
 	filesystem := make(map[string][]byte)
@@ -103,7 +121,7 @@
 	config := android.TestConfig(buildDir, nil, tc.Blueprint, filesystem)
 	ctx := android.NewTestContext(config)
 
-	registerModuleTypes(ctx)
+	setup(ctx)
 	ctx.RegisterModuleType(tc.ModuleTypeUnderTest, tc.ModuleTypeUnderTestFactory)
 
 	// A default configuration for tests to not have to specify bp2build_available on top level targets.
@@ -118,7 +136,6 @@
 		})
 	}
 	ctx.RegisterBp2BuildConfig(bp2buildConfig)
-	ctx.RegisterForBazelConversion()
 
 	_, parseErrs := ctx.ParseFileList(dir, toParse)
 	if errored(t, tc, parseErrs) {
@@ -198,6 +215,8 @@
 
 	// Prop used to indicate this conversion should be 1 module -> multiple targets
 	One_to_many_prop *bool
+
+	Api *string // File describing the APIs of this module
 }
 
 type customModule struct {
@@ -320,6 +339,7 @@
 	String_ptr_prop     *string
 	String_list_prop    []string
 	Arch_paths          bazel.LabelListAttribute
+	Api                 bazel.LabelAttribute
 }
 
 func (m *customModule) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
@@ -364,6 +384,23 @@
 	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
 }
 
+var _ android.ApiProvider = (*customModule)(nil)
+
+func (c *customModule) ConvertWithApiBp2build(ctx android.TopDownMutatorContext) {
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class: "custom_api_contribution",
+	}
+	apiAttribute := bazel.MakeLabelAttribute(
+		android.BazelLabelForModuleSrcSingle(ctx, proptools.String(c.props.Api)).Label,
+	)
+	attrs := &customBazelModuleAttributes{
+		Api: *apiAttribute,
+	}
+	ctx.CreateBazelTargetModule(props,
+		android.CommonAttributes{Name: c.Name()},
+		attrs)
+}
+
 // A bp2build mutator that uses load statements and creates a 1:M mapping from
 // module to target.
 func customBp2buildOneToMany(ctx android.TopDownMutatorContext, m *customModule) {
diff --git a/build_test.bash b/build_test.bash
index 8b91e2c..6f0fba7 100755
--- a/build_test.bash
+++ b/build_test.bash
@@ -59,7 +59,7 @@
 
 echo
 echo "Running Bazel smoke test..."
-STANDALONE_BAZEL=true "${TOP}/tools/bazel" --batch --max_idle_secs=1 info
+STANDALONE_BAZEL=true "${TOP}/tools/bazel" --batch --max_idle_secs=1 help
 
 echo
 echo "Running Soong test..."
diff --git a/cc/cc.go b/cc/cc.go
index 1c845f6..70d3962 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -3821,6 +3821,15 @@
 	}
 }
 
+func (c *Module) Partition() string {
+	if p, ok := c.installer.(interface {
+		getPartition() string
+	}); ok {
+		return p.getPartition()
+	}
+	return ""
+}
+
 var Bool = proptools.Bool
 var BoolDefault = proptools.BoolDefault
 var BoolPtr = proptools.BoolPtr
diff --git a/cc/library.go b/cc/library.go
index 441eb79..507678d 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -1089,6 +1089,12 @@
 		} else {
 			flag = "--systemapi"
 		}
+		// b/184712170, unless the lib is an NDK library, exclude all public symbols from
+		// the stub so that it is mandated that all symbols are explicitly marked with
+		// either apex or systemapi.
+		if !ctx.Module().(*Module).IsNdk(ctx.Config()) {
+			flag = flag + " --no-ndk"
+		}
 		nativeAbiResult := parseNativeAbiDefinition(ctx, symbolFile,
 			android.ApiLevelOrPanic(ctx, library.MutatedProperties.StubsVersion), flag)
 		objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
@@ -2227,6 +2233,10 @@
 	mod.ModuleBase.MakeUninstallable()
 }
 
+func (library *libraryDecorator) getPartition() string {
+	return library.path.Partition()
+}
+
 func (library *libraryDecorator) getAPIListCoverageXMLPath() android.ModuleOutPath {
 	return library.apiListCoverageXmlPath
 }
diff --git a/cc/linkable.go b/cc/linkable.go
index 2316d86..0522fc6 100644
--- a/cc/linkable.go
+++ b/cc/linkable.go
@@ -253,6 +253,9 @@
 
 	// VndkVersion returns the VNDK version string for this module.
 	VndkVersion() string
+
+	// Partition returns the partition string for this module.
+	Partition() string
 }
 
 var (
diff --git a/cc/ndkstubgen/__init__.py b/cc/ndkstubgen/__init__.py
index f893d41..efad70a 100755
--- a/cc/ndkstubgen/__init__.py
+++ b/cc/ndkstubgen/__init__.py
@@ -111,6 +111,11 @@
         action='store_true',
         dest='systemapi',
         help='Use the SystemAPI variant.')
+    parser.add_argument(
+        '--no-ndk',
+        action='store_false',
+        dest='ndk',
+        help='Do not include NDK APIs.')
 
     parser.add_argument('--api-map',
                         type=resolved_path,
@@ -147,7 +152,7 @@
         verbosity = 2
     logging.basicConfig(level=verbose_map[verbosity])
 
-    filt = symbolfile.Filter(args.arch, api, args.llndk, args.apex, args.systemapi)
+    filt = symbolfile.Filter(args.arch, api, args.llndk, args.apex, args.systemapi, args.ndk)
     with args.symbol_file.open() as symbol_file:
         try:
           versions = symbolfile.SymbolFileParser(symbol_file, api_map, filt).parse()
diff --git a/cc/ndkstubgen/test_ndkstubgen.py b/cc/ndkstubgen/test_ndkstubgen.py
index 450719b..1e0bdf3 100755
--- a/cc/ndkstubgen/test_ndkstubgen.py
+++ b/cc/ndkstubgen/test_ndkstubgen.py
@@ -424,6 +424,45 @@
         """)
         self.assertEqual(expected_version, version_file.getvalue())
 
+    def test_integration_with_nondk(self) -> None:
+        input_file = io.StringIO(textwrap.dedent("""\
+            VERSION_1 {
+                global:
+                    foo;
+                    bar; # apex
+                local:
+                    *;
+            };
+        """))
+        f = copy(self.filter)
+        f.apex = True
+        f.ndk = False   # ndk symbols should be excluded
+        parser = symbolfile.SymbolFileParser(input_file, {}, f)
+        versions = parser.parse()
+
+        src_file = io.StringIO()
+        version_file = io.StringIO()
+        symbol_list_file = io.StringIO()
+        f = copy(self.filter)
+        f.apex = True
+        f.ndk = False   # ndk symbols should be excluded
+        generator = ndkstubgen.Generator(src_file,
+                                         version_file, symbol_list_file, f)
+        generator.write(versions)
+
+        expected_src = textwrap.dedent("""\
+            void bar() {}
+        """)
+        self.assertEqual(expected_src, src_file.getvalue())
+
+        expected_version = textwrap.dedent("""\
+            VERSION_1 {
+                global:
+                    bar;
+            };
+        """)
+        self.assertEqual(expected_version, version_file.getvalue())
+
     def test_empty_stub(self) -> None:
         """Tests that empty stubs can be generated.
 
diff --git a/cc/symbolfile/__init__.py b/cc/symbolfile/__init__.py
index 471a12f..9bf07f2 100644
--- a/cc/symbolfile/__init__.py
+++ b/cc/symbolfile/__init__.py
@@ -208,12 +208,14 @@
     symbol should be omitted or not
     """
 
-    def __init__(self, arch: Arch, api: int, llndk: bool = False, apex: bool = False, systemapi: bool = False):
+    def __init__(self, arch: Arch, api: int, llndk: bool = False, apex: bool = False, systemapi:
+                 bool = False, ndk: bool = True):
         self.arch = arch
         self.api = api
         self.llndk = llndk
         self.apex = apex
         self.systemapi = systemapi
+        self.ndk = ndk
 
     def _should_omit_tags(self, tags: Tags) -> bool:
         """Returns True if the tagged object should be omitted.
@@ -253,8 +255,13 @@
 
     def should_omit_symbol(self, symbol: Symbol) -> bool:
         """Returns True if the symbol should be omitted."""
-        return self._should_omit_tags(symbol.tags)
+        if not symbol.tags.has_mode_tags and not self.ndk:
+            # Symbols that don't have mode tags are NDK. They are usually
+            # included, but have to be omitted if NDK symbols are explicitly
+            # filtered-out
+            return True
 
+        return self._should_omit_tags(symbol.tags)
 
 def symbol_in_arch(tags: Tags, arch: Arch) -> bool:
     """Returns true if the symbol is present for the given architecture."""
diff --git a/cc/symbolfile/test_symbolfile.py b/cc/symbolfile/test_symbolfile.py
index e17a8d0..856b9d7 100644
--- a/cc/symbolfile/test_symbolfile.py
+++ b/cc/symbolfile/test_symbolfile.py
@@ -308,6 +308,20 @@
     def assertInclude(self, f: Filter, s: Symbol) -> None:
         self.assertFalse(f.should_omit_symbol(s))
 
+    def test_omit_ndk(self) -> None:
+        f_ndk = self.filter
+        f_nondk = copy(f_ndk)
+        f_nondk.ndk = False
+        f_nondk.apex = True
+
+        s_ndk = Symbol('foo', Tags())
+        s_nonndk = Symbol('foo', Tags.from_strs(['apex']))
+
+        self.assertInclude(f_ndk, s_ndk)
+        self.assertOmit(f_ndk, s_nonndk)
+        self.assertOmit(f_nondk, s_ndk)
+        self.assertInclude(f_nondk, s_nonndk)
+
     def test_omit_llndk(self) -> None:
         f_none = self.filter
         f_llndk = copy(f_none)
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 0b8cc88..770ad0c 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -24,6 +24,7 @@
 	"time"
 
 	"android/soong/android"
+	"android/soong/bazel"
 	"android/soong/bp2build"
 	"android/soong/shared"
 	"android/soong/ui/metrics/bp2build_metrics_proto"
@@ -48,11 +49,12 @@
 	delveListen string
 	delvePath   string
 
-	moduleGraphFile   string
-	moduleActionsFile string
-	docFile           string
-	bazelQueryViewDir string
-	bp2buildMarker    string
+	moduleGraphFile     string
+	moduleActionsFile   string
+	docFile             string
+	bazelQueryViewDir   string
+	bazelApiBp2buildDir string
+	bp2buildMarker      string
 
 	cmdlineArgs bootstrap.Args
 )
@@ -81,6 +83,7 @@
 	flag.StringVar(&moduleActionsFile, "module_actions_file", "", "JSON file to output inputs/outputs of actions of modules")
 	flag.StringVar(&docFile, "soong_docs", "", "build documentation file to output")
 	flag.StringVar(&bazelQueryViewDir, "bazel_queryview_dir", "", "path to the bazel queryview directory relative to --top")
+	flag.StringVar(&bazelApiBp2buildDir, "bazel_api_bp2build_dir", "", "path to the bazel api_bp2build directory relative to --top")
 	flag.StringVar(&bp2buildMarker, "bp2build_marker", "", "If set, run bp2build, touch the specified marker file then exit")
 	flag.StringVar(&cmdlineArgs.OutFile, "o", "build.ninja", "the Ninja file to output")
 	flag.BoolVar(&cmdlineArgs.EmptyNinjaFile, "empty-ninja-file", false, "write out a 0-byte ninja file")
@@ -129,6 +132,8 @@
 		buildMode = android.Bp2build
 	} else if bazelQueryViewDir != "" {
 		buildMode = android.GenerateQueryView
+	} else if bazelApiBp2buildDir != "" {
+		buildMode = android.ApiBp2build
 	} else if moduleGraphFile != "" {
 		buildMode = android.GenerateModuleGraph
 	} else if docFile != "" {
@@ -178,7 +183,7 @@
 	defer ctx.EventHandler.End("queryview")
 	codegenContext := bp2build.NewCodegenContext(configuration, *ctx, bp2build.QueryView)
 	absoluteQueryViewDir := shared.JoinPath(topDir, queryviewDir)
-	if err := createBazelQueryView(codegenContext, absoluteQueryViewDir); err != nil {
+	if err := createBazelWorkspace(codegenContext, absoluteQueryViewDir); err != nil {
 		fmt.Fprintf(os.Stderr, "%s", err)
 		os.Exit(1)
 	}
@@ -186,6 +191,96 @@
 	touch(shared.JoinPath(topDir, queryviewMarker))
 }
 
+// Run the code-generation phase to convert API contributions to BUILD files.
+// Return marker file for the new synthetic workspace
+func runApiBp2build(configuration android.Config, extraNinjaDeps []string) string {
+	// Create a new context and register mutators that are only meaningful to API export
+	ctx := android.NewContext(configuration)
+	ctx.EventHandler.Begin("api_bp2build")
+	defer ctx.EventHandler.End("api_bp2build")
+	ctx.SetNameInterface(newNameResolver(configuration))
+	ctx.RegisterForApiBazelConversion()
+
+	// Register the Android.bp files in the tree
+	// Add them to the workspace's .d file
+	ctx.SetModuleListFile(cmdlineArgs.ModuleListFile)
+	if paths, err := ctx.ListModulePaths("."); err == nil {
+		extraNinjaDeps = append(extraNinjaDeps, paths...)
+	} else {
+		panic(err)
+	}
+
+	// Run the loading and analysis phase
+	ninjaDeps := bootstrap.RunBlueprint(cmdlineArgs,
+		bootstrap.StopBeforePrepareBuildActions,
+		ctx.Context,
+		configuration)
+	ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
+
+	// Add the globbed dependencies
+	globs := writeBuildGlobsNinjaFile(ctx, configuration.SoongOutDir(), configuration)
+	ninjaDeps = append(ninjaDeps, globs...)
+
+	// Run codegen to generate BUILD files
+	codegenContext := bp2build.NewCodegenContext(configuration, *ctx, bp2build.ApiBp2build)
+	absoluteApiBp2buildDir := shared.JoinPath(topDir, bazelApiBp2buildDir)
+	if err := createBazelWorkspace(codegenContext, absoluteApiBp2buildDir); err != nil {
+		fmt.Fprintf(os.Stderr, "%s", err)
+		os.Exit(1)
+	}
+	ninjaDeps = append(ninjaDeps, codegenContext.AdditionalNinjaDeps()...)
+
+	// Create soong_injection repository
+	soongInjectionFiles := bp2build.CreateSoongInjectionFiles(configuration, bp2build.CodegenMetrics{})
+	absoluteSoongInjectionDir := shared.JoinPath(topDir, configuration.SoongOutDir(), bazel.SoongInjectionDirName)
+	for _, file := range soongInjectionFiles {
+		writeReadOnlyFile(absoluteSoongInjectionDir, file)
+	}
+
+	workspace := shared.JoinPath(configuration.SoongOutDir(), "api_bp2build")
+
+	excludes := bazelArtifacts()
+	// Exclude all src BUILD files
+	excludes = append(excludes, apiBuildFileExcludes()...)
+
+	// Create the symlink forest
+	symlinkDeps := bp2build.PlantSymlinkForest(
+		configuration,
+		topDir,
+		workspace,
+		bazelApiBp2buildDir,
+		".",
+		excludes)
+	ninjaDeps = append(ninjaDeps, symlinkDeps...)
+
+	workspaceMarkerFile := workspace + ".marker"
+	writeDepFile(workspaceMarkerFile, *ctx.EventHandler, ninjaDeps)
+	touch(shared.JoinPath(topDir, workspaceMarkerFile))
+	return workspaceMarkerFile
+}
+
+// With some exceptions, api_bp2build does not have any dependencies on the checked-in BUILD files
+// Exclude them from the generated workspace to prevent unrelated errors during the loading phase
+func apiBuildFileExcludes() []string {
+	ret := make([]string, 0)
+
+	srcs, err := getExistingBazelRelatedFiles(topDir)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Error determining existing Bazel-related files: %s\n", err)
+		os.Exit(1)
+	}
+	for _, src := range srcs {
+		if src != "WORKSPACE" &&
+			src != "BUILD" &&
+			src != "BUILD.bazel" &&
+			!strings.HasPrefix(src, "build/bazel") &&
+			!strings.HasPrefix(src, "prebuilts/clang") {
+			ret = append(ret, src)
+		}
+	}
+	return ret
+}
+
 func writeMetrics(configuration android.Config, eventHandler metrics.EventHandler, metricsDir string) {
 	if len(metricsDir) < 1 {
 		fmt.Fprintf(os.Stderr, "\nMissing required env var for generating soong metrics: LOG_DIR\n")
@@ -248,6 +343,8 @@
 		return bp2buildMarker
 	} else if configuration.IsMixedBuildsEnabled() {
 		runMixedModeBuild(configuration, ctx, extraNinjaDeps)
+	} else if configuration.BuildMode == android.ApiBp2build {
+		return runApiBp2build(configuration, extraNinjaDeps)
 	} else {
 		var stopBefore bootstrap.StopBefore
 		if configuration.BuildMode == android.GenerateModuleGraph {
@@ -476,6 +573,16 @@
 	return files, nil
 }
 
+func bazelArtifacts() []string {
+	return []string{
+		"bazel-bin",
+		"bazel-genfiles",
+		"bazel-out",
+		"bazel-testlogs",
+		"bazel-" + filepath.Base(topDir),
+	}
+}
+
 // Run Soong in the bp2build mode. This creates a standalone context that registers
 // an alternate pipeline of mutators and singletons specifically for generating
 // Bazel BUILD files instead of Ninja files.
@@ -524,13 +631,7 @@
 		generatedRoot := shared.JoinPath(configuration.SoongOutDir(), "bp2build")
 		workspaceRoot := shared.JoinPath(configuration.SoongOutDir(), "workspace")
 
-		excludes := []string{
-			"bazel-bin",
-			"bazel-genfiles",
-			"bazel-out",
-			"bazel-testlogs",
-			"bazel-" + filepath.Base(topDir),
-		}
+		excludes := bazelArtifacts()
 
 		if outDir[0] != '/' {
 			excludes = append(excludes, outDir)
diff --git a/cmd/soong_build/queryview.go b/cmd/soong_build/queryview.go
index 983dbf0..cd1d6fb 100644
--- a/cmd/soong_build/queryview.go
+++ b/cmd/soong_build/queryview.go
@@ -23,8 +23,9 @@
 	"android/soong/bp2build"
 )
 
-func createBazelQueryView(ctx *bp2build.CodegenContext, bazelQueryViewDir string) error {
-	os.RemoveAll(bazelQueryViewDir)
+// A helper function to generate a Read-only Bazel workspace in outDir
+func createBazelWorkspace(ctx *bp2build.CodegenContext, outDir string) error {
+	os.RemoveAll(outDir)
 	ruleShims := bp2build.CreateRuleShims(android.ModuleTypeFactories())
 
 	res, err := bp2build.GenerateBazelTargets(ctx, true)
@@ -33,9 +34,9 @@
 	}
 
 	filesToWrite := bp2build.CreateBazelFiles(ctx.Config(), ruleShims, res.BuildDirToTargets(),
-		bp2build.QueryView)
+		ctx.Mode())
 	for _, f := range filesToWrite {
-		if err := writeReadOnlyFile(bazelQueryViewDir, f); err != nil {
+		if err := writeReadOnlyFile(outDir, f); err != nil {
 			return err
 		}
 	}
diff --git a/java/androidmk.go b/java/androidmk.go
index 75ac0e7..cd86880 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"io"
+	"strings"
 
 	"android/soong/android"
 )
@@ -398,6 +399,19 @@
 				} else {
 					for _, jniLib := range app.jniLibs {
 						entries.AddStrings("LOCAL_SOONG_JNI_LIBS_"+jniLib.target.Arch.ArchType.String(), jniLib.name)
+						var partitionTag string
+
+						// Mimic the creation of partition_tag in build/make,
+						// which defaults to an empty string when the partition is system.
+						// Otherwise, capitalize with a leading _
+						if jniLib.partition == "system" {
+							partitionTag = ""
+						} else {
+							split := strings.Split(jniLib.partition, "/")
+							partitionTag = "_" + strings.ToUpper(split[len(split)-1])
+						}
+						entries.AddStrings("LOCAL_SOONG_JNI_LIBS_PARTITION_"+jniLib.target.Arch.ArchType.String(),
+							jniLib.name+":"+partitionTag)
 					}
 				}
 
diff --git a/java/androidmk_test.go b/java/androidmk_test.go
index 197da4f..1232cd1 100644
--- a/java/androidmk_test.go
+++ b/java/androidmk_test.go
@@ -19,6 +19,9 @@
 	"testing"
 
 	"android/soong/android"
+	"android/soong/cc"
+
+	"github.com/google/blueprint/proptools"
 )
 
 func TestRequired(t *testing.T) {
@@ -252,3 +255,149 @@
 		android.AssertDeepEquals(t, "overrides property", expected.overrides, actual)
 	}
 }
+
+func TestJniPartition(t *testing.T) {
+	bp := `
+		cc_library {
+			name: "libjni_system",
+			system_shared_libs: [],
+			sdk_version: "current",
+			stl: "none",
+		}
+
+		cc_library {
+			name: "libjni_system_ext",
+			system_shared_libs: [],
+			sdk_version: "current",
+			stl: "none",
+			system_ext_specific: true,
+		}
+
+		cc_library {
+			name: "libjni_odm",
+			system_shared_libs: [],
+			sdk_version: "current",
+			stl: "none",
+			device_specific: true,
+		}
+
+		cc_library {
+			name: "libjni_product",
+			system_shared_libs: [],
+			sdk_version: "current",
+			stl: "none",
+			product_specific: true,
+		}
+
+		cc_library {
+			name: "libjni_vendor",
+			system_shared_libs: [],
+			sdk_version: "current",
+			stl: "none",
+			soc_specific: true,
+		}
+
+		android_app {
+			name: "test_app_system_jni_system",
+			privileged: true,
+			platform_apis: true,
+			certificate: "platform",
+			jni_libs: ["libjni_system"],
+		}
+
+		android_app {
+			name: "test_app_system_jni_system_ext",
+			privileged: true,
+			platform_apis: true,
+			certificate: "platform",
+			jni_libs: ["libjni_system_ext"],
+		}
+
+		android_app {
+			name: "test_app_system_ext_jni_system",
+			privileged: true,
+			platform_apis: true,
+			certificate: "platform",
+			jni_libs: ["libjni_system"],
+			system_ext_specific: true
+		}
+
+		android_app {
+			name: "test_app_system_ext_jni_system_ext",
+			sdk_version: "core_platform",
+			jni_libs: ["libjni_system_ext"],
+			system_ext_specific: true
+		}
+
+		android_app {
+			name: "test_app_product_jni_product",
+			sdk_version: "core_platform",
+			jni_libs: ["libjni_product"],
+			product_specific: true
+		}
+
+		android_app {
+			name: "test_app_vendor_jni_odm",
+			sdk_version: "core_platform",
+			jni_libs: ["libjni_odm"],
+			soc_specific: true
+		}
+
+		android_app {
+			name: "test_app_odm_jni_vendor",
+			sdk_version: "core_platform",
+			jni_libs: ["libjni_vendor"],
+			device_specific: true
+		}
+		android_app {
+			name: "test_app_system_jni_multiple",
+			privileged: true,
+			platform_apis: true,
+			certificate: "platform",
+			jni_libs: ["libjni_system", "libjni_system_ext"],
+		}
+		android_app {
+			name: "test_app_vendor_jni_multiple",
+			sdk_version: "core_platform",
+			jni_libs: ["libjni_odm", "libjni_vendor"],
+			soc_specific: true
+		}
+		`
+	arch := "arm64"
+	ctx := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		cc.PrepareForTestWithCcDefaultModules,
+		android.PrepareForTestWithAndroidMk,
+		android.FixtureModifyConfig(func(config android.Config) {
+			config.TestProductVariables.DeviceArch = proptools.StringPtr(arch)
+		}),
+	).
+		RunTestWithBp(t, bp)
+	testCases := []struct {
+		name           string
+		partitionNames []string
+		partitionTags  []string
+	}{
+		{"test_app_system_jni_system", []string{"libjni_system"}, []string{""}},
+		{"test_app_system_jni_system_ext", []string{"libjni_system_ext"}, []string{"_SYSTEM_EXT"}},
+		{"test_app_system_ext_jni_system", []string{"libjni_system"}, []string{""}},
+		{"test_app_system_ext_jni_system_ext", []string{"libjni_system_ext"}, []string{"_SYSTEM_EXT"}},
+		{"test_app_product_jni_product", []string{"libjni_product"}, []string{"_PRODUCT"}},
+		{"test_app_vendor_jni_odm", []string{"libjni_odm"}, []string{"_ODM"}},
+		{"test_app_odm_jni_vendor", []string{"libjni_vendor"}, []string{"_VENDOR"}},
+		{"test_app_system_jni_multiple", []string{"libjni_system", "libjni_system_ext"}, []string{"", "_SYSTEM_EXT"}},
+		{"test_app_vendor_jni_multiple", []string{"libjni_odm", "libjni_vendor"}, []string{"_ODM", "_VENDOR"}},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			mod := ctx.ModuleForTests(test.name, "android_common").Module()
+			entry := android.AndroidMkEntriesForTest(t, ctx.TestContext, mod)[0]
+			for i := range test.partitionNames {
+				actual := entry.EntryMap["LOCAL_SOONG_JNI_LIBS_PARTITION_"+arch][i]
+				expected := test.partitionNames[i] + ":" + test.partitionTags[i]
+				android.AssertStringEquals(t, "Expected and actual differ", expected, actual)
+			}
+		})
+	}
+}
diff --git a/java/app.go b/java/app.go
index 1955e2a..bbd9d2d 100755
--- a/java/app.go
+++ b/java/app.go
@@ -798,6 +798,7 @@
 						target:         module.Target(),
 						coverageFile:   dep.CoverageOutputFile(),
 						unstrippedFile: dep.UnstrippedOutputFile(),
+						partition:      dep.Partition(),
 					})
 				} else {
 					ctx.ModuleErrorf("dependency %q missing output file", otherName)
@@ -1489,6 +1490,20 @@
 	Certificate_name *string
 }
 
+// ParseCertificateToAttribute splits the certificate prop into a certificate
+// label attribute or a certificate_name string attribute.
+func ParseCertificateToAttribute(ctx android.TopDownMutatorContext, certificate *string) (*string, *bazel.Label) {
+	var certificateLabel *bazel.Label
+	certificateName := proptools.StringDefault(certificate, "")
+	certModule := android.SrcIsModule(certificateName)
+	if certModule != "" {
+		c := android.BazelLabelForModuleDepSingle(ctx, certificateName)
+		certificateLabel = &c
+		certificate = nil
+	}
+	return certificate, certificateLabel
+}
+
 // ConvertWithBp2build is used to convert android_app to Bazel.
 func (a *AndroidApp) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
 	commonAttrs, depLabels := a.convertLibraryAttrsBp2Build(ctx)
@@ -1498,15 +1513,7 @@
 
 	aapt := a.convertAaptAttrsWithBp2Build(ctx)
 
-	var certificate *bazel.Label
-	certificateNamePtr := a.overridableAppProperties.Certificate
-	certificateName := proptools.StringDefault(certificateNamePtr, "")
-	certModule := android.SrcIsModule(certificateName)
-	if certModule != "" {
-		c := android.BazelLabelForModuleDepSingle(ctx, certificateName)
-		certificate = &c
-		certificateNamePtr = nil
-	}
+	certificateName, certificate := ParseCertificateToAttribute(ctx, a.overridableAppProperties.Certificate)
 	attrs := &bazelAndroidAppAttributes{
 		commonAttrs,
 		aapt,
@@ -1514,7 +1521,7 @@
 		// TODO(b/209576404): handle package name override by product variable PRODUCT_MANIFEST_PACKAGE_NAME_OVERRIDES
 		a.overridableAppProperties.Package_name,
 		certificate,
-		certificateNamePtr,
+		certificateName,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
diff --git a/java/java.go b/java/java.go
index fc2af3b..5091d26 100644
--- a/java/java.go
+++ b/java/java.go
@@ -421,6 +421,7 @@
 	target         android.Target
 	coverageFile   android.OptionalPath
 	unstrippedFile android.Path
+	partition      string
 }
 
 func sdkDeps(ctx android.BottomUpMutatorContext, sdkContext android.SdkContext, d dexer) {
diff --git a/rust/compiler.go b/rust/compiler.go
index bf6a488..6055158 100644
--- a/rust/compiler.go
+++ b/rust/compiler.go
@@ -161,7 +161,7 @@
 	// This is primarily meant for rust_binary and rust_ffi modules where the default
 	// linkage of libstd might need to be overridden in some use cases. This should
 	// generally be avoided with other module types since it may cause collisions at
-	// linkage if all dependencies of the root binary module do not link against libstd\
+	// linkage if all dependencies of the root binary module do not link against libstd
 	// the same way.
 	Prefer_rlib *bool `android:"arch_variant"`
 
diff --git a/rust/rust.go b/rust/rust.go
index 7342a14..28a300b 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -1664,6 +1664,10 @@
 	}
 }
 
+func (c *Module) Partition() string {
+	return ""
+}
+
 var Bool = proptools.Bool
 var BoolDefault = proptools.BoolDefault
 var String = proptools.String
diff --git a/tests/apex_comparison_tests.sh b/tests/apex_comparison_tests.sh
index 6bc0165..61d131b 100755
--- a/tests/apex_comparison_tests.sh
+++ b/tests/apex_comparison_tests.sh
@@ -58,7 +58,7 @@
 ######################
 build/soong/soong_ui.bash --make-mode BP2BUILD_VERBOSE=1 --skip-soong-tests bp2build
 
-BAZEL_OUT="$(call_bazel info output_path)"
+BAZEL_OUT="$(call_bazel info --config=bp2build output_path)"
 
 export TARGET_PRODUCT="module_arm"
 call_bazel build --config=bp2build --config=ci --config=android \
diff --git a/tests/mixed_mode_test.sh b/tests/mixed_mode_test.sh
index b408fd3..f6fffad 100755
--- a/tests/mixed_mode_test.sh
+++ b/tests/mixed_mode_test.sh
@@ -14,7 +14,9 @@
   setup
   create_mock_bazel
 
-  STANDALONE_BAZEL=true run_bazel info
+  run_soong bp2build
+
+  run_bazel info --config=bp2build
 }
 
 test_bazel_smoke
diff --git a/ui/build/build.go b/ui/build/build.go
index ab8cd56..2022e50 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -112,10 +112,6 @@
 // checkBazelMode fails the build if there are conflicting arguments for which bazel
 // build mode to use.
 func checkBazelMode(ctx Context, config Config) {
-	if config.Environment().IsEnvTrue("USE_BAZEL_ANALYSIS") {
-		ctx.Fatalln("USE_BAZEL_ANALYSIS is deprecated. Unset USE_BAZEL_ANALYSIS.\n" +
-			"Use --bazel-mode-dev instead. For example: `m --bazel-mode-dev nothing`")
-	}
 	if config.bazelProdMode && config.bazelDevMode {
 		ctx.Fatalln("Conflicting bazel mode.\n" +
 			"Do not specify both --bazel-mode and --bazel-mode-dev")
diff --git a/ui/build/config.go b/ui/build/config.go
index 14a99d0..2060660 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -71,6 +71,7 @@
 	checkbuild      bool
 	dist            bool
 	jsonModuleGraph bool
+	apiBp2build     bool // Generate BUILD files for Soong modules that contribute APIs
 	bp2build        bool
 	queryview       bool
 	reportMkMetrics bool // Collect and report mk2bp migration progress metrics.
@@ -756,6 +757,8 @@
 			c.jsonModuleGraph = true
 		} else if arg == "bp2build" {
 			c.bp2build = true
+		} else if arg == "api_bp2build" {
+			c.apiBp2build = true
 		} else if arg == "queryview" {
 			c.queryview = true
 		} else if arg == "soong_docs" {
@@ -833,7 +836,7 @@
 		return true
 	}
 
-	if !c.JsonModuleGraph() && !c.Bp2Build() && !c.Queryview() && !c.SoongDocs() {
+	if !c.JsonModuleGraph() && !c.Bp2Build() && !c.Queryview() && !c.SoongDocs() && !c.ApiBp2build() {
 		// Command line was empty, the default Ninja target is built
 		return true
 	}
@@ -916,6 +919,10 @@
 	return shared.JoinPath(c.SoongOutDir(), "queryview.marker")
 }
 
+func (c *configImpl) ApiBp2buildMarkerFile() string {
+	return shared.JoinPath(c.SoongOutDir(), "api_bp2build.marker")
+}
+
 func (c *configImpl) ModuleGraphFile() string {
 	return shared.JoinPath(c.SoongOutDir(), "module-graph.json")
 }
@@ -957,6 +964,10 @@
 	return c.bp2build
 }
 
+func (c *configImpl) ApiBp2build() bool {
+	return c.apiBp2build
+}
+
 func (c *configImpl) Queryview() bool {
 	return c.queryview
 }
diff --git a/ui/build/soong.go b/ui/build/soong.go
index ac00fe6..e0d67cc 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -45,6 +45,7 @@
 	bp2buildTag        = "bp2build"
 	jsonModuleGraphTag = "modulegraph"
 	queryviewTag       = "queryview"
+	apiBp2buildTag     = "api_bp2build"
 	soongDocsTag       = "soong_docs"
 
 	// bootstrapEpoch is used to determine if an incremental build is incompatible with the current
@@ -237,6 +238,7 @@
 		config.NamedGlobFile(bp2buildTag),
 		config.NamedGlobFile(jsonModuleGraphTag),
 		config.NamedGlobFile(queryviewTag),
+		config.NamedGlobFile(apiBp2buildTag),
 		config.NamedGlobFile(soongDocsTag),
 	}
 }
@@ -307,6 +309,19 @@
 		fmt.Sprintf("generating the Soong module graph as a Bazel workspace at %s", queryviewDir),
 	)
 
+	// The BUILD files will be generated in out/soong/.api_bp2build (no symlinks to src files)
+	// The final workspace will be generated in out/soong/api_bp2build
+	apiBp2buildDir := filepath.Join(config.SoongOutDir(), ".api_bp2build")
+	apiBp2buildInvocation := primaryBuilderInvocation(
+		config,
+		apiBp2buildTag,
+		config.ApiBp2buildMarkerFile(),
+		[]string{
+			"--bazel_api_bp2build_dir", apiBp2buildDir,
+		},
+		fmt.Sprintf("generating BUILD files for API contributions at %s", apiBp2buildDir),
+	)
+
 	soongDocsInvocation := primaryBuilderInvocation(
 		config,
 		soongDocsTag,
@@ -345,6 +360,7 @@
 			bp2buildInvocation,
 			jsonModuleGraphInvocation,
 			queryviewInvocation,
+			apiBp2buildInvocation,
 			soongDocsInvocation},
 	}
 
@@ -417,6 +433,10 @@
 			checkEnvironmentFile(soongBuildEnv, config.UsedEnvFile(queryviewTag))
 		}
 
+		if config.ApiBp2build() {
+			checkEnvironmentFile(soongBuildEnv, config.UsedEnvFile(apiBp2buildTag))
+		}
+
 		if config.SoongDocs() {
 			checkEnvironmentFile(soongBuildEnv, config.UsedEnvFile(soongDocsTag))
 		}
@@ -480,6 +500,10 @@
 		targets = append(targets, config.QueryviewMarkerFile())
 	}
 
+	if config.ApiBp2build() {
+		targets = append(targets, config.ApiBp2buildMarkerFile())
+	}
+
 	if config.SoongDocs() {
 		targets = append(targets, config.SoongDocsHtml())
 	}