diff options
Diffstat (limited to 'src/main/java')
17 files changed, 690 insertions, 196 deletions
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(); + } } } |