Merge "Add bazel-built modules as deps on the system image" into main
diff --git a/android/bazel.go b/android/bazel.go
index 202fc48..1602b9b 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -81,6 +81,11 @@
 	// If non-nil, indicates that the module could not be converted successfully
 	// with bp2build. This will describe the reason the module could not be converted.
 	UnconvertedReason *UnconvertedReason
+
+	// The Partition this module will be installed on.
+	// TODO(b/306200980) Investigate how to handle modules that are installed in multiple
+	// partitions.
+	Partition string `blueprint:"mutated"`
 }
 
 // The reason a module could not be converted to a BUILD target via bp2build.
@@ -674,6 +679,9 @@
 
 	bModule.ConvertWithBp2build(ctx)
 
+	installCtx := &baseModuleContextToModuleInstallPathContext{ctx}
+	ctx.Module().base().setPartitionForBp2build(modulePartition(installCtx, true))
+
 	if len(ctx.Module().base().Bp2buildTargets()) == 0 && ctx.Module().base().GetUnconvertedReason() == nil {
 		panic(fmt.Errorf("illegal bp2build invariant: module '%s' was neither converted nor marked unconvertible", ctx.ModuleName()))
 	}
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 51ce3c9..0c65415 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -245,6 +245,55 @@
 
 var _ BazelContext = noopBazelContext{}
 
+func (n noopBazelContext) QueueBazelRequest(_ string, _ cqueryRequest, _ configKey) {
+	panic("unimplemented")
+}
+
+func (n noopBazelContext) QueueBazelSandwichCqueryRequests(config Config) error {
+	panic("unimplemented")
+}
+
+func (n noopBazelContext) GetOutputFiles(_ string, _ configKey) ([]string, error) {
+	panic("unimplemented")
+}
+
+func (n noopBazelContext) GetCcInfo(_ string, _ configKey) (cquery.CcInfo, error) {
+	panic("unimplemented")
+}
+
+func (n noopBazelContext) GetApexInfo(_ string, _ configKey) (cquery.ApexInfo, error) {
+	panic("unimplemented")
+}
+
+func (n noopBazelContext) GetCcUnstrippedInfo(_ string, _ configKey) (cquery.CcUnstrippedInfo, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (n noopBazelContext) GetPrebuiltFileInfo(_ string, _ configKey) (cquery.PrebuiltFileInfo, error) {
+	panic("implement me")
+}
+
+func (n noopBazelContext) InvokeBazel(_ Config, _ invokeBazelContext) error {
+	panic("unimplemented")
+}
+
+func (m noopBazelContext) OutputBase() string {
+	return ""
+}
+
+func (n noopBazelContext) IsModuleNameAllowed(_ string, _ bool) bool {
+	return false
+}
+
+func (m noopBazelContext) BuildStatementsToRegister() []*bazel.BuildStatement {
+	return []*bazel.BuildStatement{}
+}
+
+func (m noopBazelContext) AqueryDepsets() []bazel.AqueryDepset {
+	return []bazel.AqueryDepset{}
+}
+
 // A bazel context to use for tests.
 type MockBazelContext struct {
 	OutputBaseDir string
@@ -427,55 +476,6 @@
 	return cquery.PrebuiltFileInfo{}, fmt.Errorf("no bazel response for %s", key)
 }
 
-func (n noopBazelContext) QueueBazelRequest(_ string, _ cqueryRequest, _ configKey) {
-	panic("unimplemented")
-}
-
-func (n noopBazelContext) QueueBazelSandwichCqueryRequests(config Config) error {
-	panic("unimplemented")
-}
-
-func (n noopBazelContext) GetOutputFiles(_ string, _ configKey) ([]string, error) {
-	panic("unimplemented")
-}
-
-func (n noopBazelContext) GetCcInfo(_ string, _ configKey) (cquery.CcInfo, error) {
-	panic("unimplemented")
-}
-
-func (n noopBazelContext) GetApexInfo(_ string, _ configKey) (cquery.ApexInfo, error) {
-	panic("unimplemented")
-}
-
-func (n noopBazelContext) GetCcUnstrippedInfo(_ string, _ configKey) (cquery.CcUnstrippedInfo, error) {
-	//TODO implement me
-	panic("implement me")
-}
-
-func (n noopBazelContext) GetPrebuiltFileInfo(_ string, _ configKey) (cquery.PrebuiltFileInfo, error) {
-	panic("implement me")
-}
-
-func (n noopBazelContext) InvokeBazel(_ Config, _ invokeBazelContext) error {
-	panic("unimplemented")
-}
-
-func (m noopBazelContext) OutputBase() string {
-	return ""
-}
-
-func (n noopBazelContext) IsModuleNameAllowed(_ string, _ bool) bool {
-	return false
-}
-
-func (m noopBazelContext) BuildStatementsToRegister() []*bazel.BuildStatement {
-	return []*bazel.BuildStatement{}
-}
-
-func (m noopBazelContext) AqueryDepsets() []bazel.AqueryDepset {
-	return []bazel.AqueryDepset{}
-}
-
 func AddToStringSet(set map[string]bool, items []string) {
 	for _, item := range items {
 		set[item] = true
diff --git a/android/config.go b/android/config.go
index 66c0e6d..362501d 100644
--- a/android/config.go
+++ b/android/config.go
@@ -900,6 +900,10 @@
 	return c.katiEnabled
 }
 
+func (c *config) ProductVariables() ProductVariables {
+	return c.productVariables
+}
+
 func (c *config) BuildId() string {
 	return String(c.productVariables.BuildId)
 }
diff --git a/android/module.go b/android/module.go
index b73f449..250161f 100644
--- a/android/module.go
+++ b/android/module.go
@@ -572,6 +572,7 @@
 	Bp2buildTargets() []bp2buildInfo
 	GetUnconvertedBp2buildDeps() []string
 	GetMissingBp2buildDeps() []string
+	GetPartitionForBp2build() string
 
 	BuildParamsForTests() []BuildParams
 	RuleParamsForTests() map[blueprint.Rule]blueprint.RuleParams
@@ -1651,6 +1652,10 @@
 	m.commonProperties.BazelConversionStatus.Bp2buildInfo = append(m.commonProperties.BazelConversionStatus.Bp2buildInfo, info)
 }
 
+func (m *ModuleBase) setPartitionForBp2build(partition string) {
+	m.commonProperties.BazelConversionStatus.Partition = partition
+}
+
 func (m *ModuleBase) setBp2buildUnconvertible(reasonType bp2build_metrics_proto.UnconvertedReasonType, detail string) {
 	m.commonProperties.BazelConversionStatus.UnconvertedReason = &UnconvertedReason{
 		ReasonType: int(reasonType),
@@ -1667,6 +1672,11 @@
 	return m.commonProperties.BazelConversionStatus.Bp2buildInfo
 }
 
+// Bp2buildTargets returns the Bazel targets bp2build generated for this module.
+func (m *ModuleBase) GetPartitionForBp2build() string {
+	return m.commonProperties.BazelConversionStatus.Partition
+}
+
 // AddUnconvertedBp2buildDep stores module name of a dependency that was not converted to Bazel.
 func (b *baseModuleContext) AddUnconvertedBp2buildDep(dep string) {
 	unconvertedDeps := &b.Module().base().commonProperties.BazelConversionStatus.UnconvertedDeps
diff --git a/android/paths.go b/android/paths.go
index 7658299..8dd1966 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -116,6 +116,48 @@
 
 var _ ModuleInstallPathContext = ModuleContext(nil)
 
+type baseModuleContextToModuleInstallPathContext struct {
+	BaseModuleContext
+}
+
+func (ctx *baseModuleContextToModuleInstallPathContext) InstallInData() bool {
+	return ctx.Module().InstallInData()
+}
+
+func (ctx *baseModuleContextToModuleInstallPathContext) InstallInTestcases() bool {
+	return ctx.Module().InstallInTestcases()
+}
+
+func (ctx *baseModuleContextToModuleInstallPathContext) InstallInSanitizerDir() bool {
+	return ctx.Module().InstallInSanitizerDir()
+}
+
+func (ctx *baseModuleContextToModuleInstallPathContext) InstallInRamdisk() bool {
+	return ctx.Module().InstallInRamdisk()
+}
+
+func (ctx *baseModuleContextToModuleInstallPathContext) InstallInVendorRamdisk() bool {
+	return ctx.Module().InstallInVendorRamdisk()
+}
+
+func (ctx *baseModuleContextToModuleInstallPathContext) InstallInDebugRamdisk() bool {
+	return ctx.Module().InstallInDebugRamdisk()
+}
+
+func (ctx *baseModuleContextToModuleInstallPathContext) InstallInRecovery() bool {
+	return ctx.Module().InstallInRecovery()
+}
+
+func (ctx *baseModuleContextToModuleInstallPathContext) InstallInRoot() bool {
+	return ctx.Module().InstallInRoot()
+}
+
+func (ctx *baseModuleContextToModuleInstallPathContext) InstallForceOS() (*OsType, *ArchType) {
+	return ctx.Module().InstallForceOS()
+}
+
+var _ ModuleInstallPathContext = (*baseModuleContextToModuleInstallPathContext)(nil)
+
 // errorfContext is the interface containing the Errorf method matching the
 // Errorf method in blueprint.SingletonContext.
 type errorfContext interface {
@@ -1683,7 +1725,7 @@
 // module appended with paths...
 func PathForModuleInstall(ctx ModuleInstallPathContext, pathComponents ...string) InstallPath {
 	os, arch := osAndArch(ctx)
-	partition := modulePartition(ctx, os)
+	partition := modulePartition(ctx, os.Class == Device)
 	return pathForInstall(ctx, os, arch, partition, pathComponents...)
 }
 
@@ -1785,12 +1827,12 @@
 	return "/" + rel
 }
 
-func modulePartition(ctx ModuleInstallPathContext, os OsType) string {
+func modulePartition(ctx ModuleInstallPathContext, device bool) string {
 	var partition string
 	if ctx.InstallInTestcases() {
 		// "testcases" install directory can be used for host or device modules.
 		partition = "testcases"
-	} else if os.Class == Device {
+	} else if device {
 		if ctx.InstallInData() {
 			partition = "data"
 		} else if ctx.InstallInRamdisk() {
diff --git a/android/variable.go b/android/variable.go
index ab6dfef..9896f20 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -494,38 +494,40 @@
 	Release_expose_flagged_api *bool `json:",omitempty"`
 }
 
+type PartitionQualifiedVariablesType struct {
+	BuildingImage               bool   `json:",omitempty"`
+	BoardErofsCompressor        string `json:",omitempty"`
+	BoardErofsCompressHints     string `json:",omitempty"`
+	BoardErofsPclusterSize      string `json:",omitempty"`
+	BoardExtfsInodeCount        string `json:",omitempty"`
+	BoardExtfsRsvPct            string `json:",omitempty"`
+	BoardF2fsSloadCompressFlags string `json:",omitempty"`
+	BoardFileSystemCompress     string `json:",omitempty"`
+	BoardFileSystemType         string `json:",omitempty"`
+	BoardJournalSize            string `json:",omitempty"`
+	BoardPartitionReservedSize  string `json:",omitempty"`
+	BoardPartitionSize          string `json:",omitempty"`
+	BoardSquashfsBlockSize      string `json:",omitempty"`
+	BoardSquashfsCompressor     string `json:",omitempty"`
+	BoardSquashfsCompressorOpt  string `json:",omitempty"`
+	BoardSquashfsDisable4kAlign string `json:",omitempty"`
+	ProductBaseFsPath           string `json:",omitempty"`
+	ProductHeadroom             string `json:",omitempty"`
+	ProductVerityPartition      string `json:",omitempty"`
+
+	BoardAvbAddHashtreeFooterArgs string `json:",omitempty"`
+	BoardAvbKeyPath               string `json:",omitempty"`
+	BoardAvbAlgorithm             string `json:",omitempty"`
+	BoardAvbRollbackIndex         string `json:",omitempty"`
+	BoardAvbRollbackIndexLocation string `json:",omitempty"`
+}
+
 type PartitionVariables struct {
 	ProductDirectory            string `json:",omitempty"`
-	PartitionQualifiedVariables map[string]struct {
-		BuildingImage               bool   `json:",omitempty"`
-		BoardErofsCompressor        string `json:",omitempty"`
-		BoardErofsCompressHints     string `json:",omitempty"`
-		BoardErofsPclusterSize      string `json:",omitempty"`
-		BoardExtfsInodeCount        string `json:",omitempty"`
-		BoardExtfsRsvPct            string `json:",omitempty"`
-		BoardF2fsSloadCompressFlags string `json:",omitempty"`
-		BoardFileSystemCompress     string `json:",omitempty"`
-		BoardFileSystemType         string `json:",omitempty"`
-		BoardJournalSize            string `json:",omitempty"`
-		BoardPartitionReservedSize  string `json:",omitempty"`
-		BoardPartitionSize          string `json:",omitempty"`
-		BoardSquashfsBlockSize      string `json:",omitempty"`
-		BoardSquashfsCompressor     string `json:",omitempty"`
-		BoardSquashfsCompressorOpt  string `json:",omitempty"`
-		BoardSquashfsDisable4kAlign string `json:",omitempty"`
-		ProductBaseFsPath           string `json:",omitempty"`
-		ProductHeadroom             string `json:",omitempty"`
-		ProductVerityPartition      string `json:",omitempty"`
-
-		BoardAvbAddHashtreeFooterArgs string `json:",omitempty"`
-		BoardAvbKeyPath               string `json:",omitempty"`
-		BoardAvbAlgorithm             string `json:",omitempty"`
-		BoardAvbRollbackIndex         string `json:",omitempty"`
-		BoardAvbRollbackIndexLocation string `json:",omitempty"`
-	}
-	TargetUserimagesUseExt2 bool `json:",omitempty"`
-	TargetUserimagesUseExt3 bool `json:",omitempty"`
-	TargetUserimagesUseExt4 bool `json:",omitempty"`
+	PartitionQualifiedVariables map[string]PartitionQualifiedVariablesType
+	TargetUserimagesUseExt2     bool `json:",omitempty"`
+	TargetUserimagesUseExt3     bool `json:",omitempty"`
+	TargetUserimagesUseExt4     bool `json:",omitempty"`
 
 	TargetUserimagesSparseExtDisabled      bool `json:",omitempty"`
 	TargetUserimagesSparseErofsDisabled    bool `json:",omitempty"`
@@ -546,6 +548,8 @@
 	CopyImagesForTargetFilesZip          bool   `json:",omitempty"`
 
 	BoardAvbEnable bool `json:",omitempty"`
+
+	ProductPackages []string `json:",omitempty"`
 }
 
 func boolPtr(v bool) *bool {
diff --git a/bp2build/bp2build.go b/bp2build/bp2build.go
index 6c9d903..1496ca7 100644
--- a/bp2build/bp2build.go
+++ b/bp2build/bp2build.go
@@ -82,7 +82,7 @@
 		os.Exit(1)
 	}
 	var bp2buildFiles []BazelFile
-	productConfig, err := createProductConfigFiles(ctx, res.metrics)
+	productConfig, err := createProductConfigFiles(ctx, res.moduleNameToPartition, res.metrics.convertedModulePathMap)
 	ctx.Context().EventHandler.Do("CreateBazelFile", func() {
 		allTargets := make(map[string]BazelTargets)
 		for k, v := range res.buildFileToTargets {
diff --git a/bp2build/bp2build_product_config.go b/bp2build/bp2build_product_config.go
index 2adcccc..7f26bef 100644
--- a/bp2build/bp2build_product_config.go
+++ b/bp2build/bp2build_product_config.go
@@ -3,7 +3,6 @@
 import (
 	"encoding/json"
 	"fmt"
-	"os"
 	"path/filepath"
 	"reflect"
 	"sort"
@@ -53,7 +52,8 @@
 
 func createProductConfigFiles(
 	ctx *CodegenContext,
-	metrics CodegenMetrics) (createProductConfigFilesResult, error) {
+	moduleNameToPartition map[string]string,
+	convertedModulePathMap map[string]string) (createProductConfigFilesResult, error) {
 	cfg := &ctx.config
 	targetProduct := "unknown"
 	if cfg.HasDeviceProduct() {
@@ -68,16 +68,11 @@
 
 	var res createProductConfigFilesResult
 
-	productVariablesFileName := cfg.ProductVariablesFileName
-	if !strings.HasPrefix(productVariablesFileName, "/") {
-		productVariablesFileName = filepath.Join(ctx.topDir, productVariablesFileName)
-	}
-	productVariablesBytes, err := os.ReadFile(productVariablesFileName)
-	if err != nil {
-		return res, err
-	}
-	productVariables := android.ProductVariables{}
-	err = json.Unmarshal(productVariablesBytes, &productVariables)
+	productVariables := ctx.Config().ProductVariables()
+	// TODO(b/306243251): For some reason, using the real value of native_coverage makes some select
+	// statements ambiguous
+	productVariables.Native_coverage = nil
+	productVariablesBytes, err := json.Marshal(productVariables)
 	if err != nil {
 		return res, err
 	}
@@ -142,12 +137,12 @@
 			},
 		},
 	})
-	createTargets(productLabelsToVariables, res.bp2buildTargets)
+	createTargets(ctx, productLabelsToVariables, moduleNameToPartition, convertedModulePathMap, res.bp2buildTargets)
 
 	platformMappingContent, err := platformMappingContent(
 		productLabelsToVariables,
 		ctx.Config().Bp2buildSoongConfigDefinitions,
-		metrics.convertedModulePathMap)
+		convertedModulePathMap)
 	if err != nil {
 		return res, err
 	}
@@ -481,12 +476,17 @@
 	return result, nil
 }
 
-func createTargets(productLabelsToVariables map[bazelLabel]*android.ProductVariables, res map[string]BazelTargets) {
+func createTargets(
+	ctx *CodegenContext,
+	productLabelsToVariables map[bazelLabel]*android.ProductVariables,
+	moduleNameToPartition map[string]string,
+	convertedModulePathMap map[string]string,
+	res map[string]BazelTargets) {
 	createGeneratedAndroidCertificateDirectories(productLabelsToVariables, res)
 	createAvbKeyFilegroups(productLabelsToVariables, res)
 	createReleaseAconfigValueSetsFilegroup(productLabelsToVariables, res)
 	for label, variables := range productLabelsToVariables {
-		createSystemPartition(label, &variables.PartitionVarsForBazelMigrationOnlyDoNotUse, res)
+		createSystemPartition(ctx, label, &variables.PartitionVarsForBazelMigrationOnlyDoNotUse, moduleNameToPartition, convertedModulePathMap, res)
 	}
 }
 
@@ -581,7 +581,13 @@
 	}
 }
 
-func createSystemPartition(platformLabel bazelLabel, variables *android.PartitionVariables, targets map[string]BazelTargets) {
+func createSystemPartition(
+	ctx *CodegenContext,
+	platformLabel bazelLabel,
+	variables *android.PartitionVariables,
+	moduleNameToPartition map[string]string,
+	convertedModulePathMap map[string]string,
+	targets map[string]BazelTargets) {
 	if !variables.PartitionQualifiedVariables["system"].BuildingImage {
 		return
 	}
@@ -611,6 +617,26 @@
 		}
 	}
 
+	var deps []string
+	for _, mod := range variables.ProductPackages {
+		if path, ok := convertedModulePathMap[mod]; ok && ctx.Config().BazelContext.IsModuleNameAllowed(mod, false) {
+			if partition, ok := moduleNameToPartition[mod]; ok && partition == "system" {
+				if path == "//." {
+					path = "//"
+				}
+				deps = append(deps, fmt.Sprintf("        \"%s:%s\",\n", path, mod))
+			}
+		}
+	}
+	if len(deps) > 0 {
+		sort.Strings(deps)
+		extraProperties.WriteString("    deps = [\n")
+		for _, dep := range deps {
+			extraProperties.WriteString(dep)
+		}
+		extraProperties.WriteString("    ],\n")
+	}
+
 	targets[platformLabel.pkg] = append(targets[platformLabel.pkg], BazelTarget{
 		name:        "system_image",
 		packageName: platformLabel.pkg,
diff --git a/bp2build/bp2build_product_config_test.go b/bp2build/bp2build_product_config_test.go
index 02d83b4..02a2b51 100644
--- a/bp2build/bp2build_product_config_test.go
+++ b/bp2build/bp2build_product_config_test.go
@@ -2,6 +2,7 @@
 
 import (
 	"android/soong/android"
+	"android/soong/cc"
 	"android/soong/starlark_import"
 	"encoding/json"
 	"reflect"
@@ -87,3 +88,67 @@
 		}
 	}
 }
+
+func TestSystemPartitionDeps(t *testing.T) {
+	RunBp2BuildTestCase(t, func(ctx android.RegistrationContext) {
+		ctx.RegisterModuleType("cc_library", cc.LibraryFactory)
+	}, Bp2buildTestCase{
+		ExtraFixturePreparer: android.GroupFixturePreparers(
+			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				deviceProduct := "aosp_arm64"
+				variables.DeviceProduct = &deviceProduct
+				partitionVars := &variables.PartitionVarsForBazelMigrationOnlyDoNotUse
+				partitionVars.ProductDirectory = "build/make/target/product/"
+				partitionVars.ProductPackages = []string{"foo"}
+				var systemVars android.PartitionQualifiedVariablesType
+				systemVars.BuildingImage = true
+				partitionVars.PartitionQualifiedVariables = map[string]android.PartitionQualifiedVariablesType{
+					"system": systemVars,
+				}
+			}),
+			android.FixtureModifyConfig(func(config android.Config) {
+				// MockBazelContext will pretend everything is mixed-builds allowlisted.
+				// The default is noopBazelContext, which does the opposite.
+				config.BazelContext = android.MockBazelContext{}
+			}),
+		),
+		Blueprint: `
+cc_library {
+  name: "foo",
+}`,
+		ExpectedBazelTargets: []string{`android_product(
+    name = "aosp_arm64",
+    soong_variables = _soong_variables,
+)`, `partition(
+    name = "system_image",
+    base_staging_dir = "//build/bazel/bazel_sandwich:system_staging_dir",
+    base_staging_dir_file_list = "//build/bazel/bazel_sandwich:system_staging_dir_file_list",
+    root_dir = "//build/bazel/bazel_sandwich:root_staging_dir",
+    selinux_file_contexts = "//build/bazel/bazel_sandwich:selinux_file_contexts",
+    image_properties = """
+building_system_image=true
+erofs_sparse_flag=-s
+extfs_sparse_flag=-s
+f2fs_sparse_flag=-S
+skip_fsck=true
+squashfs_sparse_flag=-s
+system_disable_sparse=true
+
+""",
+    deps = [
+        "//:foo",
+    ],
+
+    type = "system",
+)`, `partition_diff_test(
+    name = "system_image_test",
+    partition1 = "//build/bazel/bazel_sandwich:make_system_image",
+    partition2 = ":system_image",
+)`, `run_test_in_build(
+    name = "run_system_image_test",
+    test = ":system_image_test",
+)`},
+		Dir:                      "build/make/target/product/aosp_arm64",
+		RunBp2buildProductConfig: true,
+	})
+}
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index ed6e2dd..d2187ff 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -279,8 +279,9 @@
 }
 
 type conversionResults struct {
-	buildFileToTargets map[string]BazelTargets
-	metrics            CodegenMetrics
+	buildFileToTargets    map[string]BazelTargets
+	moduleNameToPartition map[string]string
+	metrics               CodegenMetrics
 }
 
 func (r conversionResults) BuildDirToTargets() map[string]BazelTargets {
@@ -707,6 +708,7 @@
 	metrics := CreateCodegenMetrics()
 
 	dirs := make(map[string]bool)
+	moduleNameToPartition := make(map[string]string)
 
 	var errs []error
 
@@ -754,6 +756,9 @@
 					metrics.IncrementRuleClassCount(t.ruleClass)
 				}
 
+				// record the partition
+				moduleNameToPartition[android.RemoveOptionalPrebuiltPrefix(aModule.Name())] = aModule.GetPartitionForBp2build()
+
 				// Log the module.
 				metrics.AddConvertedModule(aModule, moduleType, dir)
 
@@ -876,8 +881,9 @@
 	}
 
 	return conversionResults{
-		buildFileToTargets: buildFileToTargets,
-		metrics:            metrics,
+		buildFileToTargets:    buildFileToTargets,
+		moduleNameToPartition: moduleNameToPartition,
+		metrics:               metrics,
 	}, errs
 }
 
diff --git a/bp2build/testing.go b/bp2build/testing.go
index b2804f9..c978164 100644
--- a/bp2build/testing.go
+++ b/bp2build/testing.go
@@ -28,6 +28,7 @@
 	"testing"
 
 	"android/soong/ui/metrics/bp2build_metrics_proto"
+
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -124,26 +125,28 @@
 	// be merged with the generated BUILD file. This allows custom BUILD targets
 	// to be used in tests, or use BUILD files to draw package boundaries.
 	KeepBuildFileForDirs []string
-}
 
-func RunBp2BuildTestCaseExtraContext(t *testing.T, registerModuleTypes func(ctx android.RegistrationContext), modifyContext func(ctx *android.TestContext), tc Bp2buildTestCase) {
-	t.Helper()
-	preparers := []android.FixturePreparer{
-		android.FixtureRegisterWithContext(registerModuleTypes),
-	}
-	if modifyContext != nil {
-		preparers = append(preparers, android.FixtureModifyContext(modifyContext))
-	}
-	preparers = append(preparers, SetBp2BuildTestRunner)
-	bp2buildSetup := android.GroupFixturePreparers(
-		preparers...,
-	)
-	runBp2BuildTestCaseWithSetup(t, bp2buildSetup, tc)
+	// An extra FixturePreparer to use when running the test. If you need multiple extra
+	// FixturePreparers, use android.GroupFixturePreparers()
+	ExtraFixturePreparer android.FixturePreparer
+
+	// If bp2build_product_config.go should run as part of the test.
+	RunBp2buildProductConfig bool
 }
 
 func RunBp2BuildTestCase(t *testing.T, registerModuleTypes func(ctx android.RegistrationContext), tc Bp2buildTestCase) {
 	t.Helper()
-	RunBp2BuildTestCaseExtraContext(t, registerModuleTypes, nil, tc)
+	preparers := []android.FixturePreparer{
+		android.FixtureRegisterWithContext(registerModuleTypes),
+	}
+	if tc.ExtraFixturePreparer != nil {
+		preparers = append(preparers, tc.ExtraFixturePreparer)
+	}
+	preparers = append(preparers, android.FixtureSetTestRunner(&bazelTestRunner{generateProductConfigTargets: tc.RunBp2buildProductConfig}))
+	bp2buildSetup := android.GroupFixturePreparers(
+		preparers...,
+	)
+	runBp2BuildTestCaseWithSetup(t, bp2buildSetup, tc)
 }
 
 func runBp2BuildTestCaseWithSetup(t *testing.T, extraPreparer android.FixturePreparer, tc Bp2buildTestCase) {
@@ -247,11 +250,10 @@
 	result.CompareAllBazelTargets(t, tc, expectedTargets, true)
 }
 
-// SetBp2BuildTestRunner customizes the test fixture mechanism to run tests in Bp2Build mode.
-var SetBp2BuildTestRunner = android.FixtureSetTestRunner(&bazelTestRunner{})
-
 // bazelTestRunner customizes the test fixture mechanism to run tests of the bp2build build mode.
-type bazelTestRunner struct{}
+type bazelTestRunner struct {
+	generateProductConfigTargets bool
+}
 
 func (b *bazelTestRunner) FinalPreparer(result *android.TestResult) android.CustomTestResult {
 	ctx := result.TestContext
@@ -274,6 +276,16 @@
 	if bazelResult.CollateErrs(errs) {
 		return
 	}
+	if b.generateProductConfigTargets {
+		productConfig, err := createProductConfigFiles(codegenCtx, res.moduleNameToPartition, res.metrics.convertedModulePathMap)
+		if err != nil {
+			bazelResult.CollateErrs([]error{err})
+			return
+		}
+		for k, v := range productConfig.bp2buildTargets {
+			res.buildFileToTargets[k] = append(res.buildFileToTargets[k], v...)
+		}
+	}
 
 	// Store additional data for access by tests.
 	bazelResult.conversionResults = res