From fc45d3307f0a5c6d8a578a4685760c4bb5679260 Mon Sep 17 00:00:00 2001 From: Pavel Minaev Date: Tue, 21 Apr 2020 17:58:57 -0700 Subject: [PATCH] Fix #136: Provide a way for the launcher to connect to adapter on another host Allow specifying host:port for the launcher to connect to. Use command line argument instead of environment variable to facilitate propagation. Fix encoding of command line arguments when propagating them to the launcher in "internalConsole" mode. --- src/debugpy/adapter/launchers.py | 25 +++++++++++++++++-------- src/debugpy/launcher/__init__.py | 4 ++-- src/debugpy/launcher/__main__.py | 25 ++++++++++++++++--------- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/debugpy/adapter/launchers.py b/src/debugpy/adapter/launchers.py index 53905ba9c..b9c1da00a 100644 --- a/src/debugpy/adapter/launchers.py +++ b/src/debugpy/adapter/launchers.py @@ -65,13 +65,13 @@ def terminate_debuggee(self): pass -def spawn_debuggee(session, start_request, launcher_path, args, console, console_title, sudo): +def spawn_debuggee( + session, start_request, launcher_path, args, console, console_title, sudo +): # -E tells sudo to propagate environment variables to the target process - this # is necessary for launcher to get DEBUGPY_LAUNCHER_PORT and DEBUGPY_LOG_DIR. cmdline = ["sudo", "-E"] if sudo else [] - cmdline += [sys.executable, launcher_path] - cmdline += args env = {} arguments = dict(start_request.arguments) @@ -90,15 +90,14 @@ def on_launcher_connected(sock): ) except Exception as exc: raise start_request.cant_handle( - "{0} couldn't create listener socket for launcher: {1}", - session, - exc, + "{0} couldn't create listener socket for launcher: {1}", session, exc ) try: _, launcher_port = listener.getsockname() + cmdline += [str(launcher_port), "--"] + cmdline += args - env[str("DEBUGPY_LAUNCHER_PORT")] = str(launcher_port) if log.log_dir is not None: env[str("DEBUGPY_LOG_DIR")] = compat.filename_str(log.log_dir) if log.stderr.levels != {"warning", "error"}: @@ -107,6 +106,14 @@ def on_launcher_connected(sock): if console == "internalConsole": log.info("{0} spawning launcher: {1!r}", session, cmdline) try: + for i, arg in enumerate(cmdline): + try: + cmdline[i] = compat.filename_str(arg) + except UnicodeEncodeError as exc: + raise start_request.cant_handle( + "Invalid command line argument {0!j}: {1}", arg, exc + ) + # If we are talking to the client over stdio, sys.stdin and sys.stdout # are redirected to avoid mangling the DAP message stream. Make sure # the launcher also respects that. @@ -138,7 +145,9 @@ def on_launcher_connected(sock): # If using sudo, it might prompt for password, and launcher won't start running # until the user enters it, so don't apply timeout in that case. - if not session.wait_for(lambda: session.launcher, timeout=(None if sudo else 10)): + if not session.wait_for( + lambda: session.launcher, timeout=(None if sudo else 10) + ): raise start_request.cant_handle("Timed out waiting for launcher to connect") try: diff --git a/src/debugpy/launcher/__init__.py b/src/debugpy/launcher/__init__.py index 4ec59ac53..14b7a40b4 100644 --- a/src/debugpy/launcher/__init__.py +++ b/src/debugpy/launcher/__init__.py @@ -16,7 +16,7 @@ """DAP message channel to the adapter.""" -def connect(launcher_port): +def connect(host, port): from debugpy.common import messaging, sockets from debugpy.launcher import handlers @@ -24,7 +24,7 @@ def connect(launcher_port): assert channel is None sock = sockets.create_client() - sock.connect(("127.0.0.1", launcher_port)) + sock.connect((host, port)) stream = messaging.JsonIOStream.from_socket(sock, "Adapter") channel = messaging.JsonMessageChannel(stream, handlers=handlers) diff --git a/src/debugpy/launcher/__main__.py b/src/debugpy/launcher/__main__.py index 4866caa55..845e0d961 100644 --- a/src/debugpy/launcher/__main__.py +++ b/src/debugpy/launcher/__main__.py @@ -31,15 +31,22 @@ def main(): # will also exit, so it doesn't need to observe the signal directly. signal.signal(signal.SIGINT, signal.SIG_IGN) - def option(name, type, *args): - try: - return type(os.environ.pop(name, *args)) - except Exception: - log.reraise_exception("Error parsing {0!r}:", name) - - launcher_port = option("DEBUGPY_LAUNCHER_PORT", int) - - launcher.connect(launcher_port) + # Everything before "--" is command line arguments for the launcher itself, + # and everything after "--" is command line arguments for the debuggee. + sep = sys.argv.index("--") + launcher_argv = sys.argv[1:sep] + sys.argv = sys.argv[sep + 1:] + + # The first argument specifies the host/port on which the adapter is waiting + # for launcher to connect. It's either host:port, or just port. + adapter = launcher_argv[0] + host, sep, port = adapter.partition(":") + if not sep: + host = "127.0.0.1" + port = adapter + port = int(port) + + launcher.connect(host, port) launcher.channel.wait() if debuggee.process is not None: