| #!/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 |
| |
| from pathlib import Path |
| from os import environ, listdir as os_listdir |
| from sys import argv as sys_argv |
| from argparse import Namespace |
| from shutil import move as sh_mv |
| from subprocess import run |
| from re import match as re_match, finditer as re_finditer |
| |
| from f4pga.context import FPGA_FAM, F4PGA_SHARE_DIR |
| |
| |
| bin_dir_path = str(Path(sys_argv[0]).resolve().parent.parent) |
| share_dir_path = str(F4PGA_SHARE_DIR) |
| |
| |
| class F4PGAException(Exception): |
| def __init__(self, message="unknown exception"): |
| self.message = message |
| |
| def __repr__(self): |
| return f"F4PGAException(message = '{self.message}')" |
| |
| def __str__(self): |
| return self.message |
| |
| |
| def decompose_depname(name: str): |
| spec = "req" |
| specchar = name[len(name) - 1] |
| if specchar == "?": |
| spec = "maybe" |
| elif specchar == "!": |
| spec = "demand" |
| if spec != "req": |
| name = name[: len(name) - 1] |
| return name, spec |
| |
| |
| def with_qualifier(name: str, q: str) -> str: |
| if q == "req": |
| return decompose_depname(name)[0] |
| if q == "maybe": |
| return decompose_depname(name)[0] + "?" |
| if q == "demand": |
| return decompose_depname(name)[0] + "!" |
| |
| |
| def resolve_modstr(modstr: str): |
| """ |
| Resolves module location given its name. |
| """ |
| modpath = Path(__file__).resolve().parent / f"modules/{modstr}.py" |
| if not modpath.exists(): |
| raise Exception(f"Unknown module <{modstr}>!") |
| return str(modpath) |
| |
| |
| def deep(fun, allow_none=False): |
| """ |
| Create a recursive string transform function for 'str | list | dict', i.e a dependency. |
| """ |
| |
| def d(paths, *args, **kwargs): |
| nonlocal allow_none |
| if type(paths) is str: |
| return fun(paths, *args, **kwargs) |
| elif type(paths) is list: |
| return [d(p, *args, **kwargs) for p in paths] |
| elif type(paths) is dict: |
| return dict([(k, d(p, *args, **kwargs)) for k, p in paths.items()]) |
| elif allow_none and (paths is None): |
| return paths |
| else: |
| raise RuntimeError(f"paths is of type {type(paths)}") |
| |
| return d |
| |
| |
| class SubprocessException(Exception): |
| return_code: int |
| |
| |
| def sub(*args, env=None, cwd=None, print_stdout_on_fail=False): |
| """ |
| Execute subroutine. |
| """ |
| |
| out = run(args, capture_output=True, env=env, cwd=cwd) |
| if out.returncode != 0: |
| print(f"[ERROR]: {args[0]} non-zero return code.\n") |
| if print_stdout_on_fail: |
| print(f"stdout:\n{out.stdout.decode()}\n\n") |
| print(f"stderr:\n{out.stderr.decode()}\n\n") |
| exit(out.returncode) |
| return out.stdout |
| |
| |
| def options_dict_to_list(opt_dict: dict): |
| """ |
| Converts a dictionary of named options for CLI program to a list. |
| Example: { "option_name": "value" } -> [ "--option_name", "value" ] |
| """ |
| |
| opts = [] |
| for key, val in opt_dict.items(): |
| opts.append(f"--{key}") |
| if not (type(val) is list and val == []): |
| opts.append(str(val)) |
| return opts |
| |
| |
| def noisy_warnings(device): |
| """ |
| Emit some noisy warnings. |
| """ |
| environ["OUR_NOISY_WARNINGS"] = f"noisy_warnings-{device}_pack.log" |
| |
| |
| def fatal(code, message): |
| """ |
| Print a message informing about an error that has occured and terminate program with a given return code. |
| """ |
| raise (Exception(f"[FATAL ERROR]: {message}")) |
| exit(code) |
| |
| |
| class ResolutionEnv: |
| """ |
| ResolutionEnv is used to hold onto mappings for variables used in flow and perform text substitutions using those |
| variables. |
| Variables can be referred in any "resolvable" string using the following syntax: 'Some static text ${variable_name}'. |
| The '${variable_name}' part will be replaced by the value associated with name 'variable_name', is such mapping |
| exists. |
| |
| values: dict |
| """ |
| |
| def __init__(self, values={}): |
| self.values = values |
| |
| def __copy__(self): |
| return ResolutionEnv(self.values.copy()) |
| |
| def resolve(self, s, final=False): |
| """ |
| Perform resolution on `s`. |
| `s` can be a `str`, a `dict` with arbitrary keys and resolvable values, or a `list` of resolvable values. |
| final=True - resolve any unknown variables into '' |
| This is a hack and probably should be removed in the future |
| """ |
| |
| if type(s) is str: |
| match_list = list(re_finditer("\$\{([^${}]*)\}", s)) |
| # Assumption: re_finditer finds matches in a left-to-right order |
| match_list.reverse() |
| for match in match_list: |
| match_str = match.group(1) |
| match_str = match_str.replace("?", "") |
| v = self.values.get(match_str) |
| if not v: |
| if final: |
| v = "" |
| else: |
| continue |
| span = match.span() |
| if type(v) is str: |
| s = s[: span[0]] + v + s[span[1] :] |
| elif type(v) is list: # Assume it's a list of strings |
| ns = list([s[: span[0]] + ve + s[span[1] :] for ve in v]) |
| s = ns |
| |
| elif type(s) is list: |
| s = list(map(self.resolve, s)) |
| elif type(s) is dict: |
| s = dict([(k, self.resolve(v)) for k, v in s.items()]) |
| return s |
| |
| def add_values(self, values: dict): |
| """ |
| Add mappings from `values`. |
| """ |
| for k, v in values.items(): |
| self.values[k] = self.resolve(v) |
| |
| |
| verbosity_level = 0 |
| |
| |
| def sfprint(verbosity: int, *args): |
| """ |
| Print with regards to currently set verbosity level. |
| """ |
| global verbosity_level |
| if verbosity <= verbosity_level: |
| print(*args) |
| |
| |
| def set_verbosity_level(level: int): |
| global verbosity_level |
| verbosity_level = level |
| |
| |
| def get_verbosity_level() -> int: |
| global verbosity_level |
| return verbosity_level |