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