def main(): from debugpy import launcher from debugpy.common import log from debugpy.launcher import debuggee log.to_file(prefix="debugpy.launcher") log.describe_environment("debugpy.launcher startup environment:") # Disable exceptions on Ctrl+C - we want to allow the debuggee process to handle # these, or not, as it sees fit. If the debuggee exits on Ctrl+C, the launcher # will also exit, so it doesn't need to observe the signal directly. signal.signal(signal.SIGINT, signal.SIG_IGN) def option(name, type, *args): try: return type(os.environ.pop(name, *args)) except Exception: log.reraise_exception("Error parsing {0!r}:", name) launcher_port = option("DEBUGPY_LAUNCHER_PORT", int) launcher.connect(launcher_port) launcher.channel.wait() if debuggee.process is not None: sys.exit(debuggee.process.returncode)
def main(): from debugpy import launcher from debugpy.common import log from debugpy.launcher import debuggee log.to_file(prefix="debugpy.launcher") log.describe_environment("debugpy.launcher startup environment:") # Disable exceptions on Ctrl+C - we want to allow the debuggee process to handle # these, or not, as it sees fit. If the debuggee exits on Ctrl+C, the launcher # will also exit, so it doesn't need to observe the signal directly. signal.signal(signal.SIGINT, signal.SIG_IGN) # Everything before "--" is command line arguments for the launcher itself, # and everything after "--" is command line arguments for the debuggee. log.info("sys.argv before parsing: {0}", sys.argv) sep = sys.argv.index("--") launcher_argv = sys.argv[1:sep] sys.argv[:] = [sys.argv[0]] + sys.argv[sep + 1:] log.info("sys.argv after patching: {0}", sys.argv) # The first argument specifies the host/port on which the adapter is waiting # for launcher to connect. It's either host:port, or just port. adapter = launcher_argv[0] host, sep, port = adapter.partition(":") if not sep: host = "127.0.0.1" port = adapter port = int(port) launcher.connect(host, port) launcher.channel.wait() if debuggee.process is not None: sys.exit(debuggee.process.returncode)
def ensure_logging(): """Starts logging to log.log_dir, if it hasn't already been done. """ if ensure_logging.ensured: return ensure_logging.ensured = True log.to_file(prefix="debugpy.server") log.describe_environment("Initial environment:")
def run_code(): # Add current directory to path, like Python itself does for -c. sys.path.insert(0, "") code = compile(options.target, "<string>", "exec") start_debugging("-c") log.describe_environment("Pre-launch environment:") log.info("Running code:\n\n{0}", options.target) eval(code, {})
def run_module(): # Add current directory to path, like Python itself does for -m. This must # be in place before trying to use find_spec below to resolve submodules. sys.path.insert(0, "") # We want to do the same thing that run_module() would do here, without # actually invoking it. On Python 3, it's exposed as a public API, but # on Python 2, we have to invoke a private function in runpy for this. # Either way, if it fails to resolve for any reason, just leave argv as is. argv_0 = sys.argv[0] try: if sys.version_info >= (3,): from importlib.util import find_spec spec = find_spec(options.target) if spec is not None: argv_0 = spec.origin else: _, _, _, argv_0 = runpy._get_module_details(options.target) except Exception: log.swallow_exception("Error determining module path for sys.argv") start_debugging(argv_0) # On Python 2, module name must be a non-Unicode string, because it ends up # a part of module's __package__, and Python will refuse to run the module # if __package__ is Unicode. target = ( compat.filename_bytes(options.target) if sys.version_info < (3,) else options.target ) log.describe_environment("Pre-launch environment:") log.info("Running module {0!r}", target) # Docs say that runpy.run_module is equivalent to -m, but it's not actually # the case for packages - -m sets __name__ to "__main__", but run_module sets # it to "pkg.__main__". This breaks everything that uses the standard pattern # __name__ == "__main__" to detect being run as a CLI app. On the other hand, # runpy._run_module_as_main is a private function that actually implements -m. try: run_module_as_main = runpy._run_module_as_main except AttributeError: log.warning("runpy._run_module_as_main is missing, falling back to run_module.") runpy.run_module(target, alter_sys=True) else: run_module_as_main(target, alter_argv=True)
def run_file(): start_debugging(options.target) # run_path has one difference with invoking Python from command-line: # if the target is a file (rather than a directory), it does not add its # parent directory to sys.path. Thus, importing other modules from the # same directory is broken unless sys.path is patched here. if os.path.isfile(options.target): dir = os.path.dirname(options.target) sys.path.insert(0, dir) else: log.debug("Not a file: {0!r}", options.target) log.describe_environment("Pre-launch environment:") log.info("Running file {0!r}", options.target) runpy.run_path(options.target, run_name=compat.force_str("__main__"))
def main(args): # If we're talking DAP over stdio, stderr is not guaranteed to be read from, # so disable it to avoid the pipe filling and locking up. This must be done # as early as possible, before the logging module starts writing to it. if args.port is None: sys.stderr = open(os.devnull, "w") from debugpy import adapter from debugpy.common import compat, log, sockets from debugpy.adapter import clients, servers, sessions if args.for_server is not None: if os.name == "posix": # On POSIX, we need to leave the process group and its session, and then # daemonize properly by double-forking (first fork already happened when # this process was spawned). os.setsid() if os.fork() != 0: sys.exit(0) for stdio in sys.stdin, sys.stdout, sys.stderr: if stdio is not None: stdio.close() if args.log_stderr: log.stderr.levels |= set(log.LEVELS) if args.log_dir is not None: log.log_dir = args.log_dir log.to_file(prefix="debugpy.adapter") log.describe_environment("debugpy.adapter startup environment:") servers.access_token = args.server_access_token if args.for_server is None: adapter.access_token = compat.force_str( codecs.encode(os.urandom(32), "hex")) endpoints = {} try: client_host, client_port = clients.serve(args.host, args.port) except Exception as exc: if args.for_server is None: raise endpoints = { "error": "Can't listen for client connections: " + str(exc) } else: endpoints["client"] = {"host": client_host, "port": client_port} if args.for_server is not None: try: server_host, server_port = servers.serve() except Exception as exc: endpoints = { "error": "Can't listen for server connections: " + str(exc) } else: endpoints["server"] = {"host": server_host, "port": server_port} log.info( "Sending endpoints info to debug server at localhost:{0}:\n{1!j}", args.for_server, endpoints, ) try: sock = sockets.create_client() try: sock.settimeout(None) sock.connect(("127.0.0.1", args.for_server)) sock_io = sock.makefile("wb", 0) try: sock_io.write(json.dumps(endpoints).encode("utf-8")) finally: sock_io.close() finally: sockets.close_socket(sock) except Exception: log.reraise_exception( "Error sending endpoints info to debug server:") if "error" in endpoints: log.error("Couldn't set up endpoints; exiting.") sys.exit(1) listener_file = os.getenv("DEBUGPY_ADAPTER_ENDPOINTS") if listener_file is not None: log.info("Writing endpoints info to {0!r}:\n{1!j}", listener_file, endpoints) def delete_listener_file(): log.info("Listener ports closed; deleting {0!r}", listener_file) try: os.remove(listener_file) except Exception: log.swallow_exception("Failed to delete {0!r}", listener_file, level="warning") try: with open(listener_file, "w") as f: atexit.register(delete_listener_file) print(json.dumps(endpoints), file=f) except Exception: log.reraise_exception("Error writing endpoints info to file:") if args.port is None: clients.Client("stdio") # These must be registered after the one above, to ensure that the listener sockets # are closed before the endpoint info file is deleted - this way, another process # can wait for the file to go away as a signal that the ports are no longer in use. atexit.register(servers.stop_serving) atexit.register(clients.stop_serving) servers.wait_until_disconnected() log.info( "All debug servers disconnected; waiting for remaining sessions...") sessions.wait_until_ended() log.info("All debug sessions have ended; exiting.")
def pytest_report_header(config): log.describe_environment(fmt("Test environment for tests-{0}", os.getpid()))