You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			184 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			184 lines
		
	
	
		
			5.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 functools
 | 
						|
 | 
						|
from debugpy.common import json, log, messaging, util
 | 
						|
 | 
						|
 | 
						|
ACCEPT_CONNECTIONS_TIMEOUT = 60
 | 
						|
 | 
						|
 | 
						|
class ComponentNotAvailable(Exception):
 | 
						|
    def __init__(self, type):
 | 
						|
        super().__init__(f"{type.__name__} is not available")
 | 
						|
 | 
						|
 | 
						|
class Component(util.Observable):
 | 
						|
    """A component managed by a debug adapter: client, launcher, or debug server.
 | 
						|
 | 
						|
    Every component belongs to a Session, which is used for synchronization and
 | 
						|
    shared data.
 | 
						|
 | 
						|
    Every component has its own message channel, and provides message handlers for
 | 
						|
    that channel. All handlers should be decorated with @Component.message_handler,
 | 
						|
    which ensures that Session is locked for the duration of the handler. Thus, only
 | 
						|
    one handler is running at any given time across all components, unless the lock
 | 
						|
    is released explicitly or via Session.wait_for().
 | 
						|
 | 
						|
    Components report changes to their attributes to Session, allowing one component
 | 
						|
    to wait_for() a change caused by another component.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, session, stream=None, channel=None):
 | 
						|
        assert (stream is None) ^ (channel is None)
 | 
						|
 | 
						|
        try:
 | 
						|
            lock_held = session.lock.acquire(blocking=False)
 | 
						|
            assert lock_held, "__init__ of a Component subclass must lock its Session"
 | 
						|
        finally:
 | 
						|
            session.lock.release()
 | 
						|
 | 
						|
        super().__init__()
 | 
						|
 | 
						|
        self.session = session
 | 
						|
 | 
						|
        if channel is None:
 | 
						|
            stream.name = str(self)
 | 
						|
            channel = messaging.JsonMessageChannel(stream, self)
 | 
						|
            channel.start()
 | 
						|
        else:
 | 
						|
            channel.name = channel.stream.name = str(self)
 | 
						|
            channel.handlers = self
 | 
						|
        self.channel = channel
 | 
						|
        self.is_connected = True
 | 
						|
 | 
						|
        # Do this last to avoid triggering useless notifications for assignments above.
 | 
						|
        self.observers += [lambda *_: self.session.notify_changed()]
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        return f"{type(self).__name__}[{self.session.id}]"
 | 
						|
 | 
						|
    @property
 | 
						|
    def client(self):
 | 
						|
        return self.session.client
 | 
						|
 | 
						|
    @property
 | 
						|
    def launcher(self):
 | 
						|
        return self.session.launcher
 | 
						|
 | 
						|
    @property
 | 
						|
    def server(self):
 | 
						|
        return self.session.server
 | 
						|
 | 
						|
    def wait_for(self, *args, **kwargs):
 | 
						|
        return self.session.wait_for(*args, **kwargs)
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def message_handler(f):
 | 
						|
        """Applied to a message handler to automatically lock and unlock the session
 | 
						|
        for its duration, and to validate the session state.
 | 
						|
 | 
						|
        If the handler raises ComponentNotAvailable or JsonIOError, converts it to
 | 
						|
        Message.cant_handle().
 | 
						|
        """
 | 
						|
 | 
						|
        @functools.wraps(f)
 | 
						|
        def lock_and_handle(self, message):
 | 
						|
            try:
 | 
						|
                with self.session:
 | 
						|
                    return f(self, message)
 | 
						|
            except ComponentNotAvailable as exc:
 | 
						|
                raise message.cant_handle("{0}", exc, silent=True)
 | 
						|
            except messaging.MessageHandlingError as exc:
 | 
						|
                if exc.cause is message:
 | 
						|
                    raise
 | 
						|
                else:
 | 
						|
                    exc.propagate(message)
 | 
						|
            except messaging.JsonIOError as exc:
 | 
						|
                raise message.cant_handle(
 | 
						|
                    "{0} disconnected unexpectedly", exc.stream.name, silent=True
 | 
						|
                )
 | 
						|
 | 
						|
        return lock_and_handle
 | 
						|
 | 
						|
    def disconnect(self):
 | 
						|
        with self.session:
 | 
						|
            self.is_connected = False
 | 
						|
            self.session.finalize("{0} has disconnected".format(self))
 | 
						|
 | 
						|
 | 
						|
def missing(session, type):
 | 
						|
    class Missing(object):
 | 
						|
        """A dummy component that raises ComponentNotAvailable whenever some
 | 
						|
        attribute is accessed on it.
 | 
						|
        """
 | 
						|
 | 
						|
        __getattr__ = __setattr__ = lambda self, *_: report()
 | 
						|
        __bool__ = __nonzero__ = lambda self: False
 | 
						|
 | 
						|
    def report():
 | 
						|
        try:
 | 
						|
            raise ComponentNotAvailable(type)
 | 
						|
        except Exception as exc:
 | 
						|
            log.reraise_exception("{0} in {1}", exc, session)
 | 
						|
 | 
						|
    return Missing()
 | 
						|
 | 
						|
 | 
						|
class Capabilities(dict):
 | 
						|
    """A collection of feature flags for a component. Corresponds to JSON properties
 | 
						|
    in the DAP "initialize" request or response, other than those that identify the
 | 
						|
    party.
 | 
						|
    """
 | 
						|
 | 
						|
    PROPERTIES = {}
 | 
						|
    """JSON property names and default values for the the capabilities represented
 | 
						|
    by instances of this class. Keys are names, and values are either default values
 | 
						|
    or validators.
 | 
						|
 | 
						|
    If the value is callable, it must be a JSON validator; see debugpy.common.json for
 | 
						|
    details. If the value is not callable, it is as if json.default(value) validator
 | 
						|
    was used instead.
 | 
						|
    """
 | 
						|
 | 
						|
    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} {1}", json.repr(name), exc)
 | 
						|
 | 
						|
            assert (
 | 
						|
                value != ()
 | 
						|
            ), f"{validate} must provide a default value for missing properties."
 | 
						|
            self[name] = value
 | 
						|
 | 
						|
        log.debug("{0}", self)
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return f"{type(self).__name__}: {json.repr(dict(self))}"
 | 
						|
 | 
						|
    def require(self, *keys):
 | 
						|
        for key in keys:
 | 
						|
            if not self[key]:
 | 
						|
                raise messaging.MessageHandlingError(
 | 
						|
                    f"{self.component} does not have capability {json.repr(key)}",
 | 
						|
                )
 |