def launch( self, target, debug=True, success=True, terminal="none", args: Optional[Iterable[str]] = None, ): """ :param args: The arguments to the launch (for instance: ["--variable", "my_var:22"] ) """ from robocorp_ls_core.debug_adapter_core.dap.dap_schema import LaunchRequest from robocorp_ls_core.debug_adapter_core.dap.dap_schema import ( LaunchRequestArguments, ) from robocorp_ls_core.debug_adapter_core.dap.dap_schema import LaunchResponse from robocorp_ls_core.debug_adapter_core.dap.dap_schema import ( RunInTerminalRequest, ) from robocorp_ls_core.basic import as_str from robocorp_ls_core.debug_adapter_core.dap.dap_schema import InitializedEvent from robocorp_ls_core.debug_adapter_core.dap.dap_schema import Response from robocorp_ls_core.debug_adapter_core.dap.dap_schema import ProcessEvent launch_args = LaunchRequestArguments(__sessionId="some_id", noDebug=not debug, target=target, terminal=terminal) if args: launch_args.kwargs["args"] = args self.write(LaunchRequest(launch_args)) if terminal == "external": run_in_terminal_request = self.read(RunInTerminalRequest) env = os.environ.copy() for key, val in run_in_terminal_request.arguments.env.to_dict( ).items(): env[as_str(key)] = as_str(val) cwd = run_in_terminal_request.arguments.cwd popen_args = run_in_terminal_request.arguments.args subprocess.Popen(popen_args, cwd=cwd, env=env) if success: # Initialized is sent just before the launch response (at which # point it's possible to send breakpoints). self.read(ProcessEvent) event = self.read(InitializedEvent) assert isinstance(event, InitializedEvent) if success: launch_response = self.read(LaunchResponse) else: launch_response = self.read(Response) assert launch_response.success == success
def not_supported_test_launch_in_external_terminal( debugger_api: _DebuggerAPI, rcc_config_location: str ): """ This is an integrated test of the debug adapter. It communicates with it as if it was VSCode. Note: we don't currently support launching in an external terminal because there's no easy way to get the pid (it'd be possible to do that by creating a wrapper script which would then really launch rcc and then it'd connect back to some port and provide the pid of the process which was spawned, but the value gained vs the effort to do so seems low, which means we can only run without a terminal for now so that we have an easy way of tracking the RCC process pid). """ from robocorp_ls_core.debug_adapter_core.dap.dap_schema import TerminatedEvent from robocorp_ls_core.debug_adapter_core.dap.dap_schema import RunInTerminalRequest import os from robocorp_ls_core.basic import as_str from robocorp_ls_core.subprocess_wrapper import subprocess debugger_api.initialize(rcc_config_location=rcc_config_location) robot = debugger_api.get_dap_case_file("minimal/robot.yaml") debugger_api.launch(robot, "task2", debug=False, terminal="external") debugger_api.configuration_done() run_in_terminal_request = debugger_api.read(RunInTerminalRequest) env = os.environ.copy() for key, val in run_in_terminal_request.arguments.env.to_dict().items(): env[as_str(key)] = as_str(val) cwd = run_in_terminal_request.arguments.cwd popen_args = run_in_terminal_request.arguments.args subprocess.Popen(popen_args, cwd=cwd, env=env) # i.e.: Big timeout because creating the environment may be slow. debugger_api.read(TerminatedEvent, timeout=120)
def _run_rcc( self, args: List[str], timeout: float = 30, error_msg: str = "", mutex_name=None, cwd: Optional[str] = None, log_errors=True, stderr=Sentinel.SENTINEL, ) -> ActionResult[str]: """ Returns an ActionResult where the result is the stdout of the executed command. :param log_errors: If false, errors won't be logged (i.e.: should be false when errors are expected). """ from robocorp_ls_core.basic import build_subprocess_kwargs from subprocess import check_output from robocorp_ls_core.subprocess_wrapper import subprocess if stderr is Sentinel.SENTINEL: stderr = subprocess.PIPE rcc_location = self.get_rcc_location() env = os.environ.copy() env.pop("PYTHONPATH", "") env.pop("PYTHONHOME", "") env.pop("VIRTUAL_ENV", "") env["PYTHONIOENCODING"] = "utf-8" env["PYTHONUNBUFFERED"] = "1" robocorp_home = self._get_robocorp_home() if robocorp_home: env["ROBOCORP_HOME"] = robocorp_home kwargs: dict = build_subprocess_kwargs(cwd, env, stderr=stderr) args = [rcc_location] + args + ["--controller", "RobocorpCode"] cmdline = " ".join([str(x) for x in args]) try: if mutex_name: from robocorp_ls_core.system_mutex import timed_acquire_mutex else: timed_acquire_mutex = NULL with timed_acquire_mutex(mutex_name, timeout=15): boutput: bytes = check_output(args, timeout=timeout, **kwargs) except CalledProcessError as e: stdout = as_str(e.stdout) stderr = as_str(e.stderr) msg = f"Error running: {cmdline}.\nROBOCORP_HOME: {robocorp_home}\n\nStdout: {stdout}\nStderr: {stderr}" if log_errors: log.exception(msg) if not error_msg: return ActionResult(success=False, message=msg) else: additional_info = [error_msg] if stdout or stderr: if stdout and stderr: additional_info.append("\nDetails: ") additional_info.append("\nStdout") additional_info.append(stdout) additional_info.append("\nStderr") additional_info.append(stderr) elif stdout: additional_info.append("\nDetails: ") additional_info.append(stdout) elif stderr: additional_info.append("\nDetails: ") additional_info.append(stderr) return ActionResult(success=False, message="".join(additional_info)) except Exception: msg = f"Error running: {args}" log.exception(msg) return ActionResult(success=False, message=msg) output = boutput.decode("utf-8", "replace") log.debug("Output from: %s:\n%s", cmdline, output) return ActionResult(success=True, message=None, result=output)
def _run_rcc( self, args: List[str], timeout: float = 30, expect_ok=True, error_msg: str = "", mutex_name=None, cwd: Optional[str] = None, ) -> ActionResult[str]: """ Returns an ActionResult where the result is the stdout of the executed command. """ from robocorp_ls_core.basic import build_subprocess_kwargs from subprocess import check_output from robocorp_ls_core.subprocess_wrapper import subprocess rcc_location = self.get_rcc_location() env = os.environ.copy() env.pop("PYTHONPATH", "") env.pop("PYTHONHOME", "") env.pop("VIRTUAL_ENV", "") env["PYTHONIOENCODING"] = "utf-8" env["PYTHONUNBUFFERED"] = "1" kwargs: dict = build_subprocess_kwargs(cwd, env, stderr=subprocess.PIPE) args = [rcc_location] + args cmdline = " ".join([str(x) for x in args]) try: if mutex_name: from robocorp_ls_core.system_mutex import timed_acquire_mutex else: timed_acquire_mutex = NULL with timed_acquire_mutex(mutex_name, timeout=15): boutput: bytes = check_output(args, timeout=timeout, **kwargs) except CalledProcessError as e: stdout = as_str(e.stdout) stderr = as_str(e.stderr) msg = f"Error running: {cmdline}.\nStdout: {stdout}\nStderr: {stderr}" log.exception(msg) if not error_msg: return ActionResult(success=False, message=msg) else: additional_info = [error_msg] if stdout or stderr: if stdout and stderr: additional_info.append("\nDetails: ") additional_info.append("\nStdout") additional_info.append(stdout) additional_info.append("\nStderr") additional_info.append(stderr) elif stdout: additional_info.append("\nDetails: ") additional_info.append(stdout) elif stderr: additional_info.append("\nDetails: ") additional_info.append(stderr) return ActionResult(success=False, message="".join(additional_info)) except Exception: msg = f"Error running: {args}" log.exception(msg) return ActionResult(success=False, message=msg) output = boutput.decode("utf-8", "replace") log.debug(f"Output from: {cmdline}:\n{output}") if expect_ok: if "OK." in output: return ActionResult(success=True, message=None, result=output) else: return ActionResult(success=True, message=None, result=output) return ActionResult( success=False, message="OK. not found in message", result=output )
def __init__(self, request, launch_response, debug_adapter_comm): """ :param LaunchRequest request: :param LaunchResponse launch_response: """ import weakref from robotframework_debug_adapter.constants import VALID_TERMINAL_OPTIONS from robotframework_debug_adapter.constants import TERMINAL_NONE from robocorp_ls_core.basic import as_str import robocorp_ls_core self._weak_debug_adapter_comm = weakref.ref(debug_adapter_comm) self._valid = True self._cmdline = [] self._popen = None self._launch_response = launch_response self._next_seq = partial(next, itertools.count(0)) self._track_process_pid = None self._sent_terminated = threading.Event() self._debug_adapter_robot_target_comm = _DebugAdapterRobotTargetComm( debug_adapter_comm) def mark_invalid(message): launch_response.success = False launch_response.message = message self._valid = False import sys target = request.arguments.kwargs.get("target") self._cwd = request.arguments.kwargs.get("cwd") self._terminal = request.arguments.kwargs.get("terminal", TERMINAL_NONE) args = request.arguments.kwargs.get("args") or [] args = [str(arg) for arg in args] env = {} request_env = request.arguments.kwargs.get("env") if isinstance(request_env, dict) and request_env: env.update(request_env) pythonpath = env.get("PYTHONPATH", "") pythonpath += ( os.pathsep + os.path.dirname(os.path.dirname(__file__)) + os.pathsep + os.path.dirname(os.path.dirname(robocorp_ls_core.__file__))) env["PYTHONPATH"] = pythonpath for key, value in os.environ.items(): if "ROBOTFRAMEWORK" in key: env[key] = value env = dict( ((as_str(key), as_str(value)) for (key, value) in env.items())) self._env = env self._run_in_debug_mode = not request.arguments.noDebug if self._terminal not in VALID_TERMINAL_OPTIONS: return mark_invalid( "Invalid terminal option: %s (must be one of: %s)" % (self._terminal, VALID_TERMINAL_OPTIONS)) try: if self._cwd is not None: if not os.path.exists(self._cwd): return mark_invalid("cwd specified does not exist: %s" % (self._cwd, )) except: log.exception("Error") return mark_invalid("Error checking if cwd (%s) exists." % (self._cwd, )) try: if target is None: return mark_invalid("target not provided in launch.") if not os.path.exists(target): return mark_invalid("File: %s does not exist." % (target, )) except: log.exception("Error") return mark_invalid("Error checking if target (%s) exists." % (target, )) if DEBUG: log.debug("Run in debug mode: %s\n" % (self._run_in_debug_mode, )) port = self._debug_adapter_robot_target_comm.start_listening() try: run_robot_py = os.path.join(os.path.dirname(__file__), "run_robot__main__.py") if not os.path.exists(run_robot_py): return mark_invalid("File: %s does not exist." % (run_robot_py, )) except: log.exception("Error") return mark_invalid( "Error checking if run_robot__main__.py exists.") else: # Note: target must be the last parameter. cmdline = ([ sys.executable, "-u", run_robot_py, "--port", str(port), "--debug" if self._run_in_debug_mode else "--no-debug", ] + args + [target]) self._cmdline = cmdline
def __init__( self, request, launch_response, debug_adapter_comm, rcc_config_location: Optional[str], ): """ :param LaunchRequest request: :param LaunchResponse launch_response: """ import weakref from robocorp_ls_core.basic import as_str from robocorp_code_debug_adapter.constants import VALID_TERMINAL_OPTIONS from robocorp_code_debug_adapter.constants import TERMINAL_NONE from robocorp_ls_core.robotframework_log import get_log_level from robocorp_code.rcc import Rcc from robocorp_ls_core.config import Config from pathlib import Path self._weak_debug_adapter_comm = weakref.ref(debug_adapter_comm) self._valid = True self._cmdline = [] self._popen = None self._launch_response = launch_response self._next_seq = partial(next, itertools.count(0)) self._track_process_pid = None self._sent_terminated = threading.Event() self._rcc_config_location = rcc_config_location def mark_invalid(message): launch_response.success = False launch_response.message = message self._valid = False robot_yaml = request.arguments.kwargs.get("robot") self._terminal = request.arguments.kwargs.get("terminal", TERMINAL_NONE) if self._terminal != TERMINAL_NONE: # We don't currently support the integrated terminal because we don't # have an easy way to get the launched process pid in this way. return mark_invalid( f"Only 'terminal=none' is supported. Found terminal: {self._terminal}" ) task_name = request.arguments.kwargs.get("task", "") args = request.arguments.kwargs.get("args") or [] if not isinstance(args, list): args = [args] args = [str(arg) for arg in args] env = {} request_env = request.arguments.kwargs.get("env") if isinstance(request_env, dict) and request_env: env.update(request_env) env = dict( ((as_str(key), as_str(value)) for (key, value) in env.items())) self._env = env self._run_in_debug_mode = not request.arguments.noDebug if self._terminal not in VALID_TERMINAL_OPTIONS: return mark_invalid( "Invalid terminal option: %s (must be one of: %s)" % (self._terminal, VALID_TERMINAL_OPTIONS)) try: if robot_yaml is None: return mark_invalid("robot not provided in launch.") if not os.path.exists(robot_yaml): return mark_invalid("File: %s does not exist." % (robot_yaml, )) except: log.exception("Error") return mark_invalid("Error checking if robot (%s) exists." % (robot_yaml, )) self._cwd = os.path.dirname(robot_yaml) try: if self._cwd is not None: if not os.path.exists(self._cwd): return mark_invalid("cwd specified does not exist: %s" % (self._cwd, )) except: log.exception("Error") return mark_invalid("Error checking if cwd (%s) exists." % (self._cwd, )) if get_log_level() > 1: log.debug("Run in debug mode: %s\n" % (self._run_in_debug_mode, )) try: config = Config() config_provider = _DefaultConfigurationProvider(config) rcc = Rcc(config_provider=config_provider) rcc_executable = rcc.get_rcc_location() if not os.path.exists(rcc_executable): return mark_invalid(f"Expected: {rcc_executable} to exist.") except: log.exception("Error") return mark_invalid("Error getting rcc executable location.") else: task_args = [] if task_name: task_args.append("--task") task_args.append(task_name) cmdline = ([rcc_executable, "task", "run", "--robot", robot_yaml] + task_args + args) if self._rcc_config_location: cmdline.append("--config") cmdline.append(self._rcc_config_location) env_json_path = Path(robot_yaml).parent / "devdata" / "env.json" if env_json_path.exists(): cmdline.append("-e") cmdline.append(str(env_json_path)) self._cmdline = cmdline