diff --git a/local/scripts/classify-cook-failure.py b/local/scripts/classify-cook-failure.py new file mode 100755 index 0000000000..a0afeb91c6 --- /dev/null +++ b/local/scripts/classify-cook-failure.py @@ -0,0 +1,365 @@ +#!/usr/bin/env python3 +"""Classify a failed `repo cook` output and suggest a fix. + +Per AGENTS.md "COMPLEX FIX CHECKLIST (v6.0-impl17)" §19.25, the Red Bear +OS cookbook build can fail in ~12 distinct, well-understood ways. +This script scans the tail of a build log and matches it against the +known failure patterns, then points the user at the documented fix. + +Usage: + repo cook kf6-kio 2>&1 | tee /tmp/build.log # capture the failure + classify-cook-failure.py /tmp/build.log # analyze the log + classify-cook-failure.py --last # analyze the last build log + +If the failure is not in the known list, the script falls back to +generic guidance (clear sysroot, re-fetch source, escalate to debug). +""" +import argparse +import re +import sys +from pathlib import Path + +PROJECT_ROOT = Path(__file__).resolve().parents[2] +LOG_ROOT = Path("/tmp") +COMMON_LOG_PATHS = [ + LOG_ROOT / "redbear-cook.log", + LOG_ROOT / "build.log", + LOG_ROOT / "cook.log", +] + + +def read_log(path: Path) -> str: + try: + return path.read_text(errors="replace") + except (OSError, UnicodeDecodeError) as e: + print(f"ERROR: cannot read {path}: {e}", file=sys.stderr) + sys.exit(1) + + +# Each rule: (name, regex_set, fix, references). The regex_set is a list +# of patterns; if ALL match, the rule fires. Fixes reference AGENTS.md +# §"COMPLEX FIX CHECKLIST (v6.0-impl17)" entry numbers where applicable. +# Rules are ordered most-specific-first. +RULES = [ + { + "name": "GLESv2 / Qt6Gui visibility", + "patterns": [ + r"(Could NOT find GLESv2|missing: GLESv2|HAVE_GLESv2.*Failed)", + ], + "fix": ( + "Qt6GuiConfig.cmake's find dependency(GLESv2) fails because the " + "ECM cross-toolchain sets -fvisibility=hidden but the " + "KDEFrameworkCompilerSettings doesn't add the matching " + "__attribute__((visibility(\"default\"))) to its export " + "macros. Add:\n" + " -DCMAKE_CXX_VISIBILITY_PRESET=default\n" + " -DGLESv2_LIBRARY=/lib/libGLESv2.so\n" + " -DGLESv2_INCLUDE_DIR=/include\n" + "(See kf6-kiconthemes/recipe.toml for the full pattern.)" + ), + "ref": "AGENTS.md §19.25 entry 6", + }, + { + "name": "KIconLoader undefined reference (visibility)", + "patterns": [ + r"undefined reference to .KIconLoader::", + ], + "fix": ( + "KIconLoader symbols are hidden by -fvisibility=hidden. Add:\n" + " -DCMAKE_CXX_VISIBILITY_PRESET=default" + ), + "ref": "AGENTS.md §19.25 entry 6 (kiconthemes fix)", + }, + { + "name": "qfloat16 linker error (libsoftfloat missing)", + "patterns": [ + r"undefined reference to .__(extendhfdf2|truncdfhf2)", + ], + "fix": ( + "Qt6 added qfloat16 (16-bit float) which uses compiler-rt " + "soft-float helpers that the relibc cross-toolchain doesn't " + "provide. libsoftfloat.a is already installed at\n" + " ~/.redoxer/x86_64-unknown-redox/toolchain/lib/libsoftfloat.a\n" + "but needs to be linked. Add:\n" + " -DCMAKE_SHARED_LINKER_FLAGS='-lsoftfloat'\n" + " -DCMAKE_EXE_LINKER_FLAGS='-lsoftfloat'" + ), + "ref": "AGENTS.md §19.25 entry 10", + }, + { + "name": "C++20 std::ranges not declared", + "patterns": [ + r"(std::ranges.*not been declared|has not been declared.*std::ranges)", + ], + "fix": ( + "KF6 6.26+ uses C++20 features. Add:\n" + " -DCMAKE_CXX_STANDARD=20\n" + " -DCMAKE_CXX_STANDARD_REQUIRED=ON" + ), + "ref": "AGENTS.md §19.25 entry 8", + }, + { + "name": "Qt6::GuiPrivate not found", + "patterns": [ + r"Could NOT find Qt6GuiPrivate", + r"find_package.*Qt6GuiPrivate.*not found", + ], + "fix": ( + "KF6 requires Qt6::GuiPrivate (e.g. for QGuiApplication " + "internals). The kf6-kimageformats / kf6-kconfigwidgets " + "recipes solve this by adding, after the find_package(Qt6Gui) " + "line in CMakeLists.txt:\n" + " find_package(Qt6GuiPrivate QUIET CONFIG)" + ), + "ref": "AGENTS.md §19.25 entry 6", + }, + { + "name": "PlasmaWaylandProtocols path-doubling bug", + "patterns": [ + r"PlasmaWaylandProtocols", + r"Could NOT find PlasmaWaylandProtocolsConfig.cmake", + ], + "fix": ( + "KF6 cross-build has a path-doubling bug for " + "PlasmaWaylandProtocols. The fix used by kf6-kguiaddons, " + "kf6-kwindowsystem, kf6-kidletime is:\n" + " -DWITH_WAYLAND=OFF (in that component's CMakeLists.txt)" + ), + "ref": "AGENTS.md §19.25 entry 5", + }, + { + "name": "ninja not found in sysroot", + "patterns": [ + r"ninja:.*No such file", + r"CMake Error.*ninja-build", + ], + "fix": ( + "The cookbook's cmake invocation uses ninja from the " + "host toolchain. Add:\n" + " -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja" + ), + "ref": "AGENTS.md §19.25 entry 7", + }, + { + "name": "kfilesystemtype static function collision", + "patterns": [ + r"determineFileSystemTypeImpl.*not declared", + r"two or more data types in declaration specifiers", + ], + "fix": ( + "kfilesystemtype.cpp uses static determineFileSystemTypeImpl " + "per-platform. Under CMAKE_SYSTEM_NAME=Linux (Redox's " + "toolchain fakes this), all 4 definitions are gated and a " + "recursive call to the same function fails. Stub the file:\n" + " see kf6-kcoreaddons/recipe.toml for the pattern" + ), + "ref": "AGENTS.md §19.25 entry 11", + }, + { + "name": "LibMount missing (kf6-kio)", + "patterns": [ + r"Could NOT find LibMount", + r"find_package.*LibMount.*not found", + ], + "fix": ( + "Redox has no libmount. In the affected recipe's CMakeLists.txt:\n" + " find_package(LibMount REQUIRED) → find_package(LibMount QUIET)\n" + " set(HAVE_LIB_MOUNT ${LibMount_FOUND}) → set(HAVE_LIB_MOUNT FALSE)" + ), + "ref": "AGENTS.md §19.25 entry 7ebffe9c2", + }, + { + "name": "kconfig stale sysroot (KF6CoreAddons version mismatch)", + "patterns": [ + r"(found unsuitable version|required is)", + ], + "fix": ( + "The per-recipe sysroot has a stale KF6CoreAddons from a " + "previous cook. Force a clean sysroot rebuild:\n" + " rm -rf local/recipes/kde//target/x86_64-unknown-redox/sysroot\n" + " repo cook # the cookbook will re-push fresh deps" + ), + "ref": "AGENTS.md §19.25 entry 9", + }, + { + "name": "libc.so.6 not found (relibc missing from sysroot)", + "patterns": [ + r"libc\.so\.6.*not found", + r"cannot find -lc\b", + ], + "fix": ( + "relibc stage.pkgar is missing from the per-recipe sysroot. " + "Same fix as stale sysroot:\n" + " rm -rf local/recipes/kde//target/x86_64-unknown-redox/sysroot\n" + " repo cook " + ), + "ref": "AGENTS.md §19.25 entry 9", + }, + { + "name": "gettext gnulib rebuild loop", + "patterns": [ + r"gettext-tools.*configure.*failed", + r"gettext.*HAVE_STDBOOL", + ], + "fix": ( + "gettext's gnulib tests for stdbool.h and search.h. Redox's " + "relibc doesn't have these yet. Restore the cached gettext " + "stage from the repo to short-circuit the rebuild:\n" + " cp repo/x86_64-unknown-redox/gettext.pkgar \\\n" + " recipes/tools/gettext/target/x86_64-unknown-redox/stage.pkgar\n" + " touch recipes/tools/gettext/target/x86_64-unknown-redox/stage.pkgar" + ), + "ref": "AGENTS.md §19.25 entry 11 (cascade workaround)", + }, + { + "name": "Python3 development headers missing", + "patterns": [ + r"Python3.*Development.*not found", + r"Python3_LIBRARIES.*Development", + ], + "fix": ( + "The kf6-kcmutils and kf6-syntaxhighlighting recipes need " + "Python3::Development. Disable the Python binding build:\n" + " -DBUILD_PYTHON_BINDINGS=OFF" + ), + "ref": "AGENTS.md §19.25 entry 11", + }, + { + "name": "cookbook_apply_patches: patch no longer applies", + "patterns": [ + r"(cookbook_apply_patches.*FAILED|ошибка применения изменений|patch failed.*does not apply)", + ], + "fix": ( + "An external patch in local/patches// no longer " + "applies to the current upstream. Run:\n" + " ./local/scripts/audit-patch-idempotency.py --component \n" + "to confirm. Re-generate the patch from a fresh checkout:\n" + " cd /tmp/audit-fresh && git clone src && cd src && git checkout \n" + " # apply your changes, then:\n" + " git diff > /local/patches//NN-fix.patch" + ), + "ref": "AGENTS.md §\"NO OVERLAY-STYLE PATCHES\" Rule 2", + }, + { + "name": "Package not found (missing dep)", + "patterns": [ + r"Package .*\bnot found\b", + r"failed to fetch.*has not been built", + ], + "fix": ( + "A dependency is referenced in [build].dependencies but " + "its package isn't in the repo. Check:\n" + " ls repo/x86_64-unknown-redox/.pkgar\n" + "If missing, cook the dep first:\n" + " repo cook " + ), + "ref": "AGENTS.md §19.25 entry 4", + }, + { + "name": "QVariant not declared in private header", + "patterns": [ + r"QString.*not declared.*qApp.*property", + r"qApp.*property.*toString.*QString", + ], + "fix": ( + "Upstream KF6 6.26+ added qApp->property().toString() in a " + "private header that doesn't include QVariant. The kf6-" + "kcolorscheme fix adds the include via python heredoc in the " + "recipe's [build].script:\n" + " python3 - <',\n" + " '#include \\n#include ') ..." + ), + "ref": "AGENTS.md §19.25 entry c6e9a46dd", + }, + { + "name": "fetch denied (protected recipe, --allow-protected missing)", + "patterns": [ + r"is not exist and unable to continue in offline mode", + r"protected recipe.*fetch disabled", + ], + "fix": ( + "sddm, relibc, kernel, base, bootloader, installer are " + "PROTECTED recipes. The cookbook won't fetch them in offline " + "mode. Use:\n" + " repo --allow-protected cook sddm\n" + "(or set REDBEAR_ALLOW_PROTECTED_FETCH=1 in the env)" + ), + "ref": "AGENTS.md §\"NO SILENT UPSTREAM PULLS\"", + }, +] + + +def classify(log: str) -> None: + matched = [] + for rule in RULES: + patterns = rule["patterns"] + if all(re.search(p, log) for p in patterns): + matched.append(rule) + + if not matched: + print("=" * 70) + print("FAILURE CLASSIFICATION: no known pattern matched.") + print("=" * 70) + print() + print("Generic guidance:") + print(" 1. Capture the full log: repo cook 2>&1 | tee /tmp/build.log") + print(" 2. Search for 'error:': grep -nE 'error:' /tmp/build.log | head -5") + print(" 3. Try a clean sysroot: rm -rf local/recipes/kde//target/x86_64-unknown-redox/sysroot") + print(" 4. Re-fetch source: rm -rf local/recipes/kde//source && repo fetch ") + print(" 5. Audit patches: ./local/scripts/audit-patch-idempotency.py") + print(" 6. As a last resort, --no-cache: ./local/scripts/build-redbear.sh redbear-full --no-cache") + print() + print("If the failure is novel, please add a new rule to") + print("classify-cook-failure.py so the next contributor benefits.") + return + + for rule in matched: + print("=" * 70) + print(f"FAILURE CLASSIFICATION: {rule['name']}") + print("=" * 70) + print() + print(rule["fix"]) + print() + if "ref" in rule: + print(f"Reference: {rule['ref']}") + print() + + +def main(): + parser = argparse.ArgumentParser( + description=( + "Classify a failed `repo cook` output and suggest a fix from " + "AGENTS.md 'COMPLEX FIX CHECKLIST (v6.0-impl17)'." + ) + ) + parser.add_argument( + "logfile", nargs="?", + help="Path to the build log. If omitted, --last is used.", + ) + parser.add_argument( + "--last", action="store_true", + help="Use the most recent /tmp/redbear-cook.log or /tmp/build.log", + ) + args = parser.parse_args() + + if args.logfile: + log_path = Path(args.logfile) + elif args.last: + for p in COMMON_LOG_PATHS: + if p.exists(): + log_path = p + break + else: + print(f"ERROR: none of {COMMON_LOG_PATHS} exist. Specify a logfile.", + file=sys.stderr) + sys.exit(1) + else: + parser.print_help() + sys.exit(0) + + log = read_log(log_path) + classify(log) + + +if __name__ == "__main__": + main()