Source code for crosstl.translator.plugin_loader
"""Plugin discovery helpers for external CrossGL Translator backends."""
from __future__ import annotations
import importlib
import logging
import os
from typing import Iterable
from .codegen.registry import BackendSpec, register_backend
from .source_registry import SourceSpec, SOURCE_REGISTRY
logger = logging.getLogger(__name__)
_DISCOVERED = {"done": False}
def _backend_root() -> str:
return os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "backend"))
def _backend_dirs() -> Iterable[str]:
backend_root = _backend_root()
if not os.path.isdir(backend_root):
return []
return [
name
for name in os.listdir(backend_root)
if os.path.isdir(os.path.join(backend_root, name)) and not name.startswith(".")
]
def _register_backend_specs(module) -> None:
if hasattr(module, "BACKEND_SPECS"):
for spec in getattr(module, "BACKEND_SPECS"):
if isinstance(spec, BackendSpec):
register_backend(spec, overwrite=True)
if hasattr(module, "BACKEND_SPEC"):
spec = getattr(module, "BACKEND_SPEC")
if isinstance(spec, BackendSpec):
register_backend(spec, overwrite=True)
def _register_source_specs(module) -> None:
if hasattr(module, "SOURCE_SPECS"):
for spec in getattr(module, "SOURCE_SPECS"):
if isinstance(spec, SourceSpec):
SOURCE_REGISTRY.register(spec, overwrite=True)
if hasattr(module, "SOURCE_SPEC"):
spec = getattr(module, "SOURCE_SPEC")
if isinstance(spec, SourceSpec):
SOURCE_REGISTRY.register(spec, overwrite=True)
[docs]
def discover_backend_plugins(force: bool = False) -> None:
"""Discover backend plugin specs under ``crosstl.backend``.
Discovery imports optional ``backend_spec`` and ``source_spec`` modules from
backend packages. Those modules can expose spec constants or a ``register``
function to attach additional source parsers and target code generators.
"""
if _DISCOVERED["done"] and not force:
return
for backend in _backend_dirs():
for module_name in ["backend_spec", "source_spec"]:
module_path = f"crosstl.backend.{backend}.{module_name}"
try:
module = importlib.import_module(module_path)
except ImportError:
continue
except Exception as exc: # pragma: no cover - pylint: disable=broad-except
logger.warning("Failed to import %s: %s", module_path, exc)
continue
try:
_register_backend_specs(module)
_register_source_specs(module)
except Exception as exc: # pragma: no cover - pylint: disable=broad-except
logger.warning("Failed to register specs from %s: %s", module_path, exc)
if hasattr(module, "register"):
try:
module.register()
except TypeError:
try:
module.register(register_backend, SOURCE_REGISTRY.register)
except Exception as exc: # pylint: disable=broad-except
logger.warning(
"Failed to call register() in %s: %s",
module_path,
exc,
)
except Exception as exc: # pylint: disable=broad-except
logger.warning(
"Failed to call register() in %s: %s", module_path, exc
)
_DISCOVERED["done"] = True