From 08693df39d0c3248068c8d0374a8aeec65391b51 Mon Sep 17 00:00:00 2001 From: Jeff Hao Date: Wed, 19 Apr 2017 14:42:16 -0700 Subject: [PATCH] Gather shared libs for split apks based on dependencies. Traverses split dependency hierarchy to determine what the split paths should be to add to the shared libs to pass down to dex2oat. Bug: 34169257 Bug: 37480811 Test: cts-tradefed run singleCommand cts -d --module CtsAppSecurityHostTestCases -t android.appsecurity.cts.SplitTests Checked not only that CTS tests pass, but that shared libraries match and collision checks are not being performed. E.g. testInstallAllFeatureSplits has dependencies where: APK_BASE <- APK_FEATURE_A <- APK_FEATURE_B and APK_BASE <- APK_FEATURE_C. Shared libraries passed to dex2oat: for base.apk: /system/framework/android.test.runner.jar for feature_a: /system/framework/android.test.runner.jar:base.apk for feature_b: /system/framework/android.test.runner.jar:base.apk:split_feature_a.apk for feature_c: /system/framework/android.test.runner.jar:base.apk Change-Id: Ia2b0c5a7c9d6c1a2e9c5bde52ae7c53ddf964b96 --- .../server/pm/PackageDexOptimizer.java | 108 ++++++++++++++---- 1 file changed, 85 insertions(+), 23 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 8413491aaef82..254bc2ac0e8bb 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -28,6 +28,7 @@ import android.os.UserHandle; import android.os.WorkSource; import android.util.Log; import android.util.Slog; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; @@ -141,7 +142,7 @@ public class PackageDexOptimizer { final String[] instructionSets = targetInstructionSets != null ? targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo); final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); - final List paths = pkg.getAllCodePathsExcludingResourceOnly(); + final List paths = pkg.getAllCodePaths(); final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo, @@ -149,18 +150,32 @@ public class PackageDexOptimizer { final boolean profileUpdated = checkForProfileUpdates && isProfileUpdated(pkg, sharedGid, compilerFilter); - String sharedLibrariesPath = getSharedLibrariesPath(sharedLibraries); + final String sharedLibrariesPath = getSharedLibrariesPath(sharedLibraries); // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags. final int dexoptFlags = getDexFlags(pkg, compilerFilter); + // Get the dependencies of each split in the package. For each code path in the package, + // this array contains the relative paths of each split it depends on, separated by colons. + String[] splitDependencies = getSplitDependencies(pkg); int result = DEX_OPT_SKIPPED; - // TODO: Iterate based on dependency hierarchy (currently alphabetically by name) - // (b/37480811). - String basePathCheck = null; - for (String path : paths) { + for (int i = 0; i < paths.size(); i++) { + // Skip paths that have no code. + if ((i == 0 && (pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0) || + (i != 0 && (pkg.splitFlags[i - 1] & ApplicationInfo.FLAG_HAS_CODE) == 0)) { + continue; + } + // Append shared libraries with split dependencies for this split. + String path = paths.get(i); + String sharedLibrariesPathWithSplits; + if (sharedLibrariesPath != null && splitDependencies[i] != null) { + sharedLibrariesPathWithSplits = sharedLibrariesPath + ":" + splitDependencies[i]; + } else { + sharedLibrariesPathWithSplits = + splitDependencies[i] != null ? splitDependencies[i] : sharedLibrariesPath; + } for (String dexCodeIsa : dexCodeInstructionSets) { int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter, profileUpdated, - sharedLibrariesPath, dexoptFlags, sharedGid, packageStats); + sharedLibrariesPathWithSplits, dexoptFlags, sharedGid, packageStats); // The end result is: // - FAILED if any path failed, // - PERFORMED if at least one path needed compilation, @@ -168,22 +183,6 @@ public class PackageDexOptimizer { if ((result != DEX_OPT_FAILED) && (newResult != DEX_OPT_SKIPPED)) { result = newResult; } - // Add the relative path of code we just compiled to the shared libraries. - int slashIndex = path.lastIndexOf('/') + 1; - String relativePath = path.substring(slashIndex); - if (sharedLibrariesPath == null) { - sharedLibrariesPath = relativePath; - } else { - sharedLibrariesPath += ":" + relativePath; - } - // Sanity check that the base paths are all the same. - String basePath = path.substring(0, slashIndex); - if (basePathCheck == null) { - basePathCheck = basePath; - } else if (!basePath.equals(basePathCheck)) { - Slog.wtf(TAG, "Split paths have different base paths: " + basePath + " and " + - basePathCheck); - } } } return result; @@ -436,6 +435,69 @@ public class PackageDexOptimizer { return sb.toString(); } + /** + * Walks dependency tree and gathers the dependencies for each split in a split apk. + * The split paths are stored as relative paths, separated by colons. + */ + private String[] getSplitDependencies(PackageParser.Package pkg) { + // Convert all the code paths to relative paths. + String baseCodePath = new File(pkg.baseCodePath).getParent(); + List paths = pkg.getAllCodePaths(); + String[] splitDependencies = new String[paths.size()]; + for (int i = 0; i < paths.size(); i++) { + File pathFile = new File(paths.get(i)); + String fileName = pathFile.getName(); + paths.set(i, fileName); + + // Sanity check that the base paths of the splits are all the same. + String basePath = pathFile.getParent(); + if (!basePath.equals(baseCodePath)) { + Slog.wtf(TAG, "Split paths have different base paths: " + basePath + " and " + + baseCodePath); + } + } + + // If there are no other dependencies, fill in the implicit dependency on the base apk. + SparseArray dependencies = pkg.applicationInfo.splitDependencies; + if (dependencies == null) { + for (int i = 1; i < paths.size(); i++) { + splitDependencies[i] = paths.get(0); + } + return splitDependencies; + } + + // Fill in the dependencies, skipping the base apk which has no dependencies. + for (int i = 1; i < dependencies.size(); i++) { + getParentDependencies(dependencies.keyAt(i), paths, dependencies, splitDependencies); + } + + return splitDependencies; + } + + /** + * Recursive method to generate dependencies for a particular split. + * The index is a key from the package's splitDependencies. + */ + private String getParentDependencies(int index, List paths, + SparseArray dependencies, String[] splitDependencies) { + // The base apk is always first, and has no dependencies. + if (index == 0) { + return null; + } + // Return the result if we've computed the dependencies for this index already. + if (splitDependencies[index] != null) { + return splitDependencies[index]; + } + // Get the dependencies for the parent of this index and append its path to it. + int parent = dependencies.get(index)[0]; + String parentDependencies = + getParentDependencies(parent, paths, dependencies, splitDependencies); + String path = parentDependencies == null ? paths.get(parent) : + parentDependencies + ":" + paths.get(parent); + splitDependencies[index] = path; + return path; + } + /** * Checks if there is an update on the profile information of the {@code pkg}. * If the compiler filter is not profile guided the method returns false.