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/build.gradle b/build.gradle
index a68c7a9..01e8ea1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,8 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
+import dx.DexMergerTask
+import dx.DxTask
import net.ltgt.gradle.errorprone.CheckSeverity
import org.gradle.internal.os.OperatingSystem
+import smali.SmaliTask
import tasks.DownloadDependency
import tasks.GetJarsFromConfiguration
import utils.Utils
@@ -1513,7 +1516,7 @@
dependsOn "dex_example_kotlin_${name}"
def exampleOutputDir = file("build/test/examplesKotlin/" + name);
def dexPath = file("${exampleOutputDir}")
- task "dex_example_kotlin_${name}"(type: dx.Dx,
+ task "dex_example_kotlin_${name}"(type: DxTask,
dependsOn: "compile_example_kotlin_${name}") {
doFirst {
if (!dexPath.exists()) {
@@ -1595,7 +1598,7 @@
if (!dexPath.exists()) {
dexPath.mkdirs()
}
- task "dex_example_${name}"(type: dx.Dx, dependsOn: "jar_example_${name}") {
+ task "dex_example_${name}"(type: DxTask, dependsOn: "jar_example_${name}") {
source = files(tasks.getByPath("jar_example_${name}")).asFileTree
destination = dexPath
debug = debug
@@ -1622,14 +1625,14 @@
if (javaFiles.empty) {
if (!smaliFiles.empty) {
dependsOn "${taskName}_smali"
- task "${taskName}_smali"(type: smali.Smali) {
+ task "${taskName}_smali"(type: SmaliTask) {
source = smaliFiles
destination = destFile
}
}
} else {
dependsOn "${taskName}_dexmerger"
- task "${taskName}_smali"(type: smali.Smali) {
+ task "${taskName}_smali"(type: SmaliTask) {
source = smaliFiles
destination = intermediateFile
}
@@ -1646,12 +1649,12 @@
destinationDir = destDir
from fileTree(dir: destDir, include: 'Test.class')
}
- task "${taskName}_dx"(type: dx.Dx, dependsOn: "${taskName}_jar") {
+ task "${taskName}_dx"(type: DxTask, dependsOn: "${taskName}_jar") {
source = fileTree(dir: destDir, include: 'Test.jar')
destination = destDir
}
task "${taskName}_dexmerger"(
- type: dx.DexMerger, dependsOn: ["${taskName}_dx", "${taskName}_smali"]) {
+ type: DexMergerTask, dependsOn: ["${taskName}_dx", "${taskName}_smali"]) {
source = fileTree(dir: destDir, include: ["classes.dex", intermediateFileName])
destination = destFile
}
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
+}