vibefpmgui.debug_adapters
Which debugger (DAP adapter) to use, per platform (see plan/debugger.md):
- Linux: gdb ≥ 14 (
gdb --interpreter=dap): it knows Fortran (arrays with their bounds, derived types, module variables). Else lldb-dap. - macOS: lldb-dap: Apple's (
xcrun -f lldb-dap, its signed debug server: no code-signing prompts), else one on the PATH. (gdb doesn't run on Apple Silicon; lldb shows no variables of gfortran's programs.) - Windows: gdb ≥ 14 on the PATH, else lldb-dap.
Finding them runs programs (gdb --version): done once, then remembered.
1"""Which debugger (DAP adapter) to use, per platform (see plan/debugger.md): 2 3- **Linux:** gdb ≥ 14 (``gdb --interpreter=dap``): it knows Fortran (arrays 4 with their bounds, derived types, module variables). Else lldb-dap. 5- **macOS:** lldb-dap: Apple's (``xcrun -f lldb-dap``, its signed debug 6 server: no code-signing prompts), else one on the PATH. (gdb doesn't run on 7 Apple Silicon; lldb shows no variables of gfortran's programs.) 8- **Windows:** gdb ≥ 14 on the PATH, else lldb-dap. 9 10Finding them runs programs (``gdb --version``): done once, then remembered. 11""" 12 13from __future__ import annotations 14 15import re 16import shutil 17import sys 18from dataclasses import dataclass 19 20from . import console 21 22GDB_MINIMUM = 14 # the first gdb with DAP 23 24 25@dataclass(frozen=True) 26class Adapter: 27 kind: str # "gdb" or "lldb" 28 command: list[str] # the adapter: its program and arguments 29 version: str = "" 30 31 @property 32 def title(self) -> str: 33 """As shown (the Debug panel's header): "gdb 18.1".""" 34 return f"{self.kind} {self.version}".strip() 35 36 def launch_arguments(self, program: str, args: list[str], cwd: str) -> dict: 37 """The ``launch`` request's arguments, as this adapter names them.""" 38 arguments = {"program": program, "args": list(args), "cwd": cwd} 39 if self.kind == "lldb": 40 arguments["stopOnEntry"] = False 41 return arguments 42 43 44def _gdb_version(gdb: str) -> tuple[int, int] | None: 45 code, output, _err = console.run([gdb, "--version"], timeout=10) 46 if code != 0 or not output: 47 return None 48 match = re.search(rb"GNU gdb[^\n]*?(\d+)\.(\d+)", output) 49 return (int(match.group(1)), int(match.group(2))) if match else None 50 51 52def gdb_adapter() -> Adapter | None: 53 """gdb on the PATH, if it's new enough to speak DAP.""" 54 gdb = shutil.which("gdb") 55 if gdb is None: 56 return None 57 version = _gdb_version(gdb) 58 if version is None or version[0] < GDB_MINIMUM: 59 return None 60 # (-q: no banner, which would be shown as the program's output.) 61 return Adapter("gdb", [gdb, "-q", "--interpreter=dap"], f"{version[0]}.{version[1]}") 62 63 64def lldb_adapter() -> Adapter | None: 65 """lldb-dap: Apple's on macOS, else one on the PATH.""" 66 if sys.platform == "darwin": 67 code, output, _err = console.run(["xcrun", "-f", "lldb-dap"], timeout=10) 68 if code == 0 and output and output.strip(): 69 return Adapter("lldb", [output.decode().strip()]) 70 program = shutil.which("lldb-dap") 71 return Adapter("lldb", [program]) if program else None 72 73 74_found: dict[str, Adapter | None] = {} 75 76 77def find_adapter() -> Adapter | None: 78 """The debugger for this platform (None: none found), looked for once.""" 79 if "adapter" not in _found: 80 finders = (lldb_adapter, gdb_adapter) if sys.platform == "darwin" else (gdb_adapter, lldb_adapter) 81 _found["adapter"] = next((a for a in (f() for f in finders) if a is not None), None) 82 return _found["adapter"] 83 84 85def forget(): 86 """Look for the debugger again next time (a new environment).""" 87 _found.clear() 88 89 90NOT_FOUND = ("No debugger was found. On Linux, install gdb 14 or newer (the project's pixi environment has it); " 91 "on macOS, lldb-dap comes with Xcode's Command Line Tools (xcode-select --install).")
GDB_MINIMUM =
14
@dataclass(frozen=True)
class
Adapter:
26@dataclass(frozen=True) 27class Adapter: 28 kind: str # "gdb" or "lldb" 29 command: list[str] # the adapter: its program and arguments 30 version: str = "" 31 32 @property 33 def title(self) -> str: 34 """As shown (the Debug panel's header): "gdb 18.1".""" 35 return f"{self.kind} {self.version}".strip() 36 37 def launch_arguments(self, program: str, args: list[str], cwd: str) -> dict: 38 """The ``launch`` request's arguments, as this adapter names them.""" 39 arguments = {"program": program, "args": list(args), "cwd": cwd} 40 if self.kind == "lldb": 41 arguments["stopOnEntry"] = False 42 return arguments
title: str
32 @property 33 def title(self) -> str: 34 """As shown (the Debug panel's header): "gdb 18.1".""" 35 return f"{self.kind} {self.version}".strip()
As shown (the Debug panel's header): "gdb 18.1".
def
launch_arguments(self, program: str, args: list[str], cwd: str) -> dict:
37 def launch_arguments(self, program: str, args: list[str], cwd: str) -> dict: 38 """The ``launch`` request's arguments, as this adapter names them.""" 39 arguments = {"program": program, "args": list(args), "cwd": cwd} 40 if self.kind == "lldb": 41 arguments["stopOnEntry"] = False 42 return arguments
The launch request's arguments, as this adapter names them.
53def gdb_adapter() -> Adapter | None: 54 """gdb on the PATH, if it's new enough to speak DAP.""" 55 gdb = shutil.which("gdb") 56 if gdb is None: 57 return None 58 version = _gdb_version(gdb) 59 if version is None or version[0] < GDB_MINIMUM: 60 return None 61 # (-q: no banner, which would be shown as the program's output.) 62 return Adapter("gdb", [gdb, "-q", "--interpreter=dap"], f"{version[0]}.{version[1]}")
gdb on the PATH, if it's new enough to speak DAP.
65def lldb_adapter() -> Adapter | None: 66 """lldb-dap: Apple's on macOS, else one on the PATH.""" 67 if sys.platform == "darwin": 68 code, output, _err = console.run(["xcrun", "-f", "lldb-dap"], timeout=10) 69 if code == 0 and output and output.strip(): 70 return Adapter("lldb", [output.decode().strip()]) 71 program = shutil.which("lldb-dap") 72 return Adapter("lldb", [program]) if program else None
lldb-dap: Apple's on macOS, else one on the PATH.
78def find_adapter() -> Adapter | None: 79 """The debugger for this platform (None: none found), looked for once.""" 80 if "adapter" not in _found: 81 finders = (lldb_adapter, gdb_adapter) if sys.platform == "darwin" else (gdb_adapter, lldb_adapter) 82 _found["adapter"] = next((a for a in (f() for f in finders) if a is not None), None) 83 return _found["adapter"]
The debugger for this platform (None: none found), looked for once.
def
forget():
86def forget(): 87 """Look for the debugger again next time (a new environment).""" 88 _found.clear()
Look for the debugger again next time (a new environment).
NOT_FOUND =
"No debugger was found. On Linux, install gdb 14 or newer (the project's pixi environment has it); on macOS, lldb-dap comes with Xcode's Command Line Tools (xcode-select --install)."