• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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