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).
130 lines
4.2 KiB
Python
Executable File
130 lines
4.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# Generates a version script for an architecture so that it can be incorporated
|
|
# into gcc_s.ver.
|
|
|
|
from collections import defaultdict
|
|
from itertools import chain
|
|
import argparse, subprocess, sys, os
|
|
|
|
|
|
def split_suffix(symbol):
|
|
"""
|
|
Splits a symbol such as `__gttf2@GCC_3.0` into a triple representing its
|
|
function name (__gttf2), version name (GCC_3.0), and version number (300).
|
|
|
|
The version number acts as a priority. Since earlier versions are more
|
|
accessible and are likely to be used more, the lower the number is, the higher
|
|
its priortiy. A symbol that has a '@@' instead of '@' has been designated by
|
|
the linker as the default symbol, and is awarded a priority of -1.
|
|
"""
|
|
if "@" not in symbol:
|
|
return None
|
|
data = [i for i in filter(lambda s: s, symbol.split("@"))]
|
|
_, version = data[-1].split("_")
|
|
version = version.replace(".", "")
|
|
priority = -1 if "@@" in symbol else int(version + "0" * (3 - len(version)))
|
|
return data[0], data[1], priority
|
|
|
|
|
|
def invert_mapping(symbol_map):
|
|
"""Transforms a map from Key->Value to Value->Key."""
|
|
store = defaultdict(list)
|
|
for symbol, (version, _) in symbol_map.items():
|
|
store[version].append(symbol)
|
|
result = []
|
|
for k, v in store.items():
|
|
v.sort()
|
|
result.append((k, v))
|
|
result.sort(key=lambda x: x[0])
|
|
return result
|
|
|
|
|
|
def intersection(llvm, gcc):
|
|
"""
|
|
Finds the intersection between the symbols extracted from compiler-rt.a/libunwind.a
|
|
and libgcc_s.so.1.
|
|
"""
|
|
common_symbols = {}
|
|
for i in gcc:
|
|
suffix_triple = split_suffix(i)
|
|
if not suffix_triple:
|
|
continue
|
|
|
|
symbol, version_name, version_number = suffix_triple
|
|
if symbol in llvm:
|
|
if symbol not in common_symbols:
|
|
common_symbols[symbol] = (version_name, version_number)
|
|
continue
|
|
if version_number < common_symbols[symbol][1]:
|
|
common_symbols[symbol] = (version_name, version_number)
|
|
return invert_mapping(common_symbols)
|
|
|
|
|
|
def find_function_names(path):
|
|
"""
|
|
Runs readelf on a binary and reduces to only defined functions. Equivalent to
|
|
`llvm-readelf --wide ${path} | grep 'FUNC' | grep -v 'UND' | awk '{print $8}'`.
|
|
"""
|
|
result = subprocess.run(args=["llvm-readelf", "-su", path], capture_output=True)
|
|
|
|
if result.returncode != 0:
|
|
print(result.stderr.decode("utf-8"), file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
stdout = result.stdout.decode("utf-8")
|
|
stdout = filter(lambda x: "FUNC" in x and "UND" not in x, stdout.split("\n"))
|
|
stdout = chain(map(lambda x: filter(None, x), (i.split(" ") for i in stdout)))
|
|
|
|
return [list(i)[7] for i in stdout]
|
|
|
|
|
|
def to_file(versioned_symbols):
|
|
path = f"{os.path.dirname(os.path.realpath(__file__))}/new-gcc_s-symbols"
|
|
with open(path, "w") as f:
|
|
f.write(
|
|
"Do not check this version script in: you should instead work "
|
|
"out which symbols are missing in `lib/gcc_s.ver` and then "
|
|
"integrate them into `lib/gcc_s.ver`. For more information, "
|
|
"please see `doc/LLVMLibgcc.rst`.\n"
|
|
)
|
|
for version, symbols in versioned_symbols:
|
|
f.write(f"{version} {{\n")
|
|
for i in symbols:
|
|
f.write(f" {i};\n")
|
|
f.write("};\n\n")
|
|
|
|
|
|
def read_args():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"--compiler_rt",
|
|
type=str,
|
|
help="Path to `libclang_rt.builtins-${ARCH}.a`.",
|
|
required=True,
|
|
)
|
|
parser.add_argument(
|
|
"--libunwind", type=str, help="Path to `libunwind.a`.", required=True
|
|
)
|
|
parser.add_argument(
|
|
"--libgcc_s",
|
|
type=str,
|
|
help="Path to `libgcc_s.so.1`. Note that unlike the other two arguments, this is a dynamic library.",
|
|
required=True,
|
|
)
|
|
return parser.parse_args()
|
|
|
|
|
|
def main():
|
|
args = read_args()
|
|
llvm = find_function_names(args.compiler_rt) + find_function_names(args.libunwind)
|
|
gcc = find_function_names(args.libgcc_s)
|
|
versioned_symbols = intersection(llvm, gcc)
|
|
# TODO(cjdb): work out a way to integrate new symbols in with the existing
|
|
# ones
|
|
to_file(versioned_symbols)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|