Merge "Add --vscode-launch-file to gdbclient.py" into main
This commit is contained in:
@@ -15,11 +15,11 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
import adb
|
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
import posixpath
|
import posixpath
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
@@ -27,14 +27,17 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import textwrap
|
import textwrap
|
||||||
|
from typing import Any, BinaryIO
|
||||||
|
|
||||||
from typing import BinaryIO, Any
|
import adb
|
||||||
|
|
||||||
# Shared functions across gdbclient.py and ndk-gdb.py.
|
# Shared functions across gdbclient.py and ndk-gdb.py.
|
||||||
import gdbrunner
|
import gdbrunner
|
||||||
|
|
||||||
g_temp_dirs = []
|
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:
|
def read_toolchain_config(root: str) -> str:
|
||||||
"""Finds out current toolchain version."""
|
"""Finds out current toolchain version."""
|
||||||
@@ -106,6 +109,14 @@ def parse_args() -> argparse.Namespace:
|
|||||||
dest="vscode_launch_props",
|
dest="vscode_launch_props",
|
||||||
help=("JSON with extra properties to add to launch parameters when using " +
|
help=("JSON with extra properties to add to launch parameters when using " +
|
||||||
"vscode-lldb forwarding."))
|
"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(
|
parser.add_argument(
|
||||||
"--env", nargs=1, action="append", metavar="VAR=VALUE",
|
"--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)
|
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:
|
def do_main() -> None:
|
||||||
required_env = ["ANDROID_BUILD_TOP",
|
required_env = ["ANDROID_BUILD_TOP",
|
||||||
"ANDROID_PRODUCT_OUT", "TARGET_PRODUCT"]
|
"ANDROID_PRODUCT_OUT", "TARGET_PRODUCT"]
|
||||||
@@ -384,9 +472,17 @@ def do_main() -> None:
|
|||||||
vscode_launch_props = None
|
vscode_launch_props = None
|
||||||
if args.vscode_launch_props:
|
if args.vscode_launch_props:
|
||||||
if args.setup_forwarding != "vscode-lldb":
|
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_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:
|
with binary_file:
|
||||||
if sys.platform.startswith("linux"):
|
if sys.platform.startswith("linux"):
|
||||||
platform_name = "linux-x86"
|
platform_name = "linux-x86"
|
||||||
@@ -446,19 +542,25 @@ def do_main() -> None:
|
|||||||
# Start lldb.
|
# Start lldb.
|
||||||
gdbrunner.start_gdb(debugger_path, setup_commands, lldb=True)
|
gdbrunner.start_gdb(debugger_path, setup_commands, lldb=True)
|
||||||
else:
|
else:
|
||||||
print("")
|
if args.setup_forwarding == "vscode-lldb" and vscode_launch_file:
|
||||||
print(setup_commands)
|
write_vscode_config(pathlib.Path(vscode_launch_file) , setup_commands)
|
||||||
print("")
|
print(f"Generated config written to '{vscode_launch_file}'")
|
||||||
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."""))
|
|
||||||
else:
|
else:
|
||||||
print(textwrap.dedent("""
|
print("")
|
||||||
Paste the lldb commands above into the lldb frontend to set up the
|
print(setup_commands)
|
||||||
lldb-server connection. Press enter in this terminal once debugging is
|
print("")
|
||||||
finished to shut lldb-server down and close all the ports."""))
|
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("")
|
print("")
|
||||||
input("Press enter to shut down lldb-server")
|
input("Press enter to shut down lldb-server")
|
||||||
|
|
||||||
|
|||||||
@@ -14,13 +14,15 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
import gdbclient
|
|
||||||
import unittest
|
|
||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
|
import textwrap
|
||||||
|
import unittest
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
import gdbclient
|
||||||
|
|
||||||
|
|
||||||
class LaunchConfigMergeTest(unittest.TestCase):
|
class LaunchConfigMergeTest(unittest.TestCase):
|
||||||
def merge_compare(self, base: dict[str, Any], to_add: dict[str, Any] | None, expected: dict[str, Any]) -> None:
|
def merge_compare(self, base: dict[str, Any], to_add: dict[str, Any] | None, expected: dict[str, Any]) -> None:
|
||||||
actual = copy.deepcopy(base)
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main(verbosity=2)
|
unittest.main(verbosity=2)
|
||||||
|
|||||||
Reference in New Issue
Block a user