[Dexter] Add option to pass a Visual Studio solution instead of a binary

This patch allows a visual studio solution file to be passed directly
into Dexter, instead of using a pre-built binary and a small internal
solution file with template arguments. This is primarily to allow
launching an application that has specific launch configuration
requirements, without needing all the details of this configuration to
be built directly into Dexter or adding a config file that simply
duplicates existing settings in the VS solution.

Reviewed By: Orlando

Differential Revision: https://reviews.llvm.org/D110167
This commit is contained in:
Stephen Tozer 2021-10-08 17:39:51 +01:00
parent f94c9af622
commit 75b316929a
7 changed files with 51 additions and 21 deletions

View File

@ -48,6 +48,8 @@ def add_builder_tool_arguments(parser):
type=str,
choices=sorted(_find_build_scripts().keys()),
help='test builder to use')
build_group.add_argument('--vs-solution', metavar="<file>",
help='provide a path to an already existing visual studio solution.')
parser.add_argument(
'--cflags', type=str, default='', help='compiler flags')
parser.add_argument('--ldflags', type=str, default='', help='linker flags')

View File

@ -16,7 +16,6 @@ from types import SimpleNamespace
from dex.command.CommandBase import StepExpectInfo
from dex.dextIR import DebuggerIR, FrameIR, LocIR, StepIR, ValueIR
from dex.utils.Exceptions import DebuggerException
from dex.utils.Exceptions import NotYetLoadedDebuggerException
from dex.utils.ReturnCode import ReturnCode
def watch_is_active(watch_info: StepExpectInfo, path, frame_idx, line_no):
@ -44,11 +43,10 @@ class DebuggerBase(object, metaclass=abc.ABCMeta):
self._interface = None
self.has_loaded = False
self._loading_error = NotYetLoadedDebuggerException()
self._loading_error = None
try:
self._interface = self._load_interface()
self.has_loaded = True
self._loading_error = None
except DebuggerException:
self._loading_error = sys.exc_info()

View File

@ -21,7 +21,9 @@ class DebuggerControllerBase(object, metaclass=abc.ABCMeta):
"""
self.debugger = debugger
with self.debugger:
self._run_debugger_custom()
if not self.debugger.loading_error:
self._run_debugger_custom()
# We may need to pickle this debugger controller after running the
# debugger. Debuggers are not picklable objects, so set to None.
self.debugger = None

View File

@ -67,6 +67,23 @@ class VisualStudio(DebuggerBase, metaclass=abc.ABCMeta): # pylint: disable=abst
super(VisualStudio, self).__init__(*args)
def _create_solution(self):
self._solution.Create(self.context.working_directory.path,
'DexterSolution')
try:
self._solution.AddFromFile(self._project_file)
except OSError:
raise LoadDebuggerException(
'could not debug the specified executable', sys.exc_info())
def _load_solution(self):
try:
self._solution.Open(self.context.options.vs_solution)
except:
raise LoadDebuggerException(
'could not load specified vs solution at {}'.
format(self.context.options.vs_solution), sys.exc_info())
def _custom_init(self):
try:
self._debugger = self._interface.Debugger
@ -76,14 +93,10 @@ class VisualStudio(DebuggerBase, metaclass=abc.ABCMeta): # pylint: disable=abst
self.context.options.show_debugger)
self._solution = self._interface.Solution
self._solution.Create(self.context.working_directory.path,
'DexterSolution')
try:
self._solution.AddFromFile(self._project_file)
except OSError:
raise LoadDebuggerException(
'could not debug the specified executable', sys.exc_info())
if self.context.options.vs_solution is None:
self._create_solution()
else:
self._load_solution()
self._fn_step = self._debugger.StepInto
self._fn_go = self._debugger.Go

View File

@ -58,7 +58,12 @@ class TestToolBase(ToolBase):
warn(self.context, '--cflags and --ldflags will be ignored when not'
' using --builder')
if options.binary:
if options.vs_solution:
options.vs_solution = os.path.abspath(options.vs_solution)
if not os.path.isfile(options.vs_solution):
raise Error('<d>could not find VS solution file</> <r>"{}"</>'
.format(options.vs_solution))
elif options.binary:
options.binary = os.path.abspath(options.binary)
if not os.path.isfile(options.binary):
raise Error('<d>could not find binary file</> <r>"{}"</>'

View File

@ -42,20 +42,26 @@ class Tool(ToolBase):
self.options = self.context.options
Timer.display = self.options.time_report
def raise_debugger_error(self, action, debugger):
msg = '<d>could not {} {}</> ({})\n'.format(
action, debugger.name, debugger.loading_error)
if self.options.verbose:
msg = '{}\n {}'.format(
msg, ' '.join(debugger.loading_error_trace))
raise Error(msg)
def go(self) -> ReturnCode:
with Timer('loading debugger'):
debugger = Debuggers(self.context).load(self.options.debugger)
with Timer('running debugger'):
if not debugger.is_available:
msg = '<d>could not load {}</> ({})\n'.format(
debugger.name, debugger.loading_error)
if self.options.verbose:
msg = '{}\n {}'.format(
msg, ' '.join(debugger.loading_error_trace))
raise Error(msg)
self.raise_debugger_error('load', debugger)
self.debugger_controller.run_debugger(debugger)
self.debugger_controller.run_debugger(debugger)
if debugger.loading_error:
self.raise_debugger_error('run', debugger)
with open(self.controller_path, 'wb') as fp:
pickle.dump(self.debugger_controller, fp)

View File

@ -108,9 +108,13 @@ class Tool(TestToolBase):
"""Build an executable from the test source with the given --builder
script and flags (--cflags, --ldflags) in the working directory.
Or, if the --binary option has been given, copy the executable provided
into the working directory and rename it to match the --builder output.
into the working directory and rename it to match the --builder output
or skip if --vs-solution was passed on the command line.
"""
if self.context.options.vs_solution:
return
options = self.context.options
if options.binary:
# Copy user's binary into the tmp working directory