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