Allow contents and image_name to be specified together

Previously, only one of the contents or image_name properties could be
specified at once which meant that there was no way to create a
prebuilt which lists its fixed contents while at the same time allowing
it to check that that the contents matched what the build configuration
required.

e.g. a prebuilt_bootclasspath_fragment that had image_name: "art",
could not list its contents and also check that those contents matched
the ART_APEX_JARS which the build configuration required.

This change allows contents and image_name to be specified together and
adds a check to make sure that the contents are consistent with the
configuration appropriate to the image_name. The check is only
performed for modules that are active so that a
prebuilt_bootclasspath_fragment which was created without coverage
enabled (the default) would not cause a build failure in a coverage
build unless it was preferred.

Bug: 177892522
Test: m nothing
Change-Id: Ie601f29f707b3f6030fa7d252afa2c4826cc9f8e
diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go
index 8bb5cb1..d0570b4 100644
--- a/java/bootclasspath_fragment.go
+++ b/java/bootclasspath_fragment.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"path/filepath"
+	"reflect"
 	"strings"
 
 	"android/soong/android"
@@ -128,8 +129,10 @@
 	if m.properties.Image_name == nil && len(contents) == 0 {
 		ctx.ModuleErrorf(`neither of the "image_name" and "contents" properties have been supplied, please supply exactly one`)
 	}
-	if m.properties.Image_name != nil && len(contents) != 0 {
-		ctx.ModuleErrorf(`both of the "image_name" and "contents" properties have been supplied, please supply exactly one`)
+
+	if len(contents) != 0 {
+		// Nothing to do.
+		return
 	}
 
 	imageName := proptools.String(m.properties.Image_name)
@@ -154,7 +157,6 @@
 
 		// Make sure that the apex specified in the configuration is consistent and is one for which
 		// this boot image is available.
-		jars := []string{}
 		commonApex := ""
 		for i := 0; i < modules.Len(); i++ {
 			apex := modules.Apex(i)
@@ -174,11 +176,50 @@
 				ctx.ModuleErrorf("ArtApexJars configuration is inconsistent, expected all jars to be in the same apex but it specifies apex %q and %q",
 					commonApex, apex)
 			}
-			jars = append(jars, jar)
 		}
 
 		// Store the jars in the Contents property so that they can be used to add dependencies.
-		m.properties.Contents = jars
+		m.properties.Contents = modules.CopyOfJars()
+	}
+}
+
+// bootclasspathImageNameContentsConsistencyCheck checks that the configuration that applies to this
+// module (if any) matches the contents.
+//
+// This should be a noop as if image_name="art" then the contents will be set from the ArtApexJars
+// config by bootclasspathFragmentInitContentsFromImage so it will be guaranteed to match. However,
+// in future this will not be the case.
+func (b *BootclasspathFragmentModule) bootclasspathImageNameContentsConsistencyCheck(ctx android.BaseModuleContext) {
+	imageName := proptools.String(b.properties.Image_name)
+	if imageName == "art" {
+		// TODO(b/177892522): Prebuilts (versioned or not) should not use the image_name property.
+		if b.MemberName() != "" {
+			// The module is a versioned prebuilt so ignore it. This is done for a couple of reasons:
+			// 1. There is no way to use this at the moment so ignoring it is safe.
+			// 2. Attempting to initialize the contents property from the configuration will end up having
+			//    the versioned prebuilt depending on the unversioned prebuilt. That will cause problems
+			//    as the unversioned prebuilt could end up with an APEX variant created for the source
+			//    APEX which will prevent it from having an APEX variant for the prebuilt APEX which in
+			//    turn will prevent it from accessing the dex implementation jar from that which will
+			//    break hidden API processing, amongst others.
+			return
+		}
+
+		// Get the configuration for the art apex jars.
+		modules := b.getImageConfig(ctx).modules
+		configuredJars := modules.CopyOfJars()
+
+		// Skip the check if the configured jars list is empty as that is a common configuration when
+		// building targets that do not result in a system image.
+		if len(configuredJars) == 0 {
+			return
+		}
+
+		contents := b.properties.Contents
+		if !reflect.DeepEqual(configuredJars, contents) {
+			ctx.ModuleErrorf("inconsistency in specification of contents. ArtApexJars configuration specifies %#v, contents property specifies %#v",
+				configuredJars, contents)
+		}
 	}
 }
 
@@ -274,6 +315,13 @@
 }
 
 func (b *BootclasspathFragmentModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// Only perform a consistency check if this module is the active module. That will prevent an
+	// unused prebuilt that was created without instrumentation from breaking an instrumentation
+	// build.
+	if isActiveModule(ctx.Module()) {
+		b.bootclasspathImageNameContentsConsistencyCheck(ctx)
+	}
+
 	// Perform hidden API processing.
 	b.generateHiddenAPIBuildActions(ctx)