Change D8 and R8 to output an empty zip when there are no bytecode in the input.

R=sgjesse@google.com, zerny@google.com

Bug: 79987419
Change-Id: I7a3c9096d09c2ca671817449668e2e5bd437d3fa
diff --git a/src/main/java/com/android/tools/r8/ClassFileConsumer.java b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
index 6d7ad87..6c9a286 100644
--- a/src/main/java/com/android/tools/r8/ClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.utils.ArchiveBuilder;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.DirectoryBuilder;
-import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.OutputBuilder;
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.io.ByteStreams;
@@ -132,11 +131,7 @@
     @Override
     public void finished(DiagnosticsHandler handler) {
       super.finished(handler);
-      try {
-        outputBuilder.close();
-      } catch (IOException e) {
-        handler.error(new ExceptionDiagnostic(e, outputBuilder.getOrigin()));
-      }
+      outputBuilder.close(handler);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java b/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java
index ca86c70..f1c0fac 100644
--- a/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.utils.ArchiveBuilder;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.DirectoryBuilder;
-import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.OutputBuilder;
 import com.android.tools.r8.utils.ZipUtils;
@@ -153,11 +152,7 @@
     @Override
     public void finished(DiagnosticsHandler handler) {
       super.finished(handler);
-      try {
-        outputBuilder.close();
-      } catch (IOException e) {
-        handler.error(new ExceptionDiagnostic(e, outputBuilder.getOrigin()));
-      }
+      outputBuilder.close(handler);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
index f26ab73..3bbab64 100644
--- a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
@@ -156,11 +156,7 @@
     @Override
     public void finished(DiagnosticsHandler handler) {
       super.finished(handler);
-      try {
-        outputBuilder.close();
-      } catch (IOException e) {
-        handler.error(new ExceptionDiagnostic(e, outputBuilder.getOrigin()));
-      }
+      outputBuilder.close(handler);
     }
 
     public static void writeResources(Path archive, List<ProgramResource> resources)
@@ -242,11 +238,7 @@
     @Override
     public void finished(DiagnosticsHandler handler) {
       super.finished(handler);
-      try {
-        outputBuilder.close();
-      } catch (IOException e) {
-        handler.error(new ExceptionDiagnostic(e, outputBuilder.getOrigin()));
-      }
+      outputBuilder.close(handler);
     }
 
     private synchronized void prepareDirectory() throws IOException {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 127ec43..9721d3b 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -199,7 +199,6 @@
       // Use a linked hash map as the order matters when addDexProgramData is called below.
       Map<VirtualFile, Future<ObjectToOffsetMapping>> offsetMappingFutures = new LinkedHashMap<>();
       for (VirtualFile newFile : distribute(executorService)) {
-        assert !newFile.isEmpty();
         if (!newFile.isEmpty()) {
           offsetMappingFutures
               .put(newFile, executorService.submit(() -> {
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
index cd21d17..25512f7 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
@@ -35,31 +35,38 @@
   @Override
   public synchronized void open() {
     assert !closed;
-    openCount ++;
+    openCount++;
   }
 
   @Override
-  public synchronized void close() throws IOException {
+  public synchronized void close(DiagnosticsHandler handler)  {
     assert !closed;
     openCount--;
     if (openCount == 0) {
       closed = true;
-      if (stream != null) {
-        stream.close();
+      try {
+        getStreamRaw().close();
         stream = null;
+      } catch (IOException e) {
+        handler.error(new ExceptionDiagnostic(e, origin));
       }
     }
   }
 
+  private ZipOutputStream getStreamRaw() throws IOException {
+    if (stream != null) {
+      return stream;
+    }
+    return new ZipOutputStream(Files.newOutputStream(
+        archive, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING));
+  }
+
   /** Get or open the zip output stream. */
   private synchronized ZipOutputStream getStream(DiagnosticsHandler handler) {
     assert !closed;
     if (stream == null) {
       try {
-        stream =
-            new ZipOutputStream(
-                Files.newOutputStream(
-                    archive, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING));
+        getStreamRaw();
       } catch (IOException e) {
         handler.error(new ExceptionDiagnostic(e, origin));
       }
diff --git a/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java b/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java
index 2dc4a2d..2058d39 100644
--- a/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java
@@ -30,7 +30,7 @@
   }
 
   @Override
-  public void close() {
+  public void close(DiagnosticsHandler handler) {
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/utils/OutputBuilder.java b/src/main/java/com/android/tools/r8/utils/OutputBuilder.java
index 27d07c4..9237485 100644
--- a/src/main/java/com/android/tools/r8/utils/OutputBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/OutputBuilder.java
@@ -7,14 +7,15 @@
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.origin.Origin;
-import java.io.Closeable;
 import java.nio.file.Path;
 
-public interface OutputBuilder extends Closeable {
+public interface OutputBuilder {
   char NAME_SEPARATOR = '/';
 
   void open();
 
+  void close(DiagnosticsHandler handler);
+
   void addDirectory(String name, DiagnosticsHandler handler);
 
   void addFile(String name, DataEntryResource content, DiagnosticsHandler handler);
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index dc16cac..a11c960 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -418,6 +418,17 @@
                     .build()));
   }
 
+  @Test
+  public void noInputOutputsEmptyZip() throws CompilationFailedException, IOException {
+    Path emptyZip = temp.getRoot().toPath().resolve("empty.zip");
+    D8.run(
+        D8Command.builder()
+            .setOutput(emptyZip, OutputMode.DexIndexed)
+            .build());
+    assertTrue(Files.exists(emptyZip));
+    assertEquals(0, new ZipFile(emptyZip.toFile()).size());
+  }
+
   private D8Command parse(String... args) throws CompilationFailedException {
     return D8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index a89cbd4..a7c7e02 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.origin.EmbeddedOrigin;
 import com.android.tools.r8.utils.FileUtils;
 import com.google.common.collect.ImmutableList;
+import java.io.IOException;
 import java.lang.reflect.Method;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -21,6 +22,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.zip.ZipFile;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -428,6 +430,17 @@
     assertTrue(result.stdout.contains("-printconfiguration"));
   }
 
+  @Test
+  public void noInputOutputsEmptyZip() throws CompilationFailedException, IOException {
+    Path emptyZip = temp.getRoot().toPath().resolve("empty.zip");
+    R8.run(
+        R8Command.builder()
+            .setOutput(emptyZip, OutputMode.DexIndexed)
+            .build());
+    assertTrue(Files.exists(emptyZip));
+    assertEquals(0, new ZipFile(emptyZip.toFile()).size());
+  }
+
   private R8Command parse(String... args) throws CompilationFailedException {
     return R8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }