Dedupe inputs in dexmerger tool similar to other tools.
RELNOTES: None.
PiperOrigin-RevId: 178013335
diff --git a/src/test/java/com/google/devtools/build/android/dexer/DexFileMergerTest.java b/src/test/java/com/google/devtools/build/android/dexer/DexFileMergerTest.java
index b459b70..ad65e09 100644
--- a/src/test/java/com/google/devtools/build/android/dexer/DexFileMergerTest.java
+++ b/src/test/java/com/google/devtools/build/android/dexer/DexFileMergerTest.java
@@ -19,7 +19,6 @@
import com.android.dex.ClassDef;
import com.android.dex.Dex;
-import com.android.dex.DexException;
import com.android.dx.command.dexer.DxContext;
import com.android.dx.dex.code.PositionList;
import com.google.common.base.Function;
@@ -68,21 +67,21 @@
}
@Test
- public void testMergeDexArchive_duplicateInputFails() throws Exception {
+ public void testMergeDexArchive_duplicateInputDeduped() throws Exception {
Path dexArchive = buildDexArchive();
- try {
- runDexFileMerger(
- 256 * 256,
- /*forceJumbo=*/ false,
- "duplicate.dex.zip",
- MultidexStrategy.MINIMAL,
- /*mainDexList=*/ null,
- /*minimalMainDex=*/ false,
- DEX_PREFIX,
- dexArchive,
- dexArchive); // input Jar twice to induce failure
- fail("DexException expected");
- } catch (DexException expected) {}
+ Path outputArchive = runDexFileMerger(
+ 256 * 256,
+ /*forceJumbo=*/ false,
+ "duplicate.dex.zip",
+ MultidexStrategy.MINIMAL,
+ /*mainDexList=*/ null,
+ /*minimalMainDex=*/ false,
+ DEX_PREFIX,
+ dexArchive,
+ dexArchive); // input Jar twice to induce duplicates
+
+ int expectedClassCount = matchingFileCount(dexArchive, ".*\\.class.dex$");
+ assertSingleDexOutput(expectedClassCount, outputArchive, "classes.dex");
}
/** Similar to {@link #testMergeDexArchive_singleOutputDex} but uses --multidex=given_shard. */
diff --git a/src/tools/android/java/com/google/devtools/build/android/dexer/DexFileMerger.java b/src/tools/android/java/com/google/devtools/build/android/dexer/DexFileMerger.java
index fded7b9..ba71e23 100644
--- a/src/tools/android/java/com/google/devtools/build/android/dexer/DexFileMerger.java
+++ b/src/tools/android/java/com/google/devtools/build/android/dexer/DexFileMerger.java
@@ -47,6 +47,7 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
@@ -82,8 +83,8 @@
converter = ExistingPathConverter.class,
abbrev = 'i',
help = "Input archives with .dex files to merge. Inputs are processed in given order, so "
- + "classes from later inputs will be added after earlier inputs. Classes mustn't appear "
- + "more than once."
+ + "classes from later inputs will be added after earlier inputs. Duplicate classes "
+ + "are dropped."
)
public List<Path> inputArchives;
@@ -232,27 +233,29 @@
System.setOut(Dexing.nullout);
}
+ HashSet<String> seen = new HashSet<>();
for (Path inputArchive : options.inputArchives) {
// Simply merge files from inputs in order. Doing that with a main dex list doesn't work,
// but we rule out more than one input with a main dex list above.
try (ZipFile zip = new ZipFile(inputArchive.toFile())) {
ArrayList<ZipEntry> dexFiles = filesToProcess(zip);
if (classesInMainDex == null) {
- processDexFiles(zip, dexFiles, out);
+ processDexFiles(zip, dexFiles, seen, out);
} else {
// To honor --main_dex_list make two passes:
// 1. process only the classes listed in the given file
// 2. process the remaining files
Predicate<ZipEntry> mainDexFilter =
ZipEntryPredicates.classFileFilter(classesInMainDex);
- processDexFiles(zip, Iterables.filter(dexFiles, mainDexFilter), out);
+ processDexFiles(zip, Iterables.filter(dexFiles, mainDexFilter), seen, out);
// Fail if main_dex_list is too big, following dx's example
checkState(out.getDexFilesWritten() == 0, "Too many classes listed in main dex list "
+ "file %s, main dex capacity exceeded", options.mainDexListFile);
if (options.minimalMainDex) {
out.flush(); // Start new .dex file if requested
}
- processDexFiles(zip, Iterables.filter(dexFiles, Predicates.not(mainDexFilter)), out);
+ processDexFiles(
+ zip, Iterables.filter(dexFiles, Predicates.not(mainDexFilter)), seen, out);
}
}
}
@@ -279,11 +282,15 @@
}
private static void processDexFiles(
- ZipFile zip, Iterable<ZipEntry> filesToProcess, DexFileAggregator out) throws IOException {
+ ZipFile zip, Iterable<ZipEntry> filesToProcess, HashSet<String> seen, DexFileAggregator out)
+ throws IOException {
for (ZipEntry entry : filesToProcess) {
String filename = entry.getName();
+ checkState(filename.endsWith(".dex"), "Input shouldn't contain .class files: %s", filename);
+ if (!seen.add(filename)) {
+ continue; // pick first occurrence of each file to match how JVM treats dupes on classpath
+ }
try (InputStream content = zip.getInputStream(entry)) {
- checkState(filename.endsWith(".dex"), "Input shouldn't contain .class files: %s", filename);
// We don't want to use the Dex(InputStream) constructor because it closes the stream,
// which will break the for loop, and it has its own bespoke way of reading the file into
// a byte buffer before effectively calling Dex(byte[]) anyway.