xref: /aosp_15_r20/external/toolchain-utils/binary_search_tool/run_bisect.py (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
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