diff --git a/platformio/commands/debug/client.py b/platformio/commands/debug/client.py index fa468bba..610f598d 100644 --- a/platformio/commands/debug/client.py +++ b/platformio/commands/debug/client.py @@ -20,6 +20,7 @@ from hashlib import sha1 from os.path import basename, dirname, isdir, join, realpath, splitext from tempfile import mkdtemp +from twisted.internet import defer # pylint: disable=import-error from twisted.internet import protocol # pylint: disable=import-error from twisted.internet import reactor # pylint: disable=import-error from twisted.internet import stdio # pylint: disable=import-error @@ -33,8 +34,6 @@ from platformio.commands.debug.server import DebugServer from platformio.compat import hashlib_encode_data, is_bytes from platformio.project.helpers import get_project_cache_dir -LOG_FILE = None - class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes @@ -42,6 +41,7 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes INIT_COMPLETED_BANNER = "PlatformIO: Initialization completed" def __init__(self, project_dir, args, debug_options, env_options): + super(GDBClient, self).__init__() self.project_dir = project_dir self.args = list(args) self.debug_options = debug_options @@ -55,10 +55,10 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes self._gdbsrc_dir = mkdtemp(dir=get_project_cache_dir(), prefix=".piodebug-") self._target_is_run = False - self._last_server_activity = 0 self._auto_continue_timer = None self._errors_buffer = b"" + @defer.inlineCallbacks def spawn(self, gdb_path, prog_path): session_hash = gdb_path + prog_path self._session_id = sha1(hashlib_encode_data(session_hash)).hexdigest() @@ -75,10 +75,10 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes "LOAD_CMDS": "\n".join(self.debug_options["load_cmds"] or []), } - self._debug_server.spawn(patterns) - + yield self._debug_server.spawn(patterns) if not patterns["DEBUG_PORT"]: patterns["DEBUG_PORT"] = self._debug_server.get_debug_port() + self.generate_pioinit(self._gdbsrc_dir, patterns) # start GDB client @@ -100,9 +100,10 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes args.extend(["--data-directory", gdb_data_dir]) args.append(patterns["PROG_PATH"]) - return reactor.spawnProcess( + transport = reactor.spawnProcess( self, gdb_path, args, path=self.project_dir, env=os.environ ) + defer.returnValue(transport) @staticmethod def _get_data_dir(gdb_path): @@ -175,12 +176,7 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes stdio.StandardIO(p) def onStdInData(self, data): - if LOG_FILE: - with open(LOG_FILE, "ab") as fp: - fp.write(data) - - self._last_server_activity = time.time() - + super(GDBClient, self).onStdInData(data) if b"-exec-run" in data: if self._target_is_run: token, _ = data.split(b"-", 1) @@ -206,11 +202,6 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes reactor.stop() def outReceived(self, data): - if LOG_FILE: - with open(LOG_FILE, "ab") as fp: - fp.write(data) - - self._last_server_activity = time.time() super(GDBClient, self).outReceived(data) self._handle_error(data) # go to init break automatically @@ -232,7 +223,7 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes def _auto_exec_continue(self): auto_exec_delay = 0.5 # in seconds - if self._last_server_activity > (time.time() - auto_exec_delay): + if self._last_activity > (time.time() - auto_exec_delay): return if self._auto_continue_timer: self._auto_continue_timer.stop() diff --git a/platformio/commands/debug/process.py b/platformio/commands/debug/process.py index d23f26b3..67557f3d 100644 --- a/platformio/commands/debug/process.py +++ b/platformio/commands/debug/process.py @@ -13,6 +13,7 @@ # limitations under the License. import signal +import time import click from twisted.internet import protocol # pylint: disable=import-error @@ -22,12 +23,11 @@ from platformio.compat import string_types from platformio.proc import get_pythonexe_path from platformio.project.helpers import get_project_core_dir -LOG_FILE = None - class BaseProcess(protocol.ProcessProtocol, object): STDOUT_CHUNK_SIZE = 2048 + LOG_FILE = None COMMON_PATTERNS = { "PLATFORMIO_HOME_DIR": get_project_core_dir(), @@ -35,6 +35,9 @@ class BaseProcess(protocol.ProcessProtocol, object): "PYTHONEXE": get_pythonexe_path(), } + def __init__(self): + self._last_activity = 0 + def apply_patterns(self, source, patterns=None): _patterns = self.COMMON_PATTERNS.copy() _patterns.update(patterns or {}) @@ -61,23 +64,30 @@ class BaseProcess(protocol.ProcessProtocol, object): return source + def onStdInData(self, data): + self._last_activity = time.time() + if self.LOG_FILE: + with open(self.LOG_FILE, "ab") as fp: + fp.write(data) + def outReceived(self, data): - if LOG_FILE: - with open(LOG_FILE, "ab") as fp: + self._last_activity = time.time() + if self.LOG_FILE: + with open(self.LOG_FILE, "ab") as fp: fp.write(data) while data: chunk = data[: self.STDOUT_CHUNK_SIZE] click.echo(chunk, nl=False) data = data[self.STDOUT_CHUNK_SIZE :] - @staticmethod - def errReceived(data): - if LOG_FILE: - with open(LOG_FILE, "ab") as fp: + def errReceived(self, data): + self._last_activity = time.time() + if self.LOG_FILE: + with open(self.LOG_FILE, "ab") as fp: fp.write(data) click.echo(data, nl=False, err=True) - @staticmethod - def processEnded(_): + def processEnded(self, _): + self._last_activity = time.time() # Allow terminating via SIGINT/CTRL+C signal.signal(signal.SIGINT, signal.default_int_handler) diff --git a/platformio/commands/debug/server.py b/platformio/commands/debug/server.py index 855628c3..a129a6aa 100644 --- a/platformio/commands/debug/server.py +++ b/platformio/commands/debug/server.py @@ -13,8 +13,10 @@ # limitations under the License. import os +import time from os.path import isdir, isfile, join +from twisted.internet import defer # pylint: disable=import-error from twisted.internet import reactor # pylint: disable=import-error from platformio import fs, util @@ -26,13 +28,15 @@ from platformio.proc import where_is_program class DebugServer(BaseProcess): def __init__(self, debug_options, env_options): + super(DebugServer, self).__init__() self.debug_options = debug_options self.env_options = env_options - self._debug_port = None + self._debug_port = ":3333" self._transport = None self._process_ended = False + @defer.inlineCallbacks def spawn(self, patterns): # pylint: disable=too-many-branches systype = util.get_systype() server = self.debug_options.get("server") @@ -62,10 +66,10 @@ class DebugServer(BaseProcess): % server_executable ) - self._debug_port = ":3333" openocd_pipe_allowed = all( [not self.debug_options["port"], "openocd" in server_executable] ) + openocd_pipe_allowed = False if openocd_pipe_allowed: args = [] if server["cwd"]: @@ -79,43 +83,67 @@ class DebugServer(BaseProcess): ) self._debug_port = '| "%s" %s' % (server_executable, str_args) self._debug_port = fs.to_unix_path(self._debug_port) - else: - env = os.environ.copy() - # prepend server "lib" folder to LD path - if ( - "windows" not in systype - and server["cwd"] - and isdir(join(server["cwd"], "lib")) - ): - ld_key = ( - "DYLD_LIBRARY_PATH" if "darwin" in systype else "LD_LIBRARY_PATH" - ) - env[ld_key] = join(server["cwd"], "lib") - if os.environ.get(ld_key): - env[ld_key] = "%s:%s" % (env[ld_key], os.environ.get(ld_key)) - # prepend BIN to PATH - if server["cwd"] and isdir(join(server["cwd"], "bin")): - env["PATH"] = "%s%s%s" % ( - join(server["cwd"], "bin"), - os.pathsep, - os.environ.get("PATH", os.environ.get("Path", "")), - ) + return self._debug_port - self._transport = reactor.spawnProcess( - self, - server_executable, - [server_executable] + server["arguments"], - path=server["cwd"], - env=env, + env = os.environ.copy() + # prepend server "lib" folder to LD path + if ( + "windows" not in systype + and server["cwd"] + and isdir(join(server["cwd"], "lib")) + ): + ld_key = "DYLD_LIBRARY_PATH" if "darwin" in systype else "LD_LIBRARY_PATH" + env[ld_key] = join(server["cwd"], "lib") + if os.environ.get(ld_key): + env[ld_key] = "%s:%s" % (env[ld_key], os.environ.get(ld_key)) + # prepend BIN to PATH + if server["cwd"] and isdir(join(server["cwd"], "bin")): + env["PATH"] = "%s%s%s" % ( + join(server["cwd"], "bin"), + os.pathsep, + os.environ.get("PATH", os.environ.get("Path", "")), ) - if "mspdebug" in server_executable.lower(): - self._debug_port = ":2000" - elif "jlink" in server_executable.lower(): - self._debug_port = ":2331" - elif "qemu" in server_executable.lower(): - self._debug_port = ":1234" - return self._transport + self._transport = reactor.spawnProcess( + self, + server_executable, + [server_executable] + server["arguments"], + path=server["cwd"], + env=env, + ) + if "mspdebug" in server_executable.lower(): + self._debug_port = ":2000" + elif "jlink" in server_executable.lower(): + self._debug_port = ":2331" + elif "qemu" in server_executable.lower(): + self._debug_port = ":1234" + + yield self._wait_until_ready() + + return self._debug_port + + @defer.inlineCallbacks + def _wait_until_ready(self): + timeout = 10 + elapsed = 0 + delay = 1 + ready_delay = 0.5 + while ( + not self._process_ended + and elapsed < timeout + and ( + not self._last_activity + or not (self._last_activity < (time.time() - ready_delay)) + ) + ): + yield self.async_sleep(delay) + elapsed += delay + + @staticmethod + def async_sleep(secs): + d = defer.Deferred() + reactor.callLater(secs, d.callback, None) + return d def get_debug_port(self): return self._debug_port