Update Smali, Dx, and DexMerger tasks to use Gradle workers

This allows these tasks to run in parallel rather than in serial, leading to improved build times.

Before:

    BUILD SUCCESSFUL in 1m 3s
    317 actionable tasks: 192 executed, 125 up-to-date

After:

    BUILD SUCCESSFUL in 25s
    317 actionable tasks: 192 executed, 125 up-to-date

Change-Id: I9089a9b80cb1f0a570fc3135afbbbc6311fd4a2c
diff --git a/buildSrc/src/main/java/dx/DxTask.java b/buildSrc/src/main/java/dx/DxTask.java
new file mode 100644
index 0000000..d8975dc
--- /dev/null
+++ b/buildSrc/src/main/java/dx/DxTask.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2019, 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 dx;
+
+import com.google.common.base.Optional;
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import javax.inject.Inject;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.workers.IsolationMode;
+import org.gradle.workers.WorkerExecutor;
+import utils.Utils;
+
+public class DxTask extends DefaultTask {
+
+  private final WorkerExecutor workerExecutor;
+
+  private FileCollection source;
+  private File destination;
+  private Optional<File> dxExecutable = Optional.absent(); // Worker API cannot handle null.
+  private boolean debug;
+
+  @Inject
+  public DxTask(WorkerExecutor workerExecutor) {
+    this.workerExecutor = workerExecutor;
+  }
+
+  @InputFiles
+  public FileCollection getSource() {
+    return source;
+  }
+
+  public void setSource(FileCollection source) {
+    this.source = source;
+  }
+
+  @OutputDirectory
+  public File getDestination() {
+    return destination;
+  }
+
+  public void setDestination(File destination) {
+    this.destination = destination;
+  }
+
+  @InputFile
+  @org.gradle.api.tasks.Optional
+  public File getDxExecutable() {
+    return dxExecutable.orNull();
+  }
+
+  public void setDxExecutable(File dxExecutable) {
+    this.dxExecutable = Optional.fromNullable(dxExecutable);
+  }
+
+  @Input
+  public boolean isDebug() {
+    return debug;
+  }
+
+  public void setDebug(boolean debug) {
+    this.debug = debug;
+  }
+
+  @TaskAction
+  void exec() {
+    workerExecutor.submit(RunDx.class, config -> {
+      config.setIsolationMode(IsolationMode.NONE);
+      config.params(source.getFiles(), destination, dxExecutable, debug);
+    });
+  }
+
+  public static class RunDx implements Runnable {
+    private final Set<File> sources;
+    private final File destination;
+    private final Optional<File> dxExecutable;
+    private final boolean debug;
+
+    @Inject
+    public RunDx(Set<File> sources, File destination, Optional<File> dxExecutable, boolean debug) {
+      this.sources = sources;
+      this.destination = destination;
+      this.dxExecutable = dxExecutable;
+      this.debug = debug;
+    }
+
+    @Override
+    public void run() {
+      try {
+        List<String> command = new ArrayList<>();
+        command.add(dxExecutable.or(Utils::dxExecutable).getCanonicalPath());
+        command.add("--dex");
+        command.add("--output");
+        command.add(destination.getCanonicalPath());
+        if (debug) {
+          command.add("--debug");
+        }
+        for (File source : sources) {
+          command.add(source.getCanonicalPath());
+        }
+
+        Process dx = new ProcessBuilder(command).inheritIO().start();
+        int exitCode = dx.waitFor();
+        if (exitCode != 0) {
+          throw new RuntimeException("dx failed with code " + exitCode);
+        }
+      } catch (IOException e) {
+        throw new UncheckedIOException(e);
+      } catch (InterruptedException e) {
+        throw new RuntimeException(e);
+      }
+    }
+  }
+}