Add support for tracing main dex classes in R8

In addition to the given main dex list, allow to handle tracing rules
for calculating the list of classes required to be kept in the main dex
regarding legacy MultiDex constraints. Main dex list and constraints
tracing are not excliusive, when both are provided their class lists are
just merged. Runtime annotations with enum values are taken into account
precisely (if classpath is complete) and Annotations without enum are
not forced into the main dex so as classes annotated by them.

In this first step support is only added to R8.
In R8, When tree shaking and legacy main dex tracing are enabled together,
the assumption is made that tree shaking is keeping every classes with
code involved in secondary dex files installation. This allows to let tree
shaking reuse the tracing made for the main dex list instead of restarting
from scratch.

This change also introduces CompilationResult as an internal result allowing
easier inspection by tests. CompilationResult is not returned by public
R8/D8 methods.

Sources of new multidex tests are modified versions of some Jack tests.

Bug: 37772111
Change-Id: I7b741ace5a990a66cd4b991197de409c795e7d95
diff --git a/src/test/examples/multidex002/fakelibrary/MultiDexExtractor.java b/src/test/examples/multidex002/fakelibrary/MultiDexExtractor.java
new file mode 100644
index 0000000..b80fcc8
--- /dev/null
+++ b/src/test/examples/multidex002/fakelibrary/MultiDexExtractor.java
@@ -0,0 +1,141 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package multidex002.fakelibrary;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.util.List;
+import multidexfakeframeworks.Context;
+
+/**
+ * Exposes application secondary dex files as files in the application data
+ * directory.
+ */
+final class MultiDexExtractor {
+
+    private static final String TAG = MultiDex.TAG;
+
+    /**
+     * We look for additional dex files named {@code classes2.dex},
+     * {@code classes3.dex}, etc.
+     */
+    private static final String DEX_PREFIX = "classes";
+    private static final String DEX_SUFFIX = ".dex";
+
+    private static final String EXTRACTED_NAME_EXT = ".classes";
+    private static final String EXTRACTED_SUFFIX = ".zip";
+    private static final int MAX_EXTRACT_ATTEMPTS = 3;
+
+    private static final String PREFS_FILE = "multidex.version";
+    private static final String KEY_TIME_STAMP = "timestamp";
+    private static final String KEY_CRC = "crc";
+    private static final String KEY_DEX_NUMBER = "dex.number";
+
+    /**
+     * Size of reading buffers.
+     */
+    private static final int BUFFER_SIZE = 0x4000;
+    /* Keep value away from 0 because it is a too probable time stamp value */
+    private static final long NO_VALUE = -1L;
+
+
+    private static List<File> loadExistingExtractions(Context context, File sourceApk, File dexDir)
+            throws IOException {
+
+        final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
+        int totalDexNumber = 1;
+        final List<File> files = null;
+
+        for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
+            String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
+            File extractedFile = new File(dexDir, fileName);
+            if (extractedFile.isFile()) {
+                files.add(extractedFile);
+            } else {
+                throw new IOException("Missing extracted secondary dex file '" +
+                        extractedFile.getPath() + "'");
+            }
+        }
+
+        return files;
+    }
+
+    private static long getTimeStamp(File archive) {
+        long timeStamp = archive.lastModified();
+        if (timeStamp == NO_VALUE) {
+            // never return NO_VALUE
+            timeStamp--;
+        }
+        return timeStamp;
+    }
+
+
+    private static long getZipCrc(File archive) throws IOException {
+        long computedValue = ZipUtil.getZipCrc(archive);
+        if (computedValue == NO_VALUE) {
+            // never return NO_VALUE
+            computedValue--;
+        }
+        return computedValue;
+    }
+
+    private static List<File> performExtractions(File sourceApk, File dexDir)
+            throws IOException {
+
+        final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
+
+        // Ensure that whatever deletions happen in prepareDexDir only happen if the zip that
+        // contains a secondary dex file in there is not consistent with the latest apk.  Otherwise,
+        // multi-process race conditions can cause a crash loop where one process deletes the zip
+        // while another had created it.
+        prepareDexDir(dexDir, extractedFilePrefix);
+
+        List<File> files = null;
+
+        return files;
+    }
+
+    /**
+     * This removes any files that do not have the correct prefix.
+     */
+    private static void prepareDexDir(File dexDir, final String extractedFilePrefix)
+            throws IOException {
+        dexDir.mkdir();
+        if (!dexDir.isDirectory()) {
+            throw new IOException("Failed to create dex directory " + dexDir.getPath());
+        }
+
+        // Clean possible old files
+        FileFilter filter = new FileFilter() {
+
+            @Override
+            public boolean accept(File pathname) {
+                return !pathname.getName().startsWith(extractedFilePrefix);
+            }
+        };
+        File[] files = dexDir.listFiles(filter);
+        if (files == null) {
+            return;
+        }
+        for (File oldFile : files) {
+            if (!oldFile.delete()) {
+            } else {
+            }
+        }
+    }
+
+    /**
+     * Closes the given {@code Closeable}. Suppresses any IO exceptions.
+     */
+    private static void closeQuietly(Closeable closeable) {
+        try {
+            closeable.close();
+        } catch (IOException e) {
+        }
+    }
+
+}