diff --git a/pycompiler_ark/Core/engine/base.py b/pycompiler_ark/Core/engine/base.py index 759362d..150ba5f 100644 --- a/pycompiler_ark/Core/engine/base.py +++ b/pycompiler_ark/Core/engine/base.py @@ -16,12 +16,83 @@ from __future__ import annotations import time +from dataclasses import dataclass from typing import TYPE_CHECKING, Callable, Optional if TYPE_CHECKING: from pycompiler_ark.Core.engine.build_context import BuildContext +@dataclass(frozen=True) +class EngineMeta: + """Métadonnées d'un moteur de compilation.""" + + id: str + name: str + version: str + required_core_version: str = "1.0.0" + required_sdk_version: str = "1.0.0" + description: str = "" + author: str = "" + + def __post_init__(self) -> None: + nid = str(self.id or "").strip() + nname = str(self.name or "").strip() + nversion = str(self.version or "").strip() + if not nid: + raise ValueError("EngineMeta invalide: 'id' requis") + if not nname: + raise ValueError("EngineMeta invalide: 'name' requis") + if not nversion: + raise ValueError("EngineMeta invalide: 'version' requis") + object.__setattr__(self, "id", nid) + object.__setattr__(self, "name", nname) + object.__setattr__(self, "version", nversion) + object.__setattr__(self, "required_core_version", str(self.required_core_version or "1.0.0").strip() or "1.0.0") + object.__setattr__(self, "required_sdk_version", str(self.required_sdk_version or "1.0.0").strip() or "1.0.0") + object.__setattr__(self, "description", str(self.description or "").strip()) + object.__setattr__(self, "author", str(self.author or "").strip()) + + +def resolve_engine_meta(engine_or_cls: object) -> EngineMeta: + """Resolve engine metadata from a meta object or legacy class attributes.""" + + meta = getattr(engine_or_cls, "meta", None) + if isinstance(meta, EngineMeta): + return meta + + if isinstance(meta, dict): + return EngineMeta( + id=str(meta.get("id") or getattr(engine_or_cls, "id", "") or "base"), + name=str(meta.get("name") or getattr(engine_or_cls, "name", "") or "BaseEngine"), + version=str(meta.get("version") or getattr(engine_or_cls, "version", "") or "1.0.0"), + required_core_version=str( + meta.get("required_core_version") + or getattr(engine_or_cls, "required_core_version", "1.0.0") + ), + required_sdk_version=str( + meta.get("required_sdk_version") + or getattr(engine_or_cls, "required_sdk_version", "1.0.0") + ), + description=str(meta.get("description") or ""), + author=str(meta.get("author") or ""), + ) + + return EngineMeta( + id=str(getattr(engine_or_cls, "id", "") or "base"), + name=str(getattr(engine_or_cls, "name", "") or "BaseEngine"), + version=str(getattr(engine_or_cls, "version", "") or "1.0.0"), + required_core_version=str( + getattr(engine_or_cls, "required_core_version", "1.0.0") + ), + required_sdk_version=str( + getattr(engine_or_cls, "required_sdk_version", "1.0.0") + ), + description=str(getattr(engine_or_cls, "description", "") or ""), + author=str(getattr(engine_or_cls, "author", "") or ""), + ) + + def log_i18n_level(gui, level: str, fr: str, en: str) -> None: """Minimal i18n log helper to avoid engine loader <-> engine_sdk circular imports.""" try: @@ -81,12 +152,30 @@ class CompilerEngine: provided via the `gui` object. """ + meta: EngineMeta = EngineMeta(id="base", name="BaseEngine", version="1.0.0") id: str = "base" name: str = "BaseEngine" version: str = "1.0.0" required_core_version: str = "1.0.0" required_sdk_version: str = "1.0.0" + def __init_subclass__(cls, **kwargs): + super().__init_subclass__(**kwargs) + try: + resolved = resolve_engine_meta(cls) + cls.meta = resolved + cls.id = resolved.id + cls.name = resolved.name + cls.version = resolved.version + cls.required_core_version = resolved.required_core_version + cls.required_sdk_version = resolved.required_sdk_version + if resolved.description and not getattr(cls, "description", None): + cls.description = resolved.description + if resolved.author and not getattr(cls, "author", None): + cls.author = resolved.author + except Exception: + pass + def preflight(self, gui, file: str) -> bool: """Perform preflight checks and setup. Return True if OK, False to abort.""" return True diff --git a/pycompiler_ark/Core/engine/validator.py b/pycompiler_ark/Core/engine/validator.py index 37d41df..2f69ffa 100644 --- a/pycompiler_ark/Core/engine/validator.py +++ b/pycompiler_ark/Core/engine/validator.py @@ -26,6 +26,8 @@ from dataclasses import dataclass from typing import List, Tuple +from pycompiler_ark.Core.engine.base import resolve_engine_meta + @dataclass class EngineCompatibilityCheckResult: @@ -90,13 +92,14 @@ def check_engine_compatibility( Returns: EngineCompatibilityCheckResult with compatibility information """ - engine_id = getattr(engine_class, "id", "unknown") - engine_name = getattr(engine_class, "name", "Unknown Engine") + meta = resolve_engine_meta(engine_class) + engine_id = meta.id + engine_name = meta.name missing_requirements = [] - # Get required versions from engine class - required_core_version = getattr(engine_class, "required_core_version", "1.0.0") - required_sdk_version = getattr(engine_class, "required_sdk_version", "1.0.0") + # Get required versions from engine metadata + required_core_version = meta.required_core_version + required_sdk_version = meta.required_sdk_version # Check Core compatibility: current >= required (accept equal or higher versions) current_core = parse_version(core_version) @@ -154,29 +157,28 @@ def validate_engines_compatibility( try: # In strict mode, reject engines that don't specify requirements if strict_mode: + meta = resolve_engine_meta(engine) has_explicit_requirements = ( - getattr(engine, "required_core_version", "1.0.0") != "1.0.0" - or getattr(engine, "required_sdk_version", "1.0.0") != "1.0.0" + meta.required_core_version != "1.0.0" + or meta.required_sdk_version != "1.0.0" ) if not has_explicit_requirements: result = EngineCompatibilityCheckResult( - engine_id=getattr(engine, "id", "unknown"), - engine_name=getattr(engine, "name", "Unknown"), + engine_id=meta.id, + engine_name=meta.name, is_compatible=False, missing_requirements=[ "No explicit version requirements specified" ], - error_message=f"Engine '{getattr(engine, 'name', 'Unknown')}' ({getattr(engine, 'id', 'unknown')}) does not specify version requirements. " - f"Please add required_core_version and required_sdk_version class attributes.", + error_message=f"Engine '{meta.name}' ({meta.id}) does not specify version requirements. " + f"Please add an EngineMeta with required_core_version and required_sdk_version.", ) incompatible_results.append(result) continue # Check compatibility - result = check_engine_compatibility( - engine, core_version, engine_sdk_version - ) + result = check_engine_compatibility(engine, core_version, engine_sdk_version) if result.is_compatible: compatible_engines.append(engine) @@ -184,9 +186,10 @@ def validate_engines_compatibility( incompatible_results.append(result) except Exception as e: + meta = resolve_engine_meta(engine) result = EngineCompatibilityCheckResult( - engine_id=getattr(engine, "id", "unknown"), - engine_name=getattr(engine, "name", "Unknown"), + engine_id=meta.id, + engine_name=meta.name, is_compatible=False, missing_requirements=[], error_message=f"Error validating engine: {str(e)}", @@ -207,8 +210,9 @@ def print_engine_compatibility_report( print(f"\n✓ Compatible: {len(compatible_engines)}") for engine in compatible_engines: - engine_name = getattr(engine, "name", "Unknown") - engine_id = getattr(engine, "id", "unknown") + meta = resolve_engine_meta(engine) + engine_name = meta.name + engine_id = meta.id print(f" - {engine_name} ({engine_id})") print(f"\n✗ Incompatible: {len(incompatible_results)}") diff --git a/pycompiler_ark/engine_sdk/__init__.py b/pycompiler_ark/engine_sdk/__init__.py index aeedf44..48c0082 100644 --- a/pycompiler_ark/engine_sdk/__init__.py +++ b/pycompiler_ark/engine_sdk/__init__.py @@ -18,7 +18,7 @@ from pycompiler_ark.Core.engine.build_context import BuildContext # Re-export the base interface used by the host -from .base import CompilerEngine +from .base import CompilerEngine, EngineMeta from pycompiler_ark.Core.engine.registry import translate from .utils import resolve_executable # executable resolution helper (SDK) from .utils import open_path @@ -31,6 +31,7 @@ __all__ = [ "engine_register", "CompilerEngine", + "EngineMeta", "resolve_executable", "open_path", "translate", diff --git a/pycompiler_ark/engine_sdk/base.py b/pycompiler_ark/engine_sdk/base.py index 64cb609..8f93be8 100644 --- a/pycompiler_ark/engine_sdk/base.py +++ b/pycompiler_ark/engine_sdk/base.py @@ -16,6 +16,6 @@ from __future__ import annotations # Stable re-export of the host base class -from pycompiler_ark.Core.engine.base import CompilerEngine # type: ignore[F401] +from pycompiler_ark.Core.engine.base import EngineMeta, CompilerEngine # type: ignore[F401] -__all__ = ["CompilerEngine"] +__all__ = ["CompilerEngine", "EngineMeta"] diff --git a/pycompiler_ark/engines/cx_freeze/__init__.py b/pycompiler_ark/engines/cx_freeze/__init__.py index 3423576..d811af9 100644 --- a/pycompiler_ark/engines/cx_freeze/__init__.py +++ b/pycompiler_ark/engines/cx_freeze/__init__.py @@ -30,6 +30,7 @@ from pycompiler_ark.engine_sdk import ( BuildContext, CompilerEngine, + EngineMeta, engine_register, translate, ) @@ -58,11 +59,11 @@ class CXFreezeEngine(CompilerEngine): - Icon specification """ - id: str = "cx_freeze" - name: str = "CX_Freeze" - version: str = "1.1.0" - required_core_version: str = "1.0.0" - required_sdk_version: str = "1.0.0" + meta = EngineMeta( + id="cx_freeze", + name="CX_Freeze", + version="1.1.0", + ) @property def required_tools(self) -> dict[str, list[str]]: diff --git a/pycompiler_ark/engines/nuitka/__init__.py b/pycompiler_ark/engines/nuitka/__init__.py index 6dec765..5e1282c 100644 --- a/pycompiler_ark/engines/nuitka/__init__.py +++ b/pycompiler_ark/engines/nuitka/__init__.py @@ -29,6 +29,7 @@ from pycompiler_ark.engine_sdk import ( BuildContext, CompilerEngine, + EngineMeta, engine_register, translate, ) @@ -55,11 +56,11 @@ class NuitkaEngine(CompilerEngine): - Icon specification """ - id: str = "nuitka" - name: str = "Nuitka" - version: str = "1.1.0" - required_core_version: str = "1.0.0" - required_sdk_version: str = "1.0.0" + meta = EngineMeta( + id="nuitka", + name="Nuitka", + version="1.1.0", + ) @property def required_tools(self) -> dict[str, list[str]]: diff --git a/pycompiler_ark/engines/pyinstaller/__init__.py b/pycompiler_ark/engines/pyinstaller/__init__.py index 0e19f8b..c8292ad 100644 --- a/pycompiler_ark/engines/pyinstaller/__init__.py +++ b/pycompiler_ark/engines/pyinstaller/__init__.py @@ -30,6 +30,7 @@ from pycompiler_ark.engine_sdk import ( BuildContext, CompilerEngine, + EngineMeta, engine_register, translate, ) @@ -59,11 +60,11 @@ class PyInstallerEngine(CompilerEngine): - Icon specification """ - id: str = "pyinstaller" - name: str = "PyInstaller" - version: str = "1.1.0" - required_core_version: str = "1.0.0" - required_sdk_version: str = "1.0.0" + meta = EngineMeta( + id="pyinstaller", + name="PyInstaller", + version="1.1.0", + ) @property def required_tools(self) -> dict[str, list[str]]: