Files
RedBear-OS/local/scripts/validate-source-trees.py
T
vasilito fd11aa716c
redbear-ci / check (push) Has been cancelled
feat: port the missing KDE Plasma dependency chain and harden the auth stack
Unblocks plasma-workspace/plasma-desktop, which required 14 packages that had
no in-tree recipe. Sources are reproducible from each recipe's tar= + blake3;
the vendored source/ trees are deliberately not committed here (212M).

New recipes:
  KF6 6.28.0   kf6-kholidays, kf6-krunner, kf6-kstatusnotifieritem,
               kf6-kunitconversion
  Plasma 6.7.2 knighttime, layer-shell-qt, libkscreen, libksysguard,
               plasma-activities-stats, plasma5support, kscreenlocker
  Qt 6.11.1    qtpositioning, qtspeech, qttools

libksysguard carries P0-redox-process-backend.patch: processes_local_p.cpp
dispatches on platform macros and had no __redox__ arm, so ProcessesLocal was
entirely undefined. Adds a real backend reading /scheme/proc/ps (pid, ppid,
real+effective ids, thread count, state) and /scheme/sys/mem, with kill() for
signals. Upstream's generic fallback is a pure stub and was not used. Absent
facilities (no setpriority/sched_setscheduler/ioprio on a microkernel) report
NotSupported rather than pretending.

Restored, no longer disabled:
  - night colour: kcms/nighttime + kwin's nightlight plugin, now that
    KNightTime/Qt6Positioning/KF6Holidays exist
  - KIO FileWidgets (file dialog, places model), wrongly swept in with the
    unportable kiod/kssld/kioworkers subdirs
  - kf6-ktexteditor text-to-speech: removes a disguised stub that rewrote
    speechEngine() to return nullptr and mangled call sites into invalid C++
  - kwin KWIN_BUILD_SCREENLOCKER=ON (needs kscreenlocker; see below)

Toolchain and recipe fixes:
  - redox-toolchain.cmake: append -lgcc. __extendhfsf2/__extendbfsf2 are
    global in libgcc.a but hidden in libgcc_s.so (the Redox libgcc version map
    stops at GCC_7.0.0 and never emits the GCC_12/13 nodes upstream exports),
    and GCC omits -lgcc for -shared, so no C++23 shared library using
    std::format<_Float16|bfloat16_t> could link.
  - qtmultimedia: rewrite /usr/src/.../meta_types paths and stage metatypes,
    so consumers using qt_internal_add_qml_module can configure.
  - qtspeech: qtmultimedia is mandatory, not optional -- upstream return()s
    with only a NOTICE without it, yielding a green build over an empty
    package. Asserts its own output so that cannot recur.
  - narrow Linux-only guards with AND NOT REDOX where the toolchain sets
    CMAKE_SYSTEM_NAME=Linux purely to get UNIX=TRUE (kio LibMount,
    libksysguard NL/Sensors, plasma-workspace NetworkManagerQt).
  - drop REQUIRED components that are declared but referenced nowhere
    (Location, QCoro6, Qt6 Test) -- verified by exhaustive grep.
  - DESTDIR installs where KDE emits absolute KDE_INSTALL_FULL_* paths that
    --prefix cannot re-root, which otherwise write into the build host.

Auth stack:
  - pam-redbear: add PAM_MODULE_UNKNOWN (28), missing from both the header and
    lib.rs; realign 30/31 to Linux-PAM's PAM_CONV_AGAIN/PAM_INCOMPLETE, which
    previously held Red Bear-only names on standard values, so anything built
    against stock PAM headers mis-decoded them.
  - redbear-authd: support yescrypt ($y$ -- the default shadow format on
    current Debian/Ubuntu/Fedora, previously an unexplained login failure),
    bcrypt and md5-crypt. Plaintext shadow entries now require the
    /etc/redbear/allow-plaintext-passwords sentinel and warn on every use,
    instead of being compared silently. 19/19 host tests pass.

Build-system hardening:
  - verify-tracked-sources.sh, wired into preflight: fails on deletion of any
    tracked vendored source, and on modifications with no baseline entry.
    local/sources/ had a dirty gate and version checks; local/recipes/*/source/
    had none, which allowed an rm -rf to delete 7958 tracked files and a
    pristine re-extract to silently overwrite committed fixes (kwin's
    std::expected/vulkan-hpp and X11 gating, kio's Q_OS_REDOX resolver guards).
  - validate-source-trees.py: resolve recipe.toml through the overlay symlink;
    it reported false MISSING for symlinked recipes.
  - test-sddm-virgl-qemu.sh: the null-proxy check used report(), which PASSES
    on regex match -- so the known Qt6 Wayland null+8 fault would report PASS
    when present. Added report_absent().

kscreenlocker builds only its cmake-level Wayland-only changes so far; the
C++ X11 removal (61 call sites) is not yet applied, so kwin's screenlocker
flag is blocked on it.
2026-08-04 10:06:39 +03:00

149 lines
5.3 KiB
Python
Executable File

#!/usr/bin/env python3
"""Validate that all source trees required by a build config exist."""
import argparse
import sys
import tomllib
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
META_PACKAGES = {"libgcc", "libstdcxx"}
def build_lookup():
lookup = {}
for root in (PROJECT_ROOT / "recipes", PROJECT_ROOT / "local/recipes"):
for recipe_toml in root.rglob("recipe.toml"):
parts = recipe_toml.parts
if "source" in parts or "target" in parts:
continue
package_name = recipe_toml.parent.name
if package_name not in lookup:
# Resolve through the overlay symlink. `recipes/<cat>/<name>` is
# frequently a symlink (whole-dir) or contains a symlinked
# recipe.toml pointing into `local/recipes/<cat>/<name>`, which is
# where the vendored `source/` tree actually lives. Recording the
# unresolved parent made the source lookup miss it and report a
# false MISSING (e.g. xwayland).
lookup[package_name] = recipe_toml.resolve().parent
return lookup
def resolve_config(config_path: Path, visited=None):
if visited is None:
visited = set()
config_path = config_path.resolve()
if config_path in visited:
return {}
visited.add(config_path)
with open(config_path, "rb") as config_file:
config = tomllib.load(config_file)
packages = dict(config.get("packages", {}))
for include in config.get("include", []):
include_path = config_path.parent / include
if include_path.exists():
included_packages = resolve_config(include_path, visited)
for package_name, package_value in packages.items():
included_packages[package_name] = package_value
packages = included_packages
return packages
def recipe_restore_path(recipe_dir: Path):
recipes_root = PROJECT_ROOT / "recipes"
try:
return recipe_dir.relative_to(recipes_root).as_posix()
except ValueError:
return None
def same_as_source_dir(recipe_dir: Path):
recipe_file = recipe_dir / "recipe.toml"
if not recipe_file.exists():
return None
with open(recipe_file, "rb") as handle:
recipe = tomllib.load(handle)
source = recipe.get("source")
if not isinstance(source, dict):
return None
same_as = source.get("same_as")
if not isinstance(same_as, str) or not same_as:
return None
return (recipe_dir / same_as).resolve() / "source"
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("config", nargs="?", default="redbear-full")
parser.add_argument("--extra-package", action="append", default=[])
parser.add_argument("--missing-paths-only", action="store_true")
parser.add_argument("--release", default="")
return parser.parse_args()
def main():
args = parse_args()
config_path = PROJECT_ROOT / "config" / f"{args.config}.toml"
if not config_path.exists():
print(f"Config not found: {config_path}", file=sys.stderr)
return 1
lookup = build_lookup()
packages = resolve_config(config_path)
requested_packages = dict(packages)
for package_name in args.extra_package:
requested_packages.setdefault(package_name, {})
missing_recipe_paths = []
missing_entries = []
present = 0
for package_name, package_conf in sorted(requested_packages.items()):
if str(package_conf) == "ignore" or package_name in META_PACKAGES:
continue
recipe_dir = lookup.get(package_name)
if recipe_dir is None:
missing_entries.append((package_name, None))
continue
source_dir = recipe_dir / "source"
if source_dir.is_dir() and any(source_dir.iterdir()):
present += 1
continue
alias_source_dir = same_as_source_dir(recipe_dir)
if alias_source_dir is not None and alias_source_dir.is_dir() and any(alias_source_dir.iterdir()):
present += 1
continue
missing_entries.append((package_name, recipe_dir))
restore_path = recipe_restore_path(recipe_dir)
if restore_path is not None:
missing_recipe_paths.append(restore_path)
if args.missing_paths_only:
seen = set()
for recipe_path in missing_recipe_paths:
if recipe_path not in seen:
print(recipe_path)
seen.add(recipe_path)
return 0 # Missing sources are non-fatal; they get fetched during build
print(f"=== Validating source trees for config: {args.config} ===")
for package_name, recipe_dir in missing_entries:
if recipe_dir is None:
print(f" NOT FOUND: {package_name}")
else:
print(f" MISSING: {recipe_dir.relative_to(PROJECT_ROOT)}")
total = present + len(missing_entries)
print(f"\n Total (config): {total}")
print(f" Present: {present}")
print(f" Missing: {len(missing_entries)}")
if missing_entries:
release = args.release or "<release>"
print(f"\nTo restore: ./local/scripts/restore-sources.sh --release={release}")
print("Source tree validation complete.")
return 0
if __name__ == "__main__":
sys.exit(main())