1# DExTer : Debugging Experience Tester 2# ~~~~~~ ~ ~~ ~ ~~ 3# 4# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 5# See https://llvm.org/LICENSE.txt for license information. 6# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 7"""This is the main entry point. 8It implements some functionality common to all subtools such as command line 9parsing and running the unit-testing harnesses, before calling the reequested 10subtool. 11""" 12 13import imp 14import os 15import sys 16 17from dex.utils import PrettyOutput, Timer 18from dex.utils import ExtArgParse as argparse 19from dex.utils import get_root_directory 20from dex.utils.Exceptions import Error, ToolArgumentError 21from dex.utils.UnitTests import unit_tests_ok 22from dex.utils.Version import version 23from dex.utils import WorkingDirectory 24from dex.utils.ReturnCode import ReturnCode 25 26 27def _output_bug_report_message(context): 28 """ In the event of a catastrophic failure, print bug report request to the 29 user. 30 """ 31 context.o.red( 32 '\n\n' 33 '<g>****************************************</>\n' 34 '<b>****************************************</>\n' 35 '****************************************\n' 36 '** **\n' 37 '** <y>This is a bug in <a>DExTer</>.</> **\n' 38 '** **\n' 39 '** <y>Please report it.</> **\n' 40 '** **\n' 41 '****************************************\n' 42 '<b>****************************************</>\n' 43 '<g>****************************************</>\n' 44 '\n' 45 '<b>system:</>\n' 46 '<d>{}</>\n\n' 47 '<b>version:</>\n' 48 '<d>{}</>\n\n' 49 '<b>args:</>\n' 50 '<d>{}</>\n' 51 '\n'.format(sys.platform, version('DExTer'), 52 [sys.executable] + sys.argv), 53 stream=PrettyOutput.stderr) 54 55 56def get_tools_directory(): 57 """ Returns directory path where DExTer tool imports can be 58 found. 59 """ 60 tools_directory = os.path.join(get_root_directory(), 'tools') 61 assert os.path.isdir(tools_directory), tools_directory 62 return tools_directory 63 64 65def get_tool_names(): 66 """ Returns a list of expected DExTer Tools 67 """ 68 return [ 69 'clang-opt-bisect', 'help', 'list-debuggers', 'no-tool-', 70 'run-debugger-internal-', 'test', 'view' 71 ] 72 73 74def _set_auto_highlights(context): 75 """Flag some strings for auto-highlighting. 76 """ 77 context.o.auto_reds.extend([ 78 r'[Ee]rror\:', 79 r'[Ee]xception\:', 80 r'un(expected|recognized) argument', 81 ]) 82 context.o.auto_yellows.extend([ 83 r'[Ww]arning\:', 84 r'\(did you mean ', 85 r'During handling of the above exception, another exception', 86 ]) 87 88 89def _get_options_and_args(context): 90 """ get the options and arguments from the commandline 91 """ 92 parser = argparse.ExtArgumentParser(context, add_help=False) 93 parser.add_argument('tool', default=None, nargs='?') 94 options, args = parser.parse_known_args(sys.argv[1:]) 95 96 return options, args 97 98 99def _get_tool_name(options): 100 """ get the name of the dexter tool (if passed) specified on the command 101 line, otherwise return 'no_tool_'. 102 """ 103 tool_name = options.tool 104 if tool_name is None: 105 tool_name = 'no_tool_' 106 else: 107 _is_valid_tool_name(tool_name) 108 return tool_name 109 110 111def _is_valid_tool_name(tool_name): 112 """ check tool name matches a tool directory within the dexter tools 113 directory. 114 """ 115 valid_tools = get_tool_names() 116 if tool_name not in valid_tools: 117 raise Error('invalid tool "{}" (choose from {})'.format( 118 tool_name, 119 ', '.join([t for t in valid_tools if not t.endswith('-')]))) 120 121 122def _import_tool_module(tool_name): 123 """ Imports the python module at the tool directory specificed by 124 tool_name. 125 """ 126 # format tool argument to reflect tool directory form. 127 tool_name = tool_name.replace('-', '_') 128 129 tools_directory = get_tools_directory() 130 module_info = imp.find_module(tool_name, [tools_directory]) 131 132 return imp.load_module(tool_name, *module_info) 133 134 135def tool_main(context, tool, args): 136 with Timer(tool.name): 137 options, defaults = tool.parse_command_line(args) 138 Timer.display = options.time_report 139 Timer.indent = options.indent_timer_level 140 Timer.fn = context.o.blue 141 context.options = options 142 context.version = version(tool.name) 143 144 if options.version: 145 context.o.green('{}\n'.format(context.version)) 146 return ReturnCode.OK 147 148 if (options.unittest != 'off' and not unit_tests_ok(context)): 149 raise Error('<d>unit test failures</>') 150 151 if options.colortest: 152 context.o.colortest() 153 return ReturnCode.OK 154 155 try: 156 tool.handle_base_options(defaults) 157 except ToolArgumentError as e: 158 raise Error(e) 159 160 dir_ = context.options.working_directory 161 with WorkingDirectory(context, dir=dir_) as context.working_directory: 162 return_code = tool.go() 163 164 return return_code 165 166 167class Context(object): 168 """Context encapsulates globally useful objects and data; passed to many 169 Dexter functions. 170 """ 171 172 def __init__(self): 173 self.o: PrettyOutput = None 174 self.working_directory: str = None 175 self.options: dict = None 176 self.version: str = None 177 self.root_directory: str = None 178 179 180def main() -> ReturnCode: 181 182 context = Context() 183 184 with PrettyOutput() as context.o: 185 try: 186 context.root_directory = get_root_directory() 187 # Flag some strings for auto-highlighting. 188 _set_auto_highlights(context) 189 options, args = _get_options_and_args(context) 190 # raises 'Error' if command line tool is invalid. 191 tool_name = _get_tool_name(options) 192 module = _import_tool_module(tool_name) 193 return tool_main(context, module.Tool(context), args) 194 except Error as e: 195 context.o.auto( 196 '\nerror: {}\n'.format(str(e)), stream=PrettyOutput.stderr) 197 try: 198 if context.options.error_debug: 199 raise 200 except AttributeError: 201 pass 202 return ReturnCode._ERROR 203 except (KeyboardInterrupt, SystemExit): 204 raise 205 except: # noqa 206 _output_bug_report_message(context) 207 raise 208