feat(debug): enable Python debug workflow and debug server integration
Adds an end-to-end Python debug workflow for the extension. Includes a new prepare-debug.ps1 script and a VS Code task. Extends the Python server and extension to coordinate a debug session and safe startup. - Add prepare-debug.ps1 and a VS Code task to build the debug bundle - Enable Python debug wiring in the server and tests - Ensure a single stable Python debug session during startup
This commit is contained in:
+47
-24
@@ -6,6 +6,7 @@ import os
|
||||
import pathlib
|
||||
import runpy
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
def update_sys_path(path_to_add: str) -> None:
|
||||
@@ -14,10 +15,43 @@ def update_sys_path(path_to_add: str) -> None:
|
||||
sys.path.append(path_to_add)
|
||||
|
||||
|
||||
# Ensure debugger is loaded before we load anything else, to debug initialization.
|
||||
debugger_path = os.getenv("DEBUGPY_PATH", None)
|
||||
if debugger_path:
|
||||
if debugger_path.endswith("debugpy"):
|
||||
def _debug_endpoint() -> tuple[str, int]:
|
||||
host = os.getenv("NXPS_DEBUG_HOST", "127.0.0.1")
|
||||
raw_port = os.getenv("NXPS_DEBUG_PORT", "5678")
|
||||
try:
|
||||
port = int(raw_port)
|
||||
except ValueError as error:
|
||||
raise RuntimeError(f"Invalid NXPS_DEBUG_PORT: {raw_port!r}") from error
|
||||
if not 1 <= port <= 65535:
|
||||
raise RuntimeError(f"NXPS_DEBUG_PORT is outside the valid range: {port}")
|
||||
return host, port
|
||||
|
||||
|
||||
def _connect_debugger(debugpy, host: str, port: int, timeout: float = 15.0) -> None:
|
||||
deadline = time.monotonic() + timeout
|
||||
last_error: OSError | None = None
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
debugpy.connect((host, port))
|
||||
debugpy.wait_for_client()
|
||||
return
|
||||
except (ConnectionRefusedError, OSError) as error:
|
||||
last_error = error
|
||||
time.sleep(0.25)
|
||||
|
||||
raise RuntimeError(
|
||||
f"Could not connect debugpy to {host}:{port} within {timeout:.0f} seconds"
|
||||
) from last_error
|
||||
|
||||
|
||||
def main() -> None:
|
||||
# Ensure debugger is loaded before we load anything else, so server
|
||||
# initialization and module-level feature registration can be debugged.
|
||||
debugger_path = os.getenv("DEBUGPY_PATH")
|
||||
if not debugger_path:
|
||||
raise RuntimeError("DEBUGPY_PATH is missing in Python debug mode")
|
||||
|
||||
if pathlib.Path(debugger_path).name.casefold() == "debugpy":
|
||||
debugger_path = os.fspath(pathlib.Path(debugger_path).parent)
|
||||
|
||||
update_sys_path(debugger_path)
|
||||
@@ -25,25 +59,14 @@ if debugger_path:
|
||||
# pylint: disable=wrong-import-position,import-error
|
||||
import debugpy
|
||||
|
||||
# 5678 is the default port, If you need to change it update it here
|
||||
# and in launch.json.
|
||||
# Connecting requires the "Python debug server" listener (launch.json) to be
|
||||
# up first. If it isn't (e.g. wrong launch config was used), don't crash the
|
||||
# whole language server - just continue running without the debugger attached.
|
||||
try:
|
||||
debugpy.connect(5678)
|
||||
except (ConnectionRefusedError, OSError) as exc:
|
||||
print(
|
||||
f"debugpy: could not connect to debug adapter on port 5678 "
|
||||
f"({exc}); continuing without debugging.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
host, port = _debug_endpoint()
|
||||
print(f"debugpy: waiting for VS Code at {host}:{port}", file=sys.stderr)
|
||||
_connect_debugger(debugpy, host, port)
|
||||
print("debugpy: VS Code attached; starting language server", file=sys.stderr)
|
||||
|
||||
# This will ensure that execution is paused as soon as the debugger
|
||||
# connects to VS Code. If you don't want to pause here comment this
|
||||
# line and set breakpoints as appropriate.
|
||||
# debugpy.breakpoint()
|
||||
server_path = os.fspath(pathlib.Path(__file__).parent / "lsp_server.py")
|
||||
runpy.run_path(server_path, run_name="__main__")
|
||||
|
||||
SERVER_PATH = os.fspath(pathlib.Path(__file__).parent / "lsp_server.py")
|
||||
# NOTE: Set breakpoint in `lsp_server.py` before continuing.
|
||||
runpy.run_path(SERVER_PATH, run_name="__main__")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user