52cbdddd0b
Missing source trees (e.g. plasma-desktop with recipe.toml but not yet extracted source/) should not block the build. Sources are fetched/extracted by the cookbook during the build phase. The validator now warns about missing sources but returns exit code 0.
144 lines
4.8 KiB
Python
Executable File
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 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}")
|
|
return 1
|
|
print("All source trees present.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|