Files
RetroDebian/builder/py/retrobuilder/loader.py
2026-03-27 23:21:55 +01:00

82 lines
2.7 KiB
Python

from __future__ import annotations
import importlib.util
from pathlib import Path
from types import ModuleType
from retrobuilder.model import BaseSpec, FeatureSpec, ProfileSpec
from retrobuilder.paths import base_dir, feature_dir, profile_file
def _load_module(path: Path, name: str) -> ModuleType:
spec = importlib.util.spec_from_file_location(name, path)
if spec is None or spec.loader is None:
raise RuntimeError(f'Cannot load module from {path}')
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def load_profile(root: Path, name: str) -> ProfileSpec:
path = profile_file(root, name)
module = _load_module(path, f'profile_{name}')
profile = getattr(module, 'PROFILE', None)
if not isinstance(profile, ProfileSpec):
raise TypeError(f'{path} must define PROFILE as ProfileSpec')
return profile
def load_base_module(root: Path, name: str) -> ModuleType:
return _load_module(base_dir(root, name) / 'entry.py', f'base_{name}')
def load_feature_module(root: Path, name: str) -> ModuleType:
return _load_module(feature_dir(root, name) / 'entry.py', f'feature_{name}')
def load_base_spec(root: Path, name: str) -> BaseSpec:
module = load_base_module(root, name)
spec = getattr(module, 'SPEC', None)
if not isinstance(spec, BaseSpec):
raise TypeError(f'bases/{name}/entry.py must define SPEC as BaseSpec')
return spec
def load_base_chain(root: Path, name: str) -> list[tuple[str, BaseSpec]]:
chain: list[tuple[str, BaseSpec]] = []
seen: set[str] = set()
current = name
while current:
if current in seen:
raise RuntimeError(f'Base inheritance loop detected at {current}')
seen.add(current)
spec = load_base_spec(root, current)
chain.append((current, spec))
current = spec.parent or ''
chain.reverse()
return chain
def load_feature_spec(root: Path, name: str) -> FeatureSpec:
module = load_feature_module(root, name)
spec = getattr(module, 'SPEC', None)
if not isinstance(spec, FeatureSpec):
raise TypeError(f'features/{name}/entry.py must define SPEC as FeatureSpec')
return spec
def load_base_entry(root: Path, name: str):
module = load_base_module(root, name)
entry_cls = getattr(module, 'Entry', None)
if entry_cls is None:
raise TypeError(f'bases/{name}/entry.py must define Entry')
return entry_cls
def load_feature_entry(root: Path, name: str):
module = load_feature_module(root, name)
entry_cls = getattr(module, 'Entry', None)
if entry_cls is None:
raise TypeError(f'features/{name}/entry.py must define Entry')
return entry_cls