api.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. # Copyright (c) Microsoft Corporation. All rights reserved.
  2. # Licensed under the MIT License. See LICENSE in the project root
  3. # for license information.
  4. import codecs
  5. import os
  6. import pydevd
  7. import socket
  8. import sys
  9. import threading
  10. import debugpy
  11. from debugpy import adapter
  12. from debugpy.common import json, log, sockets
  13. from _pydevd_bundle.pydevd_constants import get_global_debugger
  14. from pydevd_file_utils import absolute_path
  15. from debugpy.common.util import hide_debugpy_internals
  16. _tls = threading.local()
  17. # TODO: "gevent", if possible.
  18. _config = {
  19. "qt": "none",
  20. "subProcess": True,
  21. "python": sys.executable,
  22. "pythonEnv": {},
  23. }
  24. _config_valid_values = {
  25. # If property is not listed here, any value is considered valid, so long as
  26. # its type matches that of the default value in _config.
  27. "qt": ["auto", "none", "pyside", "pyside2", "pyqt4", "pyqt5"],
  28. }
  29. # This must be a global to prevent it from being garbage collected and triggering
  30. # https://bugs.python.org/issue37380.
  31. _adapter_process = None
  32. def _settrace(*args, **kwargs):
  33. log.debug("pydevd.settrace(*{0!r}, **{1!r})", args, kwargs)
  34. # The stdin in notification is not acted upon in debugpy, so, disable it.
  35. kwargs.setdefault("notify_stdin", False)
  36. try:
  37. pydevd.settrace(*args, **kwargs)
  38. except Exception:
  39. raise
  40. def ensure_logging():
  41. """Starts logging to log.log_dir, if it hasn't already been done."""
  42. if ensure_logging.ensured:
  43. return
  44. ensure_logging.ensured = True
  45. log.to_file(prefix="debugpy.server")
  46. log.describe_environment("Initial environment:")
  47. if log.log_dir is not None:
  48. pydevd.log_to(log.log_dir + "/debugpy.pydevd.log")
  49. ensure_logging.ensured = False
  50. def log_to(path):
  51. if ensure_logging.ensured:
  52. raise RuntimeError("logging has already begun")
  53. log.debug("log_to{0!r}", (path,))
  54. if path is sys.stderr:
  55. log.stderr.levels |= set(log.LEVELS)
  56. else:
  57. log.log_dir = path
  58. def configure(properties=None, **kwargs):
  59. ensure_logging()
  60. log.debug("configure{0!r}", (properties, kwargs))
  61. if properties is None:
  62. properties = kwargs
  63. else:
  64. properties = dict(properties)
  65. properties.update(kwargs)
  66. for k, v in properties.items():
  67. if k not in _config:
  68. raise ValueError("Unknown property {0!r}".format(k))
  69. expected_type = type(_config[k])
  70. if type(v) is not expected_type:
  71. raise ValueError("{0!r} must be a {1}".format(k, expected_type.__name__))
  72. valid_values = _config_valid_values.get(k)
  73. if (valid_values is not None) and (v not in valid_values):
  74. raise ValueError("{0!r} must be one of: {1!r}".format(k, valid_values))
  75. _config[k] = v
  76. def _starts_debugging(func):
  77. def debug(address, **kwargs):
  78. try:
  79. _, port = address
  80. except Exception:
  81. port = address
  82. localhost = sockets.get_default_localhost()
  83. address = (localhost, port)
  84. try:
  85. port.__index__() # ensure it's int-like
  86. except Exception:
  87. raise ValueError("expected port or (host, port)")
  88. if not (0 <= port < 2**16):
  89. raise ValueError("invalid port number")
  90. ensure_logging()
  91. log.debug("{0}({1!r}, **{2!r})", func.__name__, address, kwargs)
  92. log.info("Initial debug configuration: {0}", json.repr(_config))
  93. qt_mode = _config.get("qt", "none")
  94. if qt_mode != "none":
  95. pydevd.enable_qt_support(qt_mode)
  96. settrace_kwargs = {
  97. "suspend": False,
  98. "patch_multiprocessing": _config.get("subProcess", True),
  99. }
  100. if hide_debugpy_internals():
  101. debugpy_path = os.path.dirname(absolute_path(debugpy.__file__))
  102. settrace_kwargs["dont_trace_start_patterns"] = (debugpy_path,)
  103. settrace_kwargs["dont_trace_end_patterns"] = (str("debugpy_launcher.py"),)
  104. try:
  105. return func(address, settrace_kwargs, **kwargs)
  106. except Exception:
  107. log.reraise_exception("{0}() failed:", func.__name__, level="info")
  108. return debug
  109. @_starts_debugging
  110. def listen(address, settrace_kwargs, in_process_debug_adapter=False):
  111. # Errors below are logged with level="info", because the caller might be catching
  112. # and handling exceptions, and we don't want to spam their stderr unnecessarily.
  113. if listen.called:
  114. # Multiple calls to listen() cause the debuggee to hang
  115. raise RuntimeError("debugpy.listen() has already been called on this process")
  116. host, port = address
  117. if in_process_debug_adapter:
  118. log.info("Listening: pydevd without debugpy adapter: {0}:{1}", host, port)
  119. settrace_kwargs["patch_multiprocessing"] = False
  120. _settrace(
  121. host=host,
  122. port=port,
  123. wait_for_ready_to_run=False,
  124. block_until_connected=False,
  125. **settrace_kwargs
  126. )
  127. return
  128. import subprocess
  129. server_access_token = codecs.encode(os.urandom(32), "hex").decode("ascii")
  130. try:
  131. localhost = sockets.get_default_localhost()
  132. endpoints_listener = sockets.create_server(localhost, 0, timeout=30)
  133. except Exception as exc:
  134. log.swallow_exception("Can't listen for adapter endpoints:")
  135. raise RuntimeError("can't listen for adapter endpoints: " + str(exc))
  136. try:
  137. endpoints_host, endpoints_port = sockets.get_address(endpoints_listener)
  138. log.info(
  139. "Waiting for adapter endpoints on {0}:{1}...",
  140. endpoints_host,
  141. endpoints_port,
  142. )
  143. host, port = address
  144. adapter_args = [
  145. _config.get("python", sys.executable),
  146. os.path.dirname(adapter.__file__),
  147. "--for-server",
  148. str(endpoints_port),
  149. "--host",
  150. host,
  151. "--port",
  152. str(port),
  153. "--server-access-token",
  154. server_access_token,
  155. ]
  156. if log.log_dir is not None:
  157. adapter_args += ["--log-dir", log.log_dir]
  158. log.info("debugpy.listen() spawning adapter: {0}", json.repr(adapter_args))
  159. # On Windows, detach the adapter from our console, if any, so that it doesn't
  160. # receive Ctrl+C from it, and doesn't keep it open once we exit.
  161. creationflags = 0
  162. if sys.platform == "win32":
  163. creationflags |= 0x08000000 # CREATE_NO_WINDOW
  164. creationflags |= 0x00000200 # CREATE_NEW_PROCESS_GROUP
  165. # On embedded applications, environment variables might not contain
  166. # Python environment settings.
  167. python_env = _config.get("pythonEnv")
  168. if not bool(python_env):
  169. python_env = None
  170. # Adapter will outlive this process, so we shouldn't wait for it. However, we
  171. # need to ensure that the Popen instance for it doesn't get garbage-collected
  172. # by holding a reference to it in a non-local variable, to avoid triggering
  173. # https://bugs.python.org/issue37380.
  174. try:
  175. global _adapter_process
  176. _adapter_process = subprocess.Popen(
  177. adapter_args,
  178. close_fds=True,
  179. creationflags=creationflags,
  180. env=python_env,
  181. )
  182. if os.name == "posix":
  183. # It's going to fork again to daemonize, so we need to wait on it to
  184. # clean it up properly.
  185. _adapter_process.wait()
  186. else:
  187. # Suppress misleading warning about child process still being alive when
  188. # this process exits (https://bugs.python.org/issue38890).
  189. _adapter_process.returncode = 0
  190. pydevd.add_dont_terminate_child_pid(_adapter_process.pid)
  191. except Exception as exc:
  192. log.swallow_exception("Error spawning debug adapter:", level="info")
  193. raise RuntimeError("error spawning debug adapter: " + str(exc))
  194. try:
  195. sock, _ = endpoints_listener.accept()
  196. try:
  197. sock.settimeout(None)
  198. sock_io = sock.makefile("rb", 0)
  199. try:
  200. endpoints = json.loads(sock_io.read().decode("utf-8"))
  201. finally:
  202. sock_io.close()
  203. finally:
  204. sockets.close_socket(sock)
  205. except socket.timeout:
  206. log.swallow_exception(
  207. "Timed out waiting for adapter to connect:", level="info"
  208. )
  209. raise RuntimeError("timed out waiting for adapter to connect")
  210. except Exception as exc:
  211. log.swallow_exception("Error retrieving adapter endpoints:", level="info")
  212. raise RuntimeError("error retrieving adapter endpoints: " + str(exc))
  213. finally:
  214. endpoints_listener.close()
  215. log.info("Endpoints received from adapter: {0}", json.repr(endpoints))
  216. if "error" in endpoints:
  217. raise RuntimeError(str(endpoints["error"]))
  218. try:
  219. server_host = str(endpoints["server"]["host"])
  220. server_port = int(endpoints["server"]["port"])
  221. client_host = str(endpoints["client"]["host"])
  222. client_port = int(endpoints["client"]["port"])
  223. except Exception as exc:
  224. log.swallow_exception(
  225. "Error parsing adapter endpoints:\n{0}\n",
  226. json.repr(endpoints),
  227. level="info",
  228. )
  229. raise RuntimeError("error parsing adapter endpoints: " + str(exc))
  230. log.info(
  231. "Adapter is accepting incoming client connections on {0}:{1}",
  232. client_host,
  233. client_port,
  234. )
  235. _settrace(
  236. host=server_host,
  237. port=server_port,
  238. wait_for_ready_to_run=False,
  239. block_until_connected=True,
  240. access_token=server_access_token,
  241. **settrace_kwargs
  242. )
  243. log.info("pydevd is connected to adapter at {0}:{1}", server_host, server_port)
  244. listen.called = True
  245. return client_host, client_port
  246. listen.called = False
  247. @_starts_debugging
  248. def connect(address, settrace_kwargs, access_token=None, parent_session_pid=None):
  249. host, port = address
  250. _settrace(host=host, port=port, client_access_token=access_token, ppid=parent_session_pid or 0, **settrace_kwargs)
  251. class wait_for_client:
  252. def __call__(self):
  253. ensure_logging()
  254. log.debug("wait_for_client()")
  255. pydb = get_global_debugger()
  256. if pydb is None:
  257. raise RuntimeError("listen() or connect() must be called first")
  258. cancel_event = threading.Event()
  259. self.cancel = cancel_event.set
  260. pydevd._wait_for_attach(cancel=cancel_event)
  261. @staticmethod
  262. def cancel():
  263. raise RuntimeError("wait_for_client() must be called first")
  264. wait_for_client = wait_for_client()
  265. def is_client_connected():
  266. return pydevd._is_attached()
  267. def breakpoint():
  268. ensure_logging()
  269. if not is_client_connected():
  270. log.info("breakpoint() ignored - debugger not attached")
  271. return
  272. log.debug("breakpoint()")
  273. # Get the first frame in the stack that's not an internal frame.
  274. pydb = get_global_debugger()
  275. stop_at_frame = sys._getframe().f_back
  276. while (
  277. stop_at_frame is not None
  278. and pydb.get_file_type(stop_at_frame) == pydb.PYDEV_FILE
  279. ):
  280. stop_at_frame = stop_at_frame.f_back
  281. _settrace(
  282. suspend=True,
  283. trace_only_current_thread=True,
  284. patch_multiprocessing=False,
  285. stop_at_frame=stop_at_frame,
  286. )
  287. stop_at_frame = None
  288. def debug_this_thread():
  289. ensure_logging()
  290. log.debug("debug_this_thread()")
  291. _settrace(suspend=False)
  292. def trace_this_thread(should_trace):
  293. ensure_logging()
  294. log.debug("trace_this_thread({0!r})", should_trace)
  295. pydb = get_global_debugger()
  296. if should_trace:
  297. pydb.enable_tracing()
  298. else:
  299. pydb.disable_tracing()