1# -*- coding: utf-8 -*- 2# Copyright 2020 The ChromiumOS Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Common config and logic for binary search tool 7 8This module serves two main purposes: 9 1. Programatically include the utils module in PYTHONPATH 10 2. Create the argument parsing shared between binary_search_state.py and 11 run_bisect.py 12 13The argument parsing is handled by populating _ArgsDict with all arguments. 14_ArgsDict is required so that binary_search_state.py and run_bisect.py can 15share the argument parsing, but treat them slightly differently. For example, 16run_bisect.py requires that all argument defaults are suppressed so that 17overriding can occur properly (i.e. only options that are explicitly entered 18by the user end up in the resultant options dictionary). 19 20ArgumentDict inherits OrderedDict in order to preserve the order the args are 21created so the help text is made properly. 22""" 23 24 25import collections 26import os 27import sys 28 29 30# Programatically adding utils python path to PYTHONPATH 31if os.path.isabs(sys.argv[0]): 32 utils_pythonpath = os.path.abspath( 33 "{0}/..".format(os.path.dirname(sys.argv[0])) 34 ) 35else: 36 wdir = os.getcwd() 37 utils_pythonpath = os.path.abspath( 38 "{0}/{1}/..".format(wdir, os.path.dirname(sys.argv[0])) 39 ) 40sys.path.append(utils_pythonpath) 41 42 43class ArgumentDict(collections.OrderedDict): 44 """Wrapper around OrderedDict, represents CLI arguments for program. 45 46 AddArgument enforces the following layout: 47 { 48 ['-n', '--iterations'] : { 49 'dest': 'iterations', 50 'type': int, 51 'help': 'Number of iterations to try in the search.', 52 'default': 50 53 } 54 [arg_name1, arg_name2, ...] : { 55 arg_option1 : arg_option_val1, 56 ... 57 }, 58 ... 59 } 60 """ 61 62 _POSSIBLE_OPTIONS = [ 63 "action", 64 "nargs", 65 "const", 66 "default", 67 "type", 68 "choices", 69 "required", 70 "help", 71 "metavar", 72 "dest", 73 ] 74 75 def AddArgument(self, *args, **kwargs): 76 """Add argument to ArgsDict, has same signature as argparse.add_argument 77 78 Emulates the the argparse.add_argument method so the internal OrderedDict 79 can be safely and easily populated. Each call to this method will have a 1-1 80 corresponding call to argparse.add_argument once BuildArgParser is called. 81 82 Args: 83 *args: The names for the argument (-V, --verbose, etc.) 84 **kwargs: The options for the argument, corresponds to the args of 85 argparse.add_argument 86 87 Returns: 88 None 89 90 Raises: 91 TypeError: if args is empty or if option in kwargs is not a valid 92 option for argparse.add_argument. 93 """ 94 if not args: 95 raise TypeError("Argument needs at least one name") 96 97 for key in kwargs: 98 if key not in self._POSSIBLE_OPTIONS: 99 raise TypeError( 100 'Invalid option "%s" for argument %s' % (key, args[0]) 101 ) 102 103 self[args] = kwargs 104 105 106_ArgsDict = ArgumentDict() 107 108 109def GetArgsDict(): 110 """_ArgsDict singleton method""" 111 if not _ArgsDict: 112 _BuildArgsDict(_ArgsDict) 113 return _ArgsDict 114 115 116def BuildArgParser(parser, override=False): 117 """Add all arguments from singleton ArgsDict to parser. 118 119 Will take argparse parser and add all arguments in ArgsDict. Will ignore 120 the default and required options if override is set to True. 121 122 Args: 123 parser: type argparse.ArgumentParser, will call add_argument for every item 124 in _ArgsDict 125 override: True if being called from run_bisect.py. Used to say that default 126 and required options are to be ignored 127 128 Returns: 129 None 130 """ 131 ArgsDict = GetArgsDict() 132 133 # Have no defaults when overriding 134 for arg_names, arg_options in ArgsDict.items(): 135 if override: 136 arg_options = arg_options.copy() 137 arg_options.pop("default", None) 138 arg_options.pop("required", None) 139 140 parser.add_argument(*arg_names, **arg_options) 141 142 143def StrToBool(str_in): 144 if str_in.lower() in ["true", "t", "1"]: 145 return True 146 if str_in.lower() in ["false", "f", "0"]: 147 return False 148 149 raise AttributeError("%s is not a valid boolean string" % str_in) 150 151 152def _BuildArgsDict(args): 153 """Populate ArgumentDict with all arguments""" 154 args.AddArgument( 155 "-n", 156 "--iterations", 157 dest="iterations", 158 type=int, 159 help="Number of iterations to try in the search.", 160 default=50, 161 ) 162 args.AddArgument( 163 "-i", 164 "--get_initial_items", 165 dest="get_initial_items", 166 help="Script to run to get the initial objects. " 167 "If your script requires user input " 168 "the --verbose option must be used", 169 ) 170 args.AddArgument( 171 "-g", 172 "--switch_to_good", 173 dest="switch_to_good", 174 help="Script to run to switch to good. " 175 "If your switch script requires user input " 176 "the --verbose option must be used", 177 ) 178 args.AddArgument( 179 "-b", 180 "--switch_to_bad", 181 dest="switch_to_bad", 182 help="Script to run to switch to bad. " 183 "If your switch script requires user input " 184 "the --verbose option must be used", 185 ) 186 args.AddArgument( 187 "-I", 188 "--test_setup_script", 189 dest="test_setup_script", 190 help="Optional script to perform building, flashing, " 191 "and other setup before the test script runs.", 192 ) 193 args.AddArgument( 194 "-t", 195 "--test_script", 196 dest="test_script", 197 help="Script to run to test the " "output after packages are built.", 198 ) 199 # No input (evals to False), 200 # --prune (evals to True), 201 # --prune=False, 202 # --prune=True 203 args.AddArgument( 204 "-p", 205 "--prune", 206 dest="prune", 207 nargs="?", 208 const=True, 209 default=False, 210 type=StrToBool, 211 metavar="bool", 212 help="If True, continue until all bad items are found. " 213 "Defaults to False.", 214 ) 215 args.AddArgument( 216 "-P", 217 "--pass_bisect", 218 dest="pass_bisect", 219 default=None, 220 help="Script to generate another script for pass level bisect, " 221 "which contains command line options to build bad item. " 222 "This will also turn on pass/transformation level bisection. " 223 "Needs support of `-opt-bisect-limit`(pass) and " 224 "`-print-debug-counter`(transformation) from LLVM. " 225 "For now it only supports one single bad item, so to use it, " 226 "prune must be set to False.", 227 ) 228 # No input (evals to False), 229 # --ir_diff (evals to True), 230 # --ir_diff=False, 231 # --ir_diff=True 232 args.AddArgument( 233 "-d", 234 "--ir_diff", 235 dest="ir_diff", 236 nargs="?", 237 const=True, 238 default=False, 239 type=StrToBool, 240 metavar="bool", 241 help="Whether to print IR differences before and after bad " 242 "pass/transformation to verbose output. Defaults to False, " 243 "only works when pass_bisect is enabled.", 244 ) 245 # No input (evals to False), 246 # --noincremental (evals to True), 247 # --noincremental=False, 248 # --noincremental=True 249 args.AddArgument( 250 "-c", 251 "--noincremental", 252 dest="noincremental", 253 nargs="?", 254 const=True, 255 default=False, 256 type=StrToBool, 257 metavar="bool", 258 help="If True, don't propagate good/bad changes " 259 "incrementally. Defaults to False.", 260 ) 261 # No input (evals to False), 262 # --file_args (evals to True), 263 # --file_args=False, 264 # --file_args=True 265 args.AddArgument( 266 "-f", 267 "--file_args", 268 dest="file_args", 269 nargs="?", 270 const=True, 271 default=False, 272 type=StrToBool, 273 metavar="bool", 274 help="Whether to use a file to pass arguments to scripts. " 275 "Defaults to False.", 276 ) 277 # No input (evals to True), 278 # --verify (evals to True), 279 # --verify=False, 280 # --verify=True 281 args.AddArgument( 282 "--verify", 283 dest="verify", 284 nargs="?", 285 const=True, 286 default=True, 287 type=StrToBool, 288 metavar="bool", 289 help="Whether to run verify iterations before searching. " 290 "Defaults to True.", 291 ) 292 args.AddArgument( 293 "-N", 294 "--prune_iterations", 295 dest="prune_iterations", 296 type=int, 297 help="Number of prune iterations to try in the search.", 298 default=100, 299 ) 300 # No input (evals to False), 301 # --verbose (evals to True), 302 # --verbose=False, 303 # --verbose=True 304 args.AddArgument( 305 "-V", 306 "--verbose", 307 dest="verbose", 308 nargs="?", 309 const=True, 310 default=False, 311 type=StrToBool, 312 metavar="bool", 313 help="If True, print full output to console.", 314 ) 315 args.AddArgument( 316 "-r", 317 "--resume", 318 dest="resume", 319 action="store_true", 320 help="Resume bisection tool execution from state file." 321 "Useful if the last bisection was terminated " 322 "before it could properly finish.", 323 ) 324