Merge "Add --vscode-launch-file to gdbclient.py" into main
This commit is contained in:
@@ -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 subprocess
|
||||
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 @@ def parse_args() -> argparse.Namespace:
|
||||
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 @@ def generate_setup_script(sysroot: str, linker_search_dir: str | None, binary_na
|
||||
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 @@ def do_main() -> None:
|
||||
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 @@ def do_main() -> None:
|
||||
# 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")
|
||||
|
||||
|
||||
@@ -14,13 +14,15 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import gdbclient
|
||||
import unittest
|
||||
import copy
|
||||
import json
|
||||
|
||||
import textwrap
|
||||
import unittest
|
||||
from typing import Any
|
||||
|
||||
import gdbclient
|
||||
|
||||
|
||||
class LaunchConfigMergeTest(unittest.TestCase):
|
||||
def merge_compare(self, base: dict[str, Any], to_add: dict[str, Any] | None, expected: dict[str, Any]) -> None:
|
||||
actual = copy.deepcopy(base)
|
||||
@@ -161,5 +163,221 @@ class VsCodeLaunchGeneratorTest(unittest.TestCase):
|
||||
})
|
||||
|
||||
|
||||
class LaunchConfigInsertTest(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
# These tests can generate long diffs, so we remove the limit
|
||||
self.maxDiff = None
|
||||
|
||||
def test_insert_config(self) -> None:
|
||||
dst = textwrap.dedent("""\
|
||||
// #lldbclient-generated-begin
|
||||
// #lldbclient-generated-end""")
|
||||
to_insert = textwrap.dedent("""\
|
||||
foo
|
||||
bar""")
|
||||
self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst,
|
||||
to_insert),
|
||||
textwrap.dedent("""\
|
||||
// #lldbclient-generated-begin
|
||||
foo
|
||||
bar
|
||||
// #lldbclient-generated-end"""))
|
||||
|
||||
def test_insert_into_start(self) -> None:
|
||||
dst = textwrap.dedent("""\
|
||||
// #lldbclient-generated-begin
|
||||
// #lldbclient-generated-end
|
||||
more content""")
|
||||
to_insert = textwrap.dedent("""\
|
||||
foo
|
||||
bar""")
|
||||
self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst,
|
||||
to_insert),
|
||||
textwrap.dedent("""\
|
||||
// #lldbclient-generated-begin
|
||||
foo
|
||||
bar
|
||||
// #lldbclient-generated-end
|
||||
more content"""))
|
||||
|
||||
def test_insert_into_mid(self) -> None:
|
||||
dst = textwrap.dedent("""\
|
||||
start content
|
||||
// #lldbclient-generated-begin
|
||||
// #lldbclient-generated-end
|
||||
more content""")
|
||||
to_insert = textwrap.dedent("""\
|
||||
foo
|
||||
bar""")
|
||||
self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst,
|
||||
to_insert),
|
||||
textwrap.dedent("""\
|
||||
start content
|
||||
// #lldbclient-generated-begin
|
||||
foo
|
||||
bar
|
||||
// #lldbclient-generated-end
|
||||
more content"""))
|
||||
|
||||
def test_insert_into_end(self) -> None:
|
||||
dst = textwrap.dedent("""\
|
||||
start content
|
||||
// #lldbclient-generated-begin
|
||||
// #lldbclient-generated-end""")
|
||||
to_insert = textwrap.dedent("""\
|
||||
foo
|
||||
bar""")
|
||||
self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst,
|
||||
to_insert),
|
||||
textwrap.dedent("""\
|
||||
start content
|
||||
// #lldbclient-generated-begin
|
||||
foo
|
||||
bar
|
||||
// #lldbclient-generated-end"""))
|
||||
|
||||
def test_insert_twice(self) -> None:
|
||||
dst = textwrap.dedent("""\
|
||||
// #lldbclient-generated-begin
|
||||
// #lldbclient-generated-end
|
||||
// #lldbclient-generated-begin
|
||||
// #lldbclient-generated-end
|
||||
""")
|
||||
to_insert = 'foo'
|
||||
self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst,
|
||||
to_insert),
|
||||
textwrap.dedent("""\
|
||||
// #lldbclient-generated-begin
|
||||
foo
|
||||
// #lldbclient-generated-end
|
||||
// #lldbclient-generated-begin
|
||||
foo
|
||||
// #lldbclient-generated-end
|
||||
"""))
|
||||
|
||||
def test_preserve_space_indent(self) -> None:
|
||||
dst = textwrap.dedent("""\
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
// #lldbclient-generated-begin
|
||||
// #lldbclient-generated-end
|
||||
]
|
||||
}
|
||||
""")
|
||||
to_insert = textwrap.dedent("""\
|
||||
{
|
||||
"name": "(lldbclient.py) Attach test",
|
||||
"type": "lldb",
|
||||
"processCreateCommands": [
|
||||
"gdb-remote 123",
|
||||
"test"
|
||||
]
|
||||
}""")
|
||||
self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst,
|
||||
to_insert),
|
||||
textwrap.dedent("""\
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
// #lldbclient-generated-begin
|
||||
{
|
||||
"name": "(lldbclient.py) Attach test",
|
||||
"type": "lldb",
|
||||
"processCreateCommands": [
|
||||
"gdb-remote 123",
|
||||
"test"
|
||||
]
|
||||
}
|
||||
// #lldbclient-generated-end
|
||||
]
|
||||
}
|
||||
"""))
|
||||
|
||||
def test_preserve_tab_indent(self) -> None:
|
||||
dst = textwrap.dedent("""\
|
||||
{
|
||||
\t"version": "0.2.0",
|
||||
\t"configurations": [
|
||||
\t\t// #lldbclient-generated-begin
|
||||
\t\t// #lldbclient-generated-end
|
||||
\t]
|
||||
}
|
||||
""")
|
||||
to_insert = textwrap.dedent("""\
|
||||
{
|
||||
\t"name": "(lldbclient.py) Attach test",
|
||||
\t"type": "lldb",
|
||||
\t"processCreateCommands": [
|
||||
\t\t"gdb-remote 123",
|
||||
\t\t"test"
|
||||
\t]
|
||||
}""")
|
||||
self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst,
|
||||
to_insert),
|
||||
textwrap.dedent("""\
|
||||
{
|
||||
\t"version": "0.2.0",
|
||||
\t"configurations": [
|
||||
\t\t// #lldbclient-generated-begin
|
||||
\t\t{
|
||||
\t\t\t"name": "(lldbclient.py) Attach test",
|
||||
\t\t\t"type": "lldb",
|
||||
\t\t\t"processCreateCommands": [
|
||||
\t\t\t\t"gdb-remote 123",
|
||||
\t\t\t\t"test"
|
||||
\t\t\t]
|
||||
\t\t}
|
||||
\t\t// #lldbclient-generated-end
|
||||
\t]
|
||||
}
|
||||
"""))
|
||||
|
||||
def test_preserve_trailing_whitespace(self) -> None:
|
||||
dst = textwrap.dedent("""\
|
||||
// #lldbclient-generated-begin \t
|
||||
// #lldbclient-generated-end\t """)
|
||||
to_insert = 'foo'
|
||||
self.assertEqual(gdbclient.insert_commands_into_vscode_config(dst,
|
||||
to_insert),
|
||||
textwrap.dedent("""\
|
||||
// #lldbclient-generated-begin \t
|
||||
foo
|
||||
// #lldbclient-generated-end\t """))
|
||||
|
||||
def test_fail_if_no_begin(self) -> None:
|
||||
dst = textwrap.dedent("""\
|
||||
// #lldbclient-generated-end""")
|
||||
with self.assertRaisesRegex(ValueError, 'Did not find begin marker line'):
|
||||
gdbclient.insert_commands_into_vscode_config(dst, 'foo')
|
||||
|
||||
def test_fail_if_no_end(self) -> None:
|
||||
dst = textwrap.dedent("""\
|
||||
// #lldbclient-generated-begin""")
|
||||
with self.assertRaisesRegex(ValueError, 'Unterminated begin marker at line 1'):
|
||||
gdbclient.insert_commands_into_vscode_config(dst, 'foo')
|
||||
|
||||
def test_fail_if_begin_has_extra_text(self) -> None:
|
||||
dst = textwrap.dedent("""\
|
||||
// #lldbclient-generated-begin text
|
||||
// #lldbclient-generated-end""")
|
||||
with self.assertRaisesRegex(ValueError, 'Did not find begin marker line'):
|
||||
gdbclient.insert_commands_into_vscode_config(dst, 'foo')
|
||||
|
||||
def test_fail_if_end_has_extra_text(self) -> None:
|
||||
dst = textwrap.dedent("""\
|
||||
// #lldbclient-generated-begin
|
||||
// #lldbclient-generated-end text""")
|
||||
with self.assertRaisesRegex(ValueError, 'Unterminated begin marker at line 1'):
|
||||
gdbclient.insert_commands_into_vscode_config(dst, 'foo')
|
||||
|
||||
def test_fail_if_begin_end_swapped(self) -> None:
|
||||
dst = textwrap.dedent("""\
|
||||
// #lldbclient-generated-end
|
||||
// #lldbclient-generated-begin""")
|
||||
with self.assertRaisesRegex(ValueError, 'Unterminated begin marker at line 2'):
|
||||
gdbclient.insert_commands_into_vscode_config(dst, 'foo')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
|
||||
Reference in New Issue
Block a user