| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| # | |
| # Copyright (C) 2022 F4PGA Authors | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # | |
| # SPDX-License-Identifier: Apache-2.0 | |
| """ | |
| Dynamically import and run F4PGA modules. | |
| """ | |
| from contextlib import contextmanager | |
| import importlib.util as importlib_util | |
| from pathlib import Path | |
| from colorama import Style | |
| from f4pga.flows.module import Module, ModuleContext, get_mod_metadata | |
| from f4pga.flows.common import ResolutionEnv, deep, sfprint | |
| @contextmanager | |
| def _add_to_sys_path(path: str): | |
| import sys | |
| old_syspath = sys.path | |
| sys.path = [path] + sys.path | |
| try: | |
| yield | |
| finally: | |
| sys.path = old_syspath | |
| def import_module_from_path(path: str): | |
| absolute_path = str(Path(path).resolve()) | |
| with _add_to_sys_path(path): | |
| spec = importlib_util.spec_from_file_location(absolute_path, absolute_path) | |
| module = importlib_util.module_from_spec(spec) | |
| spec.loader.exec_module(module) | |
| return module | |
| # Once imported a module will be added to that dict to avaid re-importing it | |
| preloaded_modules = {} | |
| def get_module(path: str): | |
| global preloaded_modules | |
| cached = preloaded_modules.get(path) | |
| if cached: | |
| return cached.ModuleClass | |
| mod = import_module_from_path(path) | |
| preloaded_modules[path] = mod | |
| # All F4PGA modules should expose a `ModuleClass` type/alias which is a class implementing a Module interface | |
| return mod.ModuleClass | |
| class ModRunCtx: | |
| share: str | |
| bin: str | |
| config: "dict[str, ]" | |
| def __init__(self, share: str, bin: str, config: "dict[str, ]"): | |
| self.share = share | |
| self.bin = bin | |
| self.config = config | |
| def make_r_env(self): | |
| return ResolutionEnv(self.config["values"]) | |
| class ModuleFailException(Exception): | |
| module: str | |
| mode: str | |
| e: Exception | |
| def __init__(self, module: str, mode: str, e: Exception): | |
| self.module = module | |
| self.mode = mode | |
| self.e = e | |
| def __str__(self) -> str: | |
| return f"""ModuleFailException: | |
| Module `{self.module}` failed MODE: \'{self.mode}\' | |
| Exception `{type(self.e)}`: {self.e} | |
| """ | |
| def module_io(module: Module): | |
| return {"name": module.name, "takes": module.takes, "produces": module.produces, "meta": get_mod_metadata(module)} | |
| _deep_resolve = deep(lambda p: str(Path(p).resolve()), allow_none=True) | |
| def module_map(module: Module, ctx: ModRunCtx): | |
| try: | |
| mod_ctx = ModuleContext(module, ctx.config, ctx.make_r_env(), ctx.share, ctx.bin) | |
| except Exception as e: | |
| raise ModuleFailException(module.name, "map", e) | |
| return _deep_resolve(vars(mod_ctx.outputs)) | |
| def module_exec(module: Module, ctx: ModRunCtx): | |
| try: | |
| mod_ctx = ModuleContext(module, ctx.config, ctx.make_r_env(), ctx.share, ctx.bin) | |
| except Exception as e: | |
| raise ModuleFailException(module.name, "exec", e) | |
| sfprint(1, f"Executing module `{Style.BRIGHT + module.name + Style.RESET_ALL}`:") | |
| current_phase = 1 | |
| try: | |
| for phase_msg in module.execute(mod_ctx): | |
| sfprint(1, f" {Style.BRIGHT}[{current_phase}/{module.no_of_phases}] {Style.RESET_ALL}: {phase_msg}") | |
| current_phase += 1 | |
| except Exception as e: | |
| raise ModuleFailException(module.name, "exec", e) | |
| sfprint(1, f"Module `{Style.BRIGHT + module.name + Style.RESET_ALL}` has finished its work!") |