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

128 lines
4.2 KiB
Python

from __future__ import annotations
import json
import shutil
from pathlib import Path
from retrobuilder.model import BaseSpec, FeatureSpec, ProfileSpec
LIVE_DIR_STRUCTURE = (
'config/chroot_local-packageslists',
'config/chroot_local-packages',
'config/chroot_local-includes',
'config/chroot_local-hooks',
)
def ensure_live_structure(live_dir: Path) -> None:
for relative in LIVE_DIR_STRUCTURE:
(live_dir / relative).mkdir(parents=True, exist_ok=True)
def clear_directory_contents(path: Path) -> None:
path.mkdir(parents=True, exist_ok=True)
for child in path.iterdir():
if child.name == '.gitkeep':
continue
if child.is_dir() and not child.is_symlink():
shutil.rmtree(child)
else:
child.unlink()
def copy_tree_contents(src: Path, dst: Path) -> None:
if not src.exists():
return
dst.mkdir(parents=True, exist_ok=True)
for child in src.iterdir():
target = dst / child.name
if child.is_dir():
shutil.copytree(child, target, dirs_exist_ok=True)
else:
shutil.copy2(child, target)
def copy_glob(src_dir: Path, pattern: str, dst_dir: Path, prefix: str = '') -> None:
if not src_dir.exists():
return
dst_dir.mkdir(parents=True, exist_ok=True)
for src in sorted(src_dir.glob(pattern)):
if src.is_dir():
continue
name = f'{prefix}{src.name}' if prefix else src.name
shutil.copy2(src, dst_dir / name)
def save_json(path: Path, payload: dict) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(payload, indent=2, sort_keys=True) + '\n', encoding='utf-8')
def save_profile_metadata(
path: Path,
profile_name: str,
profile: ProfileSpec,
base_name: str,
base: BaseSpec,
base_chain: list[tuple[str, BaseSpec]] | None = None,
) -> None:
save_json(path, {
'profile_name': profile_name,
'profile': profile.to_dict(),
'base_name': base_name,
'base': base.to_dict(),
'base_chain': [
{'name': item_name, 'spec': item_spec.to_dict()}
for item_name, item_spec in (base_chain or [(base_name, base)])
],
})
def save_feature_metadata(path: Path, feature_name: str, feature: FeatureSpec) -> None:
save_json(path, {'feature_name': feature_name, 'feature': feature.to_dict()})
def apply_profile_common_configuration(root: Path, live_dir: Path, profile_name: str, profile: ProfileSpec) -> None:
ensure_live_structure(live_dir)
notes_dir = live_dir / 'builder-notes'
notes_dir.mkdir(parents=True, exist_ok=True)
notes = [
f'profile={profile_name}',
f'base={profile.base}',
f'features={"|".join(profile.features)}',
]
if profile.splash:
splash_src = root / profile.splash
if splash_src.exists():
splash_dst = notes_dir / splash_src.name
shutil.copy2(splash_src, splash_dst)
notes.append(f'splash={profile.splash}')
(notes_dir / 'profile-common.txt').write_text('\n'.join(notes) + '\n', encoding='utf-8')
def inject_module_resources(module_dir: Path, live_dir: Path, prefix: str) -> None:
copy_tree_contents(module_dir / 'packageslists', live_dir / 'config' / 'chroot_local-packageslists')
copy_tree_contents(module_dir / 'packages', live_dir / 'config' / 'chroot_local-packages')
copy_tree_contents(module_dir / 'chroot', live_dir / 'config' / 'chroot_local-includes')
copy_glob(module_dir / 'hooks', '*.sh', live_dir / 'config' / 'chroot_local-hooks', prefix=f'{prefix}_')
def profile_pre_build(live_dir: Path, profile_name: str) -> None:
marker = live_dir / 'builder-notes' / 'pre-build.txt'
marker.parent.mkdir(parents=True, exist_ok=True)
marker.write_text(f'pre-build profile={profile_name}\n', encoding='utf-8')
def profile_finalize(root: Path, profile_name: str) -> None:
live_dir = root / 'live'
out_dir = root / 'artifacts' / 'profiles' / profile_name / 'final'
out_dir.mkdir(parents=True, exist_ok=True)
summary = out_dir / 'summary.txt'
summary.write_text(
'Profile finalization complete.\n'
f'profile={profile_name}\n'
f'live_exists={live_dir.exists()}\n',
encoding='utf-8',
)