aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Gavrilovic <gavra@google.com>2017-09-08 11:40:54 +0100
committerIvan Gavrilovic <gavra@google.com>2017-09-08 11:40:54 +0100
commit4de512e03895a6a0807f3995e4fa4bd7023ebaf9 (patch)
tree37c01c8ecfcf31769cfd00e963d83c7b9dd7c133
parente2e57e78f1478d15d7aedb565f1f141907185918 (diff)
parent0524d7eaff7f277fa13c0568f75498642a21089e (diff)
downloadr8-4de512e03895a6a0807f3995e4fa4bd7023ebaf9.tar.gz
Merge remote-tracking branch 'goog/upstream-d8-0.1' into studio-master-canary
* goog/upstream-d8-0.1: Version 0.1.11.
-rw-r--r--build.gradle14
-rw-r--r--src/main/java/com/android/tools/r8/BaseCommand.java117
-rw-r--r--src/main/java/com/android/tools/r8/BaseCompilerCommand.java146
-rw-r--r--src/main/java/com/android/tools/r8/D8.java2
-rw-r--r--src/main/java/com/android/tools/r8/D8Command.java8
-rw-r--r--src/main/java/com/android/tools/r8/DexSegments.java18
-rw-r--r--src/main/java/com/android/tools/r8/Disassemble.java46
-rw-r--r--src/main/java/com/android/tools/r8/ExtractMarker.java22
-rw-r--r--src/main/java/com/android/tools/r8/GenerateMainDexList.java124
-rw-r--r--src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java200
-rw-r--r--src/main/java/com/android/tools/r8/R8.java2
-rw-r--r--src/main/java/com/android/tools/r8/R8Command.java54
-rw-r--r--src/main/java/com/android/tools/r8/compatdx/CompatDxCommandBuilder.java2
-rw-r--r--src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java38
-rw-r--r--src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSource.java14
-rw-r--r--src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceFile.java37
-rw-r--r--src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java31
-rw-r--r--src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java25
-rw-r--r--src/test/java/com/android/tools/r8/ToolHelper.java24
-rw-r--r--src/test/java/com/android/tools/r8/compatdx/CompatDxTests.java63
-rw-r--r--src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java2
-rw-r--r--src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java77
-rw-r--r--src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java14
-rw-r--r--src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java144
24 files changed, 968 insertions, 256 deletions
diff --git a/build.gradle b/build.gradle
index 63513ce32..c6fdf8ded 100644
--- a/build.gradle
+++ b/build.gradle
@@ -435,6 +435,20 @@ task DexSegments(type: Jar) {
}
}
+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 debe8a127..cbd8dd6bd 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,48 +51,27 @@ abstract class BaseCommand {
// 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() {
+ this(AndroidApp.builder(), false);
+ }
- protected Builder(CompilationMode mode) {
- this(AndroidApp.builder(), mode);
+ protected Builder(boolean ignoreDexInArchive) {
+ this(AndroidApp.builder(), ignoreDexInArchive);
}
// Internal constructor for testing.
Builder(AndroidApp app, CompilationMode mode) {
- this(AndroidApp.builder(app), mode);
+ this(AndroidApp.builder(app), false);
}
- private Builder(AndroidApp.Builder builder, CompilationMode mode) {
- assert mode != null;
+ protected Builder(AndroidApp.Builder builder, boolean ignoreDexInArchive) {
this.app = builder;
- this.mode = mode;
app.setIgnoreDexInArchive(ignoreDexInArchive);
}
@@ -177,52 +138,6 @@ abstract class BaseCommand {
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.
*
@@ -296,11 +211,7 @@ abstract class BaseCommand {
}
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 000000000..5e8f97584
--- /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/D8.java b/src/main/java/com/android/tools/r8/D8.java
index f7e65d9a2..e7a1d6ea3 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -55,7 +55,7 @@ import java.util.concurrent.ExecutorService;
*/
public final class D8 {
- private static final String VERSION = "v0.1.10";
+ private static final String VERSION = "v0.1.11";
private static final int STATUS_ERROR = 1;
private D8() {}
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index cb8c17045..5b2e5cfb3 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -27,15 +27,19 @@ import java.util.Collection;
* .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;
+ protected Builder(boolean ignoreDexInArchive) {
+ super(CompilationMode.DEBUG, ignoreDexInArchive);
+ }
+
protected Builder() {
super(CompilationMode.DEBUG);
}
diff --git a/src/main/java/com/android/tools/r8/DexSegments.java b/src/main/java/com/android/tools/r8/DexSegments.java
index 3827cae0f..24c1789ef 100644
--- a/src/main/java/com/android/tools/r8/DexSegments.java
+++ b/src/main/java/com/android/tools/r8/DexSegments.java
@@ -8,11 +8,9 @@ import com.android.tools.r8.dex.Segment;
import com.android.tools.r8.shaking.ProguardRuleParserException;
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;
@@ -24,10 +22,6 @@ public class DexSegments {
public static class Builder
extends BaseCommand.Builder<Command, Builder> {
- private Builder() {
- super(CompilationMode.RELEASE);
- }
-
@Override
Command.Builder self() {
return this;
@@ -40,8 +34,7 @@ public class DexSegments {
return new Command(isPrintHelp());
}
validate();
- return new Command(
- getAppBuilder().build(), getOutputPath(), getOutputMode(), getMode(), getMinApiLevel());
+ return new Command(getAppBuilder().build());
}
}
@@ -79,13 +72,8 @@ public class DexSegments {
}
}
- 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 2f1d523b9..6b7df778f 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -6,7 +6,6 @@ package com.android.tools.r8;
import com.android.tools.r8.shaking.ProguardRuleParserException;
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;
@@ -16,13 +15,13 @@ import java.util.concurrent.ExecutionException;
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() {
@@ -34,6 +33,15 @@ public class Disassemble {
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;
@@ -47,13 +55,7 @@ public class Disassemble {
}
validate();
- return new DisassembleCommand(
- getAppBuilder().build(),
- getOutputPath(),
- getOutputMode(),
- getMode(),
- getMinApiLevel(),
- useSmali);
+ return new DisassembleCommand(getAppBuilder().build(), getOutputPath(), useSmali);
}
}
@@ -94,6 +96,9 @@ public class Disassemble {
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);
@@ -103,23 +108,22 @@ public class Disassemble {
}
}
- 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 c1234b84e..bf3b9196b 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -3,19 +3,15 @@
// 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.shaking.ProguardRuleParserException;
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;
@@ -25,10 +21,6 @@ public class ExtractMarker {
public static class Builder
extends BaseCommand.Builder<ExtractMarker.Command, ExtractMarker.Command.Builder> {
- private Builder() {
- super(CompilationMode.RELEASE);
- }
-
@Override
ExtractMarker.Command.Builder self() {
return this;
@@ -41,8 +33,7 @@ public class ExtractMarker {
return new ExtractMarker.Command(isPrintHelp());
}
validate();
- return new ExtractMarker.Command(
- getAppBuilder().build(), getOutputPath(), getOutputMode(), getMode(), getMinApiLevel());
+ return new ExtractMarker.Command(getAppBuilder().build());
}
}
@@ -80,13 +71,8 @@ public class ExtractMarker {
}
}
- 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 000000000..44d523e6b
--- /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 000000000..76d7d56f9
--- /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/R8.java b/src/main/java/com/android/tools/r8/R8.java
index edfe01e9d..6e7e36dc9 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -71,7 +71,7 @@ import java.util.concurrent.Executors;
public class R8 {
- private static final String VERSION = "v0.1.10";
+ private static final String VERSION = "v0.1.11";
private final Timing timing = new Timing("R8");
private final InternalOptions options;
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index ec040a597..14b53c2e4 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -8,6 +8,9 @@ import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardConfiguration.Builder;
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.FileUtils;
@@ -18,19 +21,18 @@ import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
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<Path> mainDexRules = new ArrayList<>();
+ private final List<ProguardConfigurationSource> mainDexRules = new ArrayList<>();
private Path mainDexListOutput = null;
private Consumer<ProguardConfiguration.Builder> proguardConfigurationConsumer = null;
- private final List<Path> proguardConfigFiles = new ArrayList<>();
+ private final List<ProguardConfigurationSource> proguardConfigs = new ArrayList<>();
private Optional<Boolean> treeShaking = Optional.empty();
private Optional<Boolean> minification = Optional.empty();
private boolean ignoreMissingClasses = false;
@@ -68,16 +70,28 @@ public class R8Command extends BaseCommand {
/**
* Add proguard configuration file resources for automatic main dex list calculation.
*/
- public Builder addMainDexRules(Path... paths) {
- Collections.addAll(mainDexRules, paths);
+ public 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 Builder addMainDexRules(List<Path> paths) {
- mainDexRules.addAll(paths);
+ public 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 Builder addMainDexRules(List<String> lines) {
+ mainDexRules.add(new ProguardConfigurationSourceStrings(lines));
return self();
}
@@ -90,7 +104,9 @@ public class R8Command extends BaseCommand {
* Add proguard configuration file resources.
*/
public Builder addProguardConfigurationFiles(Path... paths) {
- Collections.addAll(proguardConfigFiles, paths);
+ for (Path path : paths) {
+ proguardConfigs.add(new ProguardConfigurationSourceFile(path));
+ }
return self();
}
@@ -98,7 +114,17 @@ public class R8Command extends BaseCommand {
* Add proguard configuration file resources.
*/
public Builder addProguardConfigurationFiles(List<Path> paths) {
- proguardConfigFiles.addAll(paths);
+ for (Path path : paths) {
+ proguardConfigs.add(new ProguardConfigurationSourceFile(path));
+ }
+ return self();
+ }
+
+ /**
+ * Add proguard configuration.
+ */
+ public Builder addProguardConfiguration(List<String> lines) {
+ proguardConfigs.add(new ProguardConfigurationSourceStrings(lines));
return self();
}
@@ -176,12 +202,12 @@ public class R8Command extends BaseCommand {
mainDexKeepRules = parser.getConfig().getRules();
}
ProguardConfiguration configuration;
- if (proguardConfigFiles.isEmpty()) {
+ if (proguardConfigs.isEmpty()) {
configuration = ProguardConfiguration.defaultConfiguration(factory);
} else {
ProguardConfigurationParser parser = new ProguardConfigurationParser(factory);
try {
- parser.parse(proguardConfigFiles);
+ parser.parse(proguardConfigs);
} catch (ProguardRuleParserException e) {
throw new CompilationException(e.getMessage(), e.getCause());
}
@@ -308,7 +334,7 @@ public class R8Command extends BaseCommand {
} else if (arg.equals("--no-minification")) {
builder.setMinification(false);
} else if (arg.equals("--main-dex-rules")) {
- builder.addMainDexRules(Paths.get(args[++i]));
+ 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")) {
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDxCommandBuilder.java b/src/main/java/com/android/tools/r8/compatdx/CompatDxCommandBuilder.java
index ba9daece5..55206a407 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDxCommandBuilder.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDxCommandBuilder.java
@@ -8,6 +8,6 @@ import com.android.tools.r8.D8Command;
public class CompatDxCommandBuilder extends D8Command.Builder {
CompatDxCommandBuilder() {
- ignoreDexInArchive = true;
+ super(true);
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 4fb7357dd..2667f79ff 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -22,11 +22,8 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.CharBuffer;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -85,31 +82,32 @@ public class ProguardConfigurationParser {
}
public void parse(Path path) throws ProguardRuleParserException, IOException {
- parse(Collections.singletonList(path));
+ parse(ImmutableList.of(new ProguardConfigurationSourceFile(path)));
}
- public void parse(List<Path> pathList) throws ProguardRuleParserException, IOException {
- for (Path path : pathList) {
- new ProguardFileParser(path).parse();
+ public void parse(ProguardConfigurationSource source)
+ throws ProguardRuleParserException, IOException {
+ parse(ImmutableList.of(source));
+ }
+
+ public void parse(List<ProguardConfigurationSource> sources)
+ throws ProguardRuleParserException, IOException {
+ for (ProguardConfigurationSource source : sources) {
+ new ProguardFileParser(source).parse();
}
}
private class ProguardFileParser {
- private final Path path;
+ private final String name;
private final String contents;
private int position = 0;
private Path baseDirectory;
- ProguardFileParser(Path path) throws IOException {
- this.path = path;
- contents = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
- baseDirectory = path.getParent();
- if (baseDirectory == null) {
- // path parent can be null only if it's root dir or if its a one element path relative to
- // current directory.
- baseDirectory = Paths.get(".");
- }
+ ProguardFileParser(ProguardConfigurationSource source) throws IOException {
+ contents = source.get();
+ baseDirectory = source.getBaseDirectory();
+ name = source.getName();
}
public void parse() throws ProguardRuleParserException {
@@ -267,7 +265,7 @@ public class ProguardConfigurationParser {
private void parseInclude() throws ProguardRuleParserException {
Path included = parseFileName();
try {
- new ProguardFileParser(included).parse();
+ new ProguardFileParser(new ProguardConfigurationSourceFile(included)).parse();
} catch (FileNotFoundException | NoSuchFileException e) {
throw parseError("Included file '" + included.toString() + "' not found", e);
} catch (IOException e) {
@@ -1033,12 +1031,12 @@ public class ProguardConfigurationParser {
String line = lines[lineNumber];
if (remaining <= line.length() || lineNumber == lines.length - 1) {
String arrow = CharBuffer.allocate(remaining).toString().replace( '\0', ' ' ) + '^';
- return path.toString() + ":" + (lineNumber + 1) + ":" + remaining + "\n" + line
+ return name + ":" + (lineNumber + 1) + ":" + remaining + "\n" + line
+ '\n' + arrow;
}
remaining -= (line.length() + 1); // Include newline.
}
- return path.toString();
+ return name;
}
private ProguardRuleParserException parseError(String message) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSource.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSource.java
new file mode 100644
index 000000000..b0375c0a8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSource.java
@@ -0,0 +1,14 @@
+// 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.shaking;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+public interface ProguardConfigurationSource {
+ String get() throws IOException;
+ Path getBaseDirectory();
+ String getName();
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceFile.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceFile.java
new file mode 100644
index 000000000..058c14f08
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceFile.java
@@ -0,0 +1,37 @@
+// 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.shaking;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class ProguardConfigurationSourceFile implements ProguardConfigurationSource {
+ private final Path path;
+
+ public ProguardConfigurationSourceFile(Path path) {
+ this.path = path;
+ }
+
+ public String get() throws IOException{
+ return new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
+ }
+
+ public Path getBaseDirectory() {
+ Path baseDirectory = path.getParent();
+ if (baseDirectory == null) {
+ // Path parent can be null only if it's root dir or if its a one element path relative to
+ // current directory.
+ baseDirectory = Paths.get(".");
+ }
+ return baseDirectory;
+ }
+
+ public String getName() {
+ return path.toString();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java
new file mode 100644
index 000000000..ac5f0abf3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java
@@ -0,0 +1,31 @@
+// 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.shaking;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import joptsimple.internal.Strings;
+
+public class ProguardConfigurationSourceStrings implements ProguardConfigurationSource {
+ private final List<String> config;
+
+ public ProguardConfigurationSourceStrings(List<String> config) {
+ this.config = config;
+ }
+
+ public String get() throws IOException{
+ return Strings.join(config, "\n");
+ }
+
+ public Path getBaseDirectory() {
+ return Paths.get(".");
+ }
+
+ public String getName() {
+ return "<no file>";
+ }
+}
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 0fa5051d7..d40da6131 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.ArrayList;
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;
@@ -582,5 +583,29 @@ public class RootSetBuilder {
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/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 8b8305826..e91870dd8 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -448,7 +448,9 @@ public class ToolHelper {
return ProguardConfiguration.defaultConfiguration(factory);
}
ProguardConfigurationParser parser = new ProguardConfigurationParser(factory);
- parser.parse(configPaths);
+ for (Path configPath : configPaths) {
+ parser.parse(configPath);
+ }
return parser.getConfig();
}
@@ -597,13 +599,31 @@ public class ToolHelper {
.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/compatdx/CompatDxTests.java b/src/test/java/com/android/tools/r8/compatdx/CompatDxTests.java
index dbe776de2..32227340e 100644
--- a/src/test/java/com/android/tools/r8/compatdx/CompatDxTests.java
+++ b/src/test/java/com/android/tools/r8/compatdx/CompatDxTests.java
@@ -6,33 +6,33 @@ package com.android.tools.r8.compatdx;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.maindexlist.MainDexListTests;
-import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.StringUtils.BraceType;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import java.io.IOException;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
public class CompatDxTests {
@@ -100,38 +100,26 @@ public class CompatDxTests {
}
@Test
- public void singleDexProgramFull() throws IOException, ExecutionException {
- // Generate an application that fills the whole dex file.
- AndroidApp generated =
- MainDexListTests.generateApplication(
- ImmutableList.of("A"), Constants.ANDROID_L_API, MAX_METHOD_COUNT + 1);
- Path applicationJar = temp.newFile("application.jar").toPath();
- generated.write(applicationJar, OutputMode.Indexed);
- runDexer(applicationJar.toString());
- }
-
- @Test
- public void singleDexProgramIsTooLarge() throws IOException, ExecutionException {
- // Generate an application that will not fit into a single dex file.
- AndroidApp generated = MainDexListTests.generateApplication(
- ImmutableList.of("A", "B"), Constants.ANDROID_L_API, MAX_METHOD_COUNT / 2 + 2);
- Path applicationJar = temp.newFile("application.jar").toPath();
- generated.write(applicationJar, OutputMode.Indexed);
- try {
- runDexer(applicationJar.toString());
- fail("Expect to fail, for there are many classes while multidex is not enabled.");
- } catch (CompilationError e) {
- // Make sure {@link MonoDexDistributor} was used.
- assertTrue(e.getMessage().contains("single dex file"));
- // Make sure what exceeds the limit is the number of methods.
- assertTrue(e.getMessage().contains("# methods: "
- + String.valueOf((MAX_METHOD_COUNT / 2 + 2) * 2)));
- }
+ public void keepClassesTest() throws IOException {
+ runDexerWithOutput("out.zip", "--keep-classes", EXAMPLE_JAR_FILE1);
}
@Test
- public void keepClassesTest() throws IOException {
- runDexerWithOutput("out.zip", "--keep-classes", EXAMPLE_JAR_FILE1);
+ public void ignoreDexInArchiveTest() throws IOException {
+ // Create a JAR with both a .class and a .dex file (the .dex file is just empty).
+ Path jarWithClassesAndDex = temp.newFile("test.jar").toPath();
+ Files.copy(Paths.get(EXAMPLE_JAR_FILE1), jarWithClassesAndDex,
+ StandardCopyOption.REPLACE_EXISTING);
+ URI uri = URI.create("jar:" + jarWithClassesAndDex.toUri());
+ FileSystem fileSystem = FileSystems.newFileSystem(uri, ImmutableMap.of("create", "true"));
+ Path dexFile = fileSystem.getPath("classes.dex");
+ Files.newOutputStream(dexFile, StandardOpenOption.CREATE).close();
+ fileSystem.close();
+
+ // Only test this with CompatDx, as dx does not like the empty .dex file.
+ List<String> d8Args =ImmutableList.of(
+ "--output=" + temp.newFolder("out").toString(), jarWithClassesAndDex.toString());
+ CompatDx.main(d8Args.toArray(new String[d8Args.size()]));
}
private void runDexer(String... args) throws IOException {
@@ -178,7 +166,8 @@ public class CompatDxTests {
}
Collections.addAll(dxArgs, args);
System.out.println("running: dx " + StringUtils.join(dxArgs, " "));
- ToolHelper.runDX(dxArgs.toArray(new String[dxArgs.size()]));
+ ProcessResult result = ToolHelper.runDX(dxArgs.toArray(new String[dxArgs.size()]));
+ assertEquals(result.stderr, 0, result.exitCode);
if (out == null) {
// Can't check output if explicitly not writing any.
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
index dfc20f9a8..1039475cf 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -38,7 +38,7 @@ public class MainDexListOutputTest extends TestBase {
Path mainDexListOutput = temp.getRoot().toPath().resolve("main-dex-output.txt");
R8Command command =
ToolHelper.prepareR8CommandBuilder(readClasses(HelloWorldMain.class))
- .addMainDexRules(mainDexRules)
+ .addMainDexRulesFiles(mainDexRules)
.setMainDexListOutputPath(mainDexListOutput)
.build();
ToolHelper.runR8(command);
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 57c8cf67c..ba5092fe7 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.JAR_EXTENSION;
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 @@ public class MainDexTracingTest {
mainDexRules,
expectedMainDexList,
minSdk,
- R8Command.builder(),
(options) -> {
options.inlineAccessors = false;
});
@@ -120,48 +121,86 @@ public class MainDexTracingTest {
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)
- .addMainDexRules(mainDexRules)
+ .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/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index ea282149c..3d6ab6900 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -14,6 +14,7 @@ import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.DexAccessFlags;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
import java.io.IOException;
import java.nio.file.Path;
@@ -98,11 +99,22 @@ public class ProguardConfigurationParserTest extends TestBase {
@Test
public void parse() throws IOException, ProguardRuleParserException {
- ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
+ ProguardConfigurationParser parser;
+
+ // Parse from file.
+ parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(PROGUARD_SPEC_FILE));
List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
assertEquals(24, rules.size());
assertEquals(1, rules.get(0).getMemberRules().size());
+
+ // Parse from strings.
+ parser = new ProguardConfigurationParser(new DexItemFactory());
+ List<String> lines = FileUtils.readTextFile(Paths.get(PROGUARD_SPEC_FILE));
+ parser.parse(new ProguardConfigurationSourceStrings(lines));
+ rules = parser.getConfig().getRules();
+ assertEquals(24, rules.size());
+ assertEquals(1, rules.get(0).getMemberRules().size());
}
@Test
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 000000000..64e7a5bd2
--- /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();
+ }
+}