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/DexMerger.java b/buildSrc/src/main/java/dx/DexMerger.java
deleted file mode 100644
index 6303009..0000000
--- a/buildSrc/src/main/java/dx/DexMerger.java
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright (c) 2016, 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 java.io.File;
-import java.io.IOException;
-import org.gradle.api.Action;
-import org.gradle.api.DefaultTask;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.file.FileTree;
-import org.gradle.api.tasks.InputFiles;
-import org.gradle.api.tasks.OutputDirectory;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.process.ExecSpec;
-import utils.Utils;
-
-public class DexMerger extends DefaultTask {
-
- private FileCollection source;
- private File destination;
- private File dexMergerExecutable;
- private boolean debug;
-
- @InputFiles
- public FileCollection getSource() {
- return source;
- }
-
- public void setSource(FileCollection source) {
- this.source = source;
- }
-
- @OutputFile
- public File getDestination() {
- return destination;
- }
-
- public void setDestination(File destination) {
- this.destination = destination;
- }
-
- public File getDexMergerExecutable() {
- return dexMergerExecutable;
- }
-
- public void setDexMergerExecutable(File dexMergerExecutable) {
- this.dexMergerExecutable = dexMergerExecutable;
- }
-
- @TaskAction
- void exec() {
- getProject().exec(new Action<ExecSpec>() {
- @Override
- public void execute(ExecSpec execSpec) {
- try {
- if (dexMergerExecutable == null) {
- dexMergerExecutable = Utils.dexMergerExecutable();
- }
- execSpec.setExecutable(dexMergerExecutable);
- execSpec.args(destination.getCanonicalPath());
- execSpec.args(source.getFiles());
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
- });
- }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/java/dx/DexMergerTask.java b/buildSrc/src/main/java/dx/DexMergerTask.java
new file mode 100644
index 0000000..735afa7
--- /dev/null
+++ b/buildSrc/src/main/java/dx/DexMergerTask.java
@@ -0,0 +1,110 @@
+// 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.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import javax.inject.Inject;
+import org.gradle.api.Action;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.process.ExecSpec;
+import org.gradle.workers.IsolationMode;
+import org.gradle.workers.WorkerConfiguration;
+import org.gradle.workers.WorkerExecutor;
+import utils.Utils;
+
+public class DexMergerTask extends DefaultTask {
+
+ private final WorkerExecutor workerExecutor;
+
+ private FileCollection source;
+ private File destination;
+ private Optional<File> dexMergerExecutable = Optional.absent(); // Worker API cannot handle null.
+
+ @Inject
+ public DexMergerTask(WorkerExecutor workerExecutor) {
+ this.workerExecutor = workerExecutor;
+ }
+
+ @InputFiles
+ public FileCollection getSource() {
+ return source;
+ }
+
+ public void setSource(FileCollection source) {
+ this.source = source;
+ }
+
+ @OutputFile
+ public File getDestination() {
+ return destination;
+ }
+
+ public void setDestination(File destination) {
+ this.destination = destination;
+ }
+
+ @InputFile
+ @org.gradle.api.tasks.Optional
+ public File getDexMergerExecutable() {
+ return dexMergerExecutable.orNull();
+ }
+
+ public void setDexMergerExecutable(File dexMergerExecutable) {
+ this.dexMergerExecutable = Optional.fromNullable(dexMergerExecutable);
+ }
+
+ @TaskAction
+ void exec() {
+ workerExecutor.submit(RunDexMerger.class, config -> {
+ config.setIsolationMode(IsolationMode.NONE);
+ config.params(source.getFiles(), destination, dexMergerExecutable);
+ });
+ }
+
+ public static class RunDexMerger implements Runnable {
+ private final Set<File> sources;
+ private final File destination;
+ private final Optional<File> dexMergerExecutable;
+
+ @Inject
+ public RunDexMerger(Set<File> sources, File destination, Optional<File> dexMergerExecutable) {
+ this.sources = sources;
+ this.destination = destination;
+ this.dexMergerExecutable = dexMergerExecutable;
+ }
+
+ @Override
+ public void run() {
+ try {
+ List<String> command = new ArrayList<>();
+ command.add(dexMergerExecutable.or(Utils::dexMergerExecutable).getCanonicalPath());
+ command.add(destination.getCanonicalPath());
+ for (File source : sources) {
+ command.add(source.getCanonicalPath());
+ }
+
+ Process dexMerger = new ProcessBuilder(command).inheritIO().start();
+ int exitCode = dexMerger.waitFor();
+ if (exitCode != 0) {
+ throw new RuntimeException("Dex merger failed with code " + exitCode);
+ }
+ } catch (IOException e) {
+ throw new java.io.UncheckedIOException(e);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/dx/Dx.java b/buildSrc/src/main/java/dx/Dx.java
deleted file mode 100644
index b03ad0f..0000000
--- a/buildSrc/src/main/java/dx/Dx.java
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2016, 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 java.io.File;
-import java.io.IOException;
-import org.gradle.api.Action;
-import org.gradle.api.DefaultTask;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.file.FileTree;
-import org.gradle.api.tasks.InputFiles;
-import org.gradle.api.tasks.OutputDirectory;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.process.ExecSpec;
-import utils.Utils;
-
-public class Dx extends DefaultTask {
-
- private FileCollection source;
- private File destination;
- private File dxExecutable;
- private boolean debug;
-
- @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;
- }
-
- public File getDxExecutable() {
- return dxExecutable;
- }
-
- public void setDxExecutable(File dxExecutable) {
- this.dxExecutable = dxExecutable;
- }
-
- public boolean isDebug() {
- return debug;
- }
-
- public void setDebug(boolean debug) {
- this.debug = debug;
- }
-
- @TaskAction
- void exec() {
- getProject().exec(new Action<ExecSpec>() {
- @Override
- public void execute(ExecSpec execSpec) {
- try {
- if (dxExecutable == null) {
- String dxExecutableName = Utils.toolsDir().equals("windows") ? "dx.bat" : "dx";
- dxExecutable = new File("tools/" + Utils.toolsDir() + "/dx/bin/" + dxExecutableName);
- }
- execSpec.setExecutable(dxExecutable);
- execSpec.args("--dex");
- execSpec.args("--output");
- execSpec.args(destination.getCanonicalPath());
- if (isDebug()) {
- execSpec.args("--debug");
- }
- execSpec.args(source.getFiles());
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
- });
- }
-}
\ No newline at end of file
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);
+ }
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/smali/Smali.java b/buildSrc/src/main/java/smali/Smali.java
deleted file mode 100644
index 7a5241e..0000000
--- a/buildSrc/src/main/java/smali/Smali.java
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (c) 2016, 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 java.io.File;
-import java.io.IOException;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.gradle.api.DefaultTask;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.api.file.FileTree;
-import org.gradle.api.tasks.InputFiles;
-import org.gradle.api.tasks.OutputDirectory;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.TaskAction;
-
-public class Smali extends DefaultTask {
-
- private FileTree source;
- private File destination;
- private File smaliScript;
-
- @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;
- }
-
- public File getSmaliScript() {
- return smaliScript;
- }
-
- public void setSmaliScript(File smaliScript) {
- this.smaliScript = smaliScript;
- }
-
- @TaskAction
- void exec() {
- try {
- List<String> fileNames = source.getFiles().stream().map(file -> file.toString())
- .collect(Collectors.toList());
- org.jf.smali.SmaliOptions options = new org.jf.smali.SmaliOptions();
- options.outputDexFile = destination.getCanonicalPath().toString();
- org.jf.smali.Smali.assemble(options, fileNames);
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
-}
\ No newline at end of file
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);
+ }
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/utils/Utils.java b/buildSrc/src/main/java/utils/Utils.java
index 7158e80..7843edd 100644
--- a/buildSrc/src/main/java/utils/Utils.java
+++ b/buildSrc/src/main/java/utils/Utils.java
@@ -17,8 +17,13 @@
}
}
+ public static File dxExecutable() {
+ String dxExecutableName = Utils.toolsDir().equals("windows") ? "dx.bat" : "dx";
+ return new File("tools/" + Utils.toolsDir() + "/dx/bin/" + dxExecutableName);
+ }
+
public static File dexMergerExecutable() {
String executableName = Utils.toolsDir().equals("windows") ? "dexmerger.bat" : "dexmerger";
return new File("tools/" + Utils.toolsDir() + "/dx/bin/" + executableName);
}
-}
\ No newline at end of file
+}