mirror of
https://github.com/platformio/platformio-core.git
synced 2025-07-30 10:07:14 +02:00
Wait until debug server is ready
This commit is contained in:
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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,64 @@ 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 < (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
|
||||||
|
Reference in New Issue
Block a user