blob: 4f59900f89fa2396cc5e4f84036dc7a1720a8476 [file] [log] [blame]
// 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.bisect;
import com.android.tools.r8.errors.CompilationError;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
public class BisectOptions {
private static final String HELP_FLAG = "help";
public static final String BUILD_GOOD_FLAG = "good";
public static final String BUILD_BAD_FLAG = "bad";
public static final String RESULT_GOOD_FLAG = "result-good";
public static final String RESULT_BAD_FLAG = "result-bad";
public static final String STATE_FLAG = "state";
public static final String OUTPUT_FLAG = "output";
public static final String COMMAND_FLAG = "command";
public final Path goodBuild;
public final Path badBuild;
public final Path stateFile;
public final Path command;
public final Path output;
public final Result result;
public enum Result { UNKNOWN, GOOD, BAD }
private static class ParserSpec {
OptionSpec<String> goodBuild;
OptionSpec<String> badBuild;
OptionSpec<String> command;
OptionSpec<String> stateFile;
OptionSpec<String> output;
OptionSpec<Void> resultGood;
OptionSpec<Void> resultBad;
OptionSpec<Void> help;
void init(OptionParser parser) {
help = parser.accepts(HELP_FLAG).forHelp();
resultGood = parser.accepts(RESULT_GOOD_FLAG, "Bisect again assuming previous run was good.");
resultBad = parser.accepts(RESULT_BAD_FLAG, "Bisect again assuming previous run was bad.");
goodBuild = parser.accepts(BUILD_GOOD_FLAG, "Known good APK.")
.withRequiredArg()
.describedAs("apk");
badBuild = parser.accepts(BUILD_BAD_FLAG, "Known bad APK.")
.withRequiredArg()
.describedAs("apk");
stateFile = parser.accepts(STATE_FLAG, "Bisection state.")
.requiredIf(resultGood, resultBad)
.withRequiredArg()
.describedAs("file");
output = parser.accepts(OUTPUT_FLAG, "Output directory.")
.withRequiredArg()
.describedAs("dir");
command = parser.accepts(COMMAND_FLAG, "Command to run after each bisection.")
.requiredUnless(stateFile)
.withRequiredArg()
.describedAs("file");
}
OptionSet parse(String[] args) {
OptionParser parser = new OptionParser();
init(parser);
return parser.parse(args);
}
static void printHelp(OutputStream out) throws IOException {
OptionParser parser = new OptionParser();
new ParserSpec().init(parser);
parser.printHelpOn(out);
}
}
private BisectOptions(
Path goodBuild, Path badBuild, Path stateFile, Path command, Path output, Result result) {
this.goodBuild = goodBuild;
this.badBuild = badBuild;
this.stateFile = stateFile;
this.command = command;
this.output = output;
this.result = result;
}
public static BisectOptions parse(String[] args) throws IOException {
ParserSpec parser = new ParserSpec();
OptionSet options = parser.parse(args);
if (options.has(parser.help)) {
printHelp(System.out);
return null;
}
Path goodBuild = exists(require(options, parser.goodBuild, BUILD_GOOD_FLAG), BUILD_GOOD_FLAG);
Path badBuild = exists(require(options, parser.badBuild, BUILD_BAD_FLAG), BUILD_BAD_FLAG);
Path stateFile = null;
if (options.valueOf(parser.stateFile) != null) {
stateFile = exists(options.valueOf(parser.stateFile), STATE_FLAG);
}
Path command = null;
if (options.valueOf(parser.command) != null) {
command = exists(options.valueOf(parser.command), COMMAND_FLAG);
}
Path output = null;
if (options.valueOf(parser.output) != null) {
output = directoryExists(options.valueOf(parser.output), OUTPUT_FLAG);
}
Result result = Result.UNKNOWN;
if (options.has(parser.resultGood)) {
result = Result.GOOD;
}
if (options.has(parser.resultBad)) {
if (result == Result.GOOD) {
throw new CompilationError("Cannot specify --" + RESULT_GOOD_FLAG
+ " and --" + RESULT_BAD_FLAG + " simultaneously");
}
result = Result.BAD;
}
return new BisectOptions(goodBuild, badBuild, stateFile, command, output, result);
}
private static <T> T require(OptionSet options, OptionSpec<T> option, String flag) {
T value = options.valueOf(option);
if (value != null) {
return value;
}
throw new CompilationError("Missing required option: --" + flag);
}
private static Path exists(String path, String flag) {
Path file = Paths.get(path);
if (Files.exists(file)) {
return file;
}
throw new CompilationError("File --" + flag + ": " + file + " does not exist");
}
private static Path directoryExists(String path, String flag) {
Path file = Paths.get(path);
if (Files.exists(file) && Files.isDirectory(file)) {
return file;
}
throw new CompilationError("File --" + flag + ": " + file + " is not a valid directory");
}
public static void printHelp(OutputStream out) throws IOException {
ParserSpec.printHelp(out);
}
}