|  | // 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.nio.file.Files; | 
|  | import java.nio.file.Path; | 
|  | import java.nio.file.Paths; | 
|  |  | 
|  | 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 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 { | 
|  | Path badBuild = null; | 
|  | Path goodBuild = null; | 
|  | Path stateFile = null; | 
|  | Path command = null; | 
|  | Path output = null; | 
|  | Result result = Result.UNKNOWN; | 
|  |  | 
|  | for (int i = 0; i < args.length; i++) { | 
|  | String arg = args[i].trim(); | 
|  | if (arg.equals(HELP_FLAG)) { | 
|  | printHelp(); | 
|  | return null; | 
|  | } else if (arg.equals(BUILD_BAD_FLAG)) { | 
|  | i = nextArg(i, args, BUILD_BAD_FLAG); | 
|  | badBuild = Paths.get(args[i]); | 
|  | } else if (arg.equals(BUILD_GOOD_FLAG)) { | 
|  | i = nextArg(i, args, BUILD_GOOD_FLAG); | 
|  | goodBuild = Paths.get(args[i]); | 
|  | } else if (arg.equals(COMMAND_FLAG)) { | 
|  | i = nextArg(i, args, COMMAND_FLAG); | 
|  | command = Paths.get(args[i]); | 
|  | } else if (arg.equals(OUTPUT_FLAG)) { | 
|  | i = nextArg(i, args, OUTPUT_FLAG); | 
|  | output = Paths.get(args[i]); | 
|  | } else if (arg.equals(RESULT_BAD_FLAG)) { | 
|  | result = checkSingleResult(result, Result.BAD); | 
|  | } else if (arg.equals(RESULT_GOOD_FLAG)) { | 
|  | result = checkSingleResult(result, Result.GOOD); | 
|  | } else if (arg.equals(STATE_FLAG)) { | 
|  | i = nextArg(i, args, STATE_FLAG); | 
|  | stateFile = Paths.get(args[i]); | 
|  | } | 
|  | } | 
|  | exists(require(badBuild, BUILD_BAD_FLAG), BUILD_BAD_FLAG); | 
|  | exists(require(goodBuild, BUILD_GOOD_FLAG), BUILD_GOOD_FLAG); | 
|  | if (stateFile != null) { | 
|  | exists(stateFile, STATE_FLAG); | 
|  | } | 
|  | if (command != null) { | 
|  | exists(command, COMMAND_FLAG); | 
|  | } | 
|  | if (output != null) { | 
|  | directoryExists(output, OUTPUT_FLAG); | 
|  | } | 
|  |  | 
|  | return new BisectOptions(goodBuild, badBuild, stateFile, command, output, result); | 
|  | } | 
|  |  | 
|  | private static int nextArg(int index, String[] args, String flag) { | 
|  | if (args.length == index + 1) { | 
|  | throw new CompilationError("Missing argument for: " + flag); | 
|  | } | 
|  | return index + 1; | 
|  | } | 
|  |  | 
|  | private static Path require(Path value, String flag) { | 
|  | if (value == null) { | 
|  | throw new CompilationError("Missing required option: " + flag); | 
|  | } | 
|  | return value; | 
|  | } | 
|  |  | 
|  | private static Path exists(Path path, String flag) { | 
|  | if (Files.exists(path)) { | 
|  | return path; | 
|  | } | 
|  | throw new CompilationError("File " + flag + ": " + path + " does not exist"); | 
|  | } | 
|  |  | 
|  | private static Path directoryExists(Path path, String flag) { | 
|  | if (Files.exists(path) && Files.isDirectory(path)) { | 
|  | return path; | 
|  | } | 
|  | throw new CompilationError("File " + flag + ": " + path + " is not a valid directory"); | 
|  | } | 
|  |  | 
|  | private static Result checkSingleResult(Result current, Result result) { | 
|  | if (current != Result.UNKNOWN) { | 
|  | throw new CompilationError( | 
|  | "Cannot specify " + RESULT_GOOD_FLAG + " and " + RESULT_BAD_FLAG + " simultaneously"); | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | public static void printHelp() throws IOException { | 
|  | System.out.println("--bad <apk>       Known bad APK."); | 
|  | System.out.println("--command <file>  Command to run after each bisection."); | 
|  | System.out.println("--good <apk>      Known good APK."); | 
|  | System.out.println("--help"); | 
|  | System.out.println("--output <dir>    Output directory."); | 
|  | System.out.println( | 
|  | "--result-bad      Bisect again assuming previous run was\n" + "        bad."); | 
|  | System.out.println( | 
|  | "--result-good     Bisect again assuming previous run was\n" + "        good."); | 
|  | System.out.println("--state <file>    Bisection state."); | 
|  | } | 
|  | } |