Merge "Insert reads of all locals that are not live on all outgoing edges."
diff --git a/build.gradle b/build.gradle
index 7362238..82feb00 100644
--- a/build.gradle
+++ b/build.gradle
@@ -435,6 +435,20 @@
}
}
+task maindex(type: Jar) {
+ from sourceSets.main.output
+ baseName 'maindex'
+ manifest {
+ attributes 'Main-Class': 'com.android.tools.r8.GenerateMainDexList'
+ }
+ // In order to build without dependencies, pass the exclude_deps property using:
+ // gradle -Pexclude_deps maindex
+ if (!project.hasProperty('exclude_deps')) {
+ // Also include dependencies
+ from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
+ }
+}
+
task ExtractMarker(type: Jar) {
from sourceSets.main.output
baseName 'extractmarker'
diff --git a/src/main/java/com/android/tools/r8/BaseCommand.java b/src/main/java/com/android/tools/r8/BaseCommand.java
index 9ddbd9e..cbd8dd6 100644
--- a/src/main/java/com/android/tools/r8/BaseCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCommand.java
@@ -3,51 +3,33 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
-import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OutputMode;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
+/**
+ * Base class for commands and command builders for applications/tools which take an Android
+ * application (and a main-dex list) as input.
+ */
abstract class BaseCommand {
private final boolean printHelp;
private final boolean printVersion;
private final AndroidApp app;
- private final Path outputPath;
- private final OutputMode outputMode;
- private final CompilationMode mode;
- private final int minApiLevel;
BaseCommand(boolean printHelp, boolean printVersion) {
this.printHelp = printHelp;
this.printVersion = printVersion;
// All other fields are initialized with stub/invalid values.
this.app = null;
- this.outputPath = null;
- this.outputMode = OutputMode.Indexed;
- this.mode = null;
- this.minApiLevel = 0;
}
- BaseCommand(
- AndroidApp app,
- Path outputPath,
- OutputMode outputMode,
- CompilationMode mode,
- int minApiLevel) {
+ BaseCommand(AndroidApp app) {
assert app != null;
- assert mode != null;
- assert minApiLevel > 0;
this.app = app;
- this.outputPath = outputPath;
- this.outputMode = outputMode;
- this.mode = mode;
- this.minApiLevel = minApiLevel;
// Print options are not set.
printHelp = false;
printVersion = false;
@@ -69,52 +51,27 @@
// Internal access to the internal options.
abstract InternalOptions getInternalOptions();
- public Path getOutputPath() {
- return outputPath;
- }
-
- public CompilationMode getMode() {
- return mode;
- }
-
- public int getMinApiLevel() {
- return minApiLevel;
- }
-
- public OutputMode getOutputMode() {
- return outputMode;
- }
-
- abstract static class Builder<C extends BaseCommand, B extends Builder<C, B>> {
+ abstract public static class Builder<C extends BaseCommand, B extends Builder<C, B>> {
private boolean printHelp = false;
private boolean printVersion = false;
private final AndroidApp.Builder app;
- private Path outputPath = null;
- private OutputMode outputMode = OutputMode.Indexed;
- private CompilationMode mode;
- private int minApiLevel = Constants.DEFAULT_ANDROID_API;
- // Internal flag used by CompatDx to ignore dex files in archives.
- protected boolean ignoreDexInArchive = false;
-
- protected Builder(CompilationMode mode) {
- this(AndroidApp.builder(), mode, false);
+ protected Builder() {
+ this(AndroidApp.builder(), false);
}
- protected Builder(CompilationMode mode, boolean ignoreDexInArchive) {
- this(AndroidApp.builder(), mode, ignoreDexInArchive);
+ protected Builder(boolean ignoreDexInArchive) {
+ this(AndroidApp.builder(), ignoreDexInArchive);
}
// Internal constructor for testing.
Builder(AndroidApp app, CompilationMode mode) {
- this(AndroidApp.builder(app), mode, false);
+ this(AndroidApp.builder(app), false);
}
- private Builder(AndroidApp.Builder builder, CompilationMode mode, boolean ignoreDexInArchive) {
- assert mode != null;
+ protected Builder(AndroidApp.Builder builder, boolean ignoreDexInArchive) {
this.app = builder;
- this.mode = mode;
app.setIgnoreDexInArchive(ignoreDexInArchive);
}
@@ -181,52 +138,6 @@
return self();
}
- /** Get current compilation mode. */
- public CompilationMode getMode() {
- return mode;
- }
-
- /** Set compilation mode. */
- public B setMode(CompilationMode mode) {
- assert mode != null;
- this.mode = mode;
- return self();
- }
-
- /** Get the output path. Null if not set. */
- public Path getOutputPath() {
- return outputPath;
- }
-
- /** Get the output mode. */
- public OutputMode getOutputMode() {
- return outputMode;
- }
-
- /** Set an output path. Must be an existing directory or a zip file. */
- public B setOutputPath(Path outputPath) {
- this.outputPath = outputPath;
- return self();
- }
-
- /** Set an output mode. */
- public B setOutputMode(OutputMode outputMode) {
- this.outputMode = outputMode;
- return self();
- }
-
- /** Get the minimum API level (aka SDK version). */
- public int getMinApiLevel() {
- return minApiLevel;
- }
-
- /** Set the minimum required API level (aka SDK version). */
- public B setMinApiLevel(int minApiLevel) {
- assert minApiLevel > 0;
- this.minApiLevel = minApiLevel;
- return self();
- }
-
/**
* Add main-dex list files.
*
@@ -300,11 +211,7 @@
}
protected void validate() throws CompilationException {
- if (app.hasMainDexList() && outputMode == OutputMode.FilePerClass) {
- throw new CompilationException(
- "Option --main-dex-list cannot be used with --file-per-class");
- }
- FileUtils.validateOutputFile(outputPath);
+ // Currently does nothing.
}
}
}
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
new file mode 100644
index 0000000..5e8f975
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -0,0 +1,146 @@
+// Copyright (c) 2017, 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 com.android.tools.r8;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.OutputMode;
+import java.nio.file.Path;
+
+/**
+ * Base class for commands and command builders for compiler applications/tools which besides an
+ * Android application (and a main-dex list) also takes compilation output, compilation mode and
+ * min API level as input.
+ */
+abstract class BaseCompilerCommand extends BaseCommand {
+
+ private final Path outputPath;
+ private final OutputMode outputMode;
+ private final CompilationMode mode;
+ private final int minApiLevel;
+
+ BaseCompilerCommand(boolean printHelp, boolean printVersion) {
+ super(printHelp, printVersion);
+
+ this.outputPath = null;
+ this.outputMode = OutputMode.Indexed;
+ this.mode = null;
+ this.minApiLevel = 0;
+ }
+
+ BaseCompilerCommand(
+ AndroidApp app,
+ Path outputPath,
+ OutputMode outputMode,
+ CompilationMode mode,
+ int minApiLevel) {
+ super(app);
+ assert mode != null;
+ assert minApiLevel > 0;
+ this.outputPath = outputPath;
+ this.outputMode = outputMode;
+ this.mode = mode;
+ this.minApiLevel = minApiLevel;
+ }
+
+ public Path getOutputPath() {
+ return outputPath;
+ }
+
+ public CompilationMode getMode() {
+ return mode;
+ }
+
+ public int getMinApiLevel() {
+ return minApiLevel;
+ }
+
+ public OutputMode getOutputMode() {
+ return outputMode;
+ }
+
+ abstract public static class Builder<C extends BaseCompilerCommand, B extends Builder<C, B>>
+ extends BaseCommand.Builder<C, B> {
+
+ private Path outputPath = null;
+ private OutputMode outputMode = OutputMode.Indexed;
+ private CompilationMode mode;
+ private int minApiLevel = Constants.DEFAULT_ANDROID_API;
+
+ protected Builder(CompilationMode mode) {
+ this(AndroidApp.builder(), mode, false);
+ }
+
+ protected Builder(CompilationMode mode, boolean ignoreDexInArchive) {
+ this(AndroidApp.builder(), mode, ignoreDexInArchive);
+ }
+
+ // Internal constructor for testing.
+ Builder(AndroidApp app, CompilationMode mode) {
+ this(AndroidApp.builder(app), mode, false);
+ }
+
+ private Builder(AndroidApp.Builder builder, CompilationMode mode, boolean ignoreDexInArchive) {
+ super(builder, ignoreDexInArchive);
+ assert mode != null;
+ this.mode = mode;
+ }
+
+ /** Get current compilation mode. */
+ public CompilationMode getMode() {
+ return mode;
+ }
+
+ /** Set compilation mode. */
+ public B setMode(CompilationMode mode) {
+ assert mode != null;
+ this.mode = mode;
+ return self();
+ }
+
+ /** Get the output path. Null if not set. */
+ public Path getOutputPath() {
+ return outputPath;
+ }
+
+ /** Get the output mode. */
+ public OutputMode getOutputMode() {
+ return outputMode;
+ }
+
+ /** Set an output path. Must be an existing directory or a zip file. */
+ public B setOutputPath(Path outputPath) {
+ this.outputPath = outputPath;
+ return self();
+ }
+
+ /** Set an output mode. */
+ public B setOutputMode(OutputMode outputMode) {
+ this.outputMode = outputMode;
+ return self();
+ }
+
+ /** Get the minimum API level (aka SDK version). */
+ public int getMinApiLevel() {
+ return minApiLevel;
+ }
+
+ /** Set the minimum required API level (aka SDK version). */
+ public B setMinApiLevel(int minApiLevel) {
+ assert minApiLevel > 0;
+ this.minApiLevel = minApiLevel;
+ return self();
+ }
+
+ protected void validate() throws CompilationException {
+ super.validate();
+ if (getAppBuilder().hasMainDexList() && outputMode == OutputMode.FilePerClass) {
+ throw new CompilationException(
+ "Option --main-dex-list cannot be used with --file-per-class");
+ }
+ FileUtils.validateOutputFile(outputPath);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 3618ab5..5b2e5cf 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -27,12 +27,12 @@
* .build();
* </pre>
*/
-public class D8Command extends BaseCommand {
+public class D8Command extends BaseCompilerCommand {
/**
* Builder for constructing a D8Command.
*/
- public static class Builder extends BaseCommand.Builder<D8Command, Builder> {
+ public static class Builder extends BaseCompilerCommand.Builder<D8Command, Builder> {
private boolean intermediate = false;
diff --git a/src/main/java/com/android/tools/r8/DexSegments.java b/src/main/java/com/android/tools/r8/DexSegments.java
index 4e4a19b..c27deb5 100644
--- a/src/main/java/com/android/tools/r8/DexSegments.java
+++ b/src/main/java/com/android/tools/r8/DexSegments.java
@@ -7,11 +7,9 @@
import com.android.tools.r8.dex.Segment;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OutputMode;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Closer;
import java.io.IOException;
-import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
@@ -22,10 +20,6 @@
public static class Builder
extends BaseCommand.Builder<Command, Builder> {
- private Builder() {
- super(CompilationMode.RELEASE);
- }
-
@Override
Command.Builder self() {
return this;
@@ -38,8 +32,7 @@
return new Command(isPrintHelp());
}
validate();
- return new Command(
- getAppBuilder().build(), getOutputPath(), getOutputMode(), getMode(), getMinApiLevel());
+ return new Command(getAppBuilder().build());
}
}
@@ -77,13 +70,8 @@
}
}
- private Command(
- AndroidApp inputApp,
- Path outputPath,
- OutputMode outputMode,
- CompilationMode mode,
- int minApiLevel) {
- super(inputApp, outputPath, outputMode, mode, minApiLevel);
+ private Command(AndroidApp inputApp) {
+ super(inputApp);
}
private Command(boolean printHelp) {
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index a7bfdc9..bb72d5d 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -5,7 +5,6 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OutputMode;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
@@ -15,13 +14,13 @@
public class Disassemble {
public static class DisassembleCommand extends BaseCommand {
+ private final Path outputPath;
+
public static class Builder
extends BaseCommand.Builder<DisassembleCommand, DisassembleCommand.Builder> {
- private boolean useSmali = false;
- private Builder() {
- super(CompilationMode.RELEASE);
- }
+ private Path outputPath = null;
+ private boolean useSmali = false;
@Override
DisassembleCommand.Builder self() {
@@ -33,6 +32,15 @@
return this;
}
+ public Path getOutputPath() {
+ return outputPath;
+ }
+
+ public DisassembleCommand.Builder setOutputPath(Path outputPath) {
+ this.outputPath = outputPath;
+ return this;
+ }
+
public DisassembleCommand.Builder setUseSmali(boolean useSmali) {
this.useSmali = useSmali;
return this;
@@ -46,13 +54,7 @@
}
validate();
- return new DisassembleCommand(
- getAppBuilder().build(),
- getOutputPath(),
- getOutputMode(),
- getMode(),
- getMinApiLevel(),
- useSmali);
+ return new DisassembleCommand(getAppBuilder().build(), getOutputPath(), useSmali);
}
}
@@ -93,6 +95,9 @@
builder.setUseSmali(true);
} else if (arg.equals("--pg-map")) {
builder.setProguardMapFile(Paths.get(args[++i]));
+ } else if (arg.equals("--output")) {
+ String outputPath = args[++i];
+ builder.setOutputPath(Paths.get(outputPath));
} else {
if (arg.startsWith("--")) {
throw new CompilationException("Unknown option: " + arg);
@@ -102,23 +107,22 @@
}
}
- private DisassembleCommand(
- AndroidApp inputApp,
- Path outputPath,
- OutputMode outputMode,
- CompilationMode mode,
- int minApiLevel,
- boolean useSmali) {
- super(inputApp, outputPath, outputMode, mode, minApiLevel);
- assert getOutputMode() == OutputMode.Indexed : "Only regular mode is supported in R8";
+ private DisassembleCommand(AndroidApp inputApp, Path outputPath, boolean useSmali) {
+ super(inputApp);
+ this.outputPath = outputPath;
this.useSmali = useSmali;
}
private DisassembleCommand(boolean printHelp, boolean printVersion) {
super(printHelp, printVersion);
+ this.outputPath = null;
this.useSmali = false;
}
+ public Path getOutputPath() {
+ return outputPath;
+ }
+
public boolean useSmali() {
return useSmali;
}
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
index 0db183f..d798781 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -3,18 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
-import com.google.common.collect.ImmutableList;
-
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.Timing;
-
+import com.google.common.collect.ImmutableList;
import java.io.IOException;
-import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.ExecutionException;
@@ -24,10 +20,6 @@
public static class Builder
extends BaseCommand.Builder<ExtractMarker.Command, ExtractMarker.Command.Builder> {
- private Builder() {
- super(CompilationMode.RELEASE);
- }
-
@Override
ExtractMarker.Command.Builder self() {
return this;
@@ -40,8 +32,7 @@
return new ExtractMarker.Command(isPrintHelp());
}
validate();
- return new ExtractMarker.Command(
- getAppBuilder().build(), getOutputPath(), getOutputMode(), getMode(), getMinApiLevel());
+ return new ExtractMarker.Command(getAppBuilder().build());
}
}
@@ -79,13 +70,8 @@
}
}
- private Command(
- AndroidApp inputApp,
- Path outputPath,
- OutputMode outputMode,
- CompilationMode mode,
- int minApiLevel) {
- super(inputApp, outputPath, outputMode, mode, minApiLevel);
+ private Command(AndroidApp inputApp) {
+ super(inputApp);
}
private Command(boolean printHelp) {
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
new file mode 100644
index 0000000..44d523e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2017, 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 com.android.tools.r8;
+
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.MainDexListBuilder;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.shaking.RootSetBuilder;
+import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+public class GenerateMainDexList {
+ private static final String VERSION = "v0.2.0";
+ private final Timing timing = new Timing("maindex");
+ private final InternalOptions options;
+
+ private GenerateMainDexList(InternalOptions options) {
+ this.options = options;
+ }
+
+ private List<String> run(AndroidApp app) throws IOException, ExecutionException {
+ ExecutorService executor = ThreadUtils.getExecutorService(options);
+ DexApplication application = new ApplicationReader(app, options, timing).read(executor);
+ AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
+ RootSet mainDexRootSet =
+ new RootSetBuilder(application, appInfo, options.mainDexKeepRules).run(executor);
+ Set<DexType> mainDexBaseClasses = new Enqueuer(appInfo).traceMainDex(mainDexRootSet, timing);
+ Set<DexType> mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, application).run();
+
+ List<String> result = mainDexClasses.stream()
+ .map(c -> c.toSourceString().replace('.', '/') + ".class")
+ .sorted()
+ .collect(Collectors.toList());
+
+ if (options.printMainDexListFile != null) {
+ try (OutputStream mainDexOut = Files.newOutputStream(options.printMainDexListFile,
+ StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
+ PrintWriter writer = new PrintWriter(mainDexOut);
+ result.forEach(writer::println);
+ writer.flush();
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Main API entry for computing the main-dex list.
+ *
+ * The main-dex list is represented as a list of strings, each string specifies one class to
+ * keep in the primary dex file (<code>classes.dex</code>).
+ *
+ * A class is specified using the following format: "com/example/MyClass.class". That is
+ * "/" as separator between package components, and a trailing ".class".
+ *
+ * @param command main dex-list generator command.
+ * @return classes to keep in the primary dex file.
+ */
+ public static List<String> run(GenerateMainDexListCommand command)
+ throws IOException, ExecutionException {
+ ExecutorService executorService = ThreadUtils.getExecutorService(command.getInternalOptions());
+ try {
+ return run(command, executorService);
+ } finally {
+ executorService.shutdown();
+ }
+ }
+
+ /**
+ * Main API entry for computing the main-dex list.
+ *
+ * The main-dex list is represented as a list of strings, each string specifies one class to
+ * keep in the primary dex file (<code>classes.dex</code>).
+ *
+ * A class is specified using the following format: "com/example/MyClass.class". That is
+ * "/" as separator between package components, and a trailing ".class".
+ *
+ * @param command main dex-list generator command.
+ * @param executor executor service from which to get threads for multi-threaded processing.
+ * @return classes to keep in the primary dex file.
+ */
+ public static List<String> run(GenerateMainDexListCommand command, ExecutorService executor)
+ throws IOException, ExecutionException {
+ AndroidApp app = command.getInputApp();
+ InternalOptions options = command.getInternalOptions();
+ return new GenerateMainDexList(options).run(app);
+ }
+
+ public static void main(String[] args)
+ throws IOException, ProguardRuleParserException, CompilationException, ExecutionException {
+ GenerateMainDexListCommand.Builder builder = GenerateMainDexListCommand.parse(args);
+ GenerateMainDexListCommand command = builder.build();
+ if (command.isPrintHelp()) {
+ System.out.println(GenerateMainDexListCommand.USAGE_MESSAGE);
+ return;
+ }
+ if (command.isPrintVersion()) {
+ System.out.println("MainDexListGenerator " + VERSION);
+ return;
+ }
+ List<String> result = run(command);
+ if (command.getMainDexListOutputPath() == null) {
+ result.forEach(System.out::println);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
new file mode 100644
index 0000000..76d7d56
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -0,0 +1,200 @@
+// Copyright (c) 2017, 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 com.android.tools.r8;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.shaking.ProguardConfigurationParser;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.shaking.ProguardConfigurationSource;
+import com.android.tools.r8.shaking.ProguardConfigurationSourceFile;
+import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+public class GenerateMainDexListCommand extends BaseCommand {
+
+ private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
+ private final Path mainDexListOutput;
+ private final DexItemFactory factory;
+
+ /**
+ * Get the output path for the main-dex list. Null if not set.
+ */
+ public Path getMainDexListOutputPath() {
+ return mainDexListOutput;
+ }
+
+ public static class Builder extends BaseCommand.Builder<GenerateMainDexListCommand, Builder> {
+
+ private final DexItemFactory factory = new DexItemFactory();
+ private final List<ProguardConfigurationSource> mainDexRules = new ArrayList<>();
+ private Path mainDexListOutput = null;
+
+ @Override
+ GenerateMainDexListCommand.Builder self() {
+ return this;
+ }
+
+ /**
+ * Add proguard configuration file resources for automatic main dex list calculation.
+ */
+ public GenerateMainDexListCommand.Builder addMainDexRulesFiles(Path... paths) {
+ for (Path path : paths) {
+ mainDexRules.add(new ProguardConfigurationSourceFile(path));
+ }
+ return self();
+ }
+
+ /**
+ * Add proguard configuration file resources for automatic main dex list calculation.
+ */
+ public GenerateMainDexListCommand.Builder addMainDexRulesFiles(List<Path> paths) {
+ for (Path path : paths) {
+ mainDexRules.add(new ProguardConfigurationSourceFile(path));
+ }
+ return self();
+ }
+
+ /**
+ * Add proguard configuration for automatic main dex list calculation.
+ */
+ public GenerateMainDexListCommand.Builder addMainDexRules(List<String> lines) {
+ mainDexRules.add(new ProguardConfigurationSourceStrings(lines));
+ return self();
+ }
+
+ /**
+ * Get the output path for the main-dex list. Null if not set.
+ */
+ public Path getMainDexListOutputPath() {
+ return mainDexListOutput;
+ }
+
+ /**
+ * Set the output file for the main-dex list.
+ *
+ * If the file exists it will be overwritten.
+ */
+ public GenerateMainDexListCommand.Builder setMainDexListOutputPath(Path mainDexListOutputPath) {
+ mainDexListOutput = mainDexListOutputPath;
+ return self();
+ }
+
+
+ @Override
+ public GenerateMainDexListCommand build() throws CompilationException, IOException {
+ // If printing versions ignore everything else.
+ if (isPrintHelp() || isPrintVersion()) {
+ return new GenerateMainDexListCommand(isPrintHelp(), isPrintVersion());
+ }
+
+ validate();
+ ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
+ if (this.mainDexRules.isEmpty()) {
+ mainDexKeepRules = ImmutableList.of();
+ } else {
+ ProguardConfigurationParser parser = new ProguardConfigurationParser(factory);
+ try {
+ parser.parse(mainDexRules);
+ } catch (ProguardRuleParserException e) {
+ throw new CompilationException(e.getMessage(), e.getCause());
+ }
+ mainDexKeepRules = parser.getConfig().getRules();
+ }
+
+ return new GenerateMainDexListCommand(
+ factory,
+ getAppBuilder().build(),
+ mainDexKeepRules,
+ mainDexListOutput);
+ }
+ }
+
+ static final String USAGE_MESSAGE = String.join("\n", ImmutableList.of(
+ "Usage: maindex [options] <input-files>",
+ " where <input-files> are JAR files",
+ " and options are:",
+ " --main-dex-rules <file> # Proguard keep rules for classes to place in the",
+ " # primary dex file.",
+ " --main-dex-list <file> # List of classes to place in the primary dex file.",
+ " --main-dex-list-output <file> # Output the full main-dex list in <file>.",
+ " --version # Print the version.",
+ " --help # Print this message."));
+
+
+ public static GenerateMainDexListCommand.Builder builder() {
+ return new GenerateMainDexListCommand.Builder();
+ }
+
+ public static GenerateMainDexListCommand.Builder parse(String[] args)
+ throws CompilationException, IOException {
+ GenerateMainDexListCommand.Builder builder = builder();
+ parse(args, builder);
+ return builder;
+ }
+
+ private static void parse(String[] args, GenerateMainDexListCommand.Builder builder)
+ throws CompilationException, IOException {
+ for (int i = 0; i < args.length; i++) {
+ String arg = args[i].trim();
+ if (arg.length() == 0) {
+ continue;
+ } else if (arg.equals("--help")) {
+ builder.setPrintHelp(true);
+ } else if (arg.equals("--version")) {
+ builder.setPrintVersion(true);
+ } else if (arg.equals("--main-dex-rules")) {
+ builder.addMainDexRulesFiles(Paths.get(args[++i]));
+ } else if (arg.equals("--main-dex-list")) {
+ builder.addMainDexListFiles(Paths.get(args[++i]));
+ } else if (arg.equals("--main-dex-list-output")) {
+ builder.setMainDexListOutputPath(Paths.get(args[++i]));
+ } else {
+ if (arg.startsWith("--")) {
+ throw new CompilationException("Unknown option: " + arg);
+ }
+ builder.addProgramFiles(Paths.get(arg));
+ }
+ }
+ }
+
+ private GenerateMainDexListCommand(
+ DexItemFactory factory,
+ AndroidApp inputApp,
+ ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
+ Path mainDexListOutput) {
+ super(inputApp);
+ this.factory = factory;
+ this.mainDexKeepRules = mainDexKeepRules;
+ this.mainDexListOutput = mainDexListOutput;
+ }
+
+ private GenerateMainDexListCommand(boolean printHelp, boolean printVersion) {
+ super(printHelp, printVersion);
+ this.factory = new DexItemFactory();
+ this.mainDexKeepRules = ImmutableList.of();
+ this.mainDexListOutput = null;
+ }
+
+ @Override
+ InternalOptions getInternalOptions() {
+ InternalOptions internal = new InternalOptions(factory);
+ internal.mainDexKeepRules = mainDexKeepRules;
+ if (mainDexListOutput != null) {
+ internal.printMainDexListFile = mainDexListOutput;
+ }
+ internal.minimalMainDex = internal.debug;
+ internal.removeSwitchMaps = false;
+ internal.inlineAccessors = false;
+ return internal;
+ }
+}
+
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 318b3e6..890d570 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -24,9 +24,9 @@
import java.util.Optional;
import java.util.function.Consumer;
-public class R8Command extends BaseCommand {
+public class R8Command extends BaseCompilerCommand {
- public static class Builder extends BaseCommand.Builder<R8Command, Builder> {
+ public static class Builder extends BaseCompilerCommand.Builder<R8Command, Builder> {
private final List<ProguardConfigurationSource> mainDexRules = new ArrayList<>();
private Path mainDexListOutput = null;
@@ -96,7 +96,7 @@
}
/**
- * Add proguard configuration file resources for automatic main dex list calculation.
+ * Add proguard configuration for automatic main dex list calculation.
*/
public Builder addMainDexRules(List<String> lines) {
mainDexRules.add(new ProguardConfigurationSourceStrings(lines));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 8ae2147..b698fc5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -50,7 +50,9 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.JumpInstruction;
import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.Move;
import com.android.tools.r8.ir.code.MoveType;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewArrayFilledData;
@@ -61,6 +63,7 @@
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Switch;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.Xor;
import com.android.tools.r8.ir.conversion.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
import com.android.tools.r8.utils.InternalOptions;
@@ -1545,7 +1548,7 @@
// First rewrite zero comparison.
rewriteIfWithConstZero(block);
- if (simplifyKnownBooleanCondition(dominator, block)) {
+ if (simplifyKnownBooleanCondition(code, dominator, block)) {
continue;
}
@@ -1608,8 +1611,19 @@
*
* which can be replaced by a fallthrough and the phi value can be replaced
* with the boolean value itself.
+ *
+ * We also consider the forms:
+ *
+ * ifeqz booleanValue ifnez booleanValue
+ * / \ / \
+ * \ / \ /
+ * phi(1, 0) phi(0, 1)
+ *
+ * which can be replaced by a fallthrough and the phi value can be replaced
+ * by an xor instruction which is smaller.
*/
- private boolean simplifyKnownBooleanCondition(DominatorTree dominator, BasicBlock block) {
+ private boolean simplifyKnownBooleanCondition(IRCode code, DominatorTree dominator,
+ BasicBlock block) {
If theIf = block.exit().asIf();
Value testValue = theIf.inValues().get(0);
if (theIf.isZeroTest() && testValue.knownToBeBoolean()) {
@@ -1639,6 +1653,19 @@
falseNumber.isIntegerZero())) {
phi.replaceUsers(testValue);
deadPhis++;
+ } else if ((theIf.getType() == Type.NE &&
+ trueNumber.isIntegerZero() &&
+ falseNumber.isIntegerOne()) ||
+ (theIf.getType() == Type.EQ &&
+ trueNumber.isIntegerOne() &&
+ falseNumber.isIntegerZero())) {
+ Value newOutValue = code.createValue(phi.outType(), phi.getLocalInfo());
+ phi.replaceUsers(newOutValue);
+ Instruction newInstruction = new Xor(NumericType.INT, newOutValue, testValue,
+ trueNumber.isIntegerOne() ? trueValue : falseValue);
+ newInstruction.setBlock(phi.getBlock());
+ phi.getBlock().getInstructions().add(0, newInstruction);
+ deadPhis++;
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index a11d924..dfe10cf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
@@ -272,12 +273,17 @@
return true;
}
+ // We cannot invoke <init> on other values than |this| on Dalvik 4.4.4. Compute whether
+ // the receiver to the call was the this value at the call-site.
+ boolean receiverOfInnerCallIsThisOfOuter = invoke.asInvokeDirect().getReceiver().isThis();
+
// Don't allow inlining a constructor into a non-constructor if the first use of the
// un-initialized object is not an argument of an invoke of <init>.
// Also, we cannot inline a constructor if it initializes final fields, as such is only allowed
// from within a constructor of the corresponding class.
// Lastly, we can only inline a constructor, if its own <init> call is on the method's class. If
- // we inline into a constructor, calls to super.<init> are also OK.
+ // we inline into a constructor, calls to super.<init> are also OK if the receiver of the
+ // super.<init> call is the this argument.
InstructionIterator iterator = code.instructionIterator();
Instruction instruction = iterator.next();
// A constructor always has the un-initialized object as the first argument.
@@ -290,11 +296,17 @@
if (instruction.isInvokeDirect() && !seenSuperInvoke) {
DexMethod target = instruction.asInvokeDirect().getInvokedMethod();
seenSuperInvoke = appInfo.dexItemFactory.isConstructor(target);
+ boolean callOnSupertypeOfThisInConstructor =
+ methodHolder.isImmediateSubtypeOf(target.holder)
+ && instruction.asInvokeDirect().getReceiver() == unInitializedObject
+ && receiverOfInnerCallIsThisOfOuter
+ && methodIsConstructor;
if (seenSuperInvoke
// Calls to init on same class are always OK.
&& target.holder != methodHolder
- // If we are inlining into a constructor, calls to superclass init are OK.
- && (!methodHolder.isImmediateSubtypeOf(target.holder) || !methodIsConstructor)) {
+ // If we are inlining into a constructor, calls to superclass init are OK on the
+ // |this| value in the outer context.
+ && !callOnSupertypeOfThisInConstructor) {
return false;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 52653a7..bc93fcf 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -1018,9 +1018,6 @@
}
private int getSpillRegister(LiveIntervals intervals) {
- if (intervals.isArgumentInterval()) {
- return intervals.getSplitParent().getRegister();
- }
int registerNumber = nextUnusedRegisterNumber++;
maxRegisterNumber = registerNumber;
if (intervals.getType() == MoveType.WIDE) {
@@ -1572,17 +1569,19 @@
LiveIntervals unhandledInterval,
boolean needsRegisterPair,
int candidate) {
+ List<LiveIntervals> newInactive = new ArrayList<>();
Iterator<LiveIntervals> inactiveIterator = inactive.iterator();
while (inactiveIterator.hasNext()) {
LiveIntervals intervals = inactiveIterator.next();
if ((intervals.usesRegister(candidate) ||
(needsRegisterPair && intervals.usesRegister(candidate + 1))) &&
intervals.overlaps(unhandledInterval)) {
- // If these assertions trigger we have changed the way blocked parts of intervals
- // are handled. If we ever get intervals with fixed registers in here, we need
- // to split them before the first use in the same way that we do when spilling
- // overlapping active intervals.
- assert !intervals.isLinked() || intervals.isArgumentInterval();
+ if (intervals.isLinked() && !intervals.isArgumentInterval()) {
+ int nextUsePosition = intervals.firstUseAfter(unhandledInterval.getStart());
+ LiveIntervals split = intervals.splitBefore(nextUsePosition);
+ split.setRegister(intervals.getRegister());
+ newInactive.add(split);
+ }
if (intervals.getStart() > unhandledInterval.getStart()) {
// The inactive live intervals hasn't started yet. Clear the temporary register
// assignment and move back to unhandled for register reassignment.
@@ -1597,6 +1596,7 @@
}
}
}
+ inactive.addAll(newInactive);
}
private void spillOverlappingActiveIntervals(
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index 0d8c6b8..3c2210d 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -88,6 +88,9 @@
}
public boolean isRematerializable(LinearScanRegisterAllocator registerAllocator) {
+ if (value.isArgument()) {
+ return true;
+ }
// TODO(ager): rematerialize const string as well.
if (!value.isConstNumber()) {
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
index 4a21e8a..313497c 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
@@ -26,7 +26,7 @@
this.dst = dst;
this.src = LinearScanRegisterAllocator.NO_REGISTER;
this.type = type;
- assert definition.isConstInstruction();
+ assert definition.isConstInstruction() || definition.isArgument();
this.definition = definition;
}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
index 007aa6f..a669737 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
@@ -121,8 +121,14 @@
Instruction instruction;
Value to = new FixedRegisterValue(move.type, move.dst);
if (move.definition != null) {
- ConstNumber number = move.definition.asConstNumber();
- instruction = new ConstNumber(number.type, to, number.getRawValue());
+ if (move.definition.isArgument()) {
+ int argumentRegister = move.definition.outValue().getLiveIntervals().getRegister();
+ Value from = new FixedRegisterValue(move.type, argumentRegister);
+ instruction = new Move(to, from);
+ } else {
+ ConstNumber number = move.definition.asConstNumber();
+ instruction = new ConstNumber(number.type, to, number.getRawValue());
+ }
} else {
Value from = new FixedRegisterValue(move.type, valueMap.get(move.src));
instruction = new Move(to, from);
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
index 643a3c1..cd1d880 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
@@ -31,8 +31,6 @@
// The register allocator generating moves.
private LinearScanRegisterAllocator allocator;
// All registers below this number are arguments.
- // TODO(ager): Get rid of this field, we should deal with arguments and other values that can
- // be rematerialized differently.
private final int argumentRegisterLimit;
// Mapping from instruction numbers to the block that start with that instruction if any.
private final Map<Integer, BasicBlock> blockStartMap = new HashMap<>();
@@ -257,6 +255,10 @@
// disallowed at this point we know that argument registers do not change value and
// therefore we don't have to perform spill moves. Performing spill moves will also
// make art reject the code because it loses type information for the argument.
+ //
+ // TODO(ager): We are dealing with some of these moves as rematerialization. However,
+ // we are still generating actual moves back to the original argument register.
+ // We should get rid of this method and avoid generating the moves in the first place.
private void removeArgumentRestores(Set<SpillMove> moves) {
Iterator<SpillMove> moveIterator = moves.iterator();
while (moveIterator.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 5e7b42d..4dd1880 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -28,6 +28,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
@@ -572,5 +573,29 @@
return Collections
.unmodifiableMap(dependentNoShrinking.getOrDefault(item, Collections.emptyMap()));
}
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("RootSet");
+
+ builder.append("\nnoShrinking: " + noShrinking.size());
+ builder.append("\nnoOptimization: " + noOptimization.size());
+ builder.append("\nnoObfuscation: " + noObfuscation.size());
+ builder.append("\nreasonAsked: " + reasonAsked.size());
+ builder.append("\nkeepPackageName: " + keepPackageName.size());
+ builder.append("\ncheckDiscarded: " + checkDiscarded.size());
+ builder.append("\nnoSideEffects: " + noSideEffects.size());
+ builder.append("\nassumedValues: " + assumedValues.size());
+ builder.append("\ndependentNoShrinking: " + dependentNoShrinking.size());
+
+ builder.append("\n\nNo Shrinking:");
+ noShrinking.keySet().stream()
+ .sorted(Comparator.comparing(DexItem::toSourceString))
+ .forEach(a -> builder
+ .append("\n").append(a.toSourceString()).append(" ").append(noShrinking.get(a)));
+ builder.append("\n");
+ return builder.toString();
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 4d707a8..925ba5b 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -135,7 +135,7 @@
*/
protected AndroidApp compileWithR8(List<Class> classes, String proguardConfig)
throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
- return compileWithR8(readClasses(classes), writeTextToTempFile(proguardConfig));
+ return compileWithR8(readClasses(classes), proguardConfig);
}
/**
@@ -170,10 +170,22 @@
/**
* Compile an application with R8 using the supplied proguard configuration.
*/
+ protected AndroidApp compileWithR8(AndroidApp app, String proguardConfig)
+ throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
+ return compileWithR8(app, proguardConfig, null);
+ }
+
+ /**
+ * Compile an application with R8 using the supplied proguard configuration.
+ */
protected AndroidApp compileWithR8(
AndroidApp app, String proguardConfig, Consumer<InternalOptions> optionsConsumer)
throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
- return compileWithR8(app, writeTextToTempFile(proguardConfig), optionsConsumer);
+ R8Command command =
+ ToolHelper.prepareR8CommandBuilder(app)
+ .addProguardConfiguration(ImmutableList.of(proguardConfig))
+ .build();
+ return ToolHelper.runR8(command, optionsConsumer);
}
/**
@@ -194,7 +206,15 @@
* of the specified class.
*/
public String keepMainProguardConfiguration(Class clazz) {
- return "-keep public class " + clazz.getCanonicalName() + " {\n"
+ return keepMainProguardConfiguration(clazz.getCanonicalName());
+ }
+
+ /**
+ * Generate a Proguard configuration for keeping the "public static void main(String[])" method
+ * of the specified class.
+ */
+ public String keepMainProguardConfiguration(String clazz) {
+ return "-keep public class " + clazz + " {\n"
+ " public static void main(java.lang.String[]);\n"
+ "}\n";
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 9952f70..4f84303 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -603,13 +603,31 @@
.toArray(new String[0]));
}
+ public static ProcessResult forkGenerateMainDexList(Path dir, List<String> args1, String... args2)
+ throws IOException, InterruptedException {
+ List<String> args = new ArrayList<>();
+ args.addAll(args1);
+ args.addAll(Arrays.asList(args2));
+ return forkJava(dir, GenerateMainDexList.class, args);
+ }
+
+ public static ProcessResult forkGenerateMainDexList(Path dir, String... args)
+ throws IOException, InterruptedException {
+ return forkJava(dir, GenerateMainDexList.class, args);
+ }
+
private static ProcessResult forkJava(Path dir, Class clazz, String... args)
throws IOException, InterruptedException {
+ return forkJava(dir, clazz, Arrays.asList(args));
+ }
+
+ private static ProcessResult forkJava(Path dir, Class clazz, List<String> args)
+ throws IOException, InterruptedException {
List<String> command = new ImmutableList.Builder<String>()
.add(getJavaExecutable())
.add("-cp").add(System.getProperty("java.class.path"))
.add(clazz.getCanonicalName())
- .addAll(Arrays.asList(args))
+ .addAll(args)
.build();
return runProcess(new ProcessBuilder(command).directory(dir.toFile()));
}
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress63598979.java b/src/test/java/com/android/tools/r8/jasmin/Regress63598979.java
new file mode 100644
index 0000000..091a43a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress63598979.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2017, 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 com.android.tools.r8.jasmin;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class Regress63598979 extends JasminTestBase {
+
+ @Test
+ public void testSimplifyIf() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+ clazz.addStaticMethod("test1", ImmutableList.of("Z"), "Z",
+ ".limit stack 2",
+ ".limit locals 2",
+ " iload 0",
+ " ifne L2",
+ "L1:",
+ " iconst_1",
+ " goto L3",
+ "L2:",
+ " iconst_0",
+ "L3:",
+ " ireturn");
+
+ clazz.addStaticMethod("test2", ImmutableList.of("Z"), "Z",
+ ".limit stack 2",
+ ".limit locals 2",
+ " iload 0",
+ " ifne L2",
+ "L1:",
+ " iconst_0",
+ " goto L3",
+ "L2:",
+ " iconst_1",
+ "L3:",
+ " ireturn");
+
+ clazz.addStaticMethod("test3", ImmutableList.of("Z"), "Z",
+ ".limit stack 2",
+ ".limit locals 2",
+ " iload 0",
+ " ifeq L2",
+ "L1:",
+ " iconst_0",
+ " goto L3",
+ "L2:",
+ " iconst_1",
+ "L3:",
+ " ireturn");
+
+
+ clazz.addStaticMethod("test4", ImmutableList.of("Z"), "Z",
+ ".limit stack 2",
+ ".limit locals 2",
+ " iload 0",
+ " ifeq L2",
+ "L1:",
+ " iconst_1",
+ " goto L3",
+ "L2:",
+ " iconst_0",
+ "L3:",
+ " ireturn");
+
+ clazz.addMainMethod(
+ ".limit stack 2",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " iconst_0",
+ " invokestatic Test/test1(Z)Z",
+ " invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+ " invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " iconst_1",
+ " invokestatic Test/test1(Z)Z",
+ " invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+ " invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " iconst_0",
+ " invokestatic Test/test2(Z)Z",
+ " invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+ " invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " iconst_1",
+ " invokestatic Test/test2(Z)Z",
+ " invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+ " invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " iconst_0",
+ " invokestatic Test/test3(Z)Z",
+ " invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+ " invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " iconst_1",
+ " invokestatic Test/test3(Z)Z",
+ " invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+ " invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " iconst_0",
+ " invokestatic Test/test4(Z)Z",
+ " invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+ " invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " iconst_1",
+ " invokestatic Test/test4(Z)Z",
+ " invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+ " invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ " return");
+
+ String expected = runOnJava(builder, clazz.name);
+ String artResult = runOnArtD8(builder, clazz.name);
+ assertEquals(expected, artResult);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index c7e0c18..ba5092f 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -9,16 +9,18 @@
import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
import com.android.tools.r8.CompilationResult;
+import com.android.tools.r8.GenerateMainDexList;
+import com.android.tools.r8.GenerateMainDexListCommand;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
@@ -107,7 +109,6 @@
mainDexRules,
expectedMainDexList,
minSdk,
- R8Command.builder(),
(options) -> {
options.inlineAccessors = false;
});
@@ -120,48 +121,86 @@
Path mainDexRules,
Path expectedMainDexList,
int minSdk,
- R8Command.Builder builder,
Consumer<InternalOptions> optionsConsumer)
throws Throwable {
Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
Path inputJar = Paths.get(buildDir, packageName + JAR_EXTENSION);
- builder.setMinApiLevel(minSdk);
try {
- R8Command command = builder
+ // Build main-dex list using GenerateMainDexList.
+ GenerateMainDexListCommand.Builder mdlCommandBuilder = GenerateMainDexListCommand.builder();
+ GenerateMainDexListCommand command2 = mdlCommandBuilder
.addProgramFiles(inputJar)
- .addLibraryFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION),
- Paths.get(ToolHelper.getAndroidJar(minSdk)))
+ .addProgramFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
+ .addMainDexRulesFiles(mainDexRules)
+ .build();
+ List<String> mainDexGeneratorMainDexList =
+ GenerateMainDexList.run(command2).stream()
+ .map(this::mainDexStringToDescriptor)
+ .sorted()
+ .collect(Collectors.toList());
+
+ // Build main-dex list using R8.
+ R8Command.Builder r8CommandBuilder = R8Command.builder();
+ R8Command command = r8CommandBuilder
+ .setMinApiLevel(minSdk)
+ .addProgramFiles(inputJar)
+ .addProgramFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
+ .addLibraryFiles(Paths.get(ToolHelper.getAndroidJar(minSdk)))
.setOutputPath(out)
.addMainDexRulesFiles(mainDexRules)
.build();
CompilationResult result = ToolHelper.runR8WithFullResult(command, optionsConsumer);
- List<String> resultMainDexList =
+ List<String> r8MainDexList =
result.dexApplication.mainDexList.stream()
.filter(dexType -> isApplicationClass(dexType, result))
.map(dexType -> dexType.descriptor.toString())
+ .sorted()
.collect(Collectors.toList());
- Collections.sort(resultMainDexList);
+
+ // Check that both generated lists are the same as the reference list, except for lambda
+ // classes which are only produced when running R8.
String[] refList = new String(Files.readAllBytes(
expectedMainDexList), StandardCharsets.UTF_8).split("\n");
+ int nonLambdaOffset = 0;
for (int i = 0; i < refList.length; i++) {
String reference = refList[i].trim();
- String computed = resultMainDexList.get(i);
- if (reference.contains("-$$Lambda$")) {
- // For lambda classes we check that there is a lambda class for the right containing
- // class. However, we do not check the hash for the generated lambda class. The hash
- // changes for different compiler versions because different compiler versions generate
- // different lambda implementation method names.
- reference = reference.substring(0, reference.lastIndexOf('$'));
- computed = computed.substring(0, computed.lastIndexOf('$'));
+ checkSameMainDexEntry(reference, r8MainDexList.get(i));
+ // The main dex list generator does not do any lambda desugaring.
+ if (!isLambda(reference)) {
+ checkSameMainDexEntry(reference, mainDexGeneratorMainDexList.get(i - nonLambdaOffset));
+ } else {
+ nonLambdaOffset++;
}
- Assert.assertEquals(reference, computed);
}
} catch (ExecutionException e) {
throw e.getCause();
}
}
+ private boolean isLambda(String mainDexEntry) {
+ return mainDexEntry.contains("-$$Lambda$");
+ }
+
+ private String mainDexStringToDescriptor(String mainDexString) {
+ final String CLASS_EXTENSION = ".class";
+ Assert.assertTrue(mainDexString.endsWith(CLASS_EXTENSION));
+ return DescriptorUtils.getDescriptorFromClassBinaryName(
+ mainDexString.substring(0, mainDexString.length() - CLASS_EXTENSION.length()));
+ }
+
+ private void checkSameMainDexEntry(String reference, String computed) {
+ if (isLambda(reference)) {
+ // For lambda classes we check that there is a lambda class for the right containing
+ // class. However, we do not check the hash for the generated lambda class. The hash
+ // changes for different compiler versions because different compiler versions generate
+ // different lambda implementation method names.
+ reference = reference.substring(0, reference.lastIndexOf('$'));
+ computed = computed.substring(0, computed.lastIndexOf('$'));
+ }
+ Assert.assertEquals(reference, computed);
+ }
+
private boolean isApplicationClass(DexType dexType, CompilationResult result) {
DexClass clazz = result.appInfo.definitionFor(dexType);
return clazz != null && clazz.isProgramClass();
diff --git a/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java b/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
new file mode 100644
index 0000000..674124a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
@@ -0,0 +1,118 @@
+// Copyright (c) 2017, 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 com.android.tools.r8.smali;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class RemoveWriteOfUnusedFieldsTest extends SmaliTestBase {
+
+ @Test
+ public void unreadStaticFieldsRemoved() throws Exception {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ // All these static fields are set but never read.
+ builder.addStaticField("booleanField", "Z");
+ builder.addStaticField("byteField", "B");
+ builder.addStaticField("shortField", "S");
+ builder.addStaticField("intField", "I");
+ builder.addStaticField("longField", "J");
+ builder.addStaticField("floatField", "F");
+ builder.addStaticField("doubleField", "D");
+ builder.addStaticField("charField", "C");
+ builder.addStaticField("objectField", "Ljava/lang/Object;");
+ builder.addStaticField("stringField", "Ljava/lang/String;");
+ builder.addStaticField("testField", "LTest;");
+
+ builder.addStaticMethod("void", "test", ImmutableList.of(),
+ 2,
+ "const v0, 0",
+ "sput-byte v0, LTest;->booleanField:Z",
+ "sput-byte v0, LTest;->byteField:B",
+ "sput-short v0, LTest;->shortField:S",
+ "sput v0, LTest;->intField:I",
+ "sput v0, LTest;->floatField:F",
+ "sput-char v0, LTest;->charField:C",
+ "sput-object v0, LTest;->objectField:Ljava/lang/Object;",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "sput-object v0, LTest;->testField:LTest;",
+ "const-wide v0, 0",
+ "sput-wide v0, LTest;->longField:J",
+ "sput-wide v0, LTest;->doubleField:D",
+ "return-void");
+
+ builder.addMainMethod(
+ 0,
+ " invoke-static { }, LTest;->test()V",
+ " return-void ");
+
+ AndroidApp app = compileWithR8(
+ AndroidApp.fromDexProgramData(builder.compile()),
+ keepMainProguardConfiguration("Test"),
+ options -> options.inlineAccessors = false);
+
+ DexInspector inspector = new DexInspector(app);
+ MethodSubject method = inspector.clazz("Test").method("void", "test", ImmutableList.of());
+ DexCode code = method.getMethod().getCode().asDexCode();
+ assertTrue(code.isEmptyVoidMethod());
+ }
+
+ @Test
+ public void unreadInstanceFieldsRemoved() throws Exception {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ // All these instance fields are set but never read.
+ builder.addInstanceField("booleanField", "Z");
+ builder.addInstanceField("byteField", "B");
+ builder.addInstanceField("shortField", "S");
+ builder.addInstanceField("intField", "I");
+ builder.addInstanceField("longField", "J");
+ builder.addInstanceField("floatField", "F");
+ builder.addInstanceField("doubleField", "D");
+ builder.addInstanceField("charField", "C");
+ builder.addInstanceField("objectField", "Ljava/lang/Object;");
+ builder.addInstanceField("stringField", "Ljava/lang/String;");
+ builder.addInstanceField("testField", "LTest;");
+
+ builder.addInstanceMethod("void", "test", ImmutableList.of(),
+ 2,
+ "const v0, 0",
+ "iput-byte v0, p0, LTest;->booleanField:Z",
+ "iput-byte v0, p0, LTest;->byteField:B",
+ "iput-short v0, p0, LTest;->shortField:S",
+ "iput v0, p0, LTest;->intField:I",
+ "iput v0, p0, LTest;->floatField:F",
+ "iput-char v0, p0, LTest;->charField:C",
+ "iput-object v0, p0, LTest;->objectField:Ljava/lang/Object;",
+ "iput-object v0, p0, LTest;->stringField:Ljava/lang/String;",
+ "iput-object v0, p0, LTest;->testField:LTest;",
+ "const-wide v0, 0",
+ "iput-wide v0, p0, LTest;->longField:J",
+ "iput-wide v0, p0, LTest;->doubleField:D",
+ "return-void");
+
+ builder.addMainMethod(
+ 1,
+ " new-instance v0, LTest;",
+ " invoke-virtual { v0 }, LTest;->test()V",
+ " return-void ");
+
+ AndroidApp app = compileWithR8(
+ AndroidApp.fromDexProgramData(builder.compile()),
+ keepMainProguardConfiguration("Test"),
+ options -> options.inlineAccessors = false);
+
+ DexInspector inspector = new DexInspector(app);
+ MethodSubject method = inspector.clazz("Test").method("void", "test", ImmutableList.of());
+ DexCode code = method.getMethod().getCode().asDexCode();
+ assertTrue(code.isEmptyVoidMethod());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index e2006d0..56059f6 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -24,7 +24,6 @@
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.DexInspector;
@@ -235,6 +234,45 @@
addStaticField(name, type, null);
}
+ public void addInstanceField(String name, String type) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(".field ");
+ builder.append(name);
+ builder.append(":");
+ builder.append(type);
+ getSource(currentClassName).add(builder.toString());
+ }
+
+ private MethodSignature addMethod(String flags, String returnType, String name,
+ List<String> parameters, int locals, String code) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(".method ");
+ if (flags != null && flags.length() > 0) {
+ builder.append(flags);
+ builder.append(" ");
+ }
+ builder.append(name);
+ builder.append("(");
+ for (String parameter : parameters) {
+ builder.append(DescriptorUtils.javaTypeToDescriptor(parameter));
+ }
+ builder.append(")");
+ builder.append(DescriptorUtils.javaTypeToDescriptor(returnType));
+ builder.append("\n");
+ if (locals >= 0) {
+ builder.append(".locals ");
+ builder.append(locals);
+ builder.append("\n\n");
+ assert code != null;
+ builder.append(code);
+ } else {
+ assert code == null;
+ }
+ builder.append(".end method");
+ getSource(currentClassName).add(builder.toString());
+ return new MethodSignature(currentClassName, name, returnType, parameters);
+ }
+
public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
int locals, String... instructions) {
StringBuilder builder = new StringBuilder();
@@ -266,43 +304,27 @@
private MethodSignature addStaticMethod(String flags, String returnType, String name,
List<String> parameters, int locals, String code) {
StringBuilder builder = new StringBuilder();
- builder.append(".method public static ");
- if (flags != null && flags.length() > 0) {
- builder.append(flags);
- builder.append(" ");
- }
- builder.append(name);
- builder.append("(");
- for (String parameter : parameters) {
- builder.append(DescriptorUtils.javaTypeToDescriptor(parameter));
- }
- builder.append(")");
- builder.append(DescriptorUtils.javaTypeToDescriptor(returnType));
- builder.append("\n");
- builder.append(".locals ");
- builder.append(locals);
- builder.append("\n\n");
- builder.append(code);
- builder.append(".end method");
- getSource(currentClassName).add(builder.toString());
- return new MethodSignature(currentClassName, name, returnType, parameters);
+ return addMethod("public static " + flags, returnType, name, parameters, locals, code);
}
public MethodSignature addAbstractMethod(
String returnType, String name, List<String> parameters) {
+ return addMethod("public abstract", returnType, name, parameters, -1, null);
+ }
+
+ public MethodSignature addInstanceMethod(String returnType, String name, List<String> parameters,
+ int locals, String... instructions) {
StringBuilder builder = new StringBuilder();
- builder.append(".method public abstract ");
- builder.append(name);
- builder.append("(");
- for (String parameter : parameters) {
- builder.append(DescriptorUtils.javaTypeToDescriptor(parameter));
+ for (String instruction : instructions) {
+ builder.append(instruction);
+ builder.append("\n");
}
- builder.append(")");
- builder.append(DescriptorUtils.javaTypeToDescriptor(returnType));
- builder.append("\n");
- builder.append(".end method");
- getSource(currentClassName).add(builder.toString());
- return new MethodSignature(currentClassName, name, returnType, parameters);
+ return addInstanceMethod(returnType, name, parameters, locals, builder.toString());
+ }
+
+ public MethodSignature addInstanceMethod(String returnType, String name, List<String> parameters,
+ int locals, String code) {
+ return addMethod("public", returnType, name, parameters, locals, code);
}
public MethodSignature addMainMethod(int locals, String... instructions) {
diff --git a/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java b/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
new file mode 100644
index 0000000..64e7a5b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
@@ -0,0 +1,144 @@
+// Copyright (c) 2017, 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 com.android.tools.r8.utils;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.GenerateMainDexListCommand;
+import com.android.tools.r8.ToolHelper;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+public class GenerateMainDexListCommandTest {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Rule
+ public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+ @Test
+ public void emptyCommand() throws Throwable {
+ verifyEmptyCommand(GenerateMainDexListCommand.builder().build());
+ verifyEmptyCommand(parse());
+ verifyEmptyCommand(parse(""));
+ verifyEmptyCommand(parse("", ""));
+ verifyEmptyCommand(parse(" "));
+ verifyEmptyCommand(parse(" ", " "));
+ verifyEmptyCommand(parse("\t"));
+ verifyEmptyCommand(parse("\t", "\t"));
+ }
+
+ private void verifyEmptyCommand(GenerateMainDexListCommand command) throws IOException {
+ assertEquals(0, ToolHelper.getApp(command).getDexProgramResources().size());
+ assertEquals(0, ToolHelper.getApp(command).getClassProgramResources().size());
+ assertFalse(ToolHelper.getApp(command).hasMainDexListResources());
+ }
+
+ // Add the jars used in the com.android.tools.r8.maindexlist.MainDexTracingTest test.
+ private void addInputJarsToCommandLine(List<String> args) {
+ args.add(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "multidex001" + JAR_EXTENSION)
+ .toAbsolutePath().toString());
+ args.add(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION)
+ .toAbsolutePath().toString());
+ }
+
+ // Add main-dex rules used in the com.android.tools.r8.maindexlist.MainDexTracingTest test.
+ private void addMainDexRuleToCommandLine(List<String> args) {
+ args.add("--main-dex-rules");
+ args.add(Paths.get(ToolHelper.EXAMPLES_DIR, "multidex", "main-dex-rules.txt")
+ .toAbsolutePath().toString());
+ }
+
+ @Test
+ public void defaultOutIsCwd() throws Throwable {
+ Path working = temp.getRoot().toPath();
+ String mainDexListOutput = "main-dex-list.txt";
+ Path output = working.resolve(mainDexListOutput);
+ assertFalse(Files.exists(output));
+ List<String> args = new ArrayList<>();
+ addInputJarsToCommandLine(args);
+ addMainDexRuleToCommandLine(args);
+ assertEquals(0, ToolHelper.forkGenerateMainDexList(
+ working, args, "--main-dex-list-output", mainDexListOutput).exitCode);
+ assertTrue(Files.exists(output));
+ assertTrue(Files.size(output) > 0);
+ }
+
+ @Test
+ public void validOutputPath() throws Throwable {
+ Path existingFile = temp.getRoot().toPath().resolve("existing_output");
+ try (OutputStream existingFileOut = Files.newOutputStream(existingFile,
+ StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
+ PrintWriter writer = new PrintWriter(existingFileOut);
+ writer.println("Hello, world!");
+ writer.flush();
+ }
+ Path nonExistingFile = temp.getRoot().toPath().resolve("non_existing_output");
+ assertEquals(
+ existingFile,
+ GenerateMainDexListCommand.builder().setMainDexListOutputPath(existingFile).build()
+ .getMainDexListOutputPath());
+ assertEquals(
+ nonExistingFile,
+ GenerateMainDexListCommand.builder().setMainDexListOutputPath(nonExistingFile).build()
+ .getMainDexListOutputPath());
+ assertEquals(
+ existingFile,
+ parse("--main-dex-list-output", existingFile.toString()).getMainDexListOutputPath());
+ assertEquals(
+ nonExistingFile,
+ parse("--main-dex-list-output", nonExistingFile.toString()).getMainDexListOutputPath());
+ }
+
+ @Test
+ public void nonExistingOutputFileInNonExistingDir() throws Throwable {
+ Path nonExistingFileInNonExistingDir =
+ temp.getRoot().toPath().resolve("a/path/that/does/not/exist");
+ assertEquals(
+ nonExistingFileInNonExistingDir,
+ GenerateMainDexListCommand.builder()
+ .setMainDexListOutputPath(nonExistingFileInNonExistingDir).build()
+ .getMainDexListOutputPath());
+ assertEquals(
+ nonExistingFileInNonExistingDir,
+ parse("--main-dex-list-output",
+ nonExistingFileInNonExistingDir.toString()).getMainDexListOutputPath());
+ }
+
+ @Test
+ public void mainDexRules() throws Throwable {
+ Path mainDexRules1 = temp.newFile("main-dex-1.rules").toPath();
+ Path mainDexRules2 = temp.newFile("main-dex-2.rules").toPath();
+ parse("--main-dex-rules", mainDexRules1.toString());
+ parse(
+ "--main-dex-rules", mainDexRules1.toString(), "--main-dex-rules", mainDexRules2.toString());
+ }
+
+ @Test
+ public void mainDexList() throws Throwable {
+ Path mainDexList1 = temp.newFile("main-dex-list-1.txt").toPath();
+ Path mainDexList2 = temp.newFile("main-dex-list-2.txt").toPath();
+ parse("--main-dex-list", mainDexList1.toString());
+ parse("--main-dex-list", mainDexList1.toString(), "--main-dex-list", mainDexList2.toString());
+ }
+
+ private GenerateMainDexListCommand parse(String... args) throws Throwable {
+ return GenerateMainDexListCommand.parse(args).build();
+ }
+}
diff --git a/third_party/kotlin.tar.gz.sha1 b/third_party/kotlin.tar.gz.sha1
index 942c691..f9d8e24 100644
--- a/third_party/kotlin.tar.gz.sha1
+++ b/third_party/kotlin.tar.gz.sha1
@@ -1 +1 @@
-21b6244cb0a5bca1f1d046d00540a610c02cd714
\ No newline at end of file
+28f3708659a1daa879bc8c707da54e3617a2070f
\ No newline at end of file
diff --git a/tools/notify.py b/tools/notify.py
new file mode 100644
index 0000000..f7ae50e
--- /dev/null
+++ b/tools/notify.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+# Copyright (c) 2017, 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.
+
+try:
+ import gi
+ from gi.repository import Notify
+ Notify.init("R8 build tools")
+
+ def notify(message):
+ try:
+ Notify.Notification.new("R8 build tools", message).show()
+ except:
+ return
+
+except ImportError:
+ def notify(message):
+ return
diff --git a/tools/test.py b/tools/test.py
index ab4b0d6..c0a1e02 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -14,6 +14,7 @@
import sys
import utils
import uuid
+import notify
ALL_ART_VMS = ["default", "7.0.0", "6.0.1", "5.1.1"]
BUCKET = 'r8-test-results'
@@ -149,4 +150,9 @@
return return_code
if __name__ == '__main__':
- sys.exit(Main())
+ return_code = Main()
+ if return_code != 0:
+ notify.notify("Tests failed.")
+ else:
+ notify.notify("Tests passed.")
+ sys.exit(return_code)