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).
162 lines
5.3 KiB
Python
Executable File
162 lines
5.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import argparse
|
|
import os
|
|
import re
|
|
import shlex
|
|
import subprocess
|
|
import sys
|
|
import textwrap
|
|
|
|
def get_relevant_bolt_changes(dir: str) -> str:
|
|
# Return a list of bolt source changes that are relevant to testing.
|
|
all_changes = subprocess.run(
|
|
shlex.split("git show HEAD --name-only --pretty=''"),
|
|
cwd=dir,
|
|
text=True,
|
|
stdout=subprocess.PIPE,
|
|
)
|
|
keep_bolt = subprocess.run(
|
|
shlex.split("grep '^bolt'"),
|
|
input=all_changes.stdout,
|
|
text=True,
|
|
stdout=subprocess.PIPE,
|
|
)
|
|
keep_relevant = subprocess.run(
|
|
shlex.split(
|
|
"grep -v -e '^bolt/docs' -e '^bolt/utils/docker' -e '^bolt/utils/dot2html'"
|
|
),
|
|
input=keep_bolt.stdout,
|
|
text=True,
|
|
stdout=subprocess.PIPE,
|
|
)
|
|
return keep_relevant.stdout
|
|
|
|
def get_git_ref_or_rev(dir: str) -> str:
|
|
# Run 'git symbolic-ref -q --short HEAD || git rev-parse --short HEAD'
|
|
cmd_ref = "git symbolic-ref -q --short HEAD"
|
|
ref = subprocess.run(
|
|
shlex.split(cmd_ref), cwd=dir, text=True, stdout=subprocess.PIPE
|
|
)
|
|
if not ref.returncode:
|
|
return ref.stdout.strip()
|
|
cmd_rev = "git rev-parse --short HEAD"
|
|
return subprocess.check_output(shlex.split(cmd_rev), cwd=dir, text=True).strip()
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description=textwrap.dedent(
|
|
"""
|
|
This script builds two versions of BOLT (with the current and
|
|
previous revision) and sets up symlink for llvm-bolt-wrapper.
|
|
Passes the options through to llvm-bolt-wrapper.
|
|
"""
|
|
)
|
|
)
|
|
parser.add_argument(
|
|
"build_dir",
|
|
nargs="?",
|
|
default=os.getcwd(),
|
|
help="Path to BOLT build directory, default is current " "directory",
|
|
)
|
|
parser.add_argument(
|
|
"--check-bolt-sources",
|
|
default=False,
|
|
action="store_true",
|
|
help="Create a marker file (.llvm-bolt.changes) if any relevant BOLT sources are modified",
|
|
)
|
|
parser.add_argument(
|
|
"--switch-back",
|
|
default=False,
|
|
action="store_true",
|
|
help="Checkout back to the starting revision",
|
|
)
|
|
parser.add_argument(
|
|
"--cmp-rev",
|
|
default="HEAD^",
|
|
help="Revision to checkout to compare vs HEAD",
|
|
)
|
|
args, wrapper_args = parser.parse_known_args()
|
|
bolt_path = f"{args.build_dir}/bin/llvm-bolt"
|
|
|
|
source_dir = None
|
|
# find the repo directory
|
|
with open(f"{args.build_dir}/CMakeCache.txt") as f:
|
|
for line in f:
|
|
m = re.match(r"LLVM_SOURCE_DIR:STATIC=(.*)", line)
|
|
if m:
|
|
source_dir = m.groups()[0]
|
|
if not source_dir:
|
|
sys.exit("Source directory is not found")
|
|
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
wrapper_path = f"{script_dir}/llvm-bolt-wrapper.py"
|
|
# build the current commit
|
|
subprocess.run(
|
|
shlex.split("cmake --build . --target llvm-bolt"), cwd=args.build_dir
|
|
)
|
|
# rename llvm-bolt
|
|
os.replace(bolt_path, f"{bolt_path}.new")
|
|
# memorize the old hash for logging
|
|
old_ref = get_git_ref_or_rev(source_dir)
|
|
|
|
if args.check_bolt_sources:
|
|
marker = f"{args.build_dir}/.llvm-bolt.changes"
|
|
if os.path.exists(marker):
|
|
os.remove(marker)
|
|
file_changes = get_relevant_bolt_changes(source_dir)
|
|
# Create a marker file if any relevant BOLT source files changed.
|
|
if len(file_changes) > 0:
|
|
print(f"BOLT source changes were found:\n{file_changes}")
|
|
open(marker, "a").close()
|
|
|
|
# determine whether a stash is needed
|
|
stash = subprocess.run(
|
|
shlex.split("git status --porcelain"),
|
|
cwd=source_dir,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True,
|
|
).stdout
|
|
if stash:
|
|
# save local changes before checkout
|
|
subprocess.run(shlex.split("git stash push -u"), cwd=source_dir)
|
|
# check out the previous/cmp commit
|
|
subprocess.run(shlex.split(f"git checkout -f {args.cmp_rev}"), cwd=source_dir)
|
|
# get the parent commit hash for logging
|
|
new_ref = get_git_ref_or_rev(source_dir)
|
|
# build the previous commit
|
|
subprocess.run(
|
|
shlex.split("cmake --build . --target llvm-bolt"), cwd=args.build_dir
|
|
)
|
|
# rename llvm-bolt
|
|
os.replace(bolt_path, f"{bolt_path}.old")
|
|
# set up llvm-bolt-wrapper.ini
|
|
ini = subprocess.check_output(
|
|
shlex.split(f"{wrapper_path} {bolt_path}.old {bolt_path}.new") + wrapper_args,
|
|
text=True,
|
|
)
|
|
with open(f"{args.build_dir}/bin/llvm-bolt-wrapper.ini", "w") as f:
|
|
f.write(ini)
|
|
# symlink llvm-bolt-wrapper
|
|
os.symlink(wrapper_path, bolt_path)
|
|
if args.switch_back:
|
|
if stash:
|
|
subprocess.run(shlex.split("git stash pop"), cwd=source_dir)
|
|
subprocess.run(shlex.split(f"git checkout {old_ref}"), cwd=source_dir)
|
|
else:
|
|
print(
|
|
f"The repository {source_dir} has been switched from {old_ref} "
|
|
f"to {new_ref}. Local changes were stashed. Switch back using\n\t"
|
|
f"git checkout {old_ref}\n"
|
|
)
|
|
print(
|
|
f"Build directory {args.build_dir} is ready to run BOLT tests, e.g.\n"
|
|
"\tbin/llvm-lit -sv tools/bolt/test\nor\n"
|
|
"\tbin/llvm-lit -sv tools/bolttests"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|