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/smali/SmaliTask.java b/buildSrc/src/main/java/smali/SmaliTask.java
new file mode 100644
index 0000000..845592e
--- /dev/null
+++ b/buildSrc/src/main/java/smali/SmaliTask.java
@@ -0,0 +1,84 @@
+// 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 smali;
+
+import static java.util.stream.Collectors.toList;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.List;
+import java.util.Set;
+import javax.inject.Inject;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.workers.IsolationMode;
+import org.gradle.workers.WorkerExecutor;
+import org.jf.smali.Smali;
+import org.jf.smali.SmaliOptions;
+
+public class SmaliTask extends DefaultTask {
+
+  private final WorkerExecutor workerExecutor;
+
+  private FileTree source;
+  private File destination;
+
+  @Inject
+  public SmaliTask(WorkerExecutor workerExecutor) {
+    this.workerExecutor = workerExecutor;
+  }
+
+  @InputFiles
+  public FileTree getSource() {
+    return source;
+  }
+
+  public void setSource(FileTree source) {
+    this.source = source;
+  }
+
+  @OutputFile
+  public File getDestination() {
+    return destination;
+  }
+
+  public void setDestination(File destination) {
+    this.destination = destination;
+  }
+
+  @TaskAction
+  void exec() {
+    workerExecutor.submit(RunSmali.class, config -> {
+      config.setIsolationMode(IsolationMode.NONE);
+      config.params(source.getFiles(), destination);
+    });
+  }
+
+  public static class RunSmali implements Runnable {
+    private final Set<File> sources;
+    private final File destination;
+
+    @Inject
+    public RunSmali(Set<File> sources, File destination) {
+      this.sources = sources;
+      this.destination = destination;
+    }
+
+    @Override
+    public void run() {
+      try {
+        List<String> fileNames = sources.stream().map(File::toString).collect(toList());
+        SmaliOptions options = new SmaliOptions();
+        options.outputDexFile = destination.getCanonicalPath();
+        Smali.assemble(options, fileNames);
+      } catch (IOException e) {
+        throw new UncheckedIOException(e);
+      }
+    }
+  }
+}