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