def __init__(self, component, message): """Parses an "initialize" request or response and extracts the feature flags. For every "X" in self.PROPERTIES, sets self["X"] to the corresponding value from message.payload if it's present there, or to the default value otherwise. """ assert message.is_request("initialize") or message.is_response( "initialize") self.component = component payload = message.payload for name, validate in self.PROPERTIES.items(): value = payload.get(name, ()) if not callable(validate): validate = json.default(validate) try: value = validate(value) except Exception as exc: raise message.isnt_valid("{0!j} {1}", name, exc) assert value != (), fmt( "{0!j} must provide a default value for missing properties.", validate) self[name] = value log.debug("{0}", self)
def launch_request(self, request): from ptvsd.adapter import launchers if self.session.id != 1 or len(servers.connections()): raise request.cant_handle('"attach" expected') sudo = request("sudo", json.default("Sudo" in self.session.debug_options)) if sudo: if sys.platform == "win32": raise request.cant_handle( '"sudo":true is not supported on Windows.') else: if "Sudo" in self.session.debug_options: raise request.isnt_valid( '"sudo":false and "debugOptions":["Sudo"] are mutually exclusive' ) # 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")) launchers.spawn_debuggee(self.session, request, sudo, args, console, console_title)
def launch_request(self, request): sudo = request("sudo", json.default("Sudo" in self.session.debug_options)) if sudo: if platform.system() == "Windows": raise request.cant_handle( '"sudo":true is not supported on Windows.') else: if "Sudo" in self.session.debug_options: raise request.isnt_valid( '"sudo":false and "debugOptions":["Sudo"] are mutually exclusive' ) # 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")) self.session.spawn_debuggee(request, sudo, args, console, console_title) if "RedirectOutput" in self.session.debug_options: # The launcher is doing output redirection, so we don't need the server. request.arguments["debugOptions"].remove("RedirectOutput")
def property_or_debug_option(prop_name, flag_name): assert prop_name[0].islower() and flag_name[0].isupper() value = request(prop_name, json.default(flag_name in debug_options)) if value is False and flag_name in debug_options: raise request.isnt_valid( '{0!r}:false and "debugOptions":[{1!r}] are mutually exclusive', prop_name, flag_name, ) return value
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: raise request.isnt_valid("Session is already started") self.session.no_debug = request("noDebug", json.default(False)) self.session.debug_options = set( request("debugOptions", json.array(unicode))) f(self, request) if self.server: self.server.initialize(self._initialize_request) self._initialize_request = None # 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. self.server.channel.delegate(request) if self.session.no_debug: request.respond({}) self._propagate_deferred_events() return if {"WindowsClient", "Windows"} & self.session.debug_options: client_os_type = "WINDOWS" elif {"UnixClient", "UNIX"} & self.session.debug_options: client_os_type = "UNIX" else: client_os_type = "WINDOWS" if platform.system( ) == "Windows" else "UNIX" self.server.channel.request( "setDebuggerProperty", { "skipSuspendOnBreakpointException": ("BaseException", ), "skipPrintBreakpointException": ("NameError", ), "multiThreadsSingleNotification": True, "ideOS": client_os_type, }, ) # Let the IDE 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"
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 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.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 IDE 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"
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 cmdline = [] if property_or_debug_option("sudo", "Sudo"): if sys.platform == "win32": raise request.cant_handle('"sudo":true is not supported on Windows.') else: cmdline += ["sudo"] # "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=(1,))) if not len(python): python = [compat.filename(sys.executable)] cmdline += python if not request("noDebug", json.default(False)): port = request("port", int) cmdline += [ compat.filename(os.path.dirname(ptvsd.__file__)), "--client", "--host", "127.0.0.1", "--port", str(port), ] client_access_token = request("clientAccessToken", unicode, optional=True) if client_access_token != (): cmdline += ["--client-access-token", compat.filename(client_access_token)] ptvsd_args = request("ptvsdArgs", json.array(unicode)) cmdline += ptvsd_args program = module = code = () if "program" in request: program = request("program", json.array(unicode, vectorize=True, size=(1,))) cmdline += program process_name = program[0] if "module" in request: module = request("module", json.array(unicode, vectorize=True, size=(1,))) cmdline += ["-m"] + module process_name = module[0] if "code" in request: code = request("code", json.array(unicode, vectorize=True, size=(1,))) cmdline += ["-c"] + code process_name = python[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() if "PTVSD_TEST" in env: # If we're running as part of a ptvsd 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(request("env", json.object(unicode))) if request("gevent", False): env["GEVENT_SUPPORT"] = "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 = request("console", unicode) == "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"): debuggee.wait_on_exit_predicates.append(lambda code: code == 0) if property_or_debug_option("waitOnAbnormalExit", "WaitOnAbnormalExit"): 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 {}
def disconnect_request(self, request): self.session.finalize( 'IDE requested "disconnect"', request("terminateDebuggee", json.default(bool(self.launcher))), ) return {}