Fix #156: Add a flag for the old way of handling args from a launch config

Add new property "argsExpansion", which defaults to "shell", but can be explicitly set to "none" to request no expansion.

Propagate "args" to launcher via CLI or JSON, depending on the value of "argsExpansion".

Move the logic to compute process name back to the adapter, alongside other processing of "launch" targets.
This commit is contained in:
Pavel Minaev 2020-05-06 19:35:52 -07:00 committed by Pavel Minaev
parent 7a332142ce
commit 1a44e70206
5 changed files with 45 additions and 49 deletions

View file

@ -272,22 +272,19 @@ class Client(components.Component):
return value
# Propagate command line arguments via launcher CLI rather than "args", so that
# they get shell expansion applied to them in "runInTerminal" scenarios.
program = module = code = ()
if "program" in request:
program = request("program", unicode)
args = [program]
# process_name = program
request.arguments["processName"] = program
if "module" in request:
module = request("module", unicode)
args = ["-m", module]
# process_name = module
request.arguments["processName"] = module
if "code" in request:
code = request("code", json.array(unicode, vectorize=True, size=(1,)))
args = ["-c", "\n".join(code)]
# process_name = cmdline[0]
request.arguments["processName"] = "-c"
num_targets = len([x for x in (program, module, code) if x != ()])
if num_targets == 0:
@ -299,7 +296,11 @@ class Client(components.Component):
'"program", "module", and "code" are mutually exclusive'
)
args += request("args", json.array(unicode))
# Propagate "args" via CLI if and only if shell expansion is requested.
args_expansion = request("argsExpansion", json.enum("shell", "none", optional=True))
if args_expansion == "shell":
args += request("args", json.array(unicode))
del request.arguments["args"]
cwd = request("cwd", unicode, optional=True)
if cwd == ():
@ -536,6 +537,8 @@ class Client(components.Component):
body["name"] = fmt("Subprocess {0}", conn.pid)
body["request"] = "attach"
body["subProcessId"] = conn.pid
body.pop("processName", None)
body.pop("args", None)
host = body.pop("host", None)
port = body.pop("port", None)

View file

@ -67,14 +67,14 @@ def launch_request(request):
debugpy_args = request("debugpyArgs", json.array(unicode))
cmdline += debugpy_args
# Use command line arguments propagated via launcher CLI, rather than "args", to get
# their values after shell expansion in "runInTerminal" scenarios. The command line
# parser in __main__ has already removed everything up to and including "--" by now.
# Further arguments can come via two channels: the launcher's own command line, or
# "args" in the request; effective arguments are concatenation of these two in order.
# Arguments for debugpy (such as -m) always come via CLI, but those specified by the
# user via "args" are passed differently by the adapter depending on "argsExpansion".
cmdline += sys.argv[1:]
cmdline += request("args", json.array(unicode))
# We want the process name to reflect the target. So for -m, use the module name
# that follows; otherwise use the first argument, which is either -c or filename.
process_name = compat.filename(sys.argv[1] if sys.argv[1] != "-m" else sys.argv[2])
process_name = request("processName", compat.filename(sys.executable))
env = os.environ.copy()
env_changes = request("env", json.object(unicode))

View file

@ -52,6 +52,7 @@ class DebugConfig(collections.MutableMapping):
"type": (),
# Launch
"args": [],
"argsExpansion": "shell",
"code": (),
"console": "internal",
"cwd": (),

View file

@ -6,6 +6,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera
import pytest
from debugpy.common import log
from tests import debug
from tests.debug import runners, targets
from tests.patterns import some
@ -35,7 +36,8 @@ def test_args(pyfile, target, run):
@pytest.mark.parametrize("target", targets.all)
@pytest.mark.parametrize("run", runners.all_launch)
def test_shell_expansion(pyfile, target, run):
@pytest.mark.parametrize("expansion", ["", "none", "shell"])
def test_shell_expansion(pyfile, target, run, expansion):
@pyfile
def code_to_debug():
import sys
@ -46,9 +48,11 @@ def test_shell_expansion(pyfile, target, run):
backchannel.send(sys.argv)
def expand(args):
log.info("Before expansion: {0}", args)
for i, arg in enumerate(args):
if arg.startswith("$"):
args[i] = arg[1:]
log.info("After expansion: {0}", args)
class Session(debug.Session):
def run_in_terminal(self, args, cwd, env):
@ -57,11 +61,15 @@ def test_shell_expansion(pyfile, target, run):
args = ["0", "$1", "2"]
with Session() as session:
if expansion:
session.config["argsExpansion"] = expansion
backchannel = session.open_backchannel()
with run(session, target(code_to_debug, args=args)):
pass
argv = backchannel.receive()
if session.config["console"] != "internalConsole":
if session.config["console"] != "internalConsole" and expansion != "none":
expand(args)
assert argv == [some.str] + args

View file

@ -21,6 +21,21 @@ if not tests.full:
return request.param
def expected_subprocess_config(parent_session):
config = dict(parent_session.config)
for key in "args", "listen", "postDebugTask", "preLaunchTask", "processId":
config.pop(key, None)
config.update(
{
"name": some.str,
"request": "attach",
"subProcessId": some.int,
"connect": {"host": some.str, "port": some.int},
}
)
return config
@pytest.mark.parametrize(
"start_method",
[""]
@ -114,17 +129,7 @@ def test_multiprocessing(pyfile, target, run, start_method):
with run(parent_session, target(code_to_debug, args=[start_method])):
pass
expected_child_config = dict(parent_session.config)
expected_child_config.pop("listen", None)
expected_child_config.update(
{
"name": some.str,
"request": "attach",
"subProcessId": some.int,
"connect": {"host": some.str, "port": some.int},
}
)
expected_child_config = expected_subprocess_config(parent_session)
child_config = parent_session.wait_for_next_event("debugpyAttach")
assert child_config == expected_child_config
parent_session.proceed()
@ -133,17 +138,7 @@ def test_multiprocessing(pyfile, target, run, start_method):
with child_session.start():
pass
expected_grandchild_config = dict(child_session.config)
expected_grandchild_config.pop("listen", None)
expected_grandchild_config.update(
{
"name": some.str,
"request": "attach",
"subProcessId": some.int,
"connect": {"host": some.str, "port": some.int},
}
)
expected_grandchild_config = expected_subprocess_config(child_session)
grandchild_config = child_session.wait_for_next_event("debugpyAttach")
assert grandchild_config == expected_grandchild_config
@ -200,21 +195,10 @@ def test_subprocess(pyfile, target, run, subProcess):
with run(parent_session, target(parent, args=[child])):
pass
expected_child_config = dict(parent_session.config)
for key in "processId", "listen", "preLaunchTask", "postDebugTask":
expected_child_config.pop(key, None)
expected_child_config.update(
{
"name": some.str,
"request": "attach",
"subProcessId": some.int,
"connect": {"host": some.str, "port": some.int},
}
)
if subProcess is False:
return
expected_child_config = expected_subprocess_config(parent_session)
child_config = parent_session.wait_for_next_event("debugpyAttach")
assert child_config == expected_child_config
parent_session.proceed()