Files
RedBear-OS/local/scripts/validate-source-trees.py
T
vasilito b9874d0941 feat: USB storage read/write proof + full Red Bear OS tree sync
Add redbear-usb-storage-check in-guest binary that validates USB mass
storage read and write I/O: discovers /scheme/disk/ devices, writes a
test pattern to sector 2048, reads it back, verifies match, restores
original content. Updates test-usb-storage-qemu.sh with write-proof
verification step.

Includes all accumulated Red Bear OS work: kernel patches, relibc
patches, driver infrastructure, DRM/GPU, KDE recipes, firmware,
validation tooling, build system hardening, and documentation.
2026-05-03 23:03:24 +01:00

144 lines
4.8 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:
lookup[package_name] = recipe_toml.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 1 if missing_entries else 0
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}")
return 1
print("All source trees present.")
return 0
if __name__ == "__main__":
sys.exit(main())