2022-05-23 00:16:32 +04:00

162 lines
6.3 KiB
Python

# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE in the project root
# for license information.
from __future__ import absolute_import, division, print_function, unicode_literals
import functools
import os
import sys
import debugpy
from debugpy import launcher
from debugpy.common import compat, json
from debugpy.common.compat import unicode
from debugpy.launcher import debuggee
def launch_request(request):
debug_options = set(request("debugOptions", json.array(unicode)))
# Handling of properties that can also be specified as legacy "debugOptions" flags.
# If property is explicitly set to false, but the flag is in "debugOptions", treat
# it as an error. Returns None if the property wasn't explicitly set either way.
def property_or_debug_option(prop_name, flag_name):
assert prop_name[0].islower() and flag_name[0].isupper()
value = request(prop_name, bool, optional=True)
if value == ():
value = None
if flag_name in debug_options:
if value is False:
raise request.isnt_valid(
'{0!j}:false and "debugOptions":[{1!j}] are mutually exclusive',
prop_name,
flag_name,
)
value = True
return value
python = request("python", json.array(unicode, size=(1,)))
cmdline = list(python)
if not request("noDebug", json.default(False)):
port = request("port", int)
cmdline += [
compat.filename(os.path.dirname(debugpy.__file__)),
"--connect",
launcher.adapter_host + ":" + str(port),
]
if not request("subProcess", True):
cmdline += ["--configure-subProcess", "False"]
qt_mode = request(
"qt",
json.enum(
"none", "auto", "pyside", "pyside2", "pyqt4", "pyqt5", optional=True
),
)
cmdline += ["--configure-qt", qt_mode]
adapter_access_token = request("adapterAccessToken", unicode, optional=True)
if adapter_access_token != ():
cmdline += ["--adapter-access-token", compat.filename(adapter_access_token)]
debugpy_args = request("debugpyArgs", json.array(unicode))
cmdline += debugpy_args
# Further arguments can come via two channels: the launcher's own command line, or
# "args" in the request; effective arguments are concatenation of these two in order.
# Arguments for debugpy (such as -m) always come via CLI, but those specified by the
# user via "args" are passed differently by the adapter depending on "argsExpansion".
cmdline += sys.argv[1:]
cmdline += request("args", json.array(unicode))
process_name = request("processName", compat.filename(sys.executable))
env = os.environ.copy()
env_changes = request("env", json.object((unicode, type(None))))
if sys.platform == "win32":
# Environment variables are case-insensitive on Win32, so we need to normalize
# both dicts to make sure that env vars specified in the debug configuration
# overwrite the global env vars correctly. If debug config has entries that
# differ in case only, that's an error.
env = {k.upper(): v for k, v in os.environ.items()}
new_env_changes = {}
for k, v in env_changes.items():
k_upper = k.upper()
if k_upper in new_env_changes:
if new_env_changes[k_upper] == v:
continue
else:
raise request.isnt_valid('Found duplicate in "env": {0}.'.format(k_upper))
new_env_changes[k_upper] = v
env_changes = new_env_changes
if "DEBUGPY_TEST" in env:
# If we're running as part of a debugpy test, make sure that codecov is not
# applied to the debuggee, since it will conflict with pydevd.
env.pop("COV_CORE_SOURCE", None)
env.update(env_changes)
env = {k: v for k, v in env.items() if v is not None}
if request("gevent", False):
env["GEVENT_SUPPORT"] = "True"
console = request(
"console",
json.enum(
"internalConsole", "integratedTerminal", "externalTerminal", optional=True
),
)
redirect_output = property_or_debug_option("redirectOutput", "RedirectOutput")
if redirect_output is None:
# If neither the property nor the option were specified explicitly, choose
# the default depending on console type - "internalConsole" needs it to
# provide any output at all, but it's unnecessary for the terminals.
redirect_output = console == "internalConsole"
if redirect_output:
# sys.stdout buffering must be disabled - otherwise we won't see the output
# at all until the buffer fills up.
env["PYTHONUNBUFFERED"] = "1"
# Force UTF-8 output to minimize data loss due to re-encoding.
env["PYTHONIOENCODING"] = "utf-8"
if property_or_debug_option("waitOnNormalExit", "WaitOnNormalExit"):
if console == "internalConsole":
raise request.isnt_valid(
'"waitOnNormalExit" is not supported for "console":"internalConsole"'
)
debuggee.wait_on_exit_predicates.append(lambda code: code == 0)
if property_or_debug_option("waitOnAbnormalExit", "WaitOnAbnormalExit"):
if console == "internalConsole":
raise request.isnt_valid(
'"waitOnAbnormalExit" is not supported for "console":"internalConsole"'
)
debuggee.wait_on_exit_predicates.append(lambda code: code != 0)
if sys.version_info < (3,):
# Popen() expects command line and environment to be bytes, not Unicode.
# Assume that values are filenames - it's usually either that, or numbers -
# but don't allow encoding to fail if we guessed wrong.
encode = functools.partial(compat.filename_bytes, errors="replace")
cmdline = [encode(s) for s in cmdline]
env = {encode(k): encode(v) for k, v in env.items()}
debuggee.spawn(process_name, cmdline, env, redirect_output)
return {}
def terminate_request(request):
del debuggee.wait_on_exit_predicates[:]
request.respond({})
debuggee.kill()
def disconnect():
del debuggee.wait_on_exit_predicates[:]
debuggee.kill()