blob: 92711cbec4d131566b82151d655f8dce49a05e91 [file] [log] [blame]
#!/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!")