Add --vscode-launch-props to gdbclient.py
The new argument allows the user to pass a JSON of properties to merge
into the generated launch.json config when setting up vscode-lldb
forwarding. This way the user can add pre-build tasks, extra init
commands etc.
Test: atest gdbclient_test
Test: lldbclient.py --setup-forwarding=vscode-lldb
--vscode-launch-props='{"sourceMap": {"test1": "test2"},
"postDebugTask": "Stop LLDB client", "processCreateCommands" :
["test"]}' -r test
Change-Id: I763dd15dde10421e86bc0a6ddfde974156ef1588
diff --git a/scripts/gdbclient.py b/scripts/gdbclient.py
index 9c602cc..ef32bf0 100755
--- a/scripts/gdbclient.py
+++ b/scripts/gdbclient.py
@@ -28,7 +28,7 @@
import tempfile
import textwrap
-from typing import BinaryIO
+from typing import BinaryIO, Any
# Shared functions across gdbclient.py and ndk-gdb.py.
import gdbrunner
@@ -98,8 +98,13 @@
choices=["lldb", "vscode-lldb"],
help=("Set up lldb-server and port forwarding. Prints commands or " +
".vscode/launch.json configuration needed to connect the debugging " +
- "client to the server. 'vscode' with llbd and 'vscode-lldb' both " +
+ "client to the server. 'vscode' with lldb and 'vscode-lldb' both " +
"require the 'vadimcn.vscode-lldb' extension."))
+ parser.add_argument(
+ "--vscode-launch-props", default=None,
+ dest="vscode_launch_props",
+ help=("JSON with extra properties to add to launch parameters when using " +
+ "vscode-lldb forwarding."))
parser.add_argument(
"--env", nargs=1, action="append", metavar="VAR=VALUE",
@@ -244,7 +249,49 @@
return (binary_file, pid, run_cmd)
-def generate_vscode_lldb_script(root: str, sysroot: str, binary_name: str, port: str | int, solib_search_path: list[str]) -> str:
+def merge_launch_dict(base: dict[str, Any], to_add: dict[str, Any] | None) -> None:
+ """Merges two dicts describing VSCode launch.json properties: base and
+ to_add. Base is modified in-place with items from to_add.
+ Items from to_add that are not present in base are inserted. Items that are
+ present are merged following these rules:
+ - Lists are merged with to_add elements appended to the end of base
+ list. Only a list can be merged with a list.
+ - dicts are merged recursively. Only a dict can be merged with a dict.
+ - Other present values in base get overwritten with values from to_add.
+
+ The reason for these rules is that merging in new values should prefer to
+ expand the existing set instead of overwriting where possible.
+ """
+ if to_add is None:
+ return
+
+ for key, val in to_add.items():
+ if key not in base:
+ base[key] = val
+ else:
+ if isinstance(base[key], list) and not isinstance(val, list):
+ raise ValueError(f'Cannot merge non-list into list at key={key}. ' +
+ 'You probably need to wrap your value into a list.')
+ if not isinstance(base[key], list) and isinstance(val, list):
+ raise ValueError(f'Cannot merge list into non-list at key={key}.')
+ if isinstance(base[key], dict) != isinstance(val, dict):
+ raise ValueError(f'Cannot merge dict and non-dict at key={key}')
+
+ # We don't allow the user to overwrite or interleave lists and don't allow
+ # to delete dict entries.
+ # It can be done but would make the implementation a bit more complicated
+ # and provides less value than adding elements.
+ # We expect that the config generated by gdbclient doesn't contain anything
+ # the user would want to remove.
+ if isinstance(base[key], list):
+ base[key] += val
+ elif isinstance(base[key], dict):
+ merge_launch_dict(base[key], val)
+ else:
+ base[key] = val
+
+
+def generate_vscode_lldb_script(root: str, sysroot: str, binary_name: str, port: str | int, solib_search_path: list[str], extra_props: dict[str, Any] | None) -> str:
# TODO It would be nice if we didn't need to copy this or run the
# lldbclient.py program manually. Doing this would probably require
# writing a vscode extension or modifying an existing one.
@@ -262,6 +309,7 @@
"target modules search-paths add / {}/".format(sysroot)],
"processCreateCommands": ["gdb-remote {}".format(str(port))]
}
+ merge_launch_dict(res, extra_props)
return json.dumps(res, indent=4)
def generate_lldb_script(root: str, sysroot: str, binary_name: str, port: str | int, solib_search_path: list[str]) -> str:
@@ -278,7 +326,7 @@
return '\n'.join(commands)
-def generate_setup_script(sysroot: str, linker_search_dir: str | None, binary_name: str, is64bit: bool, port: str | int, debugger: str) -> str:
+def generate_setup_script(sysroot: str, linker_search_dir: str | None, binary_name: str, is64bit: bool, port: str | int, debugger: str, vscode_launch_props: dict[str, Any] | None) -> str:
# Generate a setup script.
root = os.environ["ANDROID_BUILD_TOP"]
symbols_dir = os.path.join(sysroot, "system", "lib64" if is64bit else "lib")
@@ -294,7 +342,7 @@
if debugger == "vscode-lldb":
return generate_vscode_lldb_script(
- root, sysroot, binary_name, port, solib_search_path)
+ root, sysroot, binary_name, port, solib_search_path, vscode_launch_props)
elif debugger == 'lldb':
return generate_lldb_script(
root, sysroot, binary_name, port, solib_search_path)
@@ -332,6 +380,12 @@
# Fetch binary for -p, -n.
binary_file, pid, run_cmd = handle_switches(args, sysroot)
+ vscode_launch_props = None
+ if args.vscode_launch_props:
+ if args.setup_forwarding != "vscode-lldb":
+ raise ValueError('vscode_launch_props requires --setup-forwarding=vscode-lldb')
+ vscode_launch_props = json.loads(args.vscode_launch_props)
+
with binary_file:
if sys.platform.startswith("linux"):
platform_name = "linux-x86"
@@ -381,7 +435,8 @@
binary_name=binary_file.name,
is64bit=is64bit,
port=args.port,
- debugger=debugger)
+ debugger=debugger,
+ vscode_launch_props=vscode_launch_props)
if not args.setup_forwarding:
# Print a newline to separate our messages from the GDB session.