1*760c253cSXin Li#!/usr/bin/env python3 2*760c253cSXin Li# -*- coding: utf-8 -*- 3*760c253cSXin Li# Copyright 2020 The ChromiumOS Authors 4*760c253cSXin Li# Use of this source code is governed by a BSD-style license that can be 5*760c253cSXin Li# found in the LICENSE file. 6*760c253cSXin Li 7*760c253cSXin Li"""The unified package/object bisecting tool.""" 8*760c253cSXin Li 9*760c253cSXin Li 10*760c253cSXin Liimport abc 11*760c253cSXin Liimport argparse 12*760c253cSXin Lifrom argparse import RawTextHelpFormatter 13*760c253cSXin Liimport os 14*760c253cSXin Liimport shlex 15*760c253cSXin Liimport sys 16*760c253cSXin Li 17*760c253cSXin Lifrom binary_search_tool import binary_search_state 18*760c253cSXin Lifrom binary_search_tool import common 19*760c253cSXin Lifrom cros_utils import command_executer 20*760c253cSXin Lifrom cros_utils import logger 21*760c253cSXin Li 22*760c253cSXin Li 23*760c253cSXin Liclass Bisector(object, metaclass=abc.ABCMeta): 24*760c253cSXin Li """The abstract base class for Bisectors.""" 25*760c253cSXin Li 26*760c253cSXin Li def __init__(self, options, overrides=None): 27*760c253cSXin Li """Constructor for Bisector abstract base class 28*760c253cSXin Li 29*760c253cSXin Li Args: 30*760c253cSXin Li options: positional arguments for specific mode (board, remote, etc.) 31*760c253cSXin Li overrides: optional dict of overrides for argument defaults 32*760c253cSXin Li """ 33*760c253cSXin Li self.options = options 34*760c253cSXin Li self.overrides = overrides 35*760c253cSXin Li if not overrides: 36*760c253cSXin Li self.overrides = {} 37*760c253cSXin Li self.logger = logger.GetLogger() 38*760c253cSXin Li self.ce = command_executer.GetCommandExecuter() 39*760c253cSXin Li 40*760c253cSXin Li def _PrettyPrintArgs(self, args, overrides): 41*760c253cSXin Li """Output arguments in a nice, human readable format 42*760c253cSXin Li 43*760c253cSXin Li Will print and log all arguments for the bisecting tool and make note of 44*760c253cSXin Li which arguments have been overridden. 45*760c253cSXin Li 46*760c253cSXin Li Example output: 47*760c253cSXin Li ./run_bisect.py package daisy 172.17.211.184 -I "" -t cros_pkg/my_test.sh 48*760c253cSXin Li Performing ChromeOS Package bisection 49*760c253cSXin Li Method Config: 50*760c253cSXin Li board : daisy 51*760c253cSXin Li remote : 172.17.211.184 52*760c253cSXin Li 53*760c253cSXin Li Bisection Config: (* = overridden) 54*760c253cSXin Li get_initial_items : cros_pkg/get_initial_items.sh 55*760c253cSXin Li switch_to_good : cros_pkg/switch_to_good.sh 56*760c253cSXin Li switch_to_bad : cros_pkg/switch_to_bad.sh 57*760c253cSXin Li * test_setup_script : 58*760c253cSXin Li * test_script : cros_pkg/my_test.sh 59*760c253cSXin Li prune : True 60*760c253cSXin Li noincremental : False 61*760c253cSXin Li file_args : True 62*760c253cSXin Li 63*760c253cSXin Li Args: 64*760c253cSXin Li args: The args to be given to binary_search_state.Run. This represents 65*760c253cSXin Li how the bisection tool will run (with overridden arguments already 66*760c253cSXin Li added in). 67*760c253cSXin Li overrides: The dict of overriden arguments provided by the user. This is 68*760c253cSXin Li provided so the user can be told which arguments were 69*760c253cSXin Li overriden and with what value. 70*760c253cSXin Li """ 71*760c253cSXin Li # Output method config (board, remote, etc.) 72*760c253cSXin Li options = vars(self.options) 73*760c253cSXin Li out = "\nPerforming %s bisection\n" % self.method_name 74*760c253cSXin Li out += "Method Config:\n" 75*760c253cSXin Li max_key_len = max([len(str(x)) for x in options.keys()]) 76*760c253cSXin Li for key in sorted(options): 77*760c253cSXin Li val = options[key] 78*760c253cSXin Li key_str = str(key).rjust(max_key_len) 79*760c253cSXin Li val_str = str(val) 80*760c253cSXin Li out += " %s : %s\n" % (key_str, val_str) 81*760c253cSXin Li 82*760c253cSXin Li # Output bisection config (scripts, prune, etc.) 83*760c253cSXin Li out += "\nBisection Config: (* = overridden)\n" 84*760c253cSXin Li max_key_len = max([len(str(x)) for x in args.keys()]) 85*760c253cSXin Li # Print args in common._ArgsDict order 86*760c253cSXin Li args_order = [x["dest"] for x in common.GetArgsDict().values()] 87*760c253cSXin Li for key in sorted(args, key=args_order.index): 88*760c253cSXin Li val = args[key] 89*760c253cSXin Li key_str = str(key).rjust(max_key_len) 90*760c253cSXin Li val_str = str(val) 91*760c253cSXin Li changed_str = "*" if key in overrides else " " 92*760c253cSXin Li 93*760c253cSXin Li out += " %s %s : %s\n" % (changed_str, key_str, val_str) 94*760c253cSXin Li 95*760c253cSXin Li out += "\n" 96*760c253cSXin Li self.logger.LogOutput(out) 97*760c253cSXin Li 98*760c253cSXin Li def ArgOverride(self, args, overrides, pretty_print=True): 99*760c253cSXin Li """Override arguments based on given overrides and provide nice output 100*760c253cSXin Li 101*760c253cSXin Li Args: 102*760c253cSXin Li args: dict of arguments to be passed to binary_search_state.Run (runs 103*760c253cSXin Li dict.update, causing args to be mutated). 104*760c253cSXin Li overrides: dict of arguments to update args with 105*760c253cSXin Li pretty_print: if True print out args/overrides to user in pretty format 106*760c253cSXin Li """ 107*760c253cSXin Li args.update(overrides) 108*760c253cSXin Li if pretty_print: 109*760c253cSXin Li self._PrettyPrintArgs(args, overrides) 110*760c253cSXin Li 111*760c253cSXin Li @abc.abstractmethod 112*760c253cSXin Li def PreRun(self): 113*760c253cSXin Li pass 114*760c253cSXin Li 115*760c253cSXin Li @abc.abstractmethod 116*760c253cSXin Li def Run(self): 117*760c253cSXin Li pass 118*760c253cSXin Li 119*760c253cSXin Li @abc.abstractmethod 120*760c253cSXin Li def PostRun(self): 121*760c253cSXin Li pass 122*760c253cSXin Li 123*760c253cSXin Li 124*760c253cSXin Liclass BisectPackage(Bisector): 125*760c253cSXin Li """The class for package bisection steps.""" 126*760c253cSXin Li 127*760c253cSXin Li cros_pkg_setup = "cros_pkg/setup.sh" 128*760c253cSXin Li cros_pkg_cleanup = "cros_pkg/%s_cleanup.sh" 129*760c253cSXin Li 130*760c253cSXin Li def __init__(self, options, overrides): 131*760c253cSXin Li super(BisectPackage, self).__init__(options, overrides) 132*760c253cSXin Li self.method_name = "ChromeOS Package" 133*760c253cSXin Li self.default_kwargs = { 134*760c253cSXin Li "get_initial_items": "cros_pkg/get_initial_items.sh", 135*760c253cSXin Li "switch_to_good": "cros_pkg/switch_to_good.sh", 136*760c253cSXin Li "switch_to_bad": "cros_pkg/switch_to_bad.sh", 137*760c253cSXin Li "test_setup_script": "cros_pkg/test_setup.sh", 138*760c253cSXin Li "test_script": "cros_pkg/interactive_test.sh", 139*760c253cSXin Li "noincremental": False, 140*760c253cSXin Li "prune": True, 141*760c253cSXin Li "file_args": True, 142*760c253cSXin Li } 143*760c253cSXin Li self.setup_cmd = " ".join( 144*760c253cSXin Li (self.cros_pkg_setup, self.options.board, self.options.remote) 145*760c253cSXin Li ) 146*760c253cSXin Li self.ArgOverride(self.default_kwargs, self.overrides) 147*760c253cSXin Li 148*760c253cSXin Li def PreRun(self): 149*760c253cSXin Li ret, _, _ = self.ce.RunCommandWExceptionCleanup( 150*760c253cSXin Li self.setup_cmd, print_to_console=True 151*760c253cSXin Li ) 152*760c253cSXin Li if ret: 153*760c253cSXin Li self.logger.LogError( 154*760c253cSXin Li "Package bisector setup failed w/ error %d" % ret 155*760c253cSXin Li ) 156*760c253cSXin Li return 1 157*760c253cSXin Li return 0 158*760c253cSXin Li 159*760c253cSXin Li def Run(self): 160*760c253cSXin Li return binary_search_state.Run(**self.default_kwargs) 161*760c253cSXin Li 162*760c253cSXin Li def PostRun(self): 163*760c253cSXin Li cmd = self.cros_pkg_cleanup % self.options.board 164*760c253cSXin Li ret, _, _ = self.ce.RunCommandWExceptionCleanup( 165*760c253cSXin Li cmd, print_to_console=True 166*760c253cSXin Li ) 167*760c253cSXin Li if ret: 168*760c253cSXin Li self.logger.LogError( 169*760c253cSXin Li "Package bisector cleanup failed w/ error %d" % ret 170*760c253cSXin Li ) 171*760c253cSXin Li return 1 172*760c253cSXin Li 173*760c253cSXin Li self.logger.LogOutput( 174*760c253cSXin Li ( 175*760c253cSXin Li "Cleanup successful! To restore the bisection " 176*760c253cSXin Li "environment run the following:\n" 177*760c253cSXin Li " cd %s; %s" 178*760c253cSXin Li ) 179*760c253cSXin Li % (os.getcwd(), self.setup_cmd) 180*760c253cSXin Li ) 181*760c253cSXin Li return 0 182*760c253cSXin Li 183*760c253cSXin Li 184*760c253cSXin Liclass BisectObject(Bisector): 185*760c253cSXin Li """The class for object bisection steps.""" 186*760c253cSXin Li 187*760c253cSXin Li sysroot_wrapper_setup = "sysroot_wrapper/setup.sh" 188*760c253cSXin Li sysroot_wrapper_cleanup = "sysroot_wrapper/cleanup.sh" 189*760c253cSXin Li 190*760c253cSXin Li def __init__(self, options, overrides): 191*760c253cSXin Li super(BisectObject, self).__init__(options, overrides) 192*760c253cSXin Li self.method_name = "ChromeOS Object" 193*760c253cSXin Li self.default_kwargs = { 194*760c253cSXin Li "get_initial_items": "sysroot_wrapper/get_initial_items.sh", 195*760c253cSXin Li "switch_to_good": "sysroot_wrapper/switch_to_good.sh", 196*760c253cSXin Li "switch_to_bad": "sysroot_wrapper/switch_to_bad.sh", 197*760c253cSXin Li "test_setup_script": "sysroot_wrapper/test_setup.sh", 198*760c253cSXin Li "test_script": "sysroot_wrapper/interactive_test.sh", 199*760c253cSXin Li "noincremental": False, 200*760c253cSXin Li "prune": True, 201*760c253cSXin Li "file_args": True, 202*760c253cSXin Li } 203*760c253cSXin Li self.options = options 204*760c253cSXin Li if options.dir: 205*760c253cSXin Li os.environ["BISECT_DIR"] = options.dir 206*760c253cSXin Li self.options.dir = os.environ.get("BISECT_DIR", "/tmp/sysroot_bisect") 207*760c253cSXin Li self.setup_cmd = " ".join( 208*760c253cSXin Li ( 209*760c253cSXin Li self.sysroot_wrapper_setup, 210*760c253cSXin Li self.options.board, 211*760c253cSXin Li self.options.remote, 212*760c253cSXin Li self.options.package, 213*760c253cSXin Li str(self.options.reboot).lower(), 214*760c253cSXin Li shlex.quote(self.options.use_flags), 215*760c253cSXin Li ) 216*760c253cSXin Li ) 217*760c253cSXin Li 218*760c253cSXin Li self.ArgOverride(self.default_kwargs, overrides) 219*760c253cSXin Li 220*760c253cSXin Li def PreRun(self): 221*760c253cSXin Li ret, _, _ = self.ce.RunCommandWExceptionCleanup( 222*760c253cSXin Li self.setup_cmd, print_to_console=True 223*760c253cSXin Li ) 224*760c253cSXin Li if ret: 225*760c253cSXin Li self.logger.LogError( 226*760c253cSXin Li "Object bisector setup failed w/ error %d" % ret 227*760c253cSXin Li ) 228*760c253cSXin Li return 1 229*760c253cSXin Li 230*760c253cSXin Li os.environ["BISECT_STAGE"] = "TRIAGE" 231*760c253cSXin Li return 0 232*760c253cSXin Li 233*760c253cSXin Li def Run(self): 234*760c253cSXin Li return binary_search_state.Run(**self.default_kwargs) 235*760c253cSXin Li 236*760c253cSXin Li def PostRun(self): 237*760c253cSXin Li cmd = self.sysroot_wrapper_cleanup 238*760c253cSXin Li ret, _, _ = self.ce.RunCommandWExceptionCleanup( 239*760c253cSXin Li cmd, print_to_console=True 240*760c253cSXin Li ) 241*760c253cSXin Li if ret: 242*760c253cSXin Li self.logger.LogError( 243*760c253cSXin Li "Object bisector cleanup failed w/ error %d" % ret 244*760c253cSXin Li ) 245*760c253cSXin Li return 1 246*760c253cSXin Li self.logger.LogOutput( 247*760c253cSXin Li ( 248*760c253cSXin Li "Cleanup successful! To restore the bisection " 249*760c253cSXin Li "environment run the following:\n" 250*760c253cSXin Li " cd %s; %s" 251*760c253cSXin Li ) 252*760c253cSXin Li % (os.getcwd(), self.setup_cmd) 253*760c253cSXin Li ) 254*760c253cSXin Li return 0 255*760c253cSXin Li 256*760c253cSXin Li 257*760c253cSXin Liclass BisectAndroid(Bisector): 258*760c253cSXin Li """The class for Android bisection steps.""" 259*760c253cSXin Li 260*760c253cSXin Li android_setup = "android/setup.sh" 261*760c253cSXin Li android_cleanup = "android/cleanup.sh" 262*760c253cSXin Li default_dir = os.path.expanduser("~/ANDROID_BISECT") 263*760c253cSXin Li 264*760c253cSXin Li def __init__(self, options, overrides): 265*760c253cSXin Li super(BisectAndroid, self).__init__(options, overrides) 266*760c253cSXin Li self.method_name = "Android" 267*760c253cSXin Li self.default_kwargs = { 268*760c253cSXin Li "get_initial_items": "android/get_initial_items.sh", 269*760c253cSXin Li "switch_to_good": "android/switch_to_good.sh", 270*760c253cSXin Li "switch_to_bad": "android/switch_to_bad.sh", 271*760c253cSXin Li "test_setup_script": "android/test_setup.sh", 272*760c253cSXin Li "test_script": "android/interactive_test.sh", 273*760c253cSXin Li "prune": True, 274*760c253cSXin Li "file_args": True, 275*760c253cSXin Li "noincremental": False, 276*760c253cSXin Li } 277*760c253cSXin Li self.options = options 278*760c253cSXin Li if options.dir: 279*760c253cSXin Li os.environ["BISECT_DIR"] = options.dir 280*760c253cSXin Li self.options.dir = os.environ.get("BISECT_DIR", self.default_dir) 281*760c253cSXin Li 282*760c253cSXin Li num_jobs = "NUM_JOBS='%s'" % self.options.num_jobs 283*760c253cSXin Li device_id = "" 284*760c253cSXin Li if self.options.device_id: 285*760c253cSXin Li device_id = "ANDROID_SERIAL='%s'" % self.options.device_id 286*760c253cSXin Li 287*760c253cSXin Li self.setup_cmd = " ".join( 288*760c253cSXin Li (num_jobs, device_id, self.android_setup, self.options.android_src) 289*760c253cSXin Li ) 290*760c253cSXin Li 291*760c253cSXin Li self.ArgOverride(self.default_kwargs, overrides) 292*760c253cSXin Li 293*760c253cSXin Li def PreRun(self): 294*760c253cSXin Li ret, _, _ = self.ce.RunCommandWExceptionCleanup( 295*760c253cSXin Li self.setup_cmd, print_to_console=True 296*760c253cSXin Li ) 297*760c253cSXin Li if ret: 298*760c253cSXin Li self.logger.LogError( 299*760c253cSXin Li "Android bisector setup failed w/ error %d" % ret 300*760c253cSXin Li ) 301*760c253cSXin Li return 1 302*760c253cSXin Li 303*760c253cSXin Li os.environ["BISECT_STAGE"] = "TRIAGE" 304*760c253cSXin Li return 0 305*760c253cSXin Li 306*760c253cSXin Li def Run(self): 307*760c253cSXin Li return binary_search_state.Run(**self.default_kwargs) 308*760c253cSXin Li 309*760c253cSXin Li def PostRun(self): 310*760c253cSXin Li cmd = self.android_cleanup 311*760c253cSXin Li ret, _, _ = self.ce.RunCommandWExceptionCleanup( 312*760c253cSXin Li cmd, print_to_console=True 313*760c253cSXin Li ) 314*760c253cSXin Li if ret: 315*760c253cSXin Li self.logger.LogError( 316*760c253cSXin Li "Android bisector cleanup failed w/ error %d" % ret 317*760c253cSXin Li ) 318*760c253cSXin Li return 1 319*760c253cSXin Li self.logger.LogOutput( 320*760c253cSXin Li ( 321*760c253cSXin Li "Cleanup successful! To restore the bisection " 322*760c253cSXin Li "environment run the following:\n" 323*760c253cSXin Li " cd %s; %s" 324*760c253cSXin Li ) 325*760c253cSXin Li % (os.getcwd(), self.setup_cmd) 326*760c253cSXin Li ) 327*760c253cSXin Li return 0 328*760c253cSXin Li 329*760c253cSXin Li 330*760c253cSXin Lidef Run(bisector): 331*760c253cSXin Li log = logger.GetLogger() 332*760c253cSXin Li 333*760c253cSXin Li log.LogOutput("Setting up Bisection tool") 334*760c253cSXin Li ret = bisector.PreRun() 335*760c253cSXin Li if ret: 336*760c253cSXin Li return ret 337*760c253cSXin Li 338*760c253cSXin Li log.LogOutput("Running Bisection tool") 339*760c253cSXin Li ret = bisector.Run() 340*760c253cSXin Li if ret: 341*760c253cSXin Li return ret 342*760c253cSXin Li 343*760c253cSXin Li log.LogOutput("Cleaning up Bisection tool") 344*760c253cSXin Li ret = bisector.PostRun() 345*760c253cSXin Li if ret: 346*760c253cSXin Li return ret 347*760c253cSXin Li 348*760c253cSXin Li return 0 349*760c253cSXin Li 350*760c253cSXin Li 351*760c253cSXin Li_HELP_EPILOG = """ 352*760c253cSXin LiRun ./run_bisect.py {method} --help for individual method help/args 353*760c253cSXin Li 354*760c253cSXin Li------------------ 355*760c253cSXin Li 356*760c253cSXin LiSee README.bisect for examples on argument overriding 357*760c253cSXin Li 358*760c253cSXin LiSee below for full override argument reference: 359*760c253cSXin Li""" 360*760c253cSXin Li 361*760c253cSXin Li 362*760c253cSXin Lidef Main(argv): 363*760c253cSXin Li override_parser = argparse.ArgumentParser( 364*760c253cSXin Li add_help=False, 365*760c253cSXin Li argument_default=argparse.SUPPRESS, 366*760c253cSXin Li usage="run_bisect.py {mode} [options]", 367*760c253cSXin Li ) 368*760c253cSXin Li common.BuildArgParser(override_parser, override=True) 369*760c253cSXin Li 370*760c253cSXin Li epilog = _HELP_EPILOG + override_parser.format_help() 371*760c253cSXin Li parser = argparse.ArgumentParser( 372*760c253cSXin Li epilog=epilog, formatter_class=RawTextHelpFormatter 373*760c253cSXin Li ) 374*760c253cSXin Li subparsers = parser.add_subparsers( 375*760c253cSXin Li title="Bisect mode", 376*760c253cSXin Li description=( 377*760c253cSXin Li "Which bisection method to " 378*760c253cSXin Li "use. Each method has " 379*760c253cSXin Li "specific setup and " 380*760c253cSXin Li "arguments. Please consult " 381*760c253cSXin Li "the README for more " 382*760c253cSXin Li "information." 383*760c253cSXin Li ), 384*760c253cSXin Li ) 385*760c253cSXin Li 386*760c253cSXin Li parser_package = subparsers.add_parser("package") 387*760c253cSXin Li parser_package.add_argument("board", help="Board to target") 388*760c253cSXin Li parser_package.add_argument("remote", help="Remote machine to test on") 389*760c253cSXin Li parser_package.set_defaults(handler=BisectPackage) 390*760c253cSXin Li 391*760c253cSXin Li parser_object = subparsers.add_parser("object") 392*760c253cSXin Li parser_object.add_argument("board", help="Board to target") 393*760c253cSXin Li parser_object.add_argument("remote", help="Remote machine to test on") 394*760c253cSXin Li parser_object.add_argument("package", help="Package to emerge and test") 395*760c253cSXin Li parser_object.add_argument( 396*760c253cSXin Li "--use_flags", 397*760c253cSXin Li required=False, 398*760c253cSXin Li default="", 399*760c253cSXin Li help="Use flags passed to emerge", 400*760c253cSXin Li ) 401*760c253cSXin Li parser_object.add_argument( 402*760c253cSXin Li "--noreboot", 403*760c253cSXin Li action="store_false", 404*760c253cSXin Li dest="reboot", 405*760c253cSXin Li help="Do not reboot after updating the package (default: False)", 406*760c253cSXin Li ) 407*760c253cSXin Li parser_object.add_argument( 408*760c253cSXin Li "--dir", 409*760c253cSXin Li help=( 410*760c253cSXin Li "Bisection directory to use, sets " 411*760c253cSXin Li "$BISECT_DIR if provided. Defaults to " 412*760c253cSXin Li "current value of $BISECT_DIR (or " 413*760c253cSXin Li "/tmp/sysroot_bisect if $BISECT_DIR is " 414*760c253cSXin Li "empty)." 415*760c253cSXin Li ), 416*760c253cSXin Li ) 417*760c253cSXin Li parser_object.set_defaults(handler=BisectObject) 418*760c253cSXin Li 419*760c253cSXin Li parser_android = subparsers.add_parser("android") 420*760c253cSXin Li parser_android.add_argument( 421*760c253cSXin Li "android_src", help="Path to android source tree" 422*760c253cSXin Li ) 423*760c253cSXin Li parser_android.add_argument( 424*760c253cSXin Li "--dir", 425*760c253cSXin Li help=( 426*760c253cSXin Li "Bisection directory to use, sets " 427*760c253cSXin Li "$BISECT_DIR if provided. Defaults to " 428*760c253cSXin Li "current value of $BISECT_DIR (or " 429*760c253cSXin Li "~/ANDROID_BISECT/ if $BISECT_DIR is " 430*760c253cSXin Li "empty)." 431*760c253cSXin Li ), 432*760c253cSXin Li ) 433*760c253cSXin Li parser_android.add_argument( 434*760c253cSXin Li "-j", 435*760c253cSXin Li "--num_jobs", 436*760c253cSXin Li type=int, 437*760c253cSXin Li default=1, 438*760c253cSXin Li help=( 439*760c253cSXin Li "Number of jobs that make and various " 440*760c253cSXin Li "scripts for bisector can spawn. Setting " 441*760c253cSXin Li "this value too high can freeze up your " 442*760c253cSXin Li "machine!" 443*760c253cSXin Li ), 444*760c253cSXin Li ) 445*760c253cSXin Li parser_android.add_argument( 446*760c253cSXin Li "--device_id", 447*760c253cSXin Li default="", 448*760c253cSXin Li help=( 449*760c253cSXin Li "Device id for device used for testing. " 450*760c253cSXin Li "Use this if you have multiple Android " 451*760c253cSXin Li "devices plugged into your machine." 452*760c253cSXin Li ), 453*760c253cSXin Li ) 454*760c253cSXin Li parser_android.set_defaults(handler=BisectAndroid) 455*760c253cSXin Li 456*760c253cSXin Li options, remaining = parser.parse_known_args(argv) 457*760c253cSXin Li if remaining: 458*760c253cSXin Li overrides = override_parser.parse_args(remaining) 459*760c253cSXin Li overrides = vars(overrides) 460*760c253cSXin Li else: 461*760c253cSXin Li overrides = {} 462*760c253cSXin Li 463*760c253cSXin Li subcmd = options.handler 464*760c253cSXin Li del options.handler 465*760c253cSXin Li 466*760c253cSXin Li bisector = subcmd(options, overrides) 467*760c253cSXin Li return Run(bisector) 468*760c253cSXin Li 469*760c253cSXin Li 470*760c253cSXin Liif __name__ == "__main__": 471*760c253cSXin Li os.chdir(os.path.dirname(__file__)) 472*760c253cSXin Li sys.exit(Main(sys.argv[1:])) 473