xref: /llvm-project/cross-project-tests/debuginfo-tests/dexter/dex/tools/Main.py (revision 6779376ee917279b16e256839d236cfdf8fd9ab9)
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