Allow no output file for tracereferences CLI
Bug: 169127026
Bug: 169546956
Change-Id: I7f19620245f58fbc7df0b2b5f17f2d20b4d2fe94
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
index ce8f565..c778667 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
@@ -96,6 +96,10 @@
return TraceReferencesCommandParser.parse(args, origin, diagnosticsHandler);
}
+ public static Builder parse(Collection<String> args, Origin origin) {
+ return TraceReferencesCommandParser.parse(args.toArray(new String[args.size()]), origin);
+ }
+
public boolean isPrintHelp() {
return printHelp;
}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommandParser.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommandParser.java
index f299459..e36ad33 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommandParser.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommandParser.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.BaseCompilerCommandParser;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.JdkClassFileProvider;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.tracereferences.TraceReferencesFormattingConsumer.OutputFormat;
import com.android.tools.r8.utils.ExceptionDiagnostic;
@@ -24,29 +25,40 @@
class TraceReferencesCommandParser {
private static final Set<String> OPTIONS_WITH_PARAMETER =
- ImmutableSet.of("--lib", "--target", "--source", "--format", "--output");
+ ImmutableSet.of("--lib", "--target", "--source", "--output");
static final String USAGE_MESSAGE =
String.join(
"\n",
Iterables.concat(
Arrays.asList(
- "Usage: tracereferences [options] [@<argfile>]",
- " Each <argfile> is a file containing additional arguments (one per line)",
+ "Usage: tracereferences <command> [<options>] [@<argfile>]",
+ " Where <command> is one of:",
+ " --check # Run emitting only diagnostics messages.",
+ " --print-usage # Traced references will be output in the print-usage",
+ " # format.",
+ " --keep-rules [<keep-rules-options>]",
+ " # Traced references will be output in the keep-rules",
+ " # format.",
+ " and each <argfile> is a file containing additional options (one per line)",
" and options are:",
" --lib <file|jdk-home> # Add <file|jdk-home> runtime library.",
" --source <file> # Add <file> as a source for tracing references.",
" [--target <file>] # Add <file> as a target for tracing references. When",
" # target is not specified all references from source",
- " # outside of library are treated as a missing"
- + " references.",
- " [--format printuses|keep|keepallowobfuscation]",
- " # Format of the output. Default is 'printuses'.",
- " --output <file> # Output result in <outfile>."),
+ " # outside of library are treated as a missing",
+ " # references.",
+ " --output <file> # Output result in <outfile>. If not passed the",
+ " # result will go to standard out.",
+ " # result will go to standard out."),
BaseCompilerCommandParser.MAP_DIAGNOSTICS_USAGE_MESSAGE,
Arrays.asList(
" --version # Print the version of tracereferences.",
- " --help # Print this message.")));
+ " --help # Print this message.",
+ " and <keep-rule-options> are:",
+ " --allowobfuscation # Output keep rules with the allowobfuscation",
+ " # modifier (defaults to rules without the"
+ + " modifier)")));
/**
* Parse the tracereferences command-line.
*
@@ -76,11 +88,43 @@
.parse(args, origin, TraceReferencesCommand.builder(handler));
}
+ private enum Command {
+ CHECK,
+ PRINTUSAGE,
+ KEEP_RULES;
+
+ private OutputFormat toOutputFormat(boolean allowobfuscation) {
+ switch (this) {
+ case PRINTUSAGE:
+ return OutputFormat.PRINTUSAGE;
+ case KEEP_RULES:
+ return allowobfuscation
+ ? OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION
+ : OutputFormat.KEEP_RULES;
+ default:
+ throw new Unreachable();
+ }
+ }
+ }
+
+ private void checkCommandNotSet(
+ Command command, TraceReferencesCommand.Builder builder, Origin origin) {
+ if (command != null) {
+ builder.error(new StringDiagnostic("Multiple commands specified", origin));
+ }
+ }
+
private TraceReferencesCommand.Builder parse(
String[] args, Origin origin, TraceReferencesCommand.Builder builder) {
String[] expandedArgs = FlagFile.expandFlagFiles(args, builder::error);
Path output = null;
- OutputFormat format = null;
+ Command command = null;
+ boolean allowObfuscation = false;
+ if (expandedArgs.length == 0) {
+ builder.error(new StringDiagnostic("Missing command"));
+ return builder;
+ }
+ // Parse options.
for (int i = 0; i < expandedArgs.length; i++) {
String arg = expandedArgs[i].trim();
String nextArg = null;
@@ -97,32 +141,33 @@
continue;
} else if (arg.equals("--help")) {
builder.setPrintHelp(true);
+ return builder;
} else if (arg.equals("--version")) {
builder.setPrintVersion(true);
+ return builder;
+ } else if (arg.equals("--check")) {
+ checkCommandNotSet(command, builder, origin);
+ command = Command.CHECK;
+ } else if (arg.equals("--print-usage")) {
+ checkCommandNotSet(command, builder, origin);
+ command = Command.PRINTUSAGE;
+ } else if (arg.equals("--keep-rules")) {
+ checkCommandNotSet(command, builder, origin);
+ command = Command.KEEP_RULES;
+ } else if (arg.equals("--allowobfuscation")) {
+ allowObfuscation = true;
} else if (arg.equals("--lib")) {
addLibraryArgument(builder, origin, nextArg);
} else if (arg.equals("--target")) {
builder.addTargetFiles(Paths.get(nextArg));
} else if (arg.equals("--source")) {
builder.addSourceFiles(Paths.get(nextArg));
- } else if (arg.equals("--format")) {
- if (format != null) {
- builder.error(new StringDiagnostic("--format specified multiple times"));
- }
- if (nextArg.equals("printuses")) {
- format = TraceReferencesFormattingConsumer.OutputFormat.PRINTUSAGE;
- }
- if (nextArg.equals("keep")) {
- format = TraceReferencesFormattingConsumer.OutputFormat.KEEP_RULES;
- }
- if (nextArg.equals("keepallowobfuscation")) {
- format = TraceReferencesFormattingConsumer.OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION;
- }
- if (format == null) {
- builder.error(new StringDiagnostic("Unsupported format '" + nextArg + "'"));
- }
} else if (arg.equals("--output")) {
- output = Paths.get(nextArg);
+ if (output != null) {
+ builder.error(new StringDiagnostic("Option '--output' passed multiple times.", origin));
+ } else {
+ output = Paths.get(nextArg);
+ }
} else if (arg.startsWith("@")) {
builder.error(new StringDiagnostic("Recursive @argfiles are not supported: ", origin));
} else {
@@ -133,28 +178,50 @@
i += argsConsumed;
continue;
}
- builder.error(new StringDiagnostic("Unsupported argument '" + arg + "'"));
+ builder.error(new StringDiagnostic("Unsupported option '" + arg + "'", origin));
}
}
- if (format == null) {
- format = TraceReferencesFormattingConsumer.OutputFormat.PRINTUSAGE;
+
+ if (command == null) {
+ builder.error(
+ new StringDiagnostic(
+ "Missing command, specify one of 'check', '--print-usage' or '--keep-rules'",
+ origin));
+ return builder;
}
+
+ if (command == Command.CHECK && output != null) {
+ builder.error(
+ new StringDiagnostic(
+ "Using '--output' requires command '--print-usage' or '--keep-rules'", origin));
+ return builder;
+ }
+
+ if (command != Command.KEEP_RULES && allowObfuscation) {
+ builder.error(
+ new StringDiagnostic(
+ "Using '--allowobfuscation' requires command '--keep-rules'", origin));
+ return builder;
+ }
+
final Path finalOutput = output;
builder.setConsumer(
- new TraceReferencesFormattingConsumer(format) {
- @Override
- public void finished() {
- PrintStream out = System.out;
- if (finalOutput != null) {
- try {
- out = new PrintStream(Files.newOutputStream(finalOutput));
- } catch (IOException e) {
- builder.error(new ExceptionDiagnostic(e));
+ command == Command.CHECK
+ ? TraceReferencesConsumer.emptyConsumer()
+ : new TraceReferencesFormattingConsumer(command.toOutputFormat(allowObfuscation)) {
+ @Override
+ public void finished() {
+ PrintStream out = System.out;
+ if (finalOutput != null) {
+ try {
+ out = new PrintStream(Files.newOutputStream(finalOutput));
+ } catch (IOException e) {
+ builder.error(new ExceptionDiagnostic(e));
+ }
+ }
+ out.print(get());
}
- }
- out.print(get());
- }
- });
+ });
return builder;
}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesFormattingConsumer.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesFormattingConsumer.java
index 67167c4..1979123 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesFormattingConsumer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesFormattingConsumer.java
@@ -8,7 +8,7 @@
class TraceReferencesFormattingConsumer implements TraceReferencesConsumer {
- public enum OutputFormat {
+ enum OutputFormat {
/** Format used with the -printusage flag */
PRINTUSAGE,
/** Keep rules keeping each of the traced references */
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
index 77c1e53..148ddd1 100644
--- a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
@@ -20,13 +20,15 @@
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.ZipUtils;
import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
import com.google.common.collect.ImmutableList;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -62,10 +64,21 @@
@Test(expected = CompilationFailedException.class)
public void emptyRunCommandLine() throws Throwable {
DiagnosticsChecker.checkErrorsContains(
- "No library specified",
+ "Missing command",
handler -> {
TraceReferences.run(
- TraceReferencesCommand.parse(new String[] {""}, Origin.unknown(), handler).build());
+ TraceReferencesCommand.parse(new String[] {}, Origin.unknown(), handler).build());
+ });
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void unsupportedCommandCommandLine() throws Throwable {
+ DiagnosticsChecker.checkErrorsContains(
+ "Missing command, specify one of 'check', '--print-usage' or '--keep-rules'",
+ handler -> {
+ TraceReferences.run(
+ TraceReferencesCommand.parse(new String[] {"--xxx"}, Origin.unknown(), handler)
+ .build());
});
}
@@ -89,7 +102,7 @@
TraceReferences.run(
TraceReferencesCommand.parse(
new String[] {
- "--lib", ToolHelper.getAndroidJar(AndroidApiLevel.P).toString()
+ "--check", "--lib", ToolHelper.getAndroidJar(AndroidApiLevel.P).toString()
},
Origin.unknown(),
handler)
@@ -98,24 +111,39 @@
}
@Test(expected = CompilationFailedException.class)
- public void invalidFormatCommandLine() throws Throwable {
+ public void multipleCommandsSpecified() throws Throwable {
DiagnosticsChecker.checkErrorsContains(
- "Unsupported format 'xxx'",
+ "Multiple commands specified",
handler -> {
TraceReferences.run(
TraceReferencesCommand.parse(
- new String[] {"--format", "xxx"}, Origin.unknown(), handler)
+ new String[] {"--check", "--keep-rules"}, Origin.unknown(), handler)
.build());
});
}
@Test(expected = CompilationFailedException.class)
- public void missingFormatCommandLine() throws Throwable {
+ public void allowobfuscationWithoutKeepRule() throws Throwable {
DiagnosticsChecker.checkErrorsContains(
- "Missing parameter for --format",
+ "Using '--allowobfuscation' requires command '--keep-rules'",
handler -> {
TraceReferences.run(
- TraceReferencesCommand.parse(new String[] {"--format"}, Origin.unknown(), handler)
+ TraceReferencesCommand.parse(
+ new String[] {"--check", "--allowobfuscation"}, Origin.unknown(), handler)
+ .build());
+ });
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void allowobfuscationMultiple() throws Throwable {
+ DiagnosticsChecker.checkErrorsContains(
+ "No library specified",
+ handler -> {
+ TraceReferences.run(
+ TraceReferencesCommand.parse(
+ new String[] {"--keep-rules", "--allowobfuscation", "--allowobfuscation"},
+ Origin.unknown(),
+ handler)
.build());
});
}
@@ -123,11 +151,23 @@
@Test(expected = CompilationFailedException.class)
public void multipleFormatsCommandLine() throws Throwable {
DiagnosticsChecker.checkErrorsContains(
- "--format specified multiple times",
+ "Using '--output' requires command '--print-usage' or '--keep-rules'",
handler -> {
TraceReferences.run(
TraceReferencesCommand.parse(
- new String[] {"--format", "printuses", "--format", "keep"},
+ new String[] {"--check", "--output", "xxx"}, Origin.unknown(), handler)
+ .build());
+ });
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void outputMultiple() throws Throwable {
+ DiagnosticsChecker.checkErrorsContains(
+ "Option '--output' passed multiple times",
+ handler -> {
+ TraceReferences.run(
+ TraceReferencesCommand.parse(
+ new String[] {"--keep-rules", "--output", "xxx", "--output", "xxx"},
Origin.unknown(),
handler)
.build());
@@ -136,13 +176,13 @@
private String formatName(OutputFormat format) {
if (format == OutputFormat.PRINTUSAGE) {
- return "printuses";
+ return "--print-usage";
}
if (format == OutputFormat.KEEP_RULES) {
- return "keep";
+ return "--keep-rules";
}
assertSame(format, OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION);
- return "keepallowobfuscation";
+ return "--keep-rules";
}
public void runAndCheckOutput(
@@ -179,17 +219,23 @@
throw e;
}
- TraceReferences.run(
- TraceReferencesCommand.parse(
- new String[] {
- "--lib", ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
- "--target", targetJar.toString(),
- "--source", sourceJar.toString(),
- "--output", output.toString(),
- "--format", formatName(format)
- },
- Origin.unknown())
- .build());
+ List<String> args = new ArrayList<>();
+ args.add(formatName(format));
+ if (format == OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION) {
+ args.add("--allowobfuscation");
+ }
+ args.addAll(
+ ImmutableList.of(
+ "--lib",
+ ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
+ "--target",
+ targetJar.toString(),
+ "--source",
+ sourceJar.toString(),
+ "--output",
+ output.toString()));
+
+ TraceReferences.run(TraceReferencesCommand.parse(args, Origin.unknown()).build());
assertEquals(expected, FileUtils.readTextFile(output, Charsets.UTF_8));
}
@@ -202,6 +248,16 @@
runAndCheckOutput(targetClasses, sourceClasses, format, expected, null);
}
+ private Path zipWithTestClasses(Path zipFile, List<Class<?>> targetClasses) throws IOException {
+ return ZipBuilder.builder(zipFile)
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(),
+ targetClasses.stream()
+ .map(ToolHelper::getClassFileForTestClass)
+ .collect(Collectors.toList()))
+ .build();
+ }
+
public void runAndCheckOutput(
List<Class<?>> targetClasses,
List<Class<?>> sourceClasses,
@@ -210,22 +266,8 @@
Consumer<DiagnosticsChecker> diagnosticsCheckerConsumer)
throws Throwable {
Path dir = temp.newFolder().toPath();
- Path targetJar =
- ZipBuilder.builder(dir.resolve("target.jar"))
- .addFilesRelative(
- ToolHelper.getClassPathForTests(),
- targetClasses.stream()
- .map(ToolHelper::getClassFileForTestClass)
- .collect(Collectors.toList()))
- .build();
- Path sourceJar =
- ZipBuilder.builder(dir.resolve("source.jar"))
- .addFilesRelative(
- ToolHelper.getClassPathForTests(),
- sourceClasses.stream()
- .map(ToolHelper::getClassFileForTestClass)
- .collect(Collectors.toList()))
- .build();
+ Path targetJar = zipWithTestClasses(dir.resolve("target.jar"), targetClasses);
+ Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), sourceClasses);
runAndCheckOutput(targetJar, sourceJar, format, expected, diagnosticsCheckerConsumer);
}
@@ -276,6 +318,85 @@
"-keeppackagenames com.android.tools.r8.tracereferences")));
}
+ @Test
+ public void test_noOutput() throws Throwable {
+ Path dir = temp.newFolder().toPath();
+ Path targetJar = zipWithTestClasses(dir.resolve("target.jar"), ImmutableList.of(Target.class));
+ Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), ImmutableList.of(Source.class));
+ PrintStream originalOut = System.out;
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(baos));
+ TraceReferences.run(
+ TraceReferencesCommand.builder()
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .addTargetFiles(targetJar)
+ .addSourceFiles(sourceJar)
+ .setConsumer(TraceReferencesConsumer.emptyConsumer())
+ .build());
+ assertEquals(0, baos.size());
+ } finally {
+ System.setOut(originalOut);
+ }
+
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(baos));
+ TraceReferences.run(
+ TraceReferencesCommand.parse(
+ new String[] {
+ "--lib",
+ ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
+ "--check",
+ "--target",
+ targetJar.toString(),
+ "--source",
+ sourceJar.toString(),
+ },
+ Origin.unknown())
+ .build());
+ assertEquals(0, baos.size());
+ } finally {
+ System.setOut(originalOut);
+ }
+ }
+
+ @Test
+ public void test_stdoutOutput() throws Throwable {
+ String expected =
+ StringUtils.lines(
+ ImmutableList.of(
+ "com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target",
+ "com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target: void"
+ + " method(int)",
+ "com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target: int"
+ + " field"));
+ Path dir = temp.newFolder().toPath();
+ Path targetJar = zipWithTestClasses(dir.resolve("target.jar"), ImmutableList.of(Target.class));
+ Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), ImmutableList.of(Source.class));
+ PrintStream originalOut = System.out;
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(baos));
+ TraceReferences.run(
+ TraceReferencesCommand.parse(
+ new String[] {
+ "--lib",
+ ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
+ "--target",
+ targetJar.toString(),
+ "--source",
+ sourceJar.toString(),
+ "--print-usage",
+ },
+ Origin.unknown())
+ .build());
+ assertEquals(expected, baos.toString(Charsets.UTF_8.name()));
+ } finally {
+ System.setOut(originalOut);
+ }
+ }
+
private void checkTargetMissing(DiagnosticsChecker diagnosticsChecker) {
Field field;
Method method;
@@ -344,32 +465,19 @@
public void testMissingReference_errorToWarning() throws Throwable {
Path dir = temp.newFolder().toPath();
Path targetJar =
- ZipBuilder.builder(dir.resolve("target.jar"))
- .addFilesRelative(
- ToolHelper.getClassPathForTests(),
- ToolHelper.getClassFileForTestClass(OtherTarget.class))
- .build();
- Path sourceJar =
- ZipBuilder.builder(dir.resolve("source.jar"))
- .addFilesRelative(
- ToolHelper.getClassPathForTests(),
- ToolHelper.getClassFileForTestClass(Source.class))
- .build();
- Path output = dir.resolve("output.txt");
+ zipWithTestClasses(dir.resolve("target.jar"), ImmutableList.of(OtherTarget.class));
+ Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), ImmutableList.of(Source.class));
DiagnosticsChecker diagnosticsChecker = new DiagnosticsChecker();
TraceReferences.run(
TraceReferencesCommand.parse(
new String[] {
+ "--check",
"--lib",
ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
"--target",
targetJar.toString(),
"--source",
sourceJar.toString(),
- "--output",
- output.toString(),
- "--format",
- formatName(OutputFormat.PRINTUSAGE),
"--map-diagnostics:MissingDefinitionsDiagnostic",
"error",
"warning"
@@ -407,12 +515,7 @@
.addBytes(
DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved())
.build();
- Path sourceJar =
- ZipBuilder.builder(dir.resolve("source.jar"))
- .addFilesRelative(
- ToolHelper.getClassPathForTests(),
- ToolHelper.getClassFileForTestClass(Source.class))
- .build();
+ Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), ImmutableList.of(Source.class));
try {
runAndCheckOutput(
targetJar,
@@ -436,16 +539,7 @@
.addBytes(
DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved())
.build();
- Path sourceJar =
- ZipBuilder.builder(dir.resolve("source.jar"))
- .addFilesRelative(
- ToolHelper.getClassPathForTests(),
- ToolHelper.getClassFileForTestClass(Source.class))
- .build();
- ZipUtils.zip(
- sourceJar,
- ToolHelper.getClassPathForTests(),
- ToolHelper.getClassFileForTestClass(Source.class));
+ Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), ImmutableList.of(Source.class));
try {
runAndCheckOutput(
targetJar,
@@ -473,12 +567,7 @@
.addBytes(
DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved())
.build();
- Path sourceJar =
- ZipBuilder.builder(dir.resolve("source.jar"))
- .addFilesRelative(
- ToolHelper.getClassPathForTests(),
- ToolHelper.getClassFileForTestClass(Source.class))
- .build();
+ Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), ImmutableList.of(Source.class));
try {
runAndCheckOutput(
targetJar,