1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.build.config; 18 19 import java.io.PrintStream; 20 import java.util.Map; 21 import java.util.TreeMap; 22 23 public class Options { 24 public enum Action { 25 DEFAULT, 26 HELP 27 } 28 29 private Action mAction = Action.DEFAULT; 30 31 private String mProduct; 32 private String mVariant; 33 private String mOutDir; 34 private String mCKatiBin; 35 getAction()36 public Action getAction() { 37 return mAction; 38 } 39 getProduct()40 public String getProduct() { 41 return mProduct; 42 } 43 getVariant()44 public String getVariant() { 45 return mVariant; 46 } 47 getOutDir()48 public String getOutDir() { 49 return mOutDir != null ? mOutDir : "out"; 50 } 51 getCKatiBin()52 public String getCKatiBin() { 53 return mCKatiBin; 54 } 55 printHelp(PrintStream out)56 public static void printHelp(PrintStream out) { 57 out.println("usage: product_config"); 58 out.println(); 59 out.println("REQUIRED FLAGS"); 60 out.println(" --ckati_bin CKATI Kati binary to use."); 61 out.println(); 62 out.println("OPTIONAL FLAGS"); 63 out.println(" --hide ERROR_ID Suppress this error."); 64 out.println(" --error ERROR_ID Make this ERROR_ID a fatal error."); 65 out.println(" --help -h This message."); 66 out.println(" --warning ERROR_ID Make this ERROR_ID a warning."); 67 out.println(); 68 out.println("REQUIRED ENVIRONMENT"); 69 out.println(" TARGET_PRODUCT Product to build from lunch command."); 70 out.println(" TARGET_BUILD_VARIANT Build variant from lunch command."); 71 out.println(); 72 out.println("OPTIONAL ENVIRONMENT"); 73 out.println(" OUT_DIR Build output directory. Defaults to \"out\"."); 74 out.println(); 75 out.println("ERRORS"); 76 out.println(" The following are the errors that can be controlled on the"); 77 out.println(" commandline with the --hide --warning --error flags."); 78 79 TreeMap<Integer,Errors.Category> sorted = new TreeMap((new Errors()).getCategories()); 80 81 for (final Errors.Category category: sorted.values()) { 82 if (category.isLevelSettable()) { 83 out.println(String.format(" %-3d %s", category.getCode(), 84 category.getHelp().replace("\n", "\n "))); 85 } 86 } 87 } 88 89 static class Parser { 90 private static class ParseException extends Exception { ParseException(String message)91 public ParseException(String message) { 92 super(message); 93 } 94 } 95 96 private Errors mErrors; 97 private String[] mArgs; 98 private Map<String,String> mEnv; 99 private Options mResult = new Options(); 100 private int mIndex; 101 private boolean mSkipRequiredArgValidation; 102 Parser(Errors errors, String[] args, Map<String,String> env)103 public Parser(Errors errors, String[] args, Map<String,String> env) { 104 mErrors = errors; 105 mArgs = args; 106 mEnv = env; 107 } 108 parse()109 public Options parse() { 110 // Args 111 try { 112 while (mIndex < mArgs.length) { 113 final String arg = mArgs[mIndex]; 114 115 if ("--ckati_bin".equals(arg)) { 116 mResult.mCKatiBin = requireNextStringArg(arg); 117 } else if ("--hide".equals(arg)) { 118 handleErrorCode(arg, Errors.Level.HIDDEN); 119 } else if ("--error".equals(arg)) { 120 handleErrorCode(arg, Errors.Level.ERROR); 121 } else if ("--help".equals(arg) || "-h".equals(arg)) { 122 // Help overrides all other commands if there isn't an error, but 123 // we will stop here. 124 if (!mErrors.hadError()) { 125 mResult.mAction = Action.HELP; 126 } 127 return mResult; 128 } else if ("--warning".equals(arg)) { 129 handleErrorCode(arg, Errors.Level.WARNING); 130 } else { 131 throw new ParseException("Unknown command line argument: " + arg); 132 } 133 134 mIndex++; 135 } 136 } catch (ParseException ex) { 137 mErrors.ERROR_COMMAND_LINE.add(ex.getMessage()); 138 } 139 140 // Environment 141 mResult.mProduct = mEnv.get("TARGET_PRODUCT"); 142 mResult.mVariant = mEnv.get("TARGET_BUILD_VARIANT"); 143 mResult.mOutDir = mEnv.get("OUT_DIR"); 144 145 validateArgs(); 146 147 return mResult; 148 } 149 150 /** 151 * For testing; don't generate errors about missing arguments 152 */ setSkipRequiredArgValidation()153 public void setSkipRequiredArgValidation() { 154 mSkipRequiredArgValidation = true; 155 } 156 validateArgs()157 private void validateArgs() { 158 if (!mSkipRequiredArgValidation) { 159 if (mResult.mCKatiBin == null || "".equals(mResult.mCKatiBin)) { 160 addMissingArgError("--ckati_bin"); 161 } 162 if (mResult.mProduct == null) { 163 addMissingEnvError("TARGET_PRODUCT"); 164 } 165 if (mResult.mVariant == null) { 166 addMissingEnvError("TARGET_BUILD_VARIANT"); 167 } 168 } 169 } 170 addMissingArgError(String argName)171 private void addMissingArgError(String argName) { 172 mErrors.ERROR_COMMAND_LINE.add("Required command line argument missing: " 173 + argName); 174 } 175 addMissingEnvError(String envName)176 private void addMissingEnvError(String envName) { 177 mErrors.ERROR_COMMAND_LINE.add("Required environment variable missing: " 178 + envName); 179 } 180 getNextNonFlagArg()181 private String getNextNonFlagArg() { 182 if (mIndex == mArgs.length - 1) { 183 return null; 184 } 185 if (mArgs[mIndex + 1].startsWith("-")) { 186 return null; 187 } 188 mIndex++; 189 return mArgs[mIndex]; 190 } 191 requireNextStringArg(String arg)192 private String requireNextStringArg(String arg) throws ParseException { 193 final String val = getNextNonFlagArg(); 194 if (val == null) { 195 throw new ParseException(arg + " requires a string argument."); 196 } 197 return val; 198 } 199 requireNextNumberArg(String arg)200 private int requireNextNumberArg(String arg) throws ParseException { 201 final String val = getNextNonFlagArg(); 202 if (val == null) { 203 throw new ParseException(arg + " requires a numeric argument."); 204 } 205 try { 206 return Integer.parseInt(val); 207 } catch (NumberFormatException ex) { 208 throw new ParseException(arg + " requires a numeric argument. found: " + val); 209 } 210 } 211 handleErrorCode(String arg, Errors.Level level)212 private void handleErrorCode(String arg, Errors.Level level) throws ParseException { 213 final int code = requireNextNumberArg(arg); 214 final Errors.Category category = mErrors.getCategories().get(code); 215 if (category == null) { 216 mErrors.WARNING_UNKNOWN_COMMAND_LINE_ERROR.add("Unknown error code: " + code); 217 return; 218 } 219 if (!category.isLevelSettable()) { 220 mErrors.ERROR_COMMAND_LINE.add("Can't set level for error " + code); 221 return; 222 } 223 category.setLevel(level); 224 } 225 } 226 227 /** 228 * Parse the arguments and return an options object. 229 * <p> 230 * Updates errors with the hidden / warning / error levels. 231 * <p> 232 * Adds errors encountered to Errors object. 233 */ parse(Errors errors, String[] args, Map<String, String> env)234 public static Options parse(Errors errors, String[] args, Map<String, String> env) { 235 return (new Parser(errors, args, env)).parse(); 236 } 237 } 238