Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 08693df3 authored by Jeff Hao's avatar Jeff Hao
Browse files

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
parent ec41ae65
Loading
Loading
Loading
Loading
+85 −23
Original line number Original line Diff line number Diff line
@@ -28,6 +28,7 @@ import android.os.UserHandle;
import android.os.WorkSource;
import android.os.WorkSource;
import android.util.Log;
import android.util.Log;
import android.util.Slog;
import android.util.Slog;
import android.util.SparseArray;


import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.IndentingPrintWriter;
@@ -141,7 +142,7 @@ public class PackageDexOptimizer {
        final String[] instructionSets = targetInstructionSets != null ?
        final String[] instructionSets = targetInstructionSets != null ?
                targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
                targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
        final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
        final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
        final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
        final List<String> paths = pkg.getAllCodePaths();
        final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
        final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);


        final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo,
        final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo,
@@ -149,18 +150,32 @@ public class PackageDexOptimizer {
        final boolean profileUpdated = checkForProfileUpdates &&
        final boolean profileUpdated = checkForProfileUpdates &&
                isProfileUpdated(pkg, sharedGid, compilerFilter);
                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.
        // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
        final int dexoptFlags = getDexFlags(pkg, compilerFilter);
        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;
        int result = DEX_OPT_SKIPPED;
        // TODO: Iterate based on dependency hierarchy (currently alphabetically by name)
        for (int i = 0; i < paths.size(); i++) {
        // (b/37480811).
            // Skip paths that have no code.
        String basePathCheck = null;
            if ((i == 0 && (pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0) ||
        for (String path : paths) {
                    (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) {
            for (String dexCodeIsa : dexCodeInstructionSets) {
                int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter, profileUpdated,
                int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter, profileUpdated,
                        sharedLibrariesPath, dexoptFlags, sharedGid, packageStats);
                        sharedLibrariesPathWithSplits, dexoptFlags, sharedGid, packageStats);
                // The end result is:
                // The end result is:
                //  - FAILED if any path failed,
                //  - FAILED if any path failed,
                //  - PERFORMED if at least one path needed compilation,
                //  - PERFORMED if at least one path needed compilation,
@@ -168,22 +183,6 @@ public class PackageDexOptimizer {
                if ((result != DEX_OPT_FAILED) && (newResult != DEX_OPT_SKIPPED)) {
                if ((result != DEX_OPT_FAILED) && (newResult != DEX_OPT_SKIPPED)) {
                    result = newResult;
                    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;
        return result;
@@ -436,6 +435,69 @@ public class PackageDexOptimizer {
        return sb.toString();
        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<String> 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<int[]> 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<String> paths,
            SparseArray<int[]> 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}.
     * 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.
     * If the compiler filter is not profile guided the method returns false.