Move part of logic from construct_context.py to Soong.

construct_context.py is a script that runs at Ninja stage and constructs
class loader context arguments for dex2oat. Previously it accepted lists
of library paths and concatenated them into one class loader context
string. The script also handled the dependency of "android.hidl.manager"
on "android.hidl.base" that is not tracked by the build system and must
be handled in a special way.

Now that class loader context representation is going to change from
flat lists to trees, passing paths to individual libraries as script
arguments is no longer possible, because a list of paths cannot
represent a class loader context tree. Passing the trees in a serialized
form is also inconvenient, because the script would have to parse them,
which would complicate it a lot.

Therefore this patch ports all the concatenation and "android.hidl.base"
handling to Soong. It is not possible to port the remaining script to
Soong because Soong has no information about the targetSdkVersiion of
the dexpreopted apps (it is in the manifest and sometimes in an APK, and
has to be extracted at Ninja time).

Test: construct_context_test.py
Test: new subtests in TestUsesLibs
Test: lunch aosp_cf_x86_phone-userdebug && m
Bug: 132357300
Change-Id: Icdb03cf00d1e27e4cff3844b89bfaec4de502dd7
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index 3605d9d..66e765f 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -328,9 +328,42 @@
 	return &classLoaderContexts
 }
 
+// Find build and install paths to "android.hidl.base". The library must be present in conditional
+// class loader context for SDK version 29, because it's one of the compatibility libraries.
+func findHidlBasePaths(ctx android.PathContext, clcMap classLoaderContextMap) (android.Path, string) {
+	var hostPath android.Path
+	targetPath := UnknownInstallLibraryPath
+
+	if clc, ok := clcMap[29]; ok {
+		for i, lib := range clc.Names {
+			if lib == AndroidHidlBase {
+				hostPath = clc.Host[i]
+				targetPath = clc.Target[i]
+				break
+			}
+		}
+	}
+
+	// Fail if the library paths were not found. This may happen if the function is called at the
+	// wrong time (either before the compatibility libraries were added to context, or after they
+	// have been removed for some reason).
+	if hostPath == nil {
+		android.ReportPathErrorf(ctx, "dexpreopt cannot find build path to '%s'", AndroidHidlBase)
+	} else if targetPath == UnknownInstallLibraryPath {
+		android.ReportPathErrorf(ctx, "dexpreopt cannot find install path to '%s'", AndroidHidlBase)
+	}
+
+	return hostPath, targetPath
+}
+
 // Now that the full unconditional context is known, reconstruct conditional context.
 // Apply filters for individual libraries, mirroring what the PackageManager does when it
 // constructs class loader context on device.
+//
+// TODO(b/132357300):
+//   - move handling of android.hidl.manager -> android.hidl.base dependency here
+//   - remove android.hidl.manager and android.hidl.base unless the app is a system app.
+//
 func fixConditionalClassLoaderContext(clcMap classLoaderContextMap) {
 	usesLibs := clcMap.usesLibs()
 
@@ -352,6 +385,52 @@
 	}
 }
 
+// Return the class loader context as a string and a slice of build paths for all dependencies.
+func computeClassLoaderContext(ctx android.PathContext, clcMap classLoaderContextMap) (clcStr string, paths android.Paths) {
+	hidlBaseHostPath, hidlBaseTargetPath := findHidlBasePaths(ctx, clcMap)
+
+	for _, ver := range android.SortedIntKeys(clcMap) {
+		clc := clcMap.getValue(ver)
+
+		clcLen := len(clc.Names)
+		if clcLen != len(clc.Host) || clcLen != len(clc.Target) {
+			android.ReportPathErrorf(ctx, "ill-formed class loader context")
+		}
+
+		var hostClc, targetClc []string
+		var hostPaths android.Paths
+
+		for i := 0; i < clcLen; i++ {
+			hostStr := "PCL[" + clc.Host[i].String() + "]"
+			targetStr := "PCL[" + clc.Target[i] + "]"
+
+			// Add dependency of android.hidl.manager on android.hidl.base (it is not tracked as
+			// a regular dependency by the build system, so it needs special handling).
+			if clc.Names[i] == AndroidHidlManager {
+				hostStr += "{PCL[" + hidlBaseHostPath.String() + "]}"
+				targetStr += "{PCL[" + hidlBaseTargetPath + "]}"
+				hostPaths = append(hostPaths, hidlBaseHostPath)
+			}
+
+			hostClc = append(hostClc, hostStr)
+			targetClc = append(targetClc, targetStr)
+			hostPaths = append(hostPaths, clc.Host[i])
+		}
+
+		if hostPaths != nil {
+			sdkVerStr := fmt.Sprintf("%d", ver)
+			if ver == AnySdkVersion {
+				sdkVerStr = "any" // a special keyword that means any SDK version
+			}
+			clcStr += fmt.Sprintf(" --host-context-for-sdk %s %s", sdkVerStr, strings.Join(hostClc, "#"))
+			clcStr += fmt.Sprintf(" --target-context-for-sdk %s %s", sdkVerStr, strings.Join(targetClc, "#"))
+			paths = append(paths, hostPaths...)
+		}
+	}
+
+	return clcStr, paths
+}
+
 func dexpreoptCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, global *GlobalConfig,
 	module *ModuleConfig, rule *android.RuleBuilder, archIdx int, classLoaderContexts classLoaderContextMap,
 	profile android.WritablePath, appImage bool, generateDM bool) {
@@ -422,20 +501,11 @@
 		}
 
 		// Generate command that saves host and target class loader context in shell variables.
+		clc, paths := computeClassLoaderContext(ctx, classLoaderContexts)
 		cmd := rule.Command().
 			Text(`eval "$(`).Tool(globalSoong.ConstructContext).
-			Text(` --target-sdk-version ${target_sdk_version}`)
-		for _, ver := range android.SortedIntKeys(classLoaderContexts) {
-			if clc := classLoaderContexts.getValue(ver); len(clc.Host) > 0 {
-				verString := fmt.Sprintf("%d", ver)
-				if ver == AnySdkVersion {
-					verString = "any" // a special keyword that means any SDK version
-				}
-				cmd.Textf(`--host-classpath-for-sdk %s %s`, verString, strings.Join(clc.Host.Strings(), ":")).
-					Implicits(clc.Host).
-					Textf(`--target-classpath-for-sdk %s %s`, verString, strings.Join(clc.Target, ":"))
-			}
-		}
+			Text(` --target-sdk-version ${target_sdk_version}`).
+			Text(clc).Implicits(paths)
 		cmd.Text(`)"`)
 	} else {
 		// Pass special class loader context to skip the classpath and collision check.