Handle installation rules for co-existing prebuilts

Every module belonging to a single mainline module family will be
hidden from make, except the one which has been flagged using
apex_contributions

Details
- Introduce a new `source_apex_name` property to prebuilt_apex and
  override_apex. This property will be used to identify the source
  equivalent of a prebuilt soong apex module.
- Create an N-ary tree from source to prebuilt(s). The tree wil be
  rooted at the source module.
- In a subsequent mutator, visit every node in the tree(s). Query
  apex_contributions and store the handle of the node which is "active"
  (if any)
- In the same mutator, do another pass over the tree. Invoke
  `HideFromMake` on every node which is not "active". The two-pass
  approach is needed PrebuiltSelectionInfoProvider does not know about
  the inter source-prebuilt dependency, this dependency can only be
  known by doing a graph walk of the N-ary tree.

Some tangential implementation details
- Each prebuilt apex has an internal deapxer module that is responsible
  for generating the deapex ninja rules. The name of this internal
  module uses the BaseModuleName (without the prebuilt_ prefix). Since
  we can have multiple prebuilt soong modules in trunk stable, change
  this to follow the name of the prebuilt module in order to avoid name
  collisions. Update existing unit tests accordingly

Bug: 316179314
Test: go test ./apex -run TestInstallationRulesForMultipleApexPrebuilts
Test: m nothing --no-skip-soong-tests
Test: presubmits

Change-Id: I58aa99d5e6a9859954614e6db9a8e9e2e581642d
diff --git a/android/apex_contributions.go b/android/apex_contributions.go
index a309640..236abf6 100644
--- a/android/apex_contributions.go
+++ b/android/apex_contributions.go
@@ -102,7 +102,6 @@
 				ctx.ModuleErrorf("%s listed in apex_contributions %s does not exist\n", content, m.Name())
 			}
 			pi := &PrebuiltSelectionInfo{
-				baseModuleName:     RemoveOptionalPrebuiltPrefix(content),
 				selectedModuleName: content,
 				metadataModuleName: m.Name(),
 				apiDomain:          m.ApiDomain(),
@@ -126,7 +125,8 @@
 // This provider will be used in prebuilt_select mutator to redirect deps
 var PrebuiltSelectionInfoProvider = blueprint.NewMutatorProvider[PrebuiltSelectionInfoMap]("prebuilt_select")
 
-// Map of baseModuleName to the selected source or prebuilt
+// Map of selected module names to a metadata object
+// The metadata contains information about the api_domain of the selected module
 type PrebuiltSelectionInfoMap map[string]PrebuiltSelectionInfo
 
 // Add a new entry to the map with some validations
@@ -134,18 +134,10 @@
 	if p == nil {
 		return
 	}
-	// Do not allow dups. If the base module (without the prebuilt_) has been added before, raise an exception.
-	if old, exists := (*pm)[p.baseModuleName]; exists {
-		ctx.ModuleErrorf("Cannot use Soong module: %s from apex_contributions: %s because it has been added previously as: %s from apex_contributions: %s\n",
-			p.selectedModuleName, p.metadataModuleName, old.selectedModuleName, old.metadataModuleName,
-		)
-	}
-	(*pm)[p.baseModuleName] = *p
+	(*pm)[p.selectedModuleName] = *p
 }
 
 type PrebuiltSelectionInfo struct {
-	// e.g. libc
-	baseModuleName string
 	// e.g. (libc|prebuilt_libc)
 	selectedModuleName string
 	// Name of the apex_contributions module
@@ -156,12 +148,9 @@
 
 // Returns true if `name` is explicitly requested using one of the selected
 // apex_contributions metadata modules.
-func (p *PrebuiltSelectionInfoMap) IsSelected(baseModuleName, name string) bool {
-	if i, exists := (*p)[baseModuleName]; exists {
-		return i.selectedModuleName == name
-	} else {
-		return false
-	}
+func (p *PrebuiltSelectionInfoMap) IsSelected(name string) bool {
+	_, exists := (*p)[name]
+	return exists
 }
 
 // Return the list of soong modules selected for this api domain
diff --git a/android/prebuilt.go b/android/prebuilt.go
index 6a417a8..d2b8fa1 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -391,12 +391,19 @@
 	ctx.BottomUp("prebuilt_postdeps", PrebuiltPostDepsMutator).Parallel()
 }
 
+// Returns the name of the source module corresponding to a prebuilt module
+// For source modules, it returns its own name
+type baseModuleName interface {
+	BaseModuleName() string
+}
+
 // PrebuiltRenameMutator ensures that there always is a module with an
 // undecorated name.
 func PrebuiltRenameMutator(ctx BottomUpMutatorContext) {
 	m := ctx.Module()
 	if p := GetEmbeddedPrebuilt(m); p != nil {
-		name := m.base().BaseModuleName()
+		bmn, _ := m.(baseModuleName)
+		name := bmn.BaseModuleName()
 		if !ctx.OtherModuleExists(name) {
 			ctx.Rename(name)
 			p.properties.PrebuiltRenamedToSource = true
@@ -413,7 +420,8 @@
 	// If this module is a prebuilt, is enabled and has not been renamed to source then add a
 	// dependency onto the source if it is present.
 	if p := GetEmbeddedPrebuilt(m); p != nil && m.Enabled() && !p.properties.PrebuiltRenamedToSource {
-		name := m.base().BaseModuleName()
+		bmn, _ := m.(baseModuleName)
+		name := bmn.BaseModuleName()
 		if ctx.OtherModuleReverseDependencyVariantExists(name) {
 			ctx.AddReverseDependency(ctx.Module(), PrebuiltDepTag, name)
 			p.properties.SourceExists = true
@@ -466,6 +474,13 @@
 		})
 
 	} else if s, ok := ctx.Module().(Module); ok {
+		// Use `all_apex_contributions` for source vs prebuilt selection.
+		psi := PrebuiltSelectionInfoMap{}
+		ctx.VisitDirectDepsWithTag(PrebuiltDepTag, func(am Module) {
+			// The value of psi gets overwritten with the provider from the last visited prebuilt.
+			// But all prebuilts have the same value of the provider, so this should be idempontent.
+			psi, _ = OtherModuleProvider(ctx, am, PrebuiltSelectionInfoProvider)
+		})
 		ctx.VisitDirectDepsWithTag(PrebuiltDepTag, func(prebuiltModule Module) {
 			p := GetEmbeddedPrebuilt(prebuiltModule)
 			if p.usePrebuilt(ctx, s, prebuiltModule) {
@@ -475,7 +490,18 @@
 				s.ReplacedByPrebuilt()
 			}
 		})
+
+		// If any module in this mainline module family has been flagged using apex_contributions, disable every other module in that family
+		// Add source
+		allModules := []Module{s}
+		// Add each prebuilt
+		ctx.VisitDirectDepsWithTag(PrebuiltDepTag, func(prebuiltModule Module) {
+			allModules = append(allModules, prebuiltModule)
+		})
+		hideUnflaggedModules(ctx, psi, allModules)
+
 	}
+
 	// If this is `all_apex_contributions`, set a provider containing
 	// metadata about source vs prebuilts selection
 	if am, ok := m.(*allApexContributions); ok {
@@ -483,13 +509,41 @@
 	}
 }
 
+// If any module in this mainline module family has been flagged using apex_contributions, disable every other module in that family
+func hideUnflaggedModules(ctx BottomUpMutatorContext, psi PrebuiltSelectionInfoMap, allModulesInFamily []Module) {
+	var selectedModuleInFamily Module
+	// query all_apex_contributions to see if any module in this family has been selected
+	for _, moduleInFamily := range allModulesInFamily {
+		// validate that are no duplicates
+		if psi.IsSelected(moduleInFamily.Name()) {
+			if selectedModuleInFamily == nil {
+				// Store this so we can validate that there are no duplicates
+				selectedModuleInFamily = moduleInFamily
+			} else {
+				// There are duplicate modules from the same mainline module family
+				ctx.ModuleErrorf("Found duplicate variations of the same module in apex_contributions: %s and %s. Please remove one of these.\n", selectedModuleInFamily.Name(), moduleInFamily.Name())
+			}
+		}
+	}
+
+	// If a module has been selected, hide all other modules
+	if selectedModuleInFamily != nil {
+		for _, moduleInFamily := range allModulesInFamily {
+			if moduleInFamily.Name() != selectedModuleInFamily.Name() {
+				moduleInFamily.HideFromMake()
+			}
+		}
+	}
+}
+
 // PrebuiltPostDepsMutator replaces dependencies on the source module with dependencies on the
 // prebuilt when both modules exist and the prebuilt should be used.  When the prebuilt should not
 // be used, disable installing it.
 func PrebuiltPostDepsMutator(ctx BottomUpMutatorContext) {
 	m := ctx.Module()
 	if p := GetEmbeddedPrebuilt(m); p != nil {
-		name := m.base().BaseModuleName()
+		bmn, _ := m.(baseModuleName)
+		name := bmn.BaseModuleName()
 		if p.properties.UsePrebuilt {
 			if p.properties.SourceExists {
 				ctx.ReplaceDependenciesIf(name, func(from blueprint.Module, tag blueprint.DependencyTag, to blueprint.Module) bool {
@@ -533,13 +587,13 @@
 
 		// Stub library created by java_sdk_library_import
 		if p := GetEmbeddedPrebuilt(m); p != nil {
-			return psi.IsSelected(sln, PrebuiltNameFromSource(sln))
+			return psi.IsSelected(PrebuiltNameFromSource(sln))
 		}
 
 		// Stub library created by java_sdk_library
-		return psi.IsSelected(sln, sln)
+		return psi.IsSelected(sln)
 	}
-	return psi.IsSelected(m.base().BaseModuleName(), m.Name())
+	return psi.IsSelected(m.Name())
 }
 
 // usePrebuilt returns true if a prebuilt should be used instead of the source module.  The prebuilt
diff --git a/android/prebuilt_test.go b/android/prebuilt_test.go
index 953258e..4a69628 100644
--- a/android/prebuilt_test.go
+++ b/android/prebuilt_test.go
@@ -741,7 +741,7 @@
 			}
 		}),
 	)
-	testPrebuiltErrorWithFixture(t, `Cannot use Soong module: prebuilt_foo from apex_contributions: my_apex_contributions because it has been added previously as: foo from apex_contributions: my_apex_contributions`, `
+	testPrebuiltErrorWithFixture(t, `Found duplicate variations of the same module in apex_contributions: foo and prebuilt_foo. Please remove one of these`, `
 		source {
 			name: "foo",
 		}