cb424d7448
verify-patch-sanity.py validates every active recipe .patch has internally- consistent hunk line counts — catching the 'malformed patch at line N' failure at commit/CI/preflight time instead of hours into a cook. This cycle hit that class three times (qtwaylandscanner, sddm, xwayland), each only discovered when cookbook tried to apply the patch. Running it across the repo found 29 latent malformed patches (validated against GNU patch: e.g. relibc/P3-sysv-ipc reproduces 'malformed patch at line 22'). They were harmless only because they sit in vendored recipes (baked, not re- applied) — but would fail on any version-bump re-derivation. --fix recounts the hunk headers (body untouched) and repaired all 29. Wired into build-preflight.sh (Phase 1.0D) and redbear-ci.yml, with a unit test (test-patch-sanity.sh). Skips archived/legacy trees and unvalidatable formats (empty placeholders, bare-@@ git hunks).
251 lines
8.3 KiB
Python
251 lines
8.3 KiB
Python
##===-- sourcewin.py -----------------------------------------*- Python -*-===##
|
|
##
|
|
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
# See https://llvm.org/LICENSE.txt for license information.
|
|
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
##
|
|
##===----------------------------------------------------------------------===##
|
|
|
|
import cui
|
|
import curses
|
|
import lldb
|
|
import lldbutil
|
|
import re
|
|
import os
|
|
|
|
|
|
class SourceWin(cui.TitledWin):
|
|
def __init__(self, driver, x, y, w, h):
|
|
super(SourceWin, self).__init__(x, y, w, h, "Source")
|
|
self.sourceman = driver.getSourceManager()
|
|
self.sources = {}
|
|
|
|
self.filename = None
|
|
self.pc_line = None
|
|
self.viewline = 0
|
|
|
|
self.breakpoints = {}
|
|
|
|
self.win.scrollok(1)
|
|
|
|
self.markerPC = ":) "
|
|
self.markerBP = "B> "
|
|
self.markerNone = " "
|
|
|
|
try:
|
|
from pygments.formatters import TerminalFormatter
|
|
|
|
self.formatter = TerminalFormatter()
|
|
except ImportError:
|
|
# self.win.addstr("\nWarning: no 'pygments' library found. Syntax highlighting is disabled.")
|
|
self.lexer = None
|
|
self.formatter = None
|
|
pass
|
|
|
|
# FIXME: syntax highlight broken
|
|
self.formatter = None
|
|
self.lexer = None
|
|
|
|
def handleEvent(self, event):
|
|
if isinstance(event, int):
|
|
self.handleKey(event)
|
|
return
|
|
|
|
if isinstance(event, lldb.SBEvent):
|
|
if lldb.SBBreakpoint.EventIsBreakpointEvent(event):
|
|
self.handleBPEvent(event)
|
|
|
|
if lldb.SBProcess.EventIsProcessEvent(
|
|
event
|
|
) and not lldb.SBProcess.GetRestartedFromEvent(event):
|
|
process = lldb.SBProcess.GetProcessFromEvent(event)
|
|
if not process.IsValid():
|
|
return
|
|
if process.GetState() == lldb.eStateStopped:
|
|
self.refreshSource(process)
|
|
elif process.GetState() == lldb.eStateExited:
|
|
self.notifyExited(process)
|
|
|
|
def notifyExited(self, process):
|
|
self.win.erase()
|
|
target = lldbutil.get_description(process.GetTarget())
|
|
pid = process.GetProcessID()
|
|
ec = process.GetExitStatus()
|
|
self.win.addstr(
|
|
"\nProcess %s [%d] has exited with exit-code %d" % (target, pid, ec)
|
|
)
|
|
|
|
def pageUp(self):
|
|
if self.viewline > 0:
|
|
self.viewline = self.viewline - 1
|
|
self.refreshSource()
|
|
|
|
def pageDown(self):
|
|
if self.viewline < len(self.content) - self.height + 1:
|
|
self.viewline = self.viewline + 1
|
|
self.refreshSource()
|
|
pass
|
|
|
|
def handleKey(self, key):
|
|
if key == curses.KEY_DOWN:
|
|
self.pageDown()
|
|
elif key == curses.KEY_UP:
|
|
self.pageUp()
|
|
|
|
def updateViewline(self):
|
|
half = self.height / 2
|
|
if self.pc_line < half:
|
|
self.viewline = 0
|
|
else:
|
|
self.viewline = self.pc_line - half + 1
|
|
|
|
if self.viewline < 0:
|
|
raise Exception(
|
|
"negative viewline: pc=%d viewline=%d" % (self.pc_line, self.viewline)
|
|
)
|
|
|
|
def refreshSource(self, process=None):
|
|
(self.height, self.width) = self.win.getmaxyx()
|
|
|
|
if process is not None:
|
|
loc = process.GetSelectedThread().GetSelectedFrame().GetLineEntry()
|
|
f = loc.GetFileSpec()
|
|
self.pc_line = loc.GetLine()
|
|
|
|
if not f.IsValid():
|
|
self.win.addstr(0, 0, "Invalid source file")
|
|
return
|
|
|
|
self.filename = f.GetFilename()
|
|
path = os.path.join(f.GetDirectory(), self.filename)
|
|
self.setTitle(path)
|
|
self.content = self.getContent(path)
|
|
self.updateViewline()
|
|
|
|
if self.filename is None:
|
|
return
|
|
|
|
if self.formatter is not None:
|
|
from pygments.lexers import get_lexer_for_filename
|
|
|
|
self.lexer = get_lexer_for_filename(self.filename)
|
|
|
|
bps = (
|
|
[]
|
|
if not self.filename in self.breakpoints
|
|
else self.breakpoints[self.filename]
|
|
)
|
|
self.win.erase()
|
|
if self.content:
|
|
self.formatContent(self.content, self.pc_line, bps)
|
|
|
|
def getContent(self, path):
|
|
content = []
|
|
if path in self.sources:
|
|
content = self.sources[path]
|
|
else:
|
|
if os.path.exists(path):
|
|
with open(path) as x:
|
|
content = x.readlines()
|
|
self.sources[path] = content
|
|
return content
|
|
|
|
def formatContent(self, content, pc_line, breakpoints):
|
|
source = ""
|
|
count = 1
|
|
self.win.erase()
|
|
end = min(len(content), self.viewline + self.height)
|
|
for i in range(self.viewline, end):
|
|
line_num = i + 1
|
|
marker = self.markerNone
|
|
attr = curses.A_NORMAL
|
|
if line_num == pc_line:
|
|
attr = curses.A_REVERSE
|
|
if line_num in breakpoints:
|
|
marker = self.markerBP
|
|
line = "%s%3d %s" % (marker, line_num, self.highlight(content[i]))
|
|
if len(line) >= self.width:
|
|
line = line[0 : self.width - 1] + "\n"
|
|
self.win.addstr(line, attr)
|
|
source += line
|
|
count = count + 1
|
|
return source
|
|
|
|
def highlight(self, source):
|
|
if self.lexer and self.formatter:
|
|
from pygments import highlight
|
|
|
|
return highlight(source, self.lexer, self.formatter)
|
|
else:
|
|
return source
|
|
|
|
def addBPLocations(self, locations):
|
|
for path in locations:
|
|
lines = locations[path]
|
|
if path in self.breakpoints:
|
|
self.breakpoints[path].update(lines)
|
|
else:
|
|
self.breakpoints[path] = lines
|
|
|
|
def removeBPLocations(self, locations):
|
|
for path in locations:
|
|
lines = locations[path]
|
|
if path in self.breakpoints:
|
|
self.breakpoints[path].difference_update(lines)
|
|
else:
|
|
raise "Removing locations that were never added...no good"
|
|
|
|
def handleBPEvent(self, event):
|
|
def getLocations(event):
|
|
locs = {}
|
|
|
|
bp = lldb.SBBreakpoint.GetBreakpointFromEvent(event)
|
|
|
|
if bp.IsInternal():
|
|
# don't show anything for internal breakpoints
|
|
return
|
|
|
|
for location in bp:
|
|
# hack! getting the LineEntry via SBBreakpointLocation.GetAddress.GetLineEntry does not work good for
|
|
# inlined frames, so we get the description (which does take
|
|
# into account inlined functions) and parse it.
|
|
desc = lldbutil.get_description(location, lldb.eDescriptionLevelFull)
|
|
match = re.search(r"at\ ([^:]+):([\d]+)", desc)
|
|
try:
|
|
path = match.group(1)
|
|
line = int(match.group(2).strip())
|
|
except ValueError as e:
|
|
# bp loc unparsable
|
|
continue
|
|
|
|
if path in locs:
|
|
locs[path].add(line)
|
|
else:
|
|
locs[path] = set([line])
|
|
return locs
|
|
|
|
event_type = lldb.SBBreakpoint.GetBreakpointEventTypeFromEvent(event)
|
|
if (
|
|
event_type == lldb.eBreakpointEventTypeEnabled
|
|
or event_type == lldb.eBreakpointEventTypeAdded
|
|
or event_type == lldb.eBreakpointEventTypeLocationsResolved
|
|
or event_type == lldb.eBreakpointEventTypeLocationsAdded
|
|
):
|
|
self.addBPLocations(getLocations(event))
|
|
elif (
|
|
event_type == lldb.eBreakpointEventTypeRemoved
|
|
or event_type == lldb.eBreakpointEventTypeLocationsRemoved
|
|
or event_type == lldb.eBreakpointEventTypeDisabled
|
|
):
|
|
self.removeBPLocations(getLocations(event))
|
|
elif (
|
|
event_type == lldb.eBreakpointEventTypeCommandChanged
|
|
or event_type == lldb.eBreakpointEventTypeConditionChanged
|
|
or event_type == lldb.eBreakpointEventTypeIgnoreChanged
|
|
or event_type == lldb.eBreakpointEventTypeThreadChanged
|
|
or event_type == lldb.eBreakpointEventTypeInvalidType
|
|
):
|
|
# no-op
|
|
pass
|
|
self.refreshSource()
|