remove useless parameters

This commit is contained in:
2026-03-27 23:21:55 +01:00
parent 03ed4ebbab
commit 460468363f
8 changed files with 70 additions and 124 deletions

View File

@@ -15,10 +15,7 @@ from retrobuilder.entrypoints import BaseEntry, cli_dispatch
from retrobuilder.model import BaseSpec
SPEC = BaseSpec(
name='retrodebian-common',
description='Common inherited base resources shared by concrete bases.',
packages=(),
package_lists=(),
docker_overrides={},
)

View File

@@ -8,7 +8,6 @@ import sys
from pathlib import Path
THIS_DIR = Path(__file__).resolve().parent
PACKAGE_ROOT = THIS_DIR / 'retrobuilder'
if str(THIS_DIR) not in sys.path:
sys.path.insert(0, str(THIS_DIR))
@@ -22,7 +21,6 @@ from retrobuilder.loader import (
load_feature_spec,
load_profile,
)
from retrobuilder.model import DockerStageSpec
from retrobuilder.operations import (
apply_profile_common_configuration,
clear_directory_contents,
@@ -50,55 +48,35 @@ def root_dir() -> Path:
def list_names(path: Path) -> list[str]:
if not path.exists():
return []
return sorted(child.name for child in path.iterdir() if child.is_dir())
return sorted(item.name for item in path.iterdir() if item.is_dir())
def list_profiles(path: Path) -> list[str]:
if not path.exists():
return []
return sorted(p.stem for p in path.glob('*.py') if p.is_file())
return sorted(item.stem for item in path.iterdir() if item.is_file() and item.suffix == '.py')
def build_context(root: Path, phase: str, kind: str, profile_name: str = '', base_name: str = '', feature_name: str = '') -> BuildContext:
live_dir = root / 'live'
artifacts_root = root / 'artifacts'
def build_context(root: Path, phase: str, kind: str, profile_name: str, base_name: str = '', feature_name: str = '') -> BuildContext:
profile = load_profile(root, profile_name) if profile_name else None
base = load_base_spec(root, base_name or (profile.base if profile else '')) if (base_name or profile) else None
feature = load_feature_spec(root, feature_name) if feature_name else None
current_name = {
'profile': profile_name,
'base': base.name if base else '',
'feature': feature.name if feature else '',
}[kind]
current_dir = {
'profile': root / 'profiles' / f'{profile_name}.py',
'base': base_dir(root, base.name if base else base_name),
'feature': feature_dir(root, feature.name if feature else feature_name),
}[kind]
current_artifact_dir = {
'profile': profile_artifacts_dir(root, profile_name),
'base': base_artifacts_dir(root, base.name if base else base_name),
'feature': feature_artifacts_dir(root, feature.name if feature else feature_name),
}[kind]
return BuildContext(
project_root=root,
live_dir=live_dir,
artifacts_root=artifacts_root,
live_dir=root / 'live',
artifacts_root=root / 'artifacts',
phase=phase,
current_kind=kind,
current_name=current_name,
current_module_dir=current_dir,
current_module_artifact_dir=current_artifact_dir,
profile_name=profile.name if profile else profile_name,
profile_artifact_dir=profile_artifacts_dir(root, profile.name) if profile else None,
base_name=base.name if base else '',
base_dir=base_dir(root, base.name) if base else None,
base_artifact_dir=base_artifacts_dir(root, base.name) if base else None,
feature_name=feature.name if feature else '',
feature_dir=feature_dir(root, feature.name) if feature else None,
feature_artifact_dir=feature_artifacts_dir(root, feature.name) if feature else None,
current_name=base_name or feature_name or profile_name,
current_module_artifact_dir=(
base_artifacts_dir(root, base_name) if kind == 'base' else
feature_artifacts_dir(root, feature_name) if kind == 'feature' else
profile_artifacts_dir(root, profile_name)
),
profile_name=profile_name,
profile_artifact_dir=profile_artifacts_dir(root, profile_name) if profile_name else None,
base_name=base_name,
base_artifact_dir=base_artifacts_dir(root, base_name) if base_name else None,
feature_name=feature_name,
feature_artifact_dir=feature_artifacts_dir(root, feature_name) if feature_name else None,
profile_features=tuple(profile.features) if profile else (),
)
@@ -120,8 +98,7 @@ def run_python_phase(args: argparse.Namespace) -> int:
def export_env(args: argparse.Namespace) -> int:
root = root_dir()
kind = args.kind
ctx = build_context(root, args.phase, kind, args.profile or '', args.base or '', args.feature or '')
ctx = build_context(root, args.phase, args.kind, args.profile or '', args.base or '', args.feature or '')
values = ctx.to_env()
values['RETRODEBIAN_FAKE_LEGACY_TOOLS'] = os.environ.get('RETRODEBIAN_FAKE_LEGACY_TOOLS', '1')
write_env_file(Path(args.output), values)
@@ -133,6 +110,7 @@ def cmd_validate(args: argparse.Namespace) -> int:
for profile_name in list_profiles(root / 'profiles'):
profile = load_profile(root, profile_name)
load_base_spec(root, profile.base)
load_base_chain(root, profile.base)
for feature_name in profile.features:
load_feature_spec(root, feature_name)
print('Validation OK')
@@ -152,26 +130,11 @@ def cmd_list(args: argparse.Namespace) -> int:
return 0
def _load_module_spec(root: Path, kind: str, name: str):
if kind == 'base':
return load_base_spec(root, name)
if kind == 'feature':
return load_feature_spec(root, name)
raise ValueError(kind)
def _docker_override_field(spec, phase: str, field: str) -> str:
stage = spec.docker_overrides.get(phase)
if stage is None:
return ''
value = getattr(stage, field)
return value or ''
def cmd_module_docker(args: argparse.Namespace) -> int:
root = root_dir()
spec = _load_module_spec(root, args.kind, args.name)
print(_docker_override_field(spec, args.phase, args.field))
spec = load_base_spec(root, args.name) if args.kind == 'base' else load_feature_spec(root, args.name)
stage = spec.docker_overrides.get(args.phase)
print(getattr(stage, args.field) if stage is not None and getattr(stage, args.field) else '')
return 0
@@ -181,8 +144,8 @@ def cmd_profile_info(args: argparse.Namespace) -> int:
if args.field == 'base':
print(profile.base)
elif args.field == 'base-chain':
for base_spec in load_base_chain(root, profile.base):
print(base_spec.name)
for name, _spec in load_base_chain(root, profile.base):
print(name)
elif args.field == 'features':
for feature in profile.features:
print(feature)
@@ -199,16 +162,16 @@ def cmd_prepare_profile(args: argparse.Namespace) -> int:
live_dir = root / 'live'
clear_directory_contents(live_dir)
ensure_live_structure(live_dir)
save_profile_metadata(profile_artifacts_dir(root, profile.name) / 'profile.json', profile, base, base_chain)
apply_profile_common_configuration(root, live_dir, profile)
save_profile_metadata(profile_artifacts_dir(root, args.profile) / 'profile.json', args.profile, profile, profile.base, base, base_chain)
apply_profile_common_configuration(root, live_dir, args.profile, profile)
return 0
def cmd_save_feature_metadata(args: argparse.Namespace) -> int:
root = root_dir()
feature = load_feature_spec(root, args.feature)
target = profile_artifacts_dir(root, args.profile) / 'features' / f'{feature.name}.json'
save_feature_metadata(target, feature)
target = profile_artifacts_dir(root, args.profile) / 'features' / f'{args.feature}.json'
save_feature_metadata(target, args.feature, feature)
return 0
@@ -217,8 +180,8 @@ def cmd_inject_resources(args: argparse.Namespace) -> int:
live_dir = root / 'live'
ensure_live_structure(live_dir)
if args.kind == 'base':
for base_spec in load_base_chain(root, args.name):
inject_module_resources(base_dir(root, base_spec.name), live_dir, base_spec.name)
for base_name, _base_spec in load_base_chain(root, args.name):
inject_module_resources(base_dir(root, base_name), live_dir, base_name)
else:
inject_module_resources(feature_dir(root, args.name), live_dir, args.name)
return 0
@@ -227,7 +190,7 @@ def cmd_inject_resources(args: argparse.Namespace) -> int:
def cmd_profile_common(args: argparse.Namespace) -> int:
root = root_dir()
profile = load_profile(root, args.profile)
apply_profile_common_configuration(root, root / 'live', profile)
apply_profile_common_configuration(root, root / 'live', args.profile, profile)
return 0
@@ -268,11 +231,12 @@ def cmd_run_local(args: argparse.Namespace) -> int:
env, _ = _env_and_path(root, 'feature', 'post-gen', args.profile, feature=feature_name)
_run([sys.executable, str(feature_dir(root, feature_name) / 'entry.py'), 'post-gen'], env=env)
env, env_path = _env_and_path(root, 'base', 'pre-gen', args.profile, base=profile.base)
_run([sys.executable, str(base_dir(root, profile.base) / 'entry.py'), 'pre-gen'], env=env)
_run([str(root / 'builder' / 'bash' / 'run_generate.sh'), str(base_dir(root, profile.base)), str(env_path)])
env, _ = _env_and_path(root, 'base', 'post-gen', args.profile, base=profile.base)
_run([sys.executable, str(base_dir(root, profile.base) / 'entry.py'), 'post-gen'], env=env)
for base_name, _base_spec in load_base_chain(root, profile.base):
env, env_path = _env_and_path(root, 'base', 'pre-gen', args.profile, base=base_name)
_run([sys.executable, str(base_dir(root, base_name) / 'entry.py'), 'pre-gen'], env=env)
_run([str(root / 'builder' / 'bash' / 'run_generate.sh'), str(base_dir(root, base_name)), str(env_path)])
env, _ = _env_and_path(root, 'base', 'post-gen', args.profile, base=base_name)
_run([sys.executable, str(base_dir(root, base_name) / 'entry.py'), 'post-gen'], env=env)
_run([sys.executable, str(root / 'builder' / 'py' / 'build.py'), 'prepare-profile', '--profile', args.profile])
env, env_path = _env_and_path(root, 'profile', 'config', args.profile)
@@ -322,22 +286,22 @@ def build_parser() -> argparse.ArgumentParser:
p.set_defaults(func=cmd_profile_info)
p = sub.add_parser('module-docker')
p.add_argument('--kind', choices=['base', 'feature'], required=True)
p.add_argument('--kind', required=True, choices=['base', 'feature'])
p.add_argument('--name', required=True)
p.add_argument('--phase', required=True)
p.add_argument('field', choices=['image', 'dockerfile', 'docker_context'])
p.set_defaults(func=cmd_module_docker)
p = sub.add_parser('run-python-phase')
p.add_argument('--kind', choices=['base', 'feature'], required=True)
p.add_argument('--kind', required=True, choices=['base', 'feature'])
p.add_argument('--phase', required=True)
p.add_argument('--profile')
p.add_argument('--base')
p.add_argument('--feature')
p.add_argument('--profile', default='')
p.add_argument('--base', default='')
p.add_argument('--feature', default='')
p.set_defaults(func=run_python_phase)
p = sub.add_parser('export-env')
p.add_argument('--kind', choices=['profile', 'base', 'feature'], required=True)
p.add_argument('--kind', required=True, choices=['profile', 'base', 'feature'])
p.add_argument('--phase', required=True)
p.add_argument('--profile', default='')
p.add_argument('--base', default='')
@@ -355,7 +319,7 @@ def build_parser() -> argparse.ArgumentParser:
p.set_defaults(func=cmd_save_feature_metadata)
p = sub.add_parser('inject-resources')
p.add_argument('--kind', choices=['base', 'feature'], required=True)
p.add_argument('--kind', required=True, choices=['base', 'feature'])
p.add_argument('--name', required=True)
p.set_defaults(func=cmd_inject_resources)
@@ -381,7 +345,7 @@ def build_parser() -> argparse.ArgumentParser:
def main() -> int:
parser = build_parser()
args = parser.parse_args()
return args.func(args)
return int(args.func(args))
if __name__ == '__main__':

View File

@@ -13,15 +13,12 @@ class BuildContext:
phase: str
current_kind: str
current_name: str
current_module_dir: Path
current_module_artifact_dir: Path
profile_name: str = ''
profile_artifact_dir: Path | None = None
base_name: str = ''
base_dir: Path | None = None
base_artifact_dir: Path | None = None
feature_name: str = ''
feature_dir: Path | None = None
feature_artifact_dir: Path | None = None
profile_features: tuple[str, ...] = ()
@@ -33,7 +30,6 @@ class BuildContext:
'PHASE': self.phase,
'CURRENT_KIND': self.current_kind,
'CURRENT_NAME': self.current_name,
'CURRENT_MODULE_DIR': str(self.current_module_dir),
'CURRENT_MODULE_ARTIFACT_DIR': str(self.current_module_artifact_dir),
'PROFILE_NAME': self.profile_name,
'BASE_NAME': self.base_name,
@@ -42,12 +38,8 @@ class BuildContext:
}
if self.profile_artifact_dir is not None:
env['PROFILE_ARTIFACT_DIR'] = str(self.profile_artifact_dir)
if self.base_dir is not None:
env['BASE_DIR'] = str(self.base_dir)
if self.base_artifact_dir is not None:
env['BASE_ARTIFACT_DIR'] = str(self.base_artifact_dir)
if self.feature_dir is not None:
env['FEATURE_DIR'] = str(self.feature_dir)
if self.feature_artifact_dir is not None:
env['FEATURE_ARTIFACT_DIR'] = str(self.feature_artifact_dir)
return env
@@ -66,15 +58,12 @@ class BuildContext:
phase=env['PHASE'],
current_kind=env['CURRENT_KIND'],
current_name=env['CURRENT_NAME'],
current_module_dir=Path(env['CURRENT_MODULE_DIR']),
current_module_artifact_dir=Path(env['CURRENT_MODULE_ARTIFACT_DIR']),
profile_name=env.get('PROFILE_NAME', ''),
profile_artifact_dir=p('PROFILE_ARTIFACT_DIR'),
base_name=env.get('BASE_NAME', ''),
base_dir=p('BASE_DIR'),
base_artifact_dir=p('BASE_ARTIFACT_DIR'),
feature_name=env.get('FEATURE_NAME', ''),
feature_dir=p('FEATURE_DIR'),
feature_artifact_dir=p('FEATURE_ARTIFACT_DIR'),
profile_features=features,
)

View File

@@ -42,8 +42,8 @@ def load_base_spec(root: Path, name: str) -> BaseSpec:
return spec
def load_base_chain(root: Path, name: str) -> list[BaseSpec]:
chain: list[BaseSpec] = []
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:
@@ -51,7 +51,7 @@ def load_base_chain(root: Path, name: str) -> list[BaseSpec]:
raise RuntimeError(f'Base inheritance loop detected at {current}')
seen.add(current)
spec = load_base_spec(root, current)
chain.append(spec)
chain.append((current, spec))
current = spec.parent or ''
chain.reverse()
return chain

View File

@@ -4,16 +4,6 @@ from dataclasses import dataclass, field, asdict
from typing import Any
def _tuple(values: Any) -> tuple[str, ...]:
if values is None:
return ()
if isinstance(values, tuple):
return values
if isinstance(values, list):
return tuple(str(v) for v in values)
raise TypeError(f'Expected list/tuple/None, got {type(values)!r}')
@dataclass(frozen=True)
class DockerStageSpec:
image: str | None = None
@@ -59,13 +49,10 @@ def _docker_overrides(values: Any) -> dict[str, DockerStageSpec]:
@dataclass(frozen=True)
class ModuleSpec:
title: str = ''
description: str = ''
docker_overrides: dict[str, DockerStageSpec] = field(default_factory=dict)
def __post_init__(self) -> None:
object.__setattr__(self, 'packages', _tuple(self.packages))
object.__setattr__(self, 'package_lists', _tuple(self.package_lists))
object.__setattr__(self, 'docker_overrides', _docker_overrides(self.docker_overrides))
def to_dict(self) -> dict[str, Any]:
@@ -86,7 +73,6 @@ class FeatureSpec(ModuleSpec):
@dataclass(frozen=True)
class ProfileSpec:
title: str = ''
base: str
features: tuple[str, ...] = field(default_factory=tuple)
edition: str = ''
@@ -94,7 +80,10 @@ class ProfileSpec:
splash: str | None = None
def __post_init__(self) -> None:
object.__setattr__(self, 'features', _tuple(self.features))
if isinstance(self.features, list):
object.__setattr__(self, 'features', tuple(str(v) for v in self.features))
elif not isinstance(self.features, tuple):
raise TypeError('ProfileSpec.features must be list or tuple')
def to_dict(self) -> dict[str, Any]:
return asdict(self)

View File

@@ -59,24 +59,36 @@ def save_json(path: Path, payload: dict) -> None:
path.write_text(json.dumps(payload, indent=2, sort_keys=True) + '\n', encoding='utf-8')
def save_profile_metadata(path: Path, profile: ProfileSpec, base: BaseSpec, base_chain: list[BaseSpec] | None = None) -> None:
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': [item.to_dict() for item in (base_chain or [base])],
'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: FeatureSpec) -> None:
save_json(path, feature.to_dict())
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: ProfileSpec) -> None:
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'profile={profile_name}',
f'base={profile.base}',
f'features={"|".join(profile.features)}',
]

View File

@@ -15,12 +15,8 @@ from retrobuilder.entrypoints import FeatureEntry, cli_dispatch
from retrobuilder.model import FeatureSpec
SPEC = FeatureSpec(
name='sample-feature',
description='Sample feature used to validate the V2 builder flow.',
packages=('sample-feature-package',),
package_lists=('sample-feature.list',),
docker_overrides={
# Example: override the generate phase with a custom Etch image or Dockerfile.
# 'pre-gen': {'image': 'python:3.11-alpine'},
# 'post-gen': {'dockerfile': 'features/sample-feature/Dockerfile.python'},
# 'pre-inj': {'image': 'retrodebian/custom-lenny:latest'},

View File

@@ -1,7 +1,6 @@
from retrobuilder.model import ProfileSpec
PROFILE = ProfileSpec(
name='demo',
base='sample-base',
features=('sample-feature',),
edition='Demo',