Refactor debugging configuration, add support for server_ready_pattern // Resolve #3401
This commit is contained in:
@@ -18,16 +18,12 @@ import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
from platformio import fs
|
||||
from platformio.compat import (
|
||||
WINDOWS,
|
||||
aio_create_task,
|
||||
aio_get_running_loop,
|
||||
get_locale_encoding,
|
||||
string_types,
|
||||
)
|
||||
from platformio.proc import get_pythonexe_path
|
||||
from platformio.project.helpers import get_project_core_dir
|
||||
|
||||
|
||||
class DebugSubprocessProtocol(asyncio.SubprocessProtocol):
|
||||
@@ -61,12 +57,6 @@ class DebugBaseProcess:
|
||||
STDOUT_CHUNK_SIZE = 2048
|
||||
LOG_FILE = None
|
||||
|
||||
COMMON_PATTERNS = {
|
||||
"PLATFORMIO_HOME_DIR": get_project_core_dir(),
|
||||
"PLATFORMIO_CORE_DIR": get_project_core_dir(),
|
||||
"PYTHONEXE": get_pythonexe_path(),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.transport = None
|
||||
self._is_running = False
|
||||
@@ -155,32 +145,6 @@ class DebugBaseProcess:
|
||||
self._exit_future.set_result(True)
|
||||
self._exit_future = None
|
||||
|
||||
def apply_patterns(self, source, patterns=None):
|
||||
_patterns = self.COMMON_PATTERNS.copy()
|
||||
_patterns.update(patterns or {})
|
||||
|
||||
for key, value in _patterns.items():
|
||||
if key.endswith(("_DIR", "_PATH")):
|
||||
_patterns[key] = fs.to_unix_path(value)
|
||||
|
||||
def _replace(text):
|
||||
for key, value in _patterns.items():
|
||||
pattern = "$%s" % key
|
||||
text = text.replace(pattern, value or "")
|
||||
return text
|
||||
|
||||
if isinstance(source, string_types):
|
||||
source = _replace(source)
|
||||
elif isinstance(source, (list, dict)):
|
||||
items = enumerate(source) if isinstance(source, list) else source.items()
|
||||
for key, value in items:
|
||||
if isinstance(value, string_types):
|
||||
source[key] = _replace(value)
|
||||
elif isinstance(value, (list, dict)):
|
||||
source[key] = self.apply_patterns(value, patterns)
|
||||
|
||||
return source
|
||||
|
||||
def terminate(self):
|
||||
if not self.is_running() or not self.transport:
|
||||
return
|
||||
|
||||
@@ -23,8 +23,6 @@ from platformio import fs, proc, telemetry, util
|
||||
from platformio.cache import ContentCache
|
||||
from platformio.compat import aio_get_running_loop, hashlib_encode_data, is_bytes
|
||||
from platformio.debug import helpers
|
||||
from platformio.debug.exception import DebugInvalidOptionsError
|
||||
from platformio.debug.initcfgs import get_gdb_init_config
|
||||
from platformio.debug.process.base import DebugBaseProcess
|
||||
from platformio.debug.process.server import DebugServerProcess
|
||||
from platformio.project.helpers import get_project_cache_dir
|
||||
@@ -37,14 +35,12 @@ class DebugClientProcess(
|
||||
PIO_SRC_NAME = ".pioinit"
|
||||
INIT_COMPLETED_BANNER = "PlatformIO: Initialization completed"
|
||||
|
||||
def __init__(self, project_dir, args, debug_options, env_options):
|
||||
def __init__(self, project_dir, debug_config):
|
||||
super(DebugClientProcess, self).__init__()
|
||||
self.project_dir = project_dir
|
||||
self.args = list(args)
|
||||
self.debug_options = debug_options
|
||||
self.env_options = env_options
|
||||
self.debug_config = debug_config
|
||||
|
||||
self._server_process = DebugServerProcess(debug_options, env_options)
|
||||
self._server_process = DebugServerProcess(debug_config)
|
||||
self._session_id = None
|
||||
|
||||
if not os.path.isdir(get_project_cache_dir()):
|
||||
@@ -56,27 +52,13 @@ class DebugClientProcess(
|
||||
self._target_is_running = False
|
||||
self._errors_buffer = b""
|
||||
|
||||
async def run(self, gdb_path, prog_path):
|
||||
session_hash = gdb_path + prog_path
|
||||
async def run(self, extra_args):
|
||||
gdb_path = self.debug_config.client_executable_path
|
||||
session_hash = gdb_path + self.debug_config.program_path
|
||||
self._session_id = hashlib.sha1(hashlib_encode_data(session_hash)).hexdigest()
|
||||
self._kill_previous_session()
|
||||
|
||||
patterns = {
|
||||
"PROJECT_DIR": self.project_dir,
|
||||
"PROG_PATH": prog_path,
|
||||
"PROG_DIR": os.path.dirname(prog_path),
|
||||
"PROG_NAME": os.path.basename(os.path.splitext(prog_path)[0]),
|
||||
"DEBUG_PORT": self.debug_options["port"],
|
||||
"UPLOAD_PROTOCOL": self.debug_options["upload_protocol"],
|
||||
"INIT_BREAK": self.debug_options["init_break"] or "",
|
||||
"LOAD_CMDS": "\n".join(self.debug_options["load_cmds"] or []),
|
||||
}
|
||||
|
||||
await self._server_process.run(patterns)
|
||||
if not patterns["DEBUG_PORT"]:
|
||||
patterns["DEBUG_PORT"] = self._server_process.get_debug_port()
|
||||
|
||||
self.generate_pioinit(self._gdbsrc_dir, patterns)
|
||||
self.debug_config.port = await self._server_process.run()
|
||||
self.generate_init_script(os.path.join(self._gdbsrc_dir, self.PIO_SRC_NAME))
|
||||
|
||||
# start GDB client
|
||||
args = [
|
||||
@@ -89,13 +71,11 @@ class DebugClientProcess(
|
||||
"-l",
|
||||
"10",
|
||||
]
|
||||
args.extend(self.args)
|
||||
if not gdb_path:
|
||||
raise DebugInvalidOptionsError("GDB client is not configured")
|
||||
args.extend(list(extra_args or []))
|
||||
gdb_data_dir = self._get_data_dir(gdb_path)
|
||||
if gdb_data_dir:
|
||||
args.extend(["--data-directory", gdb_data_dir])
|
||||
args.append(patterns["PROG_PATH"])
|
||||
args.append(self.debug_config.program_path)
|
||||
await self.spawn(*args, cwd=self.project_dir, wait_until_exit=True)
|
||||
|
||||
@staticmethod
|
||||
@@ -107,13 +87,13 @@ class DebugClientProcess(
|
||||
)
|
||||
return gdb_data_dir if os.path.isdir(gdb_data_dir) else None
|
||||
|
||||
def generate_pioinit(self, dst_dir, patterns):
|
||||
def generate_init_script(self, dst):
|
||||
# default GDB init commands depending on debug tool
|
||||
commands = get_gdb_init_config(self.debug_options).split("\n")
|
||||
commands = self.debug_config.get_init_script("gdb").split("\n")
|
||||
|
||||
if self.debug_options["init_cmds"]:
|
||||
commands = self.debug_options["init_cmds"]
|
||||
commands.extend(self.debug_options["extra_cmds"])
|
||||
if self.debug_config.init_cmds:
|
||||
commands = self.debug_config.init_cmds
|
||||
commands.extend(self.debug_config.extra_cmds)
|
||||
|
||||
if not any("define pio_reset_run_target" in cmd for cmd in commands):
|
||||
commands = [
|
||||
@@ -134,20 +114,20 @@ class DebugClientProcess(
|
||||
"define pio_restart_target",
|
||||
" pio_reset_halt_target",
|
||||
" $INIT_BREAK",
|
||||
" %s" % ("continue" if patterns["INIT_BREAK"] else "next"),
|
||||
" %s" % ("continue" if self.debug_config.init_break else "next"),
|
||||
"end",
|
||||
]
|
||||
|
||||
banner = [
|
||||
"echo PlatformIO Unified Debugger -> http://bit.ly/pio-debug\\n",
|
||||
"echo PlatformIO: debug_tool = %s\\n" % self.debug_options["tool"],
|
||||
"echo PlatformIO: debug_tool = %s\\n" % self.debug_config.tool_name,
|
||||
"echo PlatformIO: Initializing remote target...\\n",
|
||||
]
|
||||
footer = ["echo %s\\n" % self.INIT_COMPLETED_BANNER]
|
||||
commands = banner + commands + footer
|
||||
|
||||
with open(os.path.join(dst_dir, self.PIO_SRC_NAME), "w") as fp:
|
||||
fp.write("\n".join(self.apply_patterns(commands, patterns)))
|
||||
with open(dst, "w") as fp:
|
||||
fp.write("\n".join(self.debug_config.reveal_patterns(commands)))
|
||||
|
||||
def connection_made(self, transport):
|
||||
super(DebugClientProcess, self).connection_made(transport)
|
||||
@@ -179,7 +159,9 @@ class DebugClientProcess(
|
||||
# go to init break automatically
|
||||
if self.INIT_COMPLETED_BANNER.encode() in data:
|
||||
telemetry.send_event(
|
||||
"Debug", "Started", telemetry.dump_run_environment(self.env_options)
|
||||
"Debug",
|
||||
"Started",
|
||||
telemetry.dump_run_environment(self.debug_config.env_options),
|
||||
)
|
||||
self._auto_exec_continue()
|
||||
|
||||
@@ -194,12 +176,12 @@ class DebugClientProcess(
|
||||
aio_get_running_loop().call_later(0.1, self._auto_exec_continue)
|
||||
return
|
||||
|
||||
if not self.debug_options["init_break"] or self._target_is_running:
|
||||
if not self.debug_config.init_break or self._target_is_running:
|
||||
return
|
||||
|
||||
self.console_log(
|
||||
"PlatformIO: Resume the execution to `debug_init_break = %s`\n"
|
||||
% self.debug_options["init_break"]
|
||||
% self.debug_config.init_break
|
||||
)
|
||||
self.console_log(
|
||||
"PlatformIO: More configuration options -> http://bit.ly/pio-debug\n"
|
||||
@@ -226,7 +208,7 @@ class DebugClientProcess(
|
||||
last_erros = re.sub(r'((~|&)"|\\n\"|\\t)', " ", last_erros, flags=re.M)
|
||||
|
||||
err = "%s -> %s" % (
|
||||
telemetry.dump_run_environment(self.env_options),
|
||||
telemetry.dump_run_environment(self.debug_config.env_options),
|
||||
last_erros,
|
||||
)
|
||||
telemetry.send_exception("DebugInitError: %s" % err)
|
||||
|
||||
@@ -13,10 +13,12 @@
|
||||
# limitations under the License.
|
||||
|
||||
import asyncio
|
||||
import fnmatch
|
||||
import os
|
||||
import time
|
||||
|
||||
from platformio import fs, util
|
||||
from platformio import fs
|
||||
from platformio.compat import MACOS, WINDOWS
|
||||
from platformio.debug.exception import DebugInvalidOptionsError
|
||||
from platformio.debug.helpers import escape_gdbmi_stream, is_gdbmi_mode
|
||||
from platformio.debug.process.base import DebugBaseProcess
|
||||
@@ -24,27 +26,22 @@ from platformio.proc import where_is_program
|
||||
|
||||
|
||||
class DebugServerProcess(DebugBaseProcess):
|
||||
def __init__(self, debug_options, env_options):
|
||||
def __init__(self, debug_config):
|
||||
super(DebugServerProcess, self).__init__()
|
||||
self.debug_options = debug_options
|
||||
self.env_options = env_options
|
||||
|
||||
self._debug_port = ":3333"
|
||||
self.debug_config = debug_config
|
||||
self._ready = False
|
||||
|
||||
async def run(self, patterns): # pylint: disable=too-many-branches
|
||||
systype = util.get_systype()
|
||||
server = self.debug_options.get("server")
|
||||
async def run(self): # pylint: disable=too-many-branches
|
||||
server = self.debug_config.server
|
||||
if not server:
|
||||
return None
|
||||
server = self.apply_patterns(server, patterns)
|
||||
server_executable = server["executable"]
|
||||
if not server_executable:
|
||||
return None
|
||||
if server["cwd"]:
|
||||
server_executable = os.path.join(server["cwd"], server_executable)
|
||||
if (
|
||||
"windows" in systype
|
||||
WINDOWS
|
||||
and not server_executable.endswith(".exe")
|
||||
and os.path.isfile(server_executable + ".exe")
|
||||
):
|
||||
@@ -56,15 +53,18 @@ class DebugServerProcess(DebugBaseProcess):
|
||||
raise DebugInvalidOptionsError(
|
||||
"\nCould not launch Debug Server '%s'. Please check that it "
|
||||
"is installed and is included in a system PATH\n\n"
|
||||
"See documentation or contact contact@platformio.org:\n"
|
||||
"See documentation:\n"
|
||||
"https://docs.platformio.org/page/plus/debugging.html\n"
|
||||
% server_executable
|
||||
)
|
||||
|
||||
openocd_pipe_allowed = all(
|
||||
[not self.debug_options["port"], "openocd" in server_executable]
|
||||
[
|
||||
not self.debug_config.env_options.get("debug_port"),
|
||||
"gdb" in self.debug_config.client_executable_path,
|
||||
"openocd" in server_executable,
|
||||
]
|
||||
)
|
||||
# openocd_pipe_allowed = False
|
||||
if openocd_pipe_allowed:
|
||||
args = []
|
||||
if server["cwd"]:
|
||||
@@ -76,18 +76,16 @@ class DebugServerProcess(DebugBaseProcess):
|
||||
str_args = " ".join(
|
||||
[arg if arg.startswith("-") else '"%s"' % arg for arg in args]
|
||||
)
|
||||
self._debug_port = '| "%s" %s' % (server_executable, str_args)
|
||||
self._debug_port = fs.to_unix_path(self._debug_port)
|
||||
return self._debug_port
|
||||
return fs.to_unix_path('| "%s" %s' % (server_executable, str_args))
|
||||
|
||||
env = os.environ.copy()
|
||||
# prepend server "lib" folder to LD path
|
||||
if (
|
||||
"windows" not in systype
|
||||
not WINDOWS
|
||||
and server["cwd"]
|
||||
and os.path.isdir(os.path.join(server["cwd"], "lib"))
|
||||
):
|
||||
ld_key = "DYLD_LIBRARY_PATH" if "darwin" in systype else "LD_LIBRARY_PATH"
|
||||
ld_key = "DYLD_LIBRARY_PATH" if MACOS else "LD_LIBRARY_PATH"
|
||||
env[ld_key] = os.path.join(server["cwd"], "lib")
|
||||
if os.environ.get(ld_key):
|
||||
env[ld_key] = "%s:%s" % (env[ld_key], os.environ.get(ld_key))
|
||||
@@ -102,20 +100,12 @@ class DebugServerProcess(DebugBaseProcess):
|
||||
await self.spawn(
|
||||
*([server_executable] + server["arguments"]), cwd=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"
|
||||
|
||||
await self._wait_until_ready()
|
||||
|
||||
return self._debug_port
|
||||
return self.debug_config.port
|
||||
|
||||
async def _wait_until_ready(self):
|
||||
ready_pattern = self.debug_options.get("server", {}).get("ready_pattern")
|
||||
ready_pattern = self.debug_config.server_ready_pattern
|
||||
timeout = 60 if ready_pattern else 10
|
||||
elapsed = 0
|
||||
delay = 0.5
|
||||
@@ -129,14 +119,11 @@ class DebugServerProcess(DebugBaseProcess):
|
||||
def _check_ready_by_pattern(self, data):
|
||||
if self._ready:
|
||||
return self._ready
|
||||
ready_pattern = self.debug_options.get("server", {}).get("ready_pattern")
|
||||
ready_pattern = self.debug_config.server_ready_pattern
|
||||
if ready_pattern:
|
||||
self._ready = ready_pattern.encode() in data
|
||||
return self._ready
|
||||
|
||||
def get_debug_port(self):
|
||||
return self._debug_port
|
||||
|
||||
def stdout_data_received(self, data):
|
||||
super(DebugServerProcess, self).stdout_data_received(
|
||||
escape_gdbmi_stream("@", data) if is_gdbmi_mode() else data
|
||||
|
||||
Reference in New Issue
Block a user