Merge branch 'feature/issue-3401-renode-support' into develop

This commit is contained in:
Ivan Kravets
2020-03-19 00:50:21 +02:00
3 changed files with 93 additions and 64 deletions

View File

@ -20,6 +20,7 @@ from hashlib import sha1
from os.path import basename, dirname, isdir, join, realpath, splitext from os.path import basename, dirname, isdir, join, realpath, splitext
from tempfile import mkdtemp 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 protocol # pylint: disable=import-error
from twisted.internet import reactor # pylint: disable=import-error from twisted.internet import reactor # pylint: disable=import-error
from twisted.internet import stdio # 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.compat import hashlib_encode_data, is_bytes
from platformio.project.helpers import get_project_cache_dir from platformio.project.helpers import get_project_cache_dir
LOG_FILE = None
class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes 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" INIT_COMPLETED_BANNER = "PlatformIO: Initialization completed"
def __init__(self, project_dir, args, debug_options, env_options): def __init__(self, project_dir, args, debug_options, env_options):
super(GDBClient, self).__init__()
self.project_dir = project_dir self.project_dir = project_dir
self.args = list(args) self.args = list(args)
self.debug_options = debug_options 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._gdbsrc_dir = mkdtemp(dir=get_project_cache_dir(), prefix=".piodebug-")
self._target_is_run = False self._target_is_run = False
self._last_server_activity = 0
self._auto_continue_timer = None self._auto_continue_timer = None
self._errors_buffer = b"" self._errors_buffer = b""
@defer.inlineCallbacks
def spawn(self, gdb_path, prog_path): def spawn(self, gdb_path, prog_path):
session_hash = gdb_path + prog_path session_hash = gdb_path + prog_path
self._session_id = sha1(hashlib_encode_data(session_hash)).hexdigest() 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 []), "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"]: if not patterns["DEBUG_PORT"]:
patterns["DEBUG_PORT"] = self._debug_server.get_debug_port() patterns["DEBUG_PORT"] = self._debug_server.get_debug_port()
self.generate_pioinit(self._gdbsrc_dir, patterns) self.generate_pioinit(self._gdbsrc_dir, patterns)
# start GDB client # start GDB client
@ -100,9 +100,10 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes
args.extend(["--data-directory", gdb_data_dir]) args.extend(["--data-directory", gdb_data_dir])
args.append(patterns["PROG_PATH"]) args.append(patterns["PROG_PATH"])
return reactor.spawnProcess( transport = reactor.spawnProcess(
self, gdb_path, args, path=self.project_dir, env=os.environ self, gdb_path, args, path=self.project_dir, env=os.environ
) )
defer.returnValue(transport)
@staticmethod @staticmethod
def _get_data_dir(gdb_path): def _get_data_dir(gdb_path):
@ -175,12 +176,7 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes
stdio.StandardIO(p) stdio.StandardIO(p)
def onStdInData(self, data): def onStdInData(self, data):
if LOG_FILE: super(GDBClient, self).onStdInData(data)
with open(LOG_FILE, "ab") as fp:
fp.write(data)
self._last_server_activity = time.time()
if b"-exec-run" in data: if b"-exec-run" in data:
if self._target_is_run: if self._target_is_run:
token, _ = data.split(b"-", 1) token, _ = data.split(b"-", 1)
@ -206,11 +202,6 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes
reactor.stop() reactor.stop()
def outReceived(self, data): 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) super(GDBClient, self).outReceived(data)
self._handle_error(data) self._handle_error(data)
# go to init break automatically # go to init break automatically
@ -232,7 +223,7 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes
def _auto_exec_continue(self): def _auto_exec_continue(self):
auto_exec_delay = 0.5 # in seconds 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 return
if self._auto_continue_timer: if self._auto_continue_timer:
self._auto_continue_timer.stop() self._auto_continue_timer.stop()

View File

@ -13,6 +13,7 @@
# limitations under the License. # limitations under the License.
import signal import signal
import time
import click import click
from twisted.internet import protocol # pylint: disable=import-error 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.proc import get_pythonexe_path
from platformio.project.helpers import get_project_core_dir from platformio.project.helpers import get_project_core_dir
LOG_FILE = None
class BaseProcess(protocol.ProcessProtocol, object): class BaseProcess(protocol.ProcessProtocol, object):
STDOUT_CHUNK_SIZE = 2048 STDOUT_CHUNK_SIZE = 2048
LOG_FILE = None
COMMON_PATTERNS = { COMMON_PATTERNS = {
"PLATFORMIO_HOME_DIR": get_project_core_dir(), "PLATFORMIO_HOME_DIR": get_project_core_dir(),
@ -35,6 +35,9 @@ class BaseProcess(protocol.ProcessProtocol, object):
"PYTHONEXE": get_pythonexe_path(), "PYTHONEXE": get_pythonexe_path(),
} }
def __init__(self):
self._last_activity = 0
def apply_patterns(self, source, patterns=None): def apply_patterns(self, source, patterns=None):
_patterns = self.COMMON_PATTERNS.copy() _patterns = self.COMMON_PATTERNS.copy()
_patterns.update(patterns or {}) _patterns.update(patterns or {})
@ -61,23 +64,30 @@ class BaseProcess(protocol.ProcessProtocol, object):
return source 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): def outReceived(self, data):
if LOG_FILE: self._last_activity = time.time()
with open(LOG_FILE, "ab") as fp: if self.LOG_FILE:
with open(self.LOG_FILE, "ab") as fp:
fp.write(data) fp.write(data)
while data: while data:
chunk = data[: self.STDOUT_CHUNK_SIZE] chunk = data[: self.STDOUT_CHUNK_SIZE]
click.echo(chunk, nl=False) click.echo(chunk, nl=False)
data = data[self.STDOUT_CHUNK_SIZE :] data = data[self.STDOUT_CHUNK_SIZE :]
@staticmethod def errReceived(self, data):
def errReceived(data): self._last_activity = time.time()
if LOG_FILE: if self.LOG_FILE:
with open(LOG_FILE, "ab") as fp: with open(self.LOG_FILE, "ab") as fp:
fp.write(data) fp.write(data)
click.echo(data, nl=False, err=True) click.echo(data, nl=False, err=True)
@staticmethod def processEnded(self, _):
def processEnded(_): self._last_activity = time.time()
# Allow terminating via SIGINT/CTRL+C # Allow terminating via SIGINT/CTRL+C
signal.signal(signal.SIGINT, signal.default_int_handler) signal.signal(signal.SIGINT, signal.default_int_handler)

View File

@ -13,8 +13,10 @@
# limitations under the License. # limitations under the License.
import os import os
import time
from os.path import isdir, isfile, join 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 twisted.internet import reactor # pylint: disable=import-error
from platformio import fs, util from platformio import fs, util
@ -26,13 +28,15 @@ from platformio.proc import where_is_program
class DebugServer(BaseProcess): class DebugServer(BaseProcess):
def __init__(self, debug_options, env_options): def __init__(self, debug_options, env_options):
super(DebugServer, self).__init__()
self.debug_options = debug_options self.debug_options = debug_options
self.env_options = env_options self.env_options = env_options
self._debug_port = None self._debug_port = ":3333"
self._transport = None self._transport = None
self._process_ended = False self._process_ended = False
@defer.inlineCallbacks
def spawn(self, patterns): # pylint: disable=too-many-branches def spawn(self, patterns): # pylint: disable=too-many-branches
systype = util.get_systype() systype = util.get_systype()
server = self.debug_options.get("server") server = self.debug_options.get("server")
@ -62,10 +66,10 @@ class DebugServer(BaseProcess):
% server_executable % server_executable
) )
self._debug_port = ":3333"
openocd_pipe_allowed = all( openocd_pipe_allowed = all(
[not self.debug_options["port"], "openocd" in server_executable] [not self.debug_options["port"], "openocd" in server_executable]
) )
openocd_pipe_allowed = False
if openocd_pipe_allowed: if openocd_pipe_allowed:
args = [] args = []
if server["cwd"]: if server["cwd"]:
@ -79,43 +83,67 @@ class DebugServer(BaseProcess):
) )
self._debug_port = '| "%s" %s' % (server_executable, str_args) self._debug_port = '| "%s" %s' % (server_executable, str_args)
self._debug_port = fs.to_unix_path(self._debug_port) self._debug_port = fs.to_unix_path(self._debug_port)
else: return self._debug_port
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", "")),
)
self._transport = reactor.spawnProcess( env = os.environ.copy()
self, # prepend server "lib" folder to LD path
server_executable, if (
[server_executable] + server["arguments"], "windows" not in systype
path=server["cwd"], and server["cwd"]
env=env, 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): def get_debug_port(self):
return self._debug_port return self._debug_port