コード例 #1
0
    def launch_request(self, request):
        from debugpy.adapter import launchers

        if self.session.id != 1 or len(servers.connections()):
            raise request.cant_handle('"attach" expected')

        # Launcher doesn't use the command line at all, but we pass the arguments so
        # that they show up in the terminal if we're using "runInTerminal".
        if "program" in request:
            args = request("program", json.array(unicode, vectorize=True, size=(1,)))
        elif "module" in request:
            args = ["-m"] + request(
                "module", json.array(unicode, vectorize=True, size=(1,))
            )
        elif "code" in request:
            args = ["-c"] + request(
                "code", json.array(unicode, vectorize=True, size=(1,))
            )
        else:
            args = []
        args += request("args", json.array(unicode))

        console = request(
            "console",
            json.enum(
                "internalConsole",
                "integratedTerminal",
                "externalTerminal",
                optional=True,
            ),
        )
        console_title = request("consoleTitle", json.default("Python Debug Console"))

        servers.serve()
        launchers.spawn_debuggee(self.session, request, args, console, console_title)
コード例 #2
0
    def get_variables(self, *varnames, **kwargs):
        """Fetches the specified variables from the frame specified by frame_id, or
        from the topmost frame in the last "stackTrace" response if frame_id is not
        specified.

        If varnames is empty, then all variables in the frame are returned. The result
        is an OrderedDict, in which every entry has variable name as the key, and a
        DAP Variable object as the value. The original order of variables as reported
        by the debugger is preserved.

        If varnames is not empty, then only the specified variables are returned.
        The result is a tuple, in which every entry is a DAP Variable object; those
        entries are in the same order as varnames.
        """

        assert self.timeline.is_frozen

        frame_id = kwargs.pop("frame_id", None)
        if frame_id is None:
            stackTrace_responses = self.all_occurrences_of(
                timeline.Response(timeline.Request("stackTrace")))
            assert stackTrace_responses, (
                "get_variables() without frame_id requires at least one response "
                'to a "stackTrace" request in the timeline.')
            stack_trace = stackTrace_responses[-1]
            frame_id = stack_trace.body.get("stackFrames",
                                            json.array())[0]("id", int)

        scopes = self.request("scopes", {"frameId": frame_id})("scopes",
                                                               json.array())
        assert len(scopes) > 0

        variables = self.request(
            "variables",
            {"variablesReference": scopes[0]("variablesReference", int)})(
                "variables", json.array())

        variables = collections.OrderedDict(
            ((v("name", unicode), v) for v in variables))
        if varnames:
            assert set(varnames) <= set(variables.keys())
            return tuple((variables[name] for name in varnames))
        else:
            return variables
コード例 #3
0
ファイル: session.py プロジェクト: daniyalsyed/debugpy
 def _process_request(self, request):
     self.timeline.record_request(request, block=False)
     if request.command == "runInTerminal":
         args = request("args", json.array(unicode))
         cwd = request("cwd", ".")
         env = request("env", json.object(unicode))
         try:
             return self.run_in_terminal(args, cwd, env)
         except Exception as exc:
             log.swallow_exception('"runInTerminal" failed:')
             raise request.cant_handle(str(exc))
     else:
         raise request.isnt_valid("not supported")
コード例 #4
0
 def _process_request(self, request):
     self.timeline.record_request(request, block=False)
     if request.command == "runInTerminal":
         args = request("args", json.array(unicode))
         cwd = request("cwd", ".")
         env = request("env", json.object(unicode))
         try:
             exe = args.pop(0)
             assert not len(self.spawn_debuggee.env)
             self.spawn_debuggee.env = env
             self.spawn_debuggee(args, cwd, exe=exe)
             return {}
         except OSError as exc:
             log.swallow_exception('"runInTerminal" failed:')
             raise request.cant_handle(str(exc))
     else:
         raise request.isnt_valid("not supported")
コード例 #5
0
    def set_breakpoints(self, path, lines):
        """Sets breakpoints in the specified file, and returns the list of all the
        corresponding DAP Breakpoint objects in the same order.

        If lines are specified, it should be an iterable in which every element is
        either a line number or a string. If it is a string, then it is translated
        to the corresponding line number via get_marked_line_numbers(path).

        If lines=all, breakpoints will be set on all the marked lines in the file.
        """

        # Don't fetch line markers unless needed - in some cases, the breakpoints
        # might be set in a file that does not exist on disk (e.g. remote attach).
        get_marked_line_numbers = lambda: code.get_marked_line_numbers(path)

        if lines is all:
            lines = get_marked_line_numbers().keys()

        def make_breakpoint(line):
            if isinstance(line, int):
                descr = str(line)
            else:
                marker = line
                line = get_marked_line_numbers()[marker]
                descr = fmt("{0} (@{1})", line, marker)
            bp_log.append((line, descr))
            return {"line": line}

        bp_log = []
        breakpoints = self.request(
            "setBreakpoints",
            {
                "source": {
                    "path": path
                },
                "breakpoints": [make_breakpoint(line) for line in lines],
            },
        )("breakpoints", json.array())

        bp_log = sorted(bp_log, key=lambda pair: pair[0])
        bp_log = ", ".join((descr for _, descr in bp_log))
        log.info("Breakpoints set in {0}: {1}", path, bp_log)

        return breakpoints
コード例 #6
0
    def wait_for_stop(
        self,
        reason=some.str,
        expected_frames=None,
        expected_text=None,
        expected_description=None,
    ):
        stopped = self.wait_for_next_event("stopped")

        expected_stopped = {
            "reason": reason,
            "threadId": some.int,
            "allThreadsStopped": True,
        }
        if expected_text is not None:
            expected_stopped["text"] = expected_text
        if expected_description is not None:
            expected_stopped["description"] = expected_description
        if stopped("reason", unicode) not in [
                "step",
                "exception",
                "breakpoint",
                "entry",
                "goto",
        ]:
            expected_stopped["preserveFocusHint"] = True
        assert stopped == some.dict.containing(expected_stopped)

        tid = stopped("threadId", int)
        stack_trace = self.request("stackTrace", {"threadId": tid})
        frames = stack_trace("stackFrames", json.array()) or []
        assert len(frames) == stack_trace("totalFrames", int)

        if expected_frames:
            assert len(expected_frames) <= len(frames)
            assert expected_frames == frames[0:len(expected_frames)]

        fid = frames[0]("id", int)
        return StopInfo(stopped, frames, tid, fid)
コード例 #7
0
ファイル: handlers.py プロジェクト: dcerniglia/dotfiles
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()}
        n = len(env_changes)
        env_changes = {k.upper(): v for k, v in env_changes.items()}
        if len(env_changes) != n:
            raise request.isnt_valid('Duplicate entries in "env"')
    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 {}
コード例 #8
0
ファイル: clients.py プロジェクト: Anna-Hanabc/Interface
    def attach_request(self, request):
        if self.session.no_debug:
            raise request.isnt_valid('"noDebug" is not supported for "attach"')

        host = request("host", unicode, optional=True)
        port = request("port", int, optional=True)
        listen = request("listen", dict, optional=True)
        connect = request("connect", dict, optional=True)
        pid = request("processId", (int, unicode), optional=True)
        sub_pid = request("subProcessId", int, optional=True)

        if host != () or port != ():
            if listen != ():
                raise request.isnt_valid(
                    '"listen" and "host"/"port" are mutually exclusive')
            if connect != ():
                raise request.isnt_valid(
                    '"connect" and "host"/"port" are mutually exclusive')
        if listen != ():
            if connect != ():
                raise request.isnt_valid(
                    '"listen" and "connect" are mutually exclusive')
            if pid != ():
                raise request.isnt_valid(
                    '"listen" and "processId" are mutually exclusive')
            if sub_pid != ():
                raise request.isnt_valid(
                    '"listen" and "subProcessId" are mutually exclusive')
        if pid != () and sub_pid != ():
            raise request.isnt_valid(
                '"processId" and "subProcessId" are mutually exclusive')

        if listen != ():
            host = listen("host", "127.0.0.1")
            port = listen("port", int)
            adapter.access_token = None
            host, port = servers.serve(host, port)
        else:
            host, port = servers.serve()

        # There are four distinct possibilities here.
        #
        # If "processId" is specified, this is attach-by-PID. We need to inject the
        # debug server into the designated process, and then wait until it connects
        # back to us. Since the injected server can crash, there must be a timeout.
        #
        # If "subProcessId" is specified, this is attach to a known subprocess, likely
        # in response to a "debugpyAttach" event. If so, the debug server should be
        # connected already, and thus the wait timeout is zero.
        #
        # If "listen" is specified, this is attach-by-socket with the server expected
        # to connect to the adapter via debugpy.connect(). There is no PID known in
        # advance, so just wait until the first server connection indefinitely, with
        # no timeout.
        #
        # If "connect" is specified, this is attach-by-socket in which the server has
        # spawned the adapter via debugpy.listen(). There is no PID known to the client
        # in advance, but the server connection should be either be there already, or
        # the server should be connecting shortly, so there must be a timeout.
        #
        # In the last two cases, if there's more than one server connection already,
        # this is a multiprocess re-attach. The client doesn't know the PID, so we just
        # connect it to the oldest server connection that we have - in most cases, it
        # will be the one for the root debuggee process, but if it has exited already,
        # it will be some subprocess.
        if pid != ():
            if not isinstance(pid, int):
                try:
                    pid = int(pid)
                except Exception:
                    raise request.isnt_valid(
                        '"processId" must be parseable as int')
            debugpy_args = request("debugpyArgs", json.array(unicode))
            servers.inject(pid, debugpy_args)
            timeout = common.PROCESS_SPAWN_TIMEOUT
            pred = lambda conn: conn.pid == pid
        else:
            if sub_pid == ():
                pred = lambda conn: True
                timeout = common.PROCESS_SPAWN_TIMEOUT if listen == (
                ) else None
            else:
                pred = lambda conn: conn.pid == sub_pid
                timeout = 0

        self.channel.send_event("debugpyWaitingForServer", {
            "host": host,
            "port": port
        })
        conn = servers.wait_for_connection(self.session, pred, timeout)
        if conn is None:
            if sub_pid != ():
                # If we can't find a matching subprocess, it's not always an error -
                # it might have already exited, or didn't even get a chance to connect.
                # To prevent the client from complaining, pretend that the "attach"
                # request was successful, but that the session terminated immediately.
                request.respond({})
                self.session.finalize(
                    fmt('No known subprocess with "subProcessId":{0}',
                        sub_pid))
                return

            raise request.cant_handle(
                ("Timed out waiting for debug server to connect." if timeout
                 else "There is no debug server connected to this adapter."),
                sub_pid,
            )

        try:
            conn.attach_to_session(self.session)
        except ValueError:
            request.cant_handle("{0} is already being debugged.", conn)
コード例 #9
0
ファイル: clients.py プロジェクト: Anna-Hanabc/Interface
    def launch_request(self, request):
        from debugpy.adapter import launchers

        if self.session.id != 1 or len(servers.connections()):
            raise request.cant_handle('"attach" expected')

        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

        # "pythonPath" is a deprecated legacy spelling. If "python" is missing, then try
        # the alternative. But if both are missing, the error message should say "python".
        python_key = "python"
        if python_key in request:
            if "pythonPath" in request:
                raise request.isnt_valid(
                    '"pythonPath" is not valid if "python" is specified')
        elif "pythonPath" in request:
            python_key = "pythonPath"
        python = request(python_key,
                         json.array(unicode, vectorize=True, size=(0, )))
        if not len(python):
            python = [compat.filename(sys.executable)]

        python += request("pythonArgs", json.array(unicode, size=(0, )))
        request.arguments["pythonArgs"] = python[1:]
        request.arguments["python"] = python

        launcher_python = request("debugLauncherPython",
                                  unicode,
                                  optional=True)
        if launcher_python == ():
            launcher_python = python[0]

        program = module = code = ()
        if "program" in request:
            program = request("program", unicode)
            args = [program]
            request.arguments["processName"] = program
        if "module" in request:
            module = request("module", unicode)
            args = ["-m", module]
            request.arguments["processName"] = module
        if "code" in request:
            code = request("code",
                           json.array(unicode, vectorize=True, size=(1, )))
            args = ["-c", "\n".join(code)]
            request.arguments["processName"] = "-c"

        num_targets = len([x for x in (program, module, code) if x != ()])
        if num_targets == 0:
            raise request.isnt_valid(
                'either "program", "module", or "code" must be specified')
        elif num_targets != 1:
            raise request.isnt_valid(
                '"program", "module", and "code" are mutually exclusive')

        # Propagate "args" via CLI if and only if shell expansion is requested.
        args_expansion = request("argsExpansion",
                                 json.enum("shell", "none", optional=True))
        if args_expansion == "shell":
            args += request("args", json.array(unicode))
            request.arguments.pop("args", None)

        cwd = request("cwd", unicode, optional=True)
        if cwd == ():
            # If it's not specified, but we're launching a file rather than a module,
            # and the specified path has a directory in it, use that.
            cwd = None if program == () else (os.path.dirname(program) or None)

        console = request(
            "console",
            json.enum(
                "internalConsole",
                "integratedTerminal",
                "externalTerminal",
                optional=True,
            ),
        )
        console_title = request("consoleTitle",
                                json.default("Python Debug Console"))

        sudo = bool(property_or_debug_option("sudo", "Sudo"))
        if sudo and sys.platform == "win32":
            raise request.cant_handle(
                '"sudo":true is not supported on Windows.')

        launcher_path = request("debugLauncherPath",
                                os.path.dirname(launcher.__file__))
        adapter_host = request("debugAdapterHost", "127.0.0.1")

        servers.serve(adapter_host)
        launchers.spawn_debuggee(
            self.session,
            request,
            [launcher_python],
            launcher_path,
            adapter_host,
            args,
            cwd,
            console,
            console_title,
            sudo,
        )
コード例 #10
0
ファイル: clients.py プロジェクト: Anna-Hanabc/Interface
        def handle(self, request):
            assert request.is_request("launch", "attach")
            if self._initialize_request is None:
                raise request.isnt_valid("Session is not initialized yet")
            if self.launcher or self.server:
                raise request.isnt_valid("Session is already started")

            self.session.no_debug = request("noDebug", json.default(False))
            if self.session.no_debug:
                servers.dont_wait_for_first_connection()

            self.session.debug_options = debug_options = set(
                request("debugOptions", json.array(unicode)))

            f(self, request)
            if request.response is not None:
                return

            if self.server:
                self.server.initialize(self._initialize_request)
                self._initialize_request = None

                arguments = request.arguments
                if self.launcher:
                    if "RedirectOutput" in debug_options:
                        # The launcher is doing output redirection, so we don't need the
                        # server to do it, as well.
                        arguments = dict(arguments)
                        arguments["debugOptions"] = list(debug_options -
                                                         {"RedirectOutput"})

                    if arguments.get("redirectOutput"):
                        arguments = dict(arguments)
                        del arguments["redirectOutput"]

                # pydevd doesn't send "initialized", and responds to the start request
                # immediately, without waiting for "configurationDone". If it changes
                # to conform to the DAP spec, we'll need to defer waiting for response.
                try:
                    self.server.channel.request(request.command, arguments)
                except messaging.NoMoreMessages:
                    # Server closed connection before we could receive the response to
                    # "attach" or "launch" - this can happen when debuggee exits shortly
                    # after starting. It's not an error, but we can't do anything useful
                    # here at this point, either, so just bail out.
                    request.respond({})
                    self.session.finalize(
                        fmt(
                            "{0} disconnected before responding to {1!j}",
                            self.server,
                            request.command,
                        ))
                    return
                except messaging.MessageHandlingError as exc:
                    exc.propagate(request)

            if self.session.no_debug:
                self.start_request = request
                self.has_started = True
                request.respond({})
                self._propagate_deferred_events()
                return

            if {"WindowsClient", "Windows"} & debug_options:
                client_os_type = "WINDOWS"
            elif {"UnixClient", "UNIX"} & debug_options:
                client_os_type = "UNIX"
            else:
                client_os_type = "WINDOWS" if sys.platform == "win32" else "UNIX"
            self.server.channel.request(
                "setDebuggerProperty",
                {
                    "skipSuspendOnBreakpointException": ("BaseException", ),
                    "skipPrintBreakpointException": ("NameError", ),
                    "multiThreadsSingleNotification": True,
                    "ideOS": client_os_type,
                },
            )

            # Let the client know that it can begin configuring the adapter.
            self.channel.send_event("initialized")

            self.start_request = request
            return messaging.NO_RESPONSE  # will respond on "configurationDone"
コード例 #11
0
ファイル: clients.py プロジェクト: leochan-star/lion
    def launch_request(self, request):
        from debugpy.adapter import launchers

        if self.session.id != 1 or len(servers.connections()):
            raise request.cant_handle('"attach" expected')

        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

        # Launcher doesn't use the command line at all, but we pass the arguments so
        # that they show up in the terminal if we're using "runInTerminal".
        if "program" in request:
            args = request("program",
                           json.array(unicode, vectorize=True, size=(1, )))
        elif "module" in request:
            args = ["-m"] + request(
                "module", json.array(unicode, vectorize=True, size=(1, )))
        elif "code" in request:
            args = ["-c"] + request(
                "code", json.array(unicode, vectorize=True, size=(1, )))
        else:
            args = []
        args += request("args", json.array(unicode))

        console = request(
            "console",
            json.enum(
                "internalConsole",
                "integratedTerminal",
                "externalTerminal",
                optional=True,
            ),
        )
        console_title = request("consoleTitle",
                                json.default("Python Debug Console"))

        sudo = bool(property_or_debug_option("sudo", "Sudo"))
        if sudo and sys.platform == "win32":
            raise request.cant_handle(
                '"sudo":true is not supported on Windows.')

        servers.serve()
        launchers.spawn_debuggee(self.session, request, args, console,
                                 console_title, sudo)
コード例 #12
0
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

    # "pythonPath" is a deprecated legacy spelling. If "python" is missing, then try
    # the alternative. But if both are missing, the error message should say "python".
    python_key = "python"
    if python_key in request:
        if "pythonPath" in request:
            raise request.isnt_valid(
                '"pythonPath" is not valid if "python" is specified')
    elif "pythonPath" in request:
        python_key = "pythonPath"
    cmdline = request(python_key,
                      json.array(unicode, vectorize=True, size=(0, )))
    if not len(cmdline):
        cmdline = [compat.filename(sys.executable)]

    if not request("noDebug", json.default(False)):
        port = request("port", int)
        cmdline += [
            compat.filename(os.path.dirname(debugpy.__file__)),
            "--connect",
            str(port),
        ]
        if not request("subProcess", True):
            cmdline += ["--configure-subProcess", "False"]
        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

    program = module = code = ()
    if "program" in request:
        program = request("program", unicode)
        cmdline += [program]
        process_name = program
    if "module" in request:
        module = request("module", unicode)
        cmdline += ["-m", module]
        process_name = module
    if "code" in request:
        code = request("code", json.array(unicode, vectorize=True, size=(1, )))
        cmdline += ["-c", "\n".join(code)]
        process_name = cmdline[0]

    num_targets = len([x for x in (program, module, code) if x != ()])
    if num_targets == 0:
        raise request.isnt_valid(
            'either "program", "module", or "code" must be specified')
    elif num_targets != 1:
        raise request.isnt_valid(
            '"program", "module", and "code" are mutually exclusive')

    cmdline += request("args", json.array(unicode))

    cwd = request("cwd", unicode, optional=True)
    if cwd == ():
        # If it's not specified, but we're launching a file rather than a module,
        # and the specified path has a directory in it, use that.
        cwd = None if program == () else (os.path.dirname(program[0]) or None)

    env = os.environ.copy()
    env_changes = request("env", json.object(unicode))
    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()}
        n = len(env_changes)
        env_changes = {k.upper(): v for k, v in env_changes.items()}
        if len(env_changes) != n:
            raise request.isnt_valid('Duplicate entries in "env"')
    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)

    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, cwd, env, redirect_output)
    return {}