From bad34258ac56f74fa1192afe3bccf4d18f5f007a Mon Sep 17 00:00:00 2001 From: Pavel Minaev Date: Fri, 12 Oct 2018 22:43:11 -0700 Subject: [PATCH] Detour pydev_monkey.patch_args and construct the appropriate ptvsd command line. --- ptvsd/multiproc.py | 98 +++++++++++++++++++++++++++++++++++++++++++ ptvsd/pydevd_hooks.py | 2 +- ptvsd/socket.py | 4 ++ 3 files changed, 103 insertions(+), 1 deletion(-) diff --git a/ptvsd/multiproc.py b/ptvsd/multiproc.py index a610fe7d..40576a0b 100644 --- a/ptvsd/multiproc.py +++ b/ptvsd/multiproc.py @@ -8,6 +8,7 @@ import atexit import itertools import os import random +import re import socket try: @@ -121,3 +122,100 @@ def init_subprocess(initial_pid, initial_request, parent_pid, parent_port, first ptvsd.wait_for_attach() +def patch_args(args): + """ + Patches a command line invoking Python such that it has the same meaning, but + the process runs under ptvsd. In general, this means that given something like: + + python -R -Q warn -m app + + the result should be: + + python -R -Q warn -m ptvsd --host localhost --port 0 --multiprocess --wait -m app + + Note that the first -m above is interpreted by Python, and the second by ptvsd. + """ + + args = list(args) + print(args) + + # First, let's find the target of the invocation. This is one of: + # + # filename.py + # -m module_name + # -c "code" + # - + # + # This needs to take into account other switches that have values: + # + # -Q -W -X --check-hash-based-pycs + # + # because in something like "-X -c", -c is a value, not a switch. + expect_value = False + for i, arg in enumerate(args): + # Skip Python binary. + if i == 0: + continue + + if arg == '-': + # We do not support debugging while reading from stdin, so just let this + # process run without debugging. + return args + + if expect_value: + expect_value = False + continue + + if not arg.startswith('-') or arg in ('-c', '-m'): + break + + if arg.startswith('--'): + expect_value = (arg == '--check-hash-based-pycs') + continue + + # All short switches other than -c and -m can be combined together, including + # those with values. So, instead of -R -B -v -Q old, we might see -RBvQ old. + # Furthermore, the value itself can be concatenated with the switch, so rather + # than -Q old, we might have -Qold. When switches are combined, any switch that + # has a value "eats" the rest of the argument; for example, -RBQv is treated as + # -R -B -Qv, and not as -R -B -Q -v. So, we need to check whether one of 'Q', + # 'W' or 'X' was present somewhere in the arg, and whether there was anything + # following it in the arg. If it was there but nothing followed after it, then + # the switch is expecting a value. + split = re.split(r'[QWX]', arg, maxsplit=1) + expect_value = (len(split) > 1 and split[-1] != '') + + else: + # Didn't find the target, so we don't know how to patch this command line; let + # it run without debugging. + return args + + if not args[i].startswith('-'): + # If it was a filename, it can be a Python file, a directory, or a zip archive + # that is treated as if it were a directory. However, pydevd only supports the + # first scenario. Distinguishing between these can be tricky, and getting it + # wrong means that process fails to launch, so be conservative. + if not args[i].endswith('.py'): + print('!!!', args[i]) + return args + + # Now we need to inject the ptvsd invocation right before the target. The target + # itself can remain as is, because ptvsd is compatible with Python in that respect. + args[i:i] = [ + '-m', 'ptvsd', + '--server-host', 'localhost', + '--port', '0', + '--multiprocess', + '--wait' + ] + + print(args) + return args + + +def patch_and_quote_args(args): + # On Windows, pydevd expects arguments to be quoted and escaped as necessary, such + # that simply concatenating them via ' ' produces a valid command line. This wraps + # patch_args and applies quoting (quote_args contains platform check), so that the + # implementation of patch_args can be kept simple. + return pydev_monkey.quote_args(patch_args(args)) diff --git a/ptvsd/pydevd_hooks.py b/ptvsd/pydevd_hooks.py index cde70ad8..aa322d63 100644 --- a/ptvsd/pydevd_hooks.py +++ b/ptvsd/pydevd_hooks.py @@ -129,7 +129,7 @@ def install(pydevd_module, address, pydevd_comm.start_client = _start_client # This is invoked when a child process is spawned with multiproc debugging enabled. - pydev_monkey._get_python_c_args = get_python_c_args + pydev_monkey.patch_args = multiproc.patch_and_quote_args # Ensure that pydevd is using our functions. pydevd_module.start_server = _start_server diff --git a/ptvsd/socket.py b/ptvsd/socket.py index a81435f5..68ddc851 100644 --- a/ptvsd/socket.py +++ b/ptvsd/socket.py @@ -76,6 +76,10 @@ def create_server(host, port, timeout=None): host = 'localhost' server = _new_sock() server.bind((host, port)) + + # _, listener_port = server.getsockname() + # print('Listening on', listener_port) + if timeout is not None: server.settimeout(timeout) server.listen(1)