Remove DexFileMerger

Bug: 155464736
Change-Id: I911b4096b3060a0a1efff5bf95e4685439a4d86a
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
index dd7da82..f079277 100644
--- a/src/main/java/com/android/tools/r8/SwissArmyKnife.java
+++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.bisect.Bisect;
 import com.android.tools.r8.compatproguard.CompatProguard;
-import com.android.tools.r8.dexfilemerger.DexFileMerger;
 import com.android.tools.r8.dexsplitter.DexSplitter;
 import com.android.tools.r8.relocator.RelocatorCommandLine;
 import java.util.Arrays;
@@ -37,9 +36,6 @@
       case "d8":
         D8.main(shift(args));
         break;
-      case "dexfilemerger":
-        DexFileMerger.main(shift(args));
-        break;
       case "dexsegments":
         DexSegments.main(shift(args));
         break;
diff --git a/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java b/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
deleted file mode 100644
index 7cbf816..0000000
--- a/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
+++ /dev/null
@@ -1,403 +0,0 @@
-// 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 com.android.tools.r8.dexfilemerger;
-
-import com.android.tools.r8.ByteDataView;
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.D8Command;
-import com.android.tools.r8.DexFileMergerHelper;
-import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.utils.ExceptionDiagnostic;
-import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.OptionsParsing;
-import com.android.tools.r8.utils.OptionsParsing.ParseContext;
-import com.android.tools.r8.utils.StringDiagnostic;
-import com.android.tools.r8.utils.ZipUtils;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardOpenOption;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-
-public class DexFileMerger {
-  /** File name prefix of a {@code .dex} file automatically loaded in an archive. */
-  private static final String DEX_PREFIX = "classes";
-
-  private static final String DEFAULT_OUTPUT_ARCHIVE_FILENAME = "classes.dex.jar";
-
-  private static final boolean PRINT_ARGS = false;
-
-  /** Strategies for outputting multiple {@code .dex} files supported by {@link DexFileMerger}. */
-  private enum MultidexStrategy {
-    /** Create exactly one .dex file. The operation will fail if .dex limits are exceeded. */
-    OFF,
-    /** Create exactly one <prefixN>.dex file with N taken from the (single) input archive. */
-    GIVEN_SHARD,
-    /**
-     * Assemble .dex files similar to {@link com.android.dx.command.dexer.Main dx}, with all but one
-     * file as large as possible.
-     */
-    MINIMAL,
-    /**
-     * Allow some leeway and sometimes use additional .dex files to speed up processing. This option
-     * exists to give flexibility but it often (or always) may be identical to {@link #MINIMAL}.
-     */
-    BEST_EFFORT;
-
-    public boolean isMultidexAllowed() {
-      switch (this) {
-        case OFF:
-        case GIVEN_SHARD:
-          return false;
-        case MINIMAL:
-        case BEST_EFFORT:
-          return true;
-      }
-      throw new AssertionError("Unknown: " + this);
-    }
-
-    public static MultidexStrategy parse(String value) {
-      switch (value) {
-        case "off":
-          return OFF;
-        case "given_shard":
-          return GIVEN_SHARD;
-        case "minimal":
-          return MINIMAL;
-        case "best_effort":
-          return BEST_EFFORT;
-        default:
-          throw new RuntimeException(
-              "Multidex argument must be either 'off', 'given_shard', 'minimal' or 'best_effort'.");
-      }
-    }
-  }
-
-  private static class Options {
-    List<String> inputArchives = new ArrayList<>();
-    String outputArchive = DEFAULT_OUTPUT_ARCHIVE_FILENAME;
-    MultidexStrategy multidexMode = MultidexStrategy.OFF;
-    String mainDexListFile = null;
-    boolean minimalMainDex = false;
-    boolean verbose = false;
-    String dexPrefix = DEX_PREFIX;
-  }
-
-
-  private static Options parseArguments(String[] args) throws IOException {
-    // We may have a single argument which is a parameter file path, prefixed with '@'.
-    if (args.length == 1 && args[0].startsWith("@")) {
-      // TODO(tamaskenez) Implement more sophisticated processing
-      // which is aligned with Blaze's
-      // com.google.devtools.common.options.ShellQuotedParamsFilePreProcessor
-      Path paramsFile = Paths.get(args[0].substring(1));
-      List<String> argsList = new ArrayList<>();
-      for (String s : Files.readAllLines(paramsFile)) {
-        s = s.trim();
-        if (s.isEmpty()) {
-          continue;
-        }
-        // Trim optional enclosing single quotes. Unescaping omitted for now.
-        if (s.length() >= 2 && s.startsWith("'") && s.endsWith("'")) {
-          s = s.substring(1, s.length() - 1);
-        }
-        argsList.add(s);
-      }
-      args = argsList.toArray(new String[argsList.size()]);
-    }
-
-    Options options = new Options();
-    ParseContext context = new ParseContext(args);
-    List<String> strings;
-    String string;
-    Boolean b;
-    while (context.head() != null) {
-      if (context.head().startsWith("@")) {
-        throw new RuntimeException("A params file must be the only argument: " + context.head());
-      }
-      strings = OptionsParsing.tryParseMulti(context, "--input");
-      if (strings != null) {
-        options.inputArchives.addAll(strings);
-        continue;
-      }
-      string = OptionsParsing.tryParseSingle(context, "--output", "-o");
-      if (string != null) {
-        options.outputArchive = string;
-        continue;
-      }
-      string = OptionsParsing.tryParseSingle(context, "--multidex", null);
-      if (string != null) {
-        options.multidexMode = MultidexStrategy.parse(string);
-        continue;
-      }
-      string = OptionsParsing.tryParseSingle(context, "--main-dex-list", null);
-      if (string != null) {
-        options.mainDexListFile = string;
-        continue;
-      }
-      b = OptionsParsing.tryParseBoolean(context, "--minimal-main-dex");
-      if (b != null) {
-        options.minimalMainDex = b;
-        continue;
-      }
-      b = OptionsParsing.tryParseBoolean(context, "--verbose");
-      if (b != null) {
-        options.verbose = b;
-        continue;
-      }
-      string = OptionsParsing.tryParseSingle(context, "--max-bytes-wasted-per-file", null);
-      if (string != null) {
-        System.err.println("Warning: '--max-bytes-wasted-per-file' is ignored.");
-        continue;
-      }
-      string = OptionsParsing.tryParseSingle(context, "--set-max-idx-number", null);
-      if (string != null) {
-        System.err.println("Warning: The '--set-max-idx-number' option is ignored.");
-        continue;
-      }
-      b = OptionsParsing.tryParseBoolean(context, "--forceJumbo");
-      if (b != null) {
-        System.err.println(
-            "Warning: '--forceJumbo' can be safely omitted. Strings will only use "
-                + "jumbo-string indexing if necessary.");
-        continue;
-      }
-      string = OptionsParsing.tryParseSingle(context, "--dex_prefix", null);
-      if (string != null) {
-        options.dexPrefix = string;
-        continue;
-      }
-      throw new RuntimeException(String.format("Unknown options: '%s'.", context.head()));
-    }
-    return options;
-  }
-
-  /**
-   * Implements a DexIndexedConsumer writing into a ZipStream with support for custom dex file name
-   * prefix, reindexing a single dex output file to a nonzero index and reporting if any data has
-   * been written.
-   */
-  private static class ArchiveConsumer implements DexIndexedConsumer {
-    private final Path path;
-    private final String prefix;
-    private final Integer singleFixedFileIndex;
-    private final Origin origin;
-    private ZipOutputStream stream = null;
-
-    private int highestIndexWritten = -1;
-    private final Map<Integer, Runnable> writers = new TreeMap<>();
-    private boolean hasWrittenSomething = false;
-
-    /** If singleFixedFileIndex is not null then we expect only one output dex file */
-    private ArchiveConsumer(Path path, String prefix, Integer singleFixedFileIndex) {
-      this.path = path;
-      this.prefix = prefix;
-      this.singleFixedFileIndex = singleFixedFileIndex;
-      this.origin = new PathOrigin(path);
-    }
-
-    private boolean hasWrittenSomething() {
-      return hasWrittenSomething;
-    }
-
-    private String getDexFileName(int fileIndex) {
-      if (singleFixedFileIndex != null) {
-        fileIndex = singleFixedFileIndex;
-      }
-      return prefix + (fileIndex == 0 ? "" : (fileIndex + 1)) + FileUtils.DEX_EXTENSION;
-    }
-
-    @Override
-    public synchronized void accept(
-        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
-      if (singleFixedFileIndex != null && fileIndex != 0) {
-        handler.error(new StringDiagnostic("Result does not fit into a single dex file."));
-        return;
-      }
-      // Make a copy of the actual bytes as they will possibly be accessed later by the runner.
-      final byte[] bytes = data.copyByteData();
-      writers.put(fileIndex, () -> writeEntry(fileIndex, bytes, descriptors, handler));
-
-      while (writers.containsKey(highestIndexWritten + 1)) {
-        ++highestIndexWritten;
-        writers.get(highestIndexWritten).run();
-        writers.remove(highestIndexWritten);
-      }
-    }
-
-    /** Get or open the zip output stream. */
-    private synchronized ZipOutputStream getStream(DiagnosticsHandler handler) {
-      if (stream == null) {
-        try {
-          stream =
-              new ZipOutputStream(
-                  Files.newOutputStream(
-                      path, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING));
-        } catch (IOException e) {
-          handler.error(new ExceptionDiagnostic(e, origin));
-        }
-      }
-      return stream;
-    }
-
-    private void writeEntry(
-        int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
-      try {
-        ZipUtils.writeToZipStream(
-            getStream(handler),
-            getDexFileName(fileIndex),
-            ByteDataView.of(data),
-            ZipEntry.DEFLATED);
-        hasWrittenSomething = true;
-      } catch (IOException e) {
-        handler.error(new ExceptionDiagnostic(e, origin));
-      }
-    }
-
-    @Override
-    public void finished(DiagnosticsHandler handler) {
-      if (!writers.isEmpty()) {
-        handler.error(
-            new StringDiagnostic(
-                "Failed to write zip, for a multidex output some of the classes.dex files were"
-                    + " not produced."));
-      }
-      try {
-        if (stream != null) {
-          stream.close();
-          stream = null;
-        }
-      } catch (IOException e) {
-        handler.error(new ExceptionDiagnostic(e, origin));
-      }
-    }
-  }
-
-  private static int parseFileIndexFromShardFilename(String inputArchive) {
-    Pattern namingPattern = Pattern.compile("([0-9]+)\\..*");
-    String name = new File(inputArchive).getName();
-    Matcher matcher = namingPattern.matcher(name);
-    if (!matcher.matches()) {
-      throw new RuntimeException(
-          String.format(
-              "Expect input named <N>.xxx.zip for --multidex=given_shard but got %s.", name));
-    }
-    int shard = Integer.parseInt(matcher.group(1));
-    if (shard <= 0) {
-      throw new RuntimeException(
-          String.format("Expect positive N in input named <N>.xxx.zip but got %d.", shard));
-    }
-    return shard;
-  }
-
-  public static void run(String[] args) throws CompilationFailedException, IOException {
-    Options options = parseArguments(args);
-
-    if (options.inputArchives.isEmpty()) {
-      throw new RuntimeException("Need at least one --input");
-    }
-
-    if (options.mainDexListFile != null && options.inputArchives.size() != 1) {
-      throw new RuntimeException(
-          "--main-dex-list only supported with exactly one --input, use DexFileSplitter for more");
-    }
-
-    if (!options.multidexMode.isMultidexAllowed()) {
-      if (options.mainDexListFile != null) {
-        throw new RuntimeException(
-            "--main-dex-list is only supported with multidex enabled, but mode is: "
-                + options.multidexMode.toString());
-      }
-      if (options.minimalMainDex) {
-        throw new RuntimeException(
-            "--minimal-main-dex is only supported with multidex enabled, but mode is: "
-                + options.multidexMode.toString());
-      }
-    }
-
-    D8Command.Builder builder = D8Command.builder();
-
-    Map<String, Integer> inputOrdering = new HashMap<>(options.inputArchives.size());
-    int sequenceNumber = 0;
-    for (String s : options.inputArchives) {
-      builder.addProgramFiles(Paths.get(s));
-      inputOrdering.put(s, sequenceNumber++);
-    }
-
-    // Determine enabling multidexing and file indexing.
-    Integer singleFixedFileIndex = null;
-    switch (options.multidexMode) {
-      case OFF:
-        singleFixedFileIndex = 0;
-        break;
-      case GIVEN_SHARD:
-        if (options.inputArchives.size() != 1) {
-          throw new RuntimeException("'--multidex=given_shard' requires exactly one --input.");
-        }
-        singleFixedFileIndex = parseFileIndexFromShardFilename(options.inputArchives.get(0)) - 1;
-        break;
-      case MINIMAL:
-      case BEST_EFFORT:
-        // Nothing to do.
-        break;
-      default:
-        throw new Unreachable("Unexpected enum: " + options.multidexMode);
-    }
-
-    if (options.mainDexListFile != null) {
-      builder.addMainDexListFiles(Paths.get(options.mainDexListFile));
-    }
-
-    ArchiveConsumer consumer =
-        new ArchiveConsumer(
-            Paths.get(options.outputArchive), options.dexPrefix, singleFixedFileIndex);
-    builder.setProgramConsumer(consumer);
-
-    DexFileMergerHelper.run(builder.build(), options.minimalMainDex, inputOrdering);
-
-    // If input was empty we still need to write out an empty zip.
-    if (!consumer.hasWrittenSomething()) {
-      File f = new File(options.outputArchive);
-      ZipOutputStream out = new ZipOutputStream(new FileOutputStream(f));
-      out.close();
-    }
-  }
-
-  public static void main(String[] args) {
-    try {
-      if (PRINT_ARGS) {
-        printArgs(args);
-      }
-      run(args);
-    } catch (CompilationFailedException | IOException e) {
-      System.err.println("Merge failed: " + e.getMessage());
-      System.exit(1);
-    }
-  }
-
-  private static void printArgs(String[] args) {
-    System.err.print("r8.DexFileMerger");
-    for (String s : args) {
-      System.err.printf(" %s", s);
-    }
-    System.err.println("");
-  }
-}
diff --git a/src/main/keep.txt b/src/main/keep.txt
index 083584f..7791d4e 100644
--- a/src/main/keep.txt
+++ b/src/main/keep.txt
@@ -8,7 +8,6 @@
 -keep public class com.android.tools.r8.D8 { public static void main(java.lang.String[]); }
 -keep public class com.android.tools.r8.R8 { public static void main(java.lang.String[]); }
 -keep public class com.android.tools.r8.ExtractMarker { public static void main(java.lang.String[]); }
--keep public class com.android.tools.r8.dexfilemerger.DexFileMerger { public static void main(java.lang.String[]); }
 -keep public class com.android.tools.r8.dexsplitter.DexSplitter { public static void main(java.lang.String[]); }
 
 -keep public class com.android.tools.r8.Version { public static java.lang.String getVersionString(); }
diff --git a/src/test/java/com/android/tools/r8/dexfilemerger/DexFileMergerTests.java b/src/test/java/com/android/tools/r8/dexfilemerger/DexFileMergerTests.java
deleted file mode 100644
index 91364d4..0000000
--- a/src/test/java/com/android/tools/r8/dexfilemerger/DexFileMergerTests.java
+++ /dev/null
@@ -1,149 +0,0 @@
-// 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 com.android.tools.r8.dexfilemerger;
-
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.D8;
-import com.android.tools.r8.D8Command;
-import com.android.tools.r8.DexFileMergerHelper;
-import com.android.tools.r8.ExtractMarker;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.dex.Marker;
-import com.android.tools.r8.maindexlist.MainDexListTests;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.AndroidApp;
-import com.google.common.collect.ImmutableList;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Collection;
-import java.util.concurrent.ExecutionException;
-import org.junit.Test;
-
-public class DexFileMergerTests extends TestBase {
-
-  private static final String CLASS_DIR = ToolHelper.EXAMPLES_BUILD_DIR + "classes/dexmergesample";
-  private static final String CLASS1_CLASS = CLASS_DIR + "/Class1.class";
-  private static final String CLASS2_CLASS = CLASS_DIR + "/Class2.class";
-  private static final int MAX_METHOD_COUNT = Constants.U16BIT_MAX;
-
-  private Path createMergerInputWithTwoClasses(OutputMode outputMode, boolean addMarker)
-      throws CompilationFailedException, IOException {
-    // Compile Class1 and Class2
-    Path mergerInputZip = temp.newFolder().toPath().resolve("merger-input.zip");
-    D8Command command =
-        D8Command.builder()
-            .setOutput(mergerInputZip, outputMode)
-            .addProgramFiles(Paths.get(CLASS1_CLASS))
-            .addProgramFiles(Paths.get(CLASS2_CLASS))
-            .build();
-
-    DexFileMergerHelper.runD8ForTesting(command, !addMarker);
-
-    return mergerInputZip;
-  }
-
-  private void testMarker(boolean addMarkerToInput)
-      throws CompilationFailedException, IOException, ResourceException, ExecutionException {
-    Path mergerInputZip = createMergerInputWithTwoClasses(OutputMode.DexIndexed, addMarkerToInput);
-    int expectedNumberOfMarkers = addMarkerToInput ? 1 : 0;
-
-    Collection<Marker> inputMarkers = ExtractMarker.extractMarkerFromDexFile(mergerInputZip);
-    assertEquals(expectedNumberOfMarkers, inputMarkers.size());
-
-    // Test that the DexFileMerger preserves markers.
-    Path mergerOutputZip = temp.getRoot().toPath().resolve("merger-out.zip");
-    DexFileMerger.main(
-        new String[] {
-          "--input", mergerInputZip.toString(), "--output", mergerOutputZip.toString()
-        });
-    Collection<Marker> outputMarkers = ExtractMarker.extractMarkerFromDexFile(mergerOutputZip);
-    assertEquals(expectedNumberOfMarkers, outputMarkers.size());
-
-    // Test that D8 when used for merging preserves markers.
-    D8.main(new String[] { mergerInputZip.toString(), "--output", mergerOutputZip.toString() });
-    Collection<Marker> d8OutputMarkers = ExtractMarker.extractMarkerFromDexFile((mergerOutputZip));
-    assertEquals(expectedNumberOfMarkers, d8OutputMarkers.size());
-  }
-
-  @Test
-  public void testMarkerPreserved()
-      throws CompilationFailedException, IOException, ResourceException, ExecutionException {
-    testMarker(true);
-  }
-
-  @Test
-  public void testMarkerNotAdded()
-      throws CompilationFailedException, IOException, ResourceException, ExecutionException {
-    testMarker(false);
-  }
-
-  @Test
-  public void mergeTwoFiles() throws CompilationFailedException, IOException {
-    Path mergerInputZip = createMergerInputWithTwoClasses(OutputMode.DexFilePerClassFile, false);
-
-    Path mergerOutputZip = temp.getRoot().toPath().resolve("merger-out.zip");
-    DexFileMerger.main(
-        new String[] {
-          "--input", mergerInputZip.toString(), "--output", mergerOutputZip.toString()
-        });
-
-    // Test by running methods of Class1 and Class2
-    for (String className : new String[] {"Class1", "Class2"}) {
-      ArtCommandBuilder builder = new ArtCommandBuilder();
-      builder.appendClasspath(mergerOutputZip.toString());
-      builder.setMainClass("dexmergesample." + className);
-      String out = ToolHelper.runArt(builder);
-      assertEquals(out, className + "\n");
-    }
-  }
-
-  private void generateClassesAndTest(int extraMethodCount, int programResourcesSize)
-      throws IOException, ExecutionException, CompilationFailedException {
-    AndroidApp generatedApp =
-        MainDexListTests.generateApplication(
-            ImmutableList.of("A", "B"),
-            AndroidApiLevel.N.getLevel(),
-            MAX_METHOD_COUNT / 2 + 1 + extraMethodCount);
-    Path appDir = temp.newFolder().toPath().resolve("merger-input.zip");
-    assertEquals(programResourcesSize, generatedApp.getDexProgramResourcesForTesting().size());
-    generatedApp.write(appDir, OutputMode.DexIndexed);
-
-    Path outZip = temp.getRoot().toPath().resolve("out.zip");
-    DexFileMerger.run(
-        new String[] {
-          "--input", appDir.toString(), "--output", outZip.toString(), "--multidex=off"
-        });
-  }
-
-  @Test
-  public void failIfTooBig() throws IOException, ExecutionException {
-    // Generates an application with two classes, each with the number of methods just enough not to
-    // fit into a single dex file.
-    try {
-      generateClassesAndTest(1, 2);
-      fail("Expect to fail");
-    } catch (CompilationFailedException e) {
-      assertThat(e.getCause().getMessage(), containsString("does not fit into a single dex file"));
-    }
-  }
-
-  @Test
-  public void failIfTooBigControl()
-      throws IOException, ExecutionException, CompilationFailedException {
-    // Control test for failIfTooBig to make sure we don't fail with less methods.
-    generateClassesAndTest(0, 1);
-  }
-}