mirror of
https://github.com/nyakang/nyaterm.git
synced 2026-11-06 16:07:55 +00:00
- Added a new GitHub Actions workflow for Windows diagnostics, allowing manual execution with specified source references and toolchains. - Implemented a Python script to apply diagnostic instrumentation to the source code, ensuring compatibility with existing releases. - Created a packaging script to build a frozen Windows x64 source with symbols and bounded panic probes, retaining necessary files for review. - Developed diagnostic metadata and breadcrumb tracking for improved debugging and analysis of application behavior. - Included unit tests to validate the functionality of the diagnostic packaging and instrumentation processes.
108 lines
5.5 KiB
Python
108 lines
5.5 KiB
Python
"""Apply reviewable diagnostic instrumentation to an isolated source checkout."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
|
|
TEMPLATES = Path(__file__).with_name("diagnostic")
|
|
|
|
|
|
def replace_once(text: str, old: str, new: str, label: str) -> str:
|
|
if text.count(old) != 1:
|
|
raise ValueError(f"diagnostic overlay anchor changed: {label}")
|
|
return text.replace(old, new, 1)
|
|
|
|
|
|
def enter_function(text: str, name: str, statement: str) -> str:
|
|
pattern = rf"(\bfn {re.escape(name)}\([^{{}}]*?\)(?:\s*->[^{{]+)?\s*\{{\n)"
|
|
text, count = re.subn(pattern, lambda match: match[0] + f" {statement}\n", text)
|
|
if count != 1:
|
|
raise ValueError(f"diagnostic function anchor changed: {name} ({count})")
|
|
return text
|
|
|
|
|
|
def record(stage: str) -> str:
|
|
return f"nyaterm_core::diagnostic_probe::record(nyaterm_core::diagnostic_probe::Stage::{stage});"
|
|
|
|
|
|
def apply_overlay(source: Path) -> list[str]:
|
|
# Prepare every edit before touching the checkout. An unfamiliar source
|
|
# revision must fail closed instead of producing partial instrumentation.
|
|
changes: dict[str, str] = {}
|
|
|
|
def read(path: str) -> str:
|
|
return changes[path] if path in changes else (source / path).read_text(encoding="utf-8")
|
|
|
|
def replace(path: str, old: str, new: str) -> None:
|
|
changes[path] = replace_once(read(path), old, new, path)
|
|
|
|
def enter(path: str, function: str, stage: str) -> None:
|
|
changes[path] = enter_function(read(path), function, record(stage))
|
|
|
|
replace("crates/nyaterm-core/src/lib.rs", "pub mod diagnostics;",
|
|
"pub mod diagnostics;\npub mod diagnostic_probe;")
|
|
replace("crates/nyaterm-app/src/lib.rs", "pub mod crash_report;",
|
|
"pub mod crash_report;\npub mod diagnostic_build;")
|
|
replace("crates/nyaterm-app/src/main.rs", "fn main() -> anyhow::Result<()> {",
|
|
"fn main() -> anyhow::Result<()> {\n"
|
|
" if nyaterm_app::diagnostic_build::run_self_test_if_requested()? {\n"
|
|
" return Ok(());\n }")
|
|
replace("crates/nyaterm-app/src/crash_report.rs",
|
|
' writeln!(writer, "Version: {}", env!("CARGO_PKG_VERSION"))?;',
|
|
' writeln!(writer, "Version: {}", env!("CARGO_PKG_VERSION"))?;\n'
|
|
" crate::diagnostic_build::write_metadata(writer)?;")
|
|
replace("crates/nyaterm-app/src/crash_report.rs",
|
|
' writeln!(writer, "\\nMessage:\\n{message}\\n\\nBacktrace:")?;',
|
|
' writeln!(writer, "\\nMessage:\\n{message}")?;\n'
|
|
" nyaterm_core::diagnostic_probe::write_snapshot(writer)?;\n"
|
|
' writeln!(writer, "\\nBacktrace:")?;')
|
|
|
|
editor = "crates/nyaterm-desktop/src/features/connections/connection_runtime/editor.rs"
|
|
enter(editor, "save_connection_editor", "SaveRequested")
|
|
enter(editor, "save_editor_connection", "SaveJobStarted")
|
|
enter(editor, "close_connection_editor", "EditorClosing")
|
|
replace(editor, " store.load_sessions()\n}",
|
|
" let result = store.load_sessions();\n"
|
|
f" if result.is_ok() {{ {record('SaveJobSucceeded')} }}\n result\n}}")
|
|
replace(editor, " Ok(Ok(sessions)) => {",
|
|
" Ok(Ok(sessions)) => {\n " + record("SaveApplied"))
|
|
window = "crates/nyaterm-desktop/src/features/connections/connection_runtime/window.rs"
|
|
replace(window, "window.defer(cx, |window, _| window.remove_window());",
|
|
"window.defer(cx, |window, _| {\n "
|
|
+ record("EditorWindowRemoving") + "\n window.remove_window();\n });")
|
|
enter("crates/nyaterm-desktop/src/features/session/session_runtime/background.rs",
|
|
"begin_background_session_start", "SessionStartRequested")
|
|
|
|
transport = "crates/nyaterm-transport/src/lib.rs"
|
|
replace(transport, " let pair = pty_system\n .openpty(local_pty_size(",
|
|
" " + record("LocalPtyOpening")
|
|
+ "\n let pair = pty_system\n .openpty(local_pty_size(")
|
|
# The marker is reached only after spawn_command returned successfully.
|
|
replace(transport, " drop(pair.slave);",
|
|
" " + record("LocalShellSpawned") + "\n drop(pair.slave);")
|
|
|
|
element = "crates/nyaterm-terminal-gpui/src/element/mod.rs"
|
|
enter(element, "request_layout", "TerminalLayout")
|
|
enter(element, "prepaint", "TerminalPrepaint")
|
|
enter(element, "paint", "TerminalPaint")
|
|
changes[element] = enter_function(read(element), "prepaint",
|
|
"nyaterm_core::diagnostic_probe::geometry(self.font_size, self.cell_width, "
|
|
"self.cell_height, window.scale_factor());")
|
|
shape_pattern = r"(?m)^( +)(let line = Arc::new\(window\.text_system\(\)\.shape_line\([\s\S]*?^\1\)\);)"
|
|
changes[element], shape_count = re.subn(shape_pattern, lambda match:
|
|
match[1] + record("TerminalShape") + "\n" + match[1] + match[2]
|
|
+ "\n" + match[1] + record("TerminalShaped"), read(element))
|
|
if shape_count != 3:
|
|
raise ValueError(f"diagnostic shape anchors changed: expected 3, got {shape_count}")
|
|
|
|
for name, crate in [("diagnostic_probe", "nyaterm-core"), ("diagnostic_build", "nyaterm-app")]:
|
|
destination = f"crates/{crate}/src/{name}.rs"
|
|
if (source / destination).exists():
|
|
raise ValueError(f"diagnostic overlay already exists: {destination}")
|
|
changes[destination] = (TEMPLATES / f"{name}.rs").read_text(encoding="utf-8")
|
|
for path, content in changes.items():
|
|
(source / path).write_text(content, encoding="utf-8", newline="\n")
|
|
return list(changes)
|