Detour pydev_monkey.patch_args and construct the appropriate ptvsd command line.

This commit is contained in:
Pavel Minaev 2018-10-12 22:43:11 -07:00
parent 317e7b0657
commit bad34258ac
3 changed files with 103 additions and 1 deletions

View file

@ -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))

View file

@ -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

View file

@ -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)