Fix #86: Shell expansion is not working in "args"

Propagate "args" to the launcher via command line rather than debug configuration JSON.

Parse "cwd" in the adapter, and apply it when spawning the launcher.
This commit is contained in:
Pavel Minaev 2020-04-21 19:24:29 -07:00 committed by Pavel Minaev
parent 58c743b496
commit 65168dd784
5 changed files with 62 additions and 60 deletions

View file

@ -272,22 +272,41 @@ class Client(components.Component):
return value
# Launcher doesn't use the command line at all, but we pass the arguments so
# that they show up in the terminal if we're using "runInTerminal".
# 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:
args = request("program", json.array(unicode, vectorize=True, size=(1,)))
elif "module" in request:
args = ["-m"] + request(
"module", json.array(unicode, vectorize=True, size=(1,))
program = request("program", unicode)
args = [program]
# process_name = program
if "module" in request:
module = request("module", unicode)
args = ["-m", module]
# process_name = module
if "code" in request:
code = request("code", json.array(unicode, vectorize=True, size=(1,)))
args = ["-c", "\n".join(code)]
# process_name = cmdline[0]
num_targets = len([x for x in (program, module, code) if x != ()])
if num_targets == 0:
raise request.isnt_valid(
'either "program", "module", or "code" must be specified'
)
elif "code" in request:
args = ["-c"] + request(
"code", json.array(unicode, vectorize=True, size=(1,))
elif num_targets != 1:
raise request.isnt_valid(
'"program", "module", and "code" are mutually exclusive'
)
else:
args = []
args += request("args", json.array(unicode))
cwd = request("cwd", unicode, optional=True)
if cwd == ():
# If it's not specified, but we're launching a file rather than a module,
# and the specified path has a directory in it, use that.
cwd = None if program == () else (os.path.dirname(program) or None)
console = request(
"console",
json.enum(
@ -307,7 +326,14 @@ class Client(components.Component):
servers.serve()
launchers.spawn_debuggee(
self.session, request, launcher_path, args, console, console_title, sudo
self.session,
request,
launcher_path,
args,
cwd,
console,
console_title,
sudo,
)
@_start_message_handler

View file

@ -66,7 +66,7 @@ class Launcher(components.Component):
def spawn_debuggee(
session, start_request, launcher_path, args, console, console_title, sudo
session, start_request, launcher_path, args, cwd, 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.
@ -119,6 +119,7 @@ def spawn_debuggee(
# the launcher also respects that.
subprocess.Popen(
cmdline,
cwd=cwd,
env=dict(list(os.environ.items()) + list(env.items())),
stdin=sys.stdin,
stdout=sys.stdout,
@ -130,16 +131,16 @@ def spawn_debuggee(
log.info('{0} spawning launcher via "runInTerminal" request.', session)
session.client.capabilities.require("supportsRunInTerminalRequest")
kinds = {"integratedTerminal": "integrated", "externalTerminal": "external"}
request_args = {
"kind": kinds[console],
"title": console_title,
"args": cmdline,
"env": env,
}
if cwd is not None:
request_args["cwd"] = cwd
try:
session.client.channel.request(
"runInTerminal",
{
"kind": kinds[console],
"title": console_title,
"args": cmdline,
"env": env,
},
)
session.client.channel.request("runInTerminal", request_args)
except messaging.MessageHandlingError as exc:
exc.propagate(start_request)

View file

@ -35,7 +35,7 @@ def main():
# 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:]
sys.argv = [sys.argv[0]] + 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.

View file

@ -31,13 +31,11 @@ def describe():
return fmt("Debuggee[PID={0}]", process.pid)
def spawn(process_name, cmdline, cwd, env, redirect_output):
def spawn(process_name, cmdline, env, redirect_output):
log.info(
"Spawning debuggee process:\n\n"
"Current directory: {0!r}\n\n"
"Command line: {1!r}\n\n"
"Environment variables: {2!r}\n\n",
cwd,
"Command line: {0!r}\n\n"
"Environment variables: {1!r}\n\n",
cmdline,
env,
)
@ -56,7 +54,7 @@ def spawn(process_name, cmdline, cwd, env, redirect_output):
try:
global process
process = subprocess.Popen(cmdline, cwd=cwd, env=env, bufsize=0, **kwargs)
process = subprocess.Popen(cmdline, env=env, bufsize=0, **kwargs)
except Exception as exc:
raise messaging.MessageHandlingError(
fmt("Couldn't spawn debuggee: {0}\n\nCommand line:{1!r}", exc, cmdline)

View file

@ -67,37 +67,14 @@ def launch_request(request):
debugpy_args = request("debugpyArgs", json.array(unicode))
cmdline += debugpy_args
program = module = code = ()
if "program" in request:
program = request("program", unicode)
cmdline += [program]
process_name = program
if "module" in request:
module = request("module", unicode)
cmdline += ["-m", module]
process_name = module
if "code" in request:
code = request("code", json.array(unicode, vectorize=True, size=(1,)))
cmdline += ["-c", "\n".join(code)]
process_name = cmdline[0]
# 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.
cmdline += sys.argv[1:]
num_targets = len([x for x in (program, module, code) if x != ()])
if num_targets == 0:
raise request.isnt_valid(
'either "program", "module", or "code" must be specified'
)
elif num_targets != 1:
raise request.isnt_valid(
'"program", "module", and "code" are mutually exclusive'
)
cmdline += request("args", json.array(unicode))
cwd = request("cwd", unicode, optional=True)
if cwd == ():
# If it's not specified, but we're launching a file rather than a module,
# and the specified path has a directory in it, use that.
cwd = None if program == () else (os.path.dirname(program[0]) or None)
# 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])
env = os.environ.copy()
env_changes = request("env", json.object(unicode))
@ -161,7 +138,7 @@ def launch_request(request):
cmdline = [encode(s) for s in cmdline]
env = {encode(k): encode(v) for k, v in env.items()}
debuggee.spawn(process_name, cmdline, cwd, env, redirect_output)
debuggee.spawn(process_name, cmdline, env, redirect_output)
return {}