def writer_thread(stream, queue): try: while True: to_write = queue.get() to_json = getattr(to_write, 'to_json', None) if to_json is not None: # Some protocol message to_write.seq = _next_seq() try: to_write = to_json() except: debug_exception('Error serializing %s to json.' % (to_write, )) continue if DEBUG: debug('Writing: %s\n' % (to_write, )) if to_write.__class__ == bytes: as_bytes = to_write else: as_bytes = to_write.encode('utf-8') stream.write('Content-Length: %s\r\n\r\n' % (len(as_bytes))) stream.write(as_bytes) stream.flush() except: debug_exception()
def _notify_on_exited(process, on_exited): try: process.wait() if DEBUG: debug('notify process exited\n') on_exited() except: debug_exception()
def main(): ''' Starts the debug adapter (creates a thread to read from stdin and another to write to stdout as expected by the vscode debug protocol). We pass the command processor to the reader thread as the idea is that the reader thread will read a message, convert it to an instance of the message in the schema and then forward it to the command processor which will interpret and act on it, posting the results to the writer queue. ''' try: import sys try: from queue import Queue except ImportError: from Queue import Queue write_queue = Queue() command_processor = CommandProcessor(write_queue) if DEBUG: debug('Starting. Args: %s\n' % (', '.join(sys.argv), )) write_to = sys.stdout read_from = sys.stdin if sys.version_info[0] <= 2: if sys.platform == "win32": # must read streams as binary on windows import os, msvcrt msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) else: # Py3 write_to = sys.stdout.buffer read_from = sys.stdin.buffer writer = threading.Thread(target=writer_thread, args=(write_to, write_queue)) reader = threading.Thread(target=reader_thread, args=(read_from, command_processor)) reader.start() writer.start() reader.join() writer.join() except: debug_exception() debug('exiting main.\n')
def __init__(self, request, launch_response, command_processor): ''' :param LaunchRequest request: :param LaunchResponse launch_response: ''' from debug_adapter._constants import VALID_CONSOLE_OPTIONS import weakref self._weak_command_processor = weakref.ref(command_processor) self._valid = True self._cmdline = [] self._popen = None import sys import os.path file_to_run = request.arguments.kwargs.get('program') self._cwd = request.arguments.kwargs.get('cwd') self._console = request.arguments.kwargs.get('console') self._run_in_debug_mode = not request.arguments.noDebug if self._console not in VALID_CONSOLE_OPTIONS: launch_response.success = False launch_response.message = 'Invalid console option: %s (must be one of: %s)' % ( self._console, VALID_CONSOLE_OPTIONS) return if not os.path.exists(self._cwd): launch_response.success = False launch_response.message = 'cwd specified does not exist: %s' % (self._cwd,) return if not os.path.exists(file_to_run): launch_response.success = False launch_response.message = 'File: %s does not exist.' % (file_to_run,) self._valid = False return # TODO: Properly handle debug/no debug mode if DEBUG: debug('Run in debug mode: %s\n' % (self._run_in_debug_mode,)) cmdline = [sys.executable, '-u', file_to_run] self._cmdline = cmdline
def send_to_stdin(self, expression): popen = self._popen if popen is not None: import threading try: debug('Sending: %s to stdin.' % (expression,)) def write_to_stdin(popen, expression): popen.stdin.write(expression) if not expression.endswith('\r') and not expression.endswith('\n'): popen.stdin.write('\n') popen.stdin.flush() # Do it in a thread (in theory the OS could have that filled up and we would never complete # trying to write -- although probably a bit far fetched, let's code as if that could actually happen). t = threading.Thread(target=write_to_stdin, args=(popen, expression)) t.setDaemon(True) t.start() except: debug_exception('Error writing: >>%s<< to stdin.' % (expression,))
def __call__(self, protocol_message): if DEBUG: debug('Process json: %s\n' % (json.dumps(protocol_message.to_dict(), indent=4, encoding='utf-8', sort_keys=True), )) try: if protocol_message.type == 'request': method_name = 'on_%s_request' % (protocol_message.command, ) on_request = getattr(self, method_name, None) if on_request is not None: on_request(protocol_message) else: if DEBUG: debug( 'Unhandled: %s not available in CommandProcessor.\n' % (method_name, )) except: debug_exception()
def read(stream): ''' Reads one message from the stream and returns the related dict (or None if EOF was reached). :param stream: The stream we should be reading from. :return dict|NoneType: The dict which represents a message or None if the stream was closed. ''' headers = {} while True: # Interpret the http protocol headers line = stream.readline() # The trailing \r\n should be there. if DEBUG: debug( 'read line: >>%s<<\n' % (line.replace('\r', '\\r').replace('\n', '\\n')), ) if not line: # EOF return None line = line.strip().decode('ascii') if not line: # Read just a new line without any contents break try: name, value = line.split(': ', 1) except ValueError: raise RuntimeError('invalid header line: {}'.format(line)) headers[name] = value if not headers: raise RuntimeError('got message without headers') size = int(headers['Content-Length']) # Get the actual json body = stream.read(size) return json.loads(body.decode('utf-8'))
def launch(self): from debug_adapter import schema from debug_adapter._constants import CONSOLE_EXTERNAL from debug_adapter._constants import CONSOLE_INTEGRATED from debug_adapter._constants import CONSOLE_NONE import threading # Note: using a weak-reference so that callbacks don't keep it alive weak_command_processor = self._weak_command_processor console = self._console if not weak_command_processor().supports_run_in_terminal: # If the client doesn't support running in the terminal we fallback to using the debug console. console = CONSOLE_NONE def on_exited(): command_processor = weak_command_processor() if command_processor is not None: restart = False terminated_event = schema.TerminatedEvent(body=schema.TerminatedEventBody(restart=restart)) command_processor.write_message(terminated_event) threads = [] if console == CONSOLE_NONE: import subprocess if DEBUG: debug('Launching in "none" console: %s' % (self._cmdline,)) self._popen = subprocess.Popen(self._cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, cwd=self._cwd) def on_output(output, category): command_processor = weak_command_processor() if command_processor is not None: output_event = schema.OutputEvent(schema.OutputEventBody(output, category=category)) command_processor.write_message(output_event) threads.append(threading.Thread(target=_read_stream, args=(self._popen.stdout, on_output, 'stdout'))) threads.append(threading.Thread(target=_read_stream, args=(self._popen.stderr, on_output, 'stderr'))) threads.append(threading.Thread(target=_notify_on_exited, args=(self._popen, on_exited))) elif console in (CONSOLE_INTEGRATED, CONSOLE_EXTERNAL): kind = 'external' if console == CONSOLE_INTEGRATED: kind = 'internal' if DEBUG: debug('Launching in "%s" console: %s' % (kind, self._cmdline,)) command_processor = weak_command_processor() if command_processor is not None: # TODO: Provide an env command_processor.write_message(schema.RunInTerminalRequest(schema.RunInTerminalRequestArguments( cwd=self._cwd, args=self._cmdline, kind=kind))) # When the user runs in the integrated terminal or in the external terminal, in regular run mode (i.e.: # no debug) , say that this session has been finished (he can close the process from the related # terminal). on_exited() for t in threads: t.setDaemon(True) for t in threads: t.start()