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