Add --vscode-launch-file to gdbclient.py
The new argument makes the generator write the VSCode launch.json config
into a file instead of stdout.
The generator uses marker lines to insert the config. This way the user
can control where in the file the launch config is written.
Test: atest gdbclient_test
Test: lldbclient.py --setup-forwarding vscode-lldb \
--vscode-launch-props= \
'{"internalConsoleOptions" : "openOnSessionStart"}' \
--vscode-launch-file=.vscode/launch.json -r test
Change-Id: I92b3f479b5ebcb722933938f52d0f23ff098beac
diff --git a/scripts/gdbclient.py b/scripts/gdbclient.py
index b053a48..f994278 100755
--- a/scripts/gdbclient.py
+++ b/scripts/gdbclient.py
@@ -15,11 +15,11 @@
# limitations under the License.
#
-import adb
import argparse
import json
import logging
import os
+import pathlib
import posixpath
import re
import shutil
@@ -27,14 +27,17 @@
import sys
import tempfile
import textwrap
+from typing import Any, BinaryIO
-from typing import BinaryIO, Any
-
+import adb
# Shared functions across gdbclient.py and ndk-gdb.py.
import gdbrunner
g_temp_dirs = []
+g_vscode_config_marker_begin = '// #lldbclient-generated-begin'
+g_vscode_config_marker_end = '// #lldbclient-generated-end'
+
def read_toolchain_config(root: str) -> str:
"""Finds out current toolchain version."""
@@ -106,6 +109,14 @@
dest="vscode_launch_props",
help=("JSON with extra properties to add to launch parameters when using " +
"vscode-lldb forwarding."))
+ parser.add_argument(
+ "--vscode-launch-file", default=None,
+ dest="vscode_launch_file",
+ help=textwrap.dedent(f"""Path to .vscode/launch.json file for the generated launch
+ config when using vscode-lldb forwarding. The file needs to
+ contain two marker lines: '{g_vscode_config_marker_begin}'
+ and '{g_vscode_config_marker_end}'. The config will be written inline
+ between these lines, replacing any text that is already there."""))
parser.add_argument(
"--env", nargs=1, action="append", metavar="VAR=VALUE",
@@ -351,6 +362,83 @@
raise Exception("Unknown debugger type " + debugger)
+def insert_commands_into_vscode_config(dst_launch_config: str, setup_commands: str) -> str:
+ """Inserts setup commands into launch config between two marker lines.
+ Marker lines are set in global variables g_vscode_config_marker_end and g_vscode_config_marker_end.
+ The commands are inserted with the same indentation as the first marker line.
+
+ Args:
+ dst_launch_config: Config to insert commands into.
+ setup_commands: Commands to insert.
+ Returns:
+ Config with inserted commands.
+ Raises:
+ ValueError if the begin marker is not found or not terminated with an end marker.
+ """
+
+ # We expect the files to be small (~10s KB), so we use simple string concatenation
+ # for simplicity and readability even if it is slower.
+ output = ""
+ found_at_least_one_begin = False
+ unterminated_begin_line = None
+
+ # It might be tempting to rewrite this using find() or even regexes,
+ # but keeping track of line numbers, preserving whitespace, and detecting indent
+ # becomes tricky enough that this simple loop is more clear.
+ for linenum, line in enumerate(dst_launch_config.splitlines(keepends=True), start=1):
+ if unterminated_begin_line != None:
+ if line.strip() == g_vscode_config_marker_end:
+ unterminated_begin_line = None
+ else:
+ continue
+ output += line
+ if line.strip() == g_vscode_config_marker_begin:
+ found_at_least_one_begin = True
+ unterminated_begin_line = linenum
+ marker_indent = line[:line.find(g_vscode_config_marker_begin)]
+ output += textwrap.indent(setup_commands, marker_indent) + '\n'
+
+ if not found_at_least_one_begin:
+ raise ValueError(f"Did not find begin marker line '{g_vscode_config_marker_begin}' " +
+ "in the VSCode launch file")
+
+ if unterminated_begin_line is not None:
+ raise ValueError(f"Unterminated begin marker at line {unterminated_begin_line} " +
+ f"in the VSCode launch file. Add end marker line to file: '{g_vscode_config_marker_end}'")
+
+ return output
+
+
+def replace_file_contents(dst_path: os.PathLike, contents: str) -> None:
+ """Replaces the contents of the file pointed to by dst_path.
+
+ This function writes the new contents into a temporary file, then atomically swaps it with
+ the target file. This way if a write fails, the original file is not overwritten.
+
+ Args:
+ dst_path: The path to the file to be replaced.
+ contents: The new contents of the file.
+ Raises:
+ Forwards exceptions from underlying filesystem methods.
+ """
+ tempf = tempfile.NamedTemporaryFile('w', delete=False)
+ try:
+ tempf.write(contents)
+ os.replace(tempf.name, dst_path)
+ except:
+ os.remove(tempf.name)
+ raise
+
+
+def write_vscode_config(vscode_launch_file: pathlib.Path, setup_commands: str) -> None:
+ """Writes setup_commands into the file pointed by vscode_launch_file.
+
+ See insert_commands_into_vscode_config for the description of how the setup commands are written.
+ """
+ contents = insert_commands_into_vscode_config(vscode_launch_file.read_text(), setup_commands)
+ replace_file_contents(vscode_launch_file, contents)
+
+
def do_main() -> None:
required_env = ["ANDROID_BUILD_TOP",
"ANDROID_PRODUCT_OUT", "TARGET_PRODUCT"]
@@ -384,9 +472,17 @@
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')
+ raise ValueError(
+ 'vscode-launch-props requires --setup-forwarding=vscode-lldb')
vscode_launch_props = json.loads(args.vscode_launch_props)
+ vscode_launch_file = None
+ if args.vscode_launch_file:
+ if args.setup_forwarding != "vscode-lldb":
+ raise ValueError(
+ 'vscode-launch-file requires --setup-forwarding=vscode-lldb')
+ vscode_launch_file = args.vscode_launch_file
+
with binary_file:
if sys.platform.startswith("linux"):
platform_name = "linux-x86"
@@ -446,19 +542,25 @@
# Start lldb.
gdbrunner.start_gdb(debugger_path, setup_commands, lldb=True)
else:
- print("")
- print(setup_commands)
- print("")
- if args.setup_forwarding == "vscode-lldb":
- print(textwrap.dedent("""
- Paste the above json into .vscode/launch.json and start the debugger as
- normal. Press enter in this terminal once debugging is finished to shut
- lldb-server down and close all the ports."""))
+ if args.setup_forwarding == "vscode-lldb" and vscode_launch_file:
+ write_vscode_config(pathlib.Path(vscode_launch_file) , setup_commands)
+ print(f"Generated config written to '{vscode_launch_file}'")
else:
- print(textwrap.dedent("""
- Paste the lldb commands above into the lldb frontend to set up the
- lldb-server connection. Press enter in this terminal once debugging is
- finished to shut lldb-server down and close all the ports."""))
+ print("")
+ print(setup_commands)
+ print("")
+ if args.setup_forwarding == "vscode-lldb":
+ print(textwrap.dedent("""
+ Paste the above json into .vscode/launch.json and start the debugger as
+ normal."""))
+ else:
+ print(textwrap.dedent("""
+ Paste the lldb commands above into the lldb frontend to set up the
+ lldb-server connection."""))
+
+ print(textwrap.dedent("""
+ Press enter in this terminal once debugging is finished to shut lldb-server
+ down and close all the ports."""))
print("")
input("Press enter to shut down lldb-server")