Format: text/x-python
⬇ Download File (5.7 KB)
   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  
   5  import os
   6  import sys
   7  
   8  import debugpy
   9  from debugpy import launcher
  10  from debugpy.common import json
  11  from debugpy.launcher import debuggee
  12  
  13  
  14  def launch_request(request):
  15      debug_options = set(request("debugOptions", json.array(str)))
  16  
  17      # Handling of properties that can also be specified as legacy "debugOptions" flags.
  18      # If property is explicitly set to false, but the flag is in "debugOptions", treat
  19      # it as an error. Returns None if the property wasn't explicitly set either way.
  20      def property_or_debug_option(prop_name, flag_name):
  21          assert prop_name[0].islower() and flag_name[0].isupper()
  22  
  23          value = request(prop_name, bool, optional=True)
  24          if value == ():
  25              value = None
  26  
  27          if flag_name in debug_options:
  28              if value is False:
  29                  raise request.isnt_valid(
  30                      '{0}:false and "debugOptions":[{1}] are mutually exclusive',
  31                      json.repr(prop_name),
  32                      json.repr(flag_name),
  33                  )
  34              value = True
  35  
  36          return value
  37  
  38      python = request("python", json.array(str, size=(1,)))
  39      cmdline = list(python)
  40  
  41      if not request("noDebug", json.default(False)):
  42          # see https://github.com/microsoft/debugpy/issues/861
  43          if sys.version_info[:2] >= (3, 11):
  44              cmdline += ["-X", "frozen_modules=off"]
  45  
  46          port = request("port", int)
  47          cmdline += [
  48              os.path.dirname(debugpy.__file__),
  49              "--connect",
  50              launcher.adapter_host + ":" + str(port),
  51          ]
  52  
  53          if not request("subProcess", True):
  54              cmdline += ["--configure-subProcess", "False"]
  55  
  56          qt_mode = request(
  57              "qt",
  58              json.enum(
  59                  "none", "auto", "pyside", "pyside2", "pyqt4", "pyqt5", optional=True
  60              ),
  61          )
  62          cmdline += ["--configure-qt", qt_mode]
  63  
  64          adapter_access_token = request("adapterAccessToken", str, optional=True)
  65          if adapter_access_token != ():
  66              cmdline += ["--adapter-access-token", adapter_access_token]
  67  
  68          debugpy_args = request("debugpyArgs", json.array(str))
  69          cmdline += debugpy_args
  70  
  71      # Use the copy of arguments that was propagated via the command line rather than
  72      # "args" in the request itself, to allow for shell expansion.
  73      cmdline += sys.argv[1:]
  74  
  75      process_name = request("processName", sys.executable)
  76  
  77      env = os.environ.copy()
  78      env_changes = request("env", json.object((str, type(None))))
  79      if sys.platform == "win32":
  80          # Environment variables are case-insensitive on Win32, so we need to normalize
  81          # both dicts to make sure that env vars specified in the debug configuration
  82          # overwrite the global env vars correctly. If debug config has entries that
  83          # differ in case only, that's an error.
  84          env = {k.upper(): v for k, v in os.environ.items()}
  85          new_env_changes = {}
  86          for k, v in env_changes.items():
  87              k_upper = k.upper()
  88              if k_upper in new_env_changes:
  89                  if new_env_changes[k_upper] == v:
  90                      continue
  91                  else:
  92                      raise request.isnt_valid(
  93                          'Found duplicate in "env": {0}.'.format(k_upper)
  94                      )
  95              new_env_changes[k_upper] = v
  96          env_changes = new_env_changes
  97      if "DEBUGPY_TEST" in env:
  98          # If we're running as part of a debugpy test, make sure that codecov is not
  99          # applied to the debuggee, since it will conflict with pydevd.
 100          env.pop("COV_CORE_SOURCE", None)
 101      env.update(env_changes)
 102      env = {k: v for k, v in env.items() if v is not None}
 103  
 104      if request("gevent", False):
 105          env["GEVENT_SUPPORT"] = "True"
 106  
 107      console = request(
 108          "console",
 109          json.enum(
 110              "internalConsole", "integratedTerminal", "externalTerminal", optional=True
 111          ),
 112      )
 113  
 114      redirect_output = property_or_debug_option("redirectOutput", "RedirectOutput")
 115      if redirect_output is None:
 116          # If neither the property nor the option were specified explicitly, choose
 117          # the default depending on console type - "internalConsole" needs it to
 118          # provide any output at all, but it's unnecessary for the terminals.
 119          redirect_output = console == "internalConsole"
 120      if redirect_output:
 121          # sys.stdout buffering must be disabled - otherwise we won't see the output
 122          # at all until the buffer fills up.
 123          env["PYTHONUNBUFFERED"] = "1"
 124          # Force UTF-8 output to minimize data loss due to re-encoding.
 125          env["PYTHONIOENCODING"] = "utf-8"
 126  
 127      if property_or_debug_option("waitOnNormalExit", "WaitOnNormalExit"):
 128          if console == "internalConsole":
 129              raise request.isnt_valid(
 130                  '"waitOnNormalExit" is not supported for "console":"internalConsole"'
 131              )
 132          debuggee.wait_on_exit_predicates.append(lambda code: code == 0)
 133      if property_or_debug_option("waitOnAbnormalExit", "WaitOnAbnormalExit"):
 134          if console == "internalConsole":
 135              raise request.isnt_valid(
 136                  '"waitOnAbnormalExit" is not supported for "console":"internalConsole"'
 137              )
 138          debuggee.wait_on_exit_predicates.append(lambda code: code != 0)
 139  
 140      debuggee.spawn(process_name, cmdline, env, redirect_output)
 141      return {}
 142  
 143  
 144  def terminate_request(request):
 145      del debuggee.wait_on_exit_predicates[:]
 146      request.respond({})
 147      debuggee.kill()
 148  
 149  
 150  def disconnect():
 151      del debuggee.wait_on_exit_predicates[:]
 152      debuggee.kill()
 153