docker-composer/venv/lib/python3.8/site-packages/debugpy/adapter/__main__.py
2022-12-13 14:20:23 +00:00

224 lines
7.9 KiB
Python

# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE in the project root
# for license information.
import argparse
import atexit
import codecs
import locale
import os
import sys
# WARNING: debugpy and submodules must not be imported on top level in this module,
# and should be imported locally inside main() instead.
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 = stderr = open(os.devnull, "w")
atexit.register(stderr.close)
from debugpy import adapter
from debugpy.common import json, 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 = codecs.encode(os.urandom(32), "hex").decode("ascii")
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}",
args.for_server,
json.repr(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}", listener_file, json.repr(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 _parse_argv(argv):
parser = argparse.ArgumentParser()
parser.add_argument(
"--for-server", type=int, metavar="PORT", help=argparse.SUPPRESS
)
parser.add_argument(
"--port",
type=int,
default=None,
metavar="PORT",
help="start the adapter in debugServer mode on the specified port",
)
parser.add_argument(
"--host",
type=str,
default="127.0.0.1",
metavar="HOST",
help="start the adapter in debugServer mode on the specified host",
)
parser.add_argument(
"--access-token", type=str, help="access token expected from the server"
)
parser.add_argument(
"--server-access-token", type=str, help="access token expected by the server"
)
parser.add_argument(
"--log-dir",
type=str,
metavar="DIR",
help="enable logging and use DIR to save adapter logs",
)
parser.add_argument(
"--log-stderr", action="store_true", help="enable logging to stderr"
)
args = parser.parse_args(argv[1:])
if args.port is None:
if args.log_stderr:
parser.error("--log-stderr requires --port")
if args.for_server is not None:
parser.error("--for-server requires --port")
return args
if __name__ == "__main__":
# debugpy can also be invoked directly rather than via -m. In this case, the first
# entry on sys.path is the one added automatically by Python for the directory
# containing this file. This means that import debugpy will not work, since we need
# the parent directory of debugpy/ to be in sys.path, rather than debugpy/adapter/.
#
# The other issue is that many other absolute imports will break, because they
# will be resolved relative to debugpy/adapter/ - e.g. `import state` will then try
# to import debugpy/adapter/state.py.
#
# To fix both, we need to replace the automatically added entry such that it points
# at parent directory of debugpy/ instead of debugpy/adapter, import debugpy with that
# in sys.path, and then remove the first entry entry altogether, so that it doesn't
# affect any further imports we might do. For example, suppose the user did:
#
# python /foo/bar/debugpy/adapter ...
#
# At the beginning of this script, sys.path will contain "/foo/bar/debugpy/adapter"
# as the first entry. What we want is to replace it with "/foo/bar', then import
# debugpy with that in effect, and then remove the replaced entry before any more
# code runs. The imported debugpy module will remain in sys.modules, and thus all
# future imports of it or its submodules will resolve accordingly.
if "debugpy" not in sys.modules:
# Do not use dirname() to walk up - this can be a relative path, e.g. ".".
sys.path[0] = sys.path[0] + "/../../"
__import__("debugpy")
del sys.path[0]
# Apply OS-global and user-specific locale settings.
try:
locale.setlocale(locale.LC_ALL, "")
except Exception:
# On POSIX, locale is set via environment variables, and this can fail if
# those variables reference a non-existing locale. Ignore and continue using
# the default "C" locale if so.
pass
main(_parse_argv(sys.argv))