diff --git a/docs/news.d/961.feature.rst b/docs/news.d/961.feature.rst new file mode 100644 index 000000000..d3b2e886e --- /dev/null +++ b/docs/news.d/961.feature.rst @@ -0,0 +1 @@ +Added python typing for the vunit python API. \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 08cf64b25..09112f8dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,3 +97,6 @@ commands= vcomponents: {envpython} vunit/vhdl/verification_components/run.py --clean coverage: {envpython} -m coverage run --branch --source vunit/ -m pytest tests/ """ +[[tool.mypy.overrides]] +module = "vunit.ui.*" +disallow_untyped_defs = true \ No newline at end of file diff --git a/tests/common.py b/tests/common.py index a6b35df36..e092e2386 100644 --- a/tests/common.py +++ b/tests/common.py @@ -72,7 +72,7 @@ def set_env(**environ): >>> "PLUGINS_DIR" in os.environ False - :type environ: dict[str, unicode] + :type environ: Dict[str, unicode] :param environ: Environment variables to set """ old_environ = dict(os.environ) diff --git a/vunit/configuration.py b/vunit/configuration.py index a6e01a532..43a684d45 100644 --- a/vunit/configuration.py +++ b/vunit/configuration.py @@ -12,6 +12,8 @@ import inspect from pathlib import Path from copy import copy +from collections import OrderedDict +from typing import Any, Callable, List, Union from vunit.sim_if.factory import SIMULATOR_FACTORY @@ -111,7 +113,7 @@ def set_vhdl_configuration_name(self, name): """ self.vhdl_configuration_name = name - def set_generic(self, name, value): + def set_generic(self, name: str, value: Any) -> None: """ Set generic """ @@ -128,7 +130,7 @@ def set_generic(self, name, value): else: self.generics[name] = value - def set_sim_option(self, name, value): + def set_sim_option(self, name: str, value: Union[str, List[str], bool]): """ Set sim option """ @@ -195,11 +197,10 @@ class ConfigurationVisitor(object): def _check_enabled(self): pass - @staticmethod - def get_configuration_dicts(): + def get_configuration_dicts(self) -> "List[OrderedDict[Any, Configuration]]": raise NotImplementedError - def set_attribute(self, name, value): + def set_attribute(self, name: str, value: Any): """ Set attribute """ @@ -226,7 +227,7 @@ def set_vhdl_configuration_name(self, value: str): for config in configs.values(): config.set_vhdl_configuration_name(value) - def set_sim_option(self, name, value, overwrite=True): + def set_sim_option(self, name: str, value: Union[str, List[str], bool], overwrite=True) -> None: """ Set sim option @@ -240,7 +241,7 @@ def set_sim_option(self, name, value, overwrite=True): continue config.set_sim_option(name, value) - def set_pre_config(self, value): + def set_pre_config(self, value: Callable) -> None: """ Set pre_config function """ diff --git a/vunit/library.py b/vunit/library.py index 94142943d..86cee4f75 100644 --- a/vunit/library.py +++ b/vunit/library.py @@ -11,8 +11,13 @@ """ import logging +from typing import List, cast, TYPE_CHECKING +from vunit.design_unit import Entity, VHDLDesignUnit from vunit.vhdl_standard import VHDLStandard +if TYPE_CHECKING: + from vunit.source_file import SourceFile, VHDLSourceFile, VerilogSourceFile + LOGGER = logging.getLogger(__name__) @@ -46,7 +51,7 @@ def __init__(self, name: str, directory: str, vhdl_standard: VHDLStandard, is_ex self._is_external = is_external - def add_source_file(self, source_file): + def add_source_file(self, source_file: "SourceFile") -> "SourceFile": """ Add source file to library unless it exists @@ -104,7 +109,7 @@ def _check_duplication(self, dictionary, design_unit): if design_unit.name in dictionary: self._warning_on_duplication(design_unit, dictionary[design_unit.name].source_file.name) - def add_vhdl_design_units(self, design_units): + def add_vhdl_design_units(self, design_units: List[VHDLDesignUnit]): """ Add VHDL design units to the library """ @@ -119,7 +124,7 @@ def add_vhdl_design_units(self, design_units): self._entities[design_unit.name] = design_unit for architecture in self._architectures[design_unit.name].values(): - design_unit.add_architecture(architecture) + cast(Entity, design_unit).add_architecture(architecture) else: if design_unit.unit_type == "architecture": diff --git a/vunit/project.py b/vunit/project.py index c9bc4f14c..645e18280 100644 --- a/vunit/project.py +++ b/vunit/project.py @@ -9,10 +9,10 @@ """ Functionality to represent and operate on a HDL code project """ -from typing import Optional, Union +from collections import OrderedDict +from typing import List, Optional, Union from pathlib import Path import logging -from collections import OrderedDict from vunit.hashing import hash_string from vunit.dependency_graph import DependencyGraph, CircularDependencyException from vunit.vhdl_parser import VHDLParser @@ -45,15 +45,15 @@ def __init__(self, depend_on_package_body=False, database=None): self._database = database self._vhdl_parser = VHDLParser(database=self._database) self._verilog_parser = VerilogParser(database=self._database) - self._libraries = OrderedDict() + self._libraries: OrderedDict[str, Library] = OrderedDict() # Mapping between library lower case name and real library name self._lower_library_names_dict = {} - self._source_files_in_order = [] + self._source_files_in_order: List[SourceFile] = [] self._manual_dependencies = [] self._depend_on_package_body = depend_on_package_body self._builtin_libraries = set(["ieee", "std"]) - def _validate_new_library_name(self, library_name): + def _validate_new_library_name(self, library_name: str) -> None: """ Check that the library_name is valid or raise RuntimeError """ @@ -74,7 +74,7 @@ def _validate_new_library_name(self, library_name): f"Library name {self._lower_library_names_dict[lower_name]!r} previously defined" ) - def add_builtin_library(self, logical_name): + def add_builtin_library(self, logical_name: str) -> None: """ Add a builtin library name that does not give missing dependency warnings """ @@ -82,7 +82,7 @@ def add_builtin_library(self, logical_name): def add_library( self, - logical_name, + logical_name: str, directory: Union[str, Path], vhdl_standard: VHDLStandard = VHDL.STD_2008, is_external=False, @@ -118,7 +118,7 @@ def add_source_file( # pylint: disable=too-many-arguments defines=None, vhdl_standard: Optional[VHDLStandard] = None, no_parse=False, - ): + ) -> SourceFile: """ Add a file_name as a source file in library_name with file_type @@ -515,7 +515,9 @@ def _get_files_to_recompile(self, files, dependency_graph, incremental): result_list.append(source_file) return result_list - def get_dependencies_in_compile_order(self, target_files=None, implementation_dependencies=False): + def get_dependencies_in_compile_order( + self, target_files=None, implementation_dependencies=False + ) -> List[SourceFile]: """ Get a list of dependencies of target files including the target files. @@ -591,7 +593,7 @@ def comparison_key(source_file): return sorted(files, key=comparison_key) - def get_source_files_in_order(self): + def get_source_files_in_order(self) -> List[SourceFile]: """ Get a list of source files in the order they were added to the project """ diff --git a/vunit/sim_if/__init__.py b/vunit/sim_if/__init__.py index 5596dbd3c..84955408c 100644 --- a/vunit/sim_if/__init__.py +++ b/vunit/sim_if/__init__.py @@ -13,11 +13,13 @@ from os import environ, listdir, pathsep import subprocess from pathlib import Path -from typing import List +from typing import Any, List, Union from ..ostools import Process, simplify_path from ..exceptions import CompileError from ..color_printer import NO_COLOR_PRINTER +OptionType = Union[str, List[str], bool] + class Option(object): """ @@ -184,7 +186,7 @@ def supports_coverage(): """ return False - def merge_coverage(self, file_name, args): # pylint: disable=unused-argument + def merge_coverage(self, file_name, args): """ Hook for simulator interface to creating coverage reports """ @@ -416,7 +418,7 @@ def validate(self, value): raise ValueError(f"Option {self.name!r} must be one of {self._legal_values!s}. Got {value!r}") -def is_string_not_iterable(value): +def is_string_not_iterable(value: Any) -> bool: """ Returns True if value is a string and not another iterable """ diff --git a/vunit/sim_if/factory.py b/vunit/sim_if/factory.py index 0fa9d0d4b..8d30db279 100644 --- a/vunit/sim_if/factory.py +++ b/vunit/sim_if/factory.py @@ -9,13 +9,14 @@ """ import os +from typing import Dict, List, Union from .activehdl import ActiveHDLInterface from .ghdl import GHDLInterface from .incisive import IncisiveInterface from .modelsim import ModelSimInterface from .nvc import NVCInterface from .rivierapro import RivieraProInterface -from . import BooleanOption, ListOfStringOption, VHDLAssertLevelOption +from . import BooleanOption, ListOfStringOption, Option, VHDLAssertLevelOption class SimulatorFactory(object): @@ -37,7 +38,7 @@ def supported_simulators(): NVCInterface, ] - def _extract_compile_options(self): + def _extract_compile_options(self) -> Dict[str, Option]: """ Return all supported compile options """ @@ -51,7 +52,7 @@ def _extract_compile_options(self): result[opt.name] = opt return result - def _extract_sim_options(self): + def _extract_sim_options(self) -> Dict[str, Option]: """ Return all supported sim options """ @@ -75,7 +76,7 @@ def _extract_sim_options(self): return result - def check_sim_option(self, name, value): + def check_sim_option(self, name: str, value: Union[str, List[str], bool]): """ Check that sim_option has legal name and value """ @@ -94,7 +95,7 @@ def check_compile_option_name(self, name): if name not in known_options: raise ValueError(f"Unknown compile_option {name!r}, expected one of {known_options!r}") - def check_compile_option(self, name, value): + def check_compile_option(self, name: str, value: Union[str, List[str], bool]) -> None: """ Check that the compile option is valid """ diff --git a/vunit/source_file.py b/vunit/source_file.py index 85265e7dc..dedd94400 100644 --- a/vunit/source_file.py +++ b/vunit/source_file.py @@ -8,10 +8,11 @@ Functionality to represent and operate on VHDL and Verilog source files """ from pathlib import Path -from typing import Union +from typing import Dict, Union import logging from copy import copy import traceback +from vunit.sim_if import OptionType from vunit.sim_if.factory import SIMULATOR_FACTORY from vunit.hashing import hash_string from vunit.vhdl_parser import VHDLReference @@ -35,7 +36,7 @@ def __init__(self, name, library, file_type): self.file_type = file_type self.design_units = [] self._content_hash = None - self._compile_options = {} + self._compile_options: Dict[str, OptionType] = {} # The file name before preprocessing self.original_name = name @@ -70,14 +71,14 @@ def __hash__(self): def __repr__(self): return f"SourceFile({self.name!s}, {self.library.name!s})" - def set_compile_option(self, name, value): + def set_compile_option(self, name: str, value: OptionType): """ Set compile option """ SIMULATOR_FACTORY.check_compile_option(name, value) self._compile_options[name] = copy(value) - def add_compile_option(self, name, value): + def add_compile_option(self, name: str, value: OptionType): """ Add compile option """ @@ -86,7 +87,7 @@ def add_compile_option(self, name, value): if name not in self._compile_options: self._compile_options[name] = copy(value) else: - self._compile_options[name] += value + self._compile_options[name] += value # type: ignore @property def compile_options(self): @@ -118,6 +119,9 @@ def content_hash(self): """ return hash_string(self._content_hash + self._compile_options_hash()) + def add_to_library(self, library: Library): + raise NotImplementedError + class VerilogSourceFile(SourceFile): """ @@ -347,7 +351,7 @@ def add_to_library(self, library): FILE_TYPES = ("vhdl",) + VERILOG_FILE_TYPES -def file_type_of(file_name): +def file_type_of(file_name: Union[str, Path]) -> str: """ Return the file type of file_name based on the file ending """ diff --git a/vunit/test/bench.py b/vunit/test/bench.py index 90962edb3..a79148355 100644 --- a/vunit/test/bench.py +++ b/vunit/test/bench.py @@ -14,6 +14,7 @@ import bisect import collections from collections import OrderedDict +from typing import Any, Iterable, List, Union from ..ostools import file_exists from ..cached import cached from ..vhdl_parser import remove_comments as remove_vhdl_comments @@ -37,7 +38,7 @@ def __init__(self, design_unit, database=None): self._database = database self._individual_tests = False - self._configs = {} + self._configs: OrderedDict[Any, Configuration] = OrderedDict() self._test_cases = [] self._implicit_test = None @@ -142,7 +143,7 @@ def get_test_case(self, name): return test_case raise KeyError(name) - def get_configuration_dicts(self): # pylint: disable=arguments-differ + def get_configuration_dicts(self) -> "List[OrderedDict[str, Configuration]]": # pylint: disable=arguments-differ """ Get all configurations within the test bench @@ -166,7 +167,7 @@ def _get_configurations_to_run(self): del configs[DEFAULT_NAME] return configs.values() - def scan_tests_from_file(self, file_name): + def scan_tests_from_file(self, file_name: Union[str, Path]) -> None: """ Scan file for test cases and attributes """ @@ -337,7 +338,7 @@ def __init__(self, test, design_unit, enable_configuration, default_config): assert test.is_explicit self.design_unit = design_unit self._enable_configuration = enable_configuration - self._configs = OrderedDict({default_config.name: default_config}) + self._configs: OrderedDict[Any, Configuration] = OrderedDict({default_config.name: default_config}) @property def name(self): @@ -358,13 +359,13 @@ def _check_enabled(self): if not self._enable_configuration: raise RuntimeError("Individual test configuration is not possible with run_all_in_same_sim") - def get_configuration_dicts(self): # pylint: disable=arguments-differ + def get_configuration_dicts(self) -> List[OrderedDict[Any, Configuration]]: # pylint: disable=arguments-differ """ Get all configurations of this test """ return [self._configs] - def _get_configurations_to_run(self): + def _get_configurations_to_run(self) -> Iterable[Configuration]: """ Get all simulation runs for this test bench """ diff --git a/vunit/test/bench_list.py b/vunit/test/bench_list.py index 3b3aaf3a9..c127d7c7f 100644 --- a/vunit/test/bench_list.py +++ b/vunit/test/bench_list.py @@ -10,7 +10,10 @@ import re import logging +from typing import List from collections import OrderedDict + +from vunit.source_file import SourceFile from .list import TestList from .bench import TestBench @@ -23,10 +26,10 @@ class TestBenchList(object): """ def __init__(self, database=None): - self._libraries = OrderedDict() + self._libraries: OrderedDict[str, OrderedDict[str, TestBench]] = OrderedDict() self._database = database - def add_from_source_file(self, source_file): + def add_from_source_file(self, source_file: SourceFile) -> None: """ Scan test benches from the source file and add to test bench list """ @@ -36,7 +39,7 @@ def add_from_source_file(self, source_file): if design_unit.is_module or design_unit.is_entity: self._add_test_bench(TestBench(design_unit, self._database)) - def _add_test_bench(self, test_bench): + def _add_test_bench(self, test_bench: TestBench) -> None: """ Add the test bench """ @@ -44,13 +47,13 @@ def _add_test_bench(self, test_bench): self._libraries[test_bench.library_name] = OrderedDict() self._libraries[test_bench.library_name][test_bench.name] = test_bench - def get_test_bench(self, library_name, name): + def get_test_bench(self, library_name, name) -> TestBench: return self._libraries[library_name][name] - def get_test_benches_in_library(self, library_name): + def get_test_benches_in_library(self, library_name: str) -> List[TestBench]: return list(self._libraries.get(library_name, {}).values()) - def get_test_benches(self): + def get_test_benches(self) -> List[TestBench]: """ Get all test benches """ @@ -60,7 +63,7 @@ def get_test_benches(self): result.append(test_bench) return result - def create_tests(self, simulator_if, elaborate_only): + def create_tests(self, simulator_if, elaborate_only) -> TestList: """ Create all test cases from the test benches """ @@ -69,7 +72,7 @@ def create_tests(self, simulator_if, elaborate_only): test_bench.create_tests(simulator_if, elaborate_only, test_list) return test_list - def warn_when_empty(self): + def warn_when_empty(self) -> None: """ Log a warning when there are no test benches """ diff --git a/vunit/ui/__init__.py b/vunit/ui/__init__.py index 3ddc531e3..f7e6500cd 100644 --- a/vunit/ui/__init__.py +++ b/vunit/ui/__init__.py @@ -10,21 +10,26 @@ Public VUnit User Interface (UI) """ +from __future__ import annotations +from argparse import Namespace import csv import sys import traceback import logging import json import os -from typing import Optional, Set, Union +from typing import Any, Callable, Dict, List, Optional, Set, Union, cast, TYPE_CHECKING from pathlib import Path from fnmatch import fnmatch +from vunit.test.list import TestList + +from vunit.ui.preprocessor import Preprocessor from ..database import PickledDataBase, DataBase from .. import ostools from ..vunit_cli import VUnitCLI from ..sim_if.factory import SIMULATOR_FACTORY -from ..sim_if import SimulatorInterface +from ..sim_if import OptionType, SimulatorInterface from ..color_printer import COLOR_PRINTER, NO_COLOR_PRINTER from ..project import Project @@ -44,6 +49,10 @@ from .results import Results +if TYPE_CHECKING: + from vunit.source_file import SourceFile as Source_File + + class VUnit(object): # pylint: disable=too-many-instance-attributes, too-many-public-methods """ The public interface of VUnit @@ -58,9 +67,9 @@ class VUnit(object): # pylint: disable=too-many-instance-attributes, too-many-p @classmethod def from_argv( cls, - argv=None, + argv: Optional[Namespace] = None, vhdl_standard: Optional[str] = None, - ): + ) -> VUnit: """ Create VUnit instance from command line arguments. @@ -89,9 +98,9 @@ def from_argv( @classmethod def from_args( cls, - args, + args: Namespace, vhdl_standard: Optional[str] = None, - ): + ) -> "VUnit": """ Create VUnit instance from args namespace. Intended for users who adds custom command line options. @@ -113,9 +122,9 @@ def from_args( def __init__( self, - args, + args: Namespace, vhdl_standard: Optional[str] = None, - ): + ) -> None: self._args = args self._configure_logging(args.log_level) self._output_path = str(Path(args.output_path).resolve()) @@ -125,7 +134,7 @@ def __init__( else: self._printer = COLOR_PRINTER - def test_filter(name, attribute_names): + def test_filter(name: str, attribute_names: set) -> bool: keep = any(fnmatch(name, pattern) for pattern in args.test_patterns) if args.with_attributes is not None: @@ -162,7 +171,7 @@ def test_filter(name, attribute_names): self._builtins = Builtins(self, self._vhdl_standard, simulator_class) - def _create_database(self): + def _create_database(self) -> PickledDataBase: """ Create a persistent database to store expensive parse results @@ -185,12 +194,12 @@ def _create_database(self): if create_new: database = DataBase(project_database_file_name, new=True) - database[key] = version + cast(DataBase, database)[key] = version return PickledDataBase(database) @staticmethod - def _configure_logging(log_level): + def _configure_logging(log_level: str) -> None: """ Configure logging based on log_level string """ @@ -207,7 +216,9 @@ def _which_vhdl_standard(self, vhdl_standard: Optional[str]) -> VHDLStandard: return VHDL.standard(vhdl_standard) - def add_external_library(self, library_name, path: Union[str, Path], vhdl_standard: Optional[str] = None): + def add_external_library( + self, library_name: str, path: Union[str, Path], vhdl_standard: Optional[str] = None + ) -> Library: """ Add an externally compiled library as a black-box @@ -233,7 +244,9 @@ def add_external_library(self, library_name, path: Union[str, Path], vhdl_standa ) return self.library(library_name) - def add_source_files_from_csv(self, project_csv_path: Union[str, Path], vhdl_standard: Optional[str] = None): + def add_source_files_from_csv( + self, project_csv_path: Union[str, Path], vhdl_standard: Optional[str] = None + ) -> SourceFileList: """ Add a project configuration, mapping all the libraries and files @@ -269,8 +282,8 @@ def add_library( self, library_name: str, vhdl_standard: Optional[str] = None, - allow_duplicate: Optional[bool] = False, - ): + allow_duplicate: bool = False, + ) -> Library: """ Add a library managed by VUnit. @@ -297,7 +310,7 @@ def add_library( raise ValueError(f"Library {library_name!s} already added. Use allow_duplicate to ignore this error.") return self.library(library_name) - def library(self, library_name: str): + def library(self, library_name: str) -> Library: """ Get a library @@ -310,9 +323,9 @@ def library(self, library_name: str): def get_libraries( self, - pattern="*", - allow_empty: Optional[bool] = False, - ): + pattern: str = "*", + allow_empty: bool = False, + ) -> LibraryList: """ Get a list of libraries @@ -320,7 +333,7 @@ def get_libraries( :param allow_empty: To disable an error if no labraries matched the pattern :returns: A :class:`.LibraryList` object """ - results = [] + results: List[Library] = [] for library in self._project.get_libraries(): if not fnmatch(library.name, pattern): @@ -332,7 +345,7 @@ def get_libraries( return LibraryList(results) - def set_attribute(self, name: str, value: str, allow_empty: Optional[bool] = False): + def set_attribute(self, name: str, value: str, allow_empty: bool = False) -> None: """ Set a value of attribute in all |configurations| @@ -353,7 +366,7 @@ def set_attribute(self, name: str, value: str, allow_empty: Optional[bool] = Fal for test_bench in check_not_empty(test_benches, allow_empty, "No test benches found"): test_bench.set_attribute(name, value) - def set_generic(self, name: str, value: str, allow_empty: Optional[bool] = False): + def set_generic(self, name: str, value: str, allow_empty: bool = False) -> None: """ Set a value of generic in all |configurations| @@ -374,7 +387,7 @@ def set_generic(self, name: str, value: str, allow_empty: Optional[bool] = False for test_bench in check_not_empty(test_benches, allow_empty, "No test benches found"): test_bench.set_generic(name.lower(), value) - def set_parameter(self, name: str, value: str, allow_empty: Optional[bool] = False): + def set_parameter(self, name: str, value: Union[str, int], allow_empty: bool = False) -> None: """ Set value of parameter in all |configurations| @@ -398,10 +411,10 @@ def set_parameter(self, name: str, value: str, allow_empty: Optional[bool] = Fal def set_sim_option( self, name: str, - value: str, - allow_empty: Optional[bool] = False, - overwrite: Optional[bool] = True, - ): + value: OptionType, + allow_empty: bool = False, + overwrite: bool = True, + ) -> None: """ Set simulation option in all |configurations| @@ -423,7 +436,7 @@ def set_sim_option( for test_bench in check_not_empty(test_benches, allow_empty, "No test benches found"): test_bench.set_sim_option(name, value, overwrite) - def set_compile_option(self, name: str, value: str, allow_empty: Optional[bool] = False): + def set_compile_option(self, name: str, value: OptionType, allow_empty: bool = False) -> None: """ Set compile option of all files @@ -445,7 +458,7 @@ def set_compile_option(self, name: str, value: str, allow_empty: Optional[bool] for source_file in check_not_empty(source_files, allow_empty, "No source files found"): source_file.set_compile_option(name, value) - def add_compile_option(self, name: str, value: str, allow_empty: Optional[bool] = False): + def add_compile_option(self, name: str, value: OptionType, allow_empty: bool = False) -> None: """ Add compile option to all files @@ -460,7 +473,7 @@ def add_compile_option(self, name: str, value: str, allow_empty: Optional[bool] for source_file in check_not_empty(source_files, allow_empty, "No source files found"): source_file.add_compile_option(name, value) - def get_source_file(self, file_name: Union[str, Path], library_name: Optional[str] = None): + def get_source_file(self, file_name: Union[str, Path], library_name: Optional[str] = None) -> SourceFile: """ Get a source file @@ -483,10 +496,10 @@ def get_source_file(self, file_name: Union[str, Path], library_name: Optional[st def get_source_files( self, - pattern="*", + pattern: str = "*", library_name: Optional[str] = None, - allow_empty: Optional[bool] = False, - ): + allow_empty: bool = False, + ) -> SourceFileList: """ Get a list of source files @@ -520,16 +533,16 @@ def get_source_files( def add_source_files( # pylint: disable=too-many-arguments self, - pattern, + pattern: Union[str, Path], library_name: str, - preprocessors=None, - include_dirs=None, - defines=None, - allow_empty: Optional[bool] = False, + preprocessors: Optional[List[Preprocessor]] = None, + include_dirs: Optional[list] = None, + defines: Optional[dict] = None, + allow_empty: bool = False, vhdl_standard: Optional[str] = None, - no_parse: Optional[bool] = False, - file_type=None, - ): + no_parse: bool = False, + file_type: Optional[str] = None, + ) -> SourceFileList: """ Add source files matching wildcard pattern to library @@ -567,13 +580,13 @@ def add_source_file( # pylint: disable=too-many-arguments self, file_name: Union[str, Path], library_name: str, - preprocessors=None, - include_dirs=None, - defines=None, + preprocessors: Optional[List[Preprocessor]] = None, + include_dirs: Optional[List[Any]] = None, + defines: Optional[dict] = None, vhdl_standard: Optional[str] = None, - no_parse: Optional[bool] = False, - file_type=None, - ): + no_parse: bool = False, + file_type: Optional[str] = None, + ) -> SourceFile: """ Add source file to library @@ -605,7 +618,9 @@ def add_source_file( # pylint: disable=too-many-arguments file_type=file_type, ) - def _preprocess(self, library_name: str, file_name: Union[str, Path], preprocessors): + def _preprocess( + self, library_name: str, file_name: Union[str, Path], preprocessors: Optional[List[Preprocessor]] + ) -> str: """ Preprocess file_name within library_name using explicit preprocessors if preprocessors is None then use implicit globally defined preprocessors. @@ -648,7 +663,7 @@ def _preprocess(self, library_name: str, file_name: Union[str, Path], preprocess ostools.write_file(pp_file_name, code, encoding=HDL_FILE_ENCODING) return pp_file_name - def add_preprocessor(self, preprocessor): + def add_preprocessor(self, preprocessor: Preprocessor) -> None: """ Adds a custom preprocessor to be used on all files. Must be called before adding any files. @@ -656,7 +671,12 @@ def add_preprocessor(self, preprocessor): """ self._preprocessors.append((preprocessor)) - def enable_location_preprocessing(self, additional_subprograms=None, exclude_subprograms=None, order=100): + def enable_location_preprocessing( + self, + additional_subprograms: Optional[List[str]] = None, + exclude_subprograms: Optional[List[str]] = None, + order: int = 100, + ) -> None: """ Inserts file name and line number information into VUnit check and log subprograms calls. Custom subprograms can also be added. Must be called before adding any files. @@ -686,7 +706,7 @@ def enable_location_preprocessing(self, additional_subprograms=None, exclude_sub self.add_preprocessor(preprocessor) - def enable_check_preprocessing(self, order=200): + def enable_check_preprocessing(self, order: int = 200) -> None: """ Inserts error context information into VUnit check_relation calls. @@ -696,7 +716,7 @@ def enable_check_preprocessing(self, order=200): """ self.add_preprocessor(CheckPreprocessor(order)) - def main(self, post_run=None): + def main(self, post_run: Optional[Callable[[Results], None]] = None) -> None: """ Run vunit main function and exit @@ -723,7 +743,7 @@ def main(self, post_run=None): sys.exit(0) - def _create_tests(self, simulator_if: Union[None, SimulatorInterface]): + def _create_tests(self, simulator_if: Optional[SimulatorInterface]) -> TestList: """ Create the test cases """ @@ -732,7 +752,7 @@ def _create_tests(self, simulator_if: Union[None, SimulatorInterface]): test_list.keep_matches(self._test_filter) return test_list - def _main(self, post_run): + def _main(self, post_run: Optional[Callable[[Results], None]]) -> bool: """ Base vunit main function without performing exit """ @@ -752,7 +772,7 @@ def _main(self, post_run): all_ok = self._main_run(post_run) return all_ok - def _create_simulator_if(self): + def _create_simulator_if(self) -> SimulatorInterface: """ Create new simulator instance """ @@ -770,7 +790,7 @@ def _create_simulator_if(self): return self._simulator_class.from_args(args=self._args, output_path=self._simulator_output_path) - def _main_run(self, post_run): + def _main_run(self, post_run: Optional[Callable[[Results], None]]) -> bool: """ Main with running tests """ @@ -794,7 +814,7 @@ def _main_run(self, post_run): report.print_str() if post_run is not None: - post_run(results=Results(self._output_path, simulator_if, report)) + post_run(Results(self._output_path, simulator_if, report)) del simulator_if @@ -804,7 +824,7 @@ def _main_run(self, post_run): return report.all_ok() - def _main_list_only(self): + def _main_list_only(self) -> bool: """ Main function when only listing test cases """ @@ -814,7 +834,7 @@ def _main_list_only(self): print(f"Listed {test_list.num_tests} tests") return True - def _main_export_json(self, json_file_name: Union[str, Path]): # pylint: disable=too-many-locals + def _main_export_json(self, json_file_name: Union[str, Path]) -> bool: # pylint: disable=too-many-locals """ Main function when exporting to JSON """ @@ -869,7 +889,7 @@ def _main_export_json(self, json_file_name: Union[str, Path]): # pylint: disabl return True - def _main_list_files_only(self): + def _main_list_files_only(self) -> bool: """ Main function when only listing files """ @@ -879,7 +899,7 @@ def _main_list_files_only(self): print(f"Listed {len(files)} files") return True - def _main_compile_only(self): + def _main_compile_only(self) -> bool: """ Main function when only compiling """ @@ -887,7 +907,7 @@ def _main_compile_only(self): self._compile(simulator_if) return True - def _create_output_path(self, clean: bool): + def _create_output_path(self, clean: bool) -> None: """ Create or re-create the output path if necessary """ @@ -903,14 +923,14 @@ def vhdl_standard(self) -> str: return str(self._vhdl_standard) @property - def _preprocessed_path(self): + def _preprocessed_path(self) -> str: return str(Path(self._output_path) / "preprocessed") @property - def codecs_path(self): + def codecs_path(self) -> str: return str(Path(self._output_path) / "codecs") - def _compile(self, simulator_if: SimulatorInterface): + def _compile(self, simulator_if: SimulatorInterface) -> None: """ Compile entire project """ @@ -927,7 +947,7 @@ def _compile(self, simulator_if: SimulatorInterface): target_files=target_files, ) - def _get_testbench_files(self, simulator_if: Union[None, SimulatorInterface]): + def _get_testbench_files(self, simulator_if: Union[None, SimulatorInterface]) -> List["Source_File"]: """ Return the list of all test bench files for the currently selected tests to run """ @@ -938,7 +958,7 @@ def _get_testbench_files(self, simulator_if: Union[None, SimulatorInterface]): for file_name in tb_file_names ] - def _run_test(self, test_cases, report): + def _run_test(self, test_cases: TestList, report: TestReport) -> None: """ Run the test suites and return the report """ @@ -961,7 +981,7 @@ def _run_test(self, test_cases, report): ) runner.run(test_cases) - def add_verilog_builtins(self): + def add_verilog_builtins(self) -> None: """ Add VUnit Verilog builtin libraries. @@ -972,7 +992,9 @@ def add_verilog_builtins(self): """ self._builtins.add_verilog_builtins() - def add_vhdl_builtins(self, external=None, use_external_log=None): + def add_vhdl_builtins( + self, external: Optional[Dict[str, List[str]]] = None, use_external_log: Optional[Union[str, Path]] = None + ) -> None: """ Add VUnit VHDL builtin libraries. @@ -996,43 +1018,43 @@ def add_vhdl_builtins(self, external=None, use_external_log=None): """ self._builtins.add_vhdl_builtins(external=external, use_external_log=use_external_log) - def add_com(self): + def add_com(self) -> None: """ Add communication package """ self._builtins.add("com") - def add_array_util(self): + def add_array_util(self) -> None: """ Add array util """ self._builtins.add("array_util") - def add_random(self): + def add_random(self) -> None: """ Add random """ self._builtins.add("random") - def add_verification_components(self): + def add_verification_components(self) -> None: """ Add verification component library """ self._builtins.add("verification_components") - def add_osvvm(self): + def add_osvvm(self) -> None: """ Add osvvm library """ self._builtins.add("osvvm") - def add_json4vhdl(self): + def add_json4vhdl(self) -> None: """ Add JSON-for-VHDL library """ self._builtins.add("json4vhdl") - def get_compile_order(self, source_files=None): + def get_compile_order(self, source_files: Optional[List[SourceFile]] = None) -> SourceFileList: """ Get the compile order of all or specific source files and their dependencies. @@ -1053,10 +1075,10 @@ def get_compile_order(self, source_files=None): source_files = self.get_source_files(allow_empty=True) target_files = [source_file._source_file for source_file in source_files] # pylint: disable=protected-access - source_files = self._project.get_dependencies_in_compile_order(target_files) - return SourceFileList([SourceFile(source_file, self._project, self) for source_file in source_files]) + source_files_ordered = self._project.get_dependencies_in_compile_order(target_files) + return SourceFileList([SourceFile(source_file, self._project, self) for source_file in source_files_ordered]) - def get_implementation_subset(self, source_files): + def get_implementation_subset(self, source_files: List[SourceFile]) -> SourceFileList: """ Get the subset of files which are required to successfully elaborate the list of input files without any missing @@ -1066,10 +1088,12 @@ def get_implementation_subset(self, source_files): :returns: A list of :class:`.SourceFile` objects which is the implementation subset. """ target_files = [source_file._source_file for source_file in source_files] # pylint: disable=protected-access - source_files = self._project.get_dependencies_in_compile_order(target_files, implementation_dependencies=True) - return SourceFileList([SourceFile(source_file, self._project, self) for source_file in source_files]) + source_files_ordered = self._project.get_dependencies_in_compile_order( + target_files, implementation_dependencies=True + ) + return SourceFileList([SourceFile(source_file, self._project, self) for source_file in source_files_ordered]) - def get_simulator_name(self): + def get_simulator_name(self) -> Optional[str]: """ Get the name of the simulator used. @@ -1079,7 +1103,7 @@ def get_simulator_name(self): return None return self._simulator_class.name - def simulator_supports_coverage(self): + def simulator_supports_coverage(self) -> Union[None, bool]: """ Returns True when the simulator supports coverage diff --git a/vunit/ui/common.py b/vunit/ui/common.py index 3ba87335a..081141da2 100644 --- a/vunit/ui/common.py +++ b/vunit/ui/common.py @@ -12,8 +12,8 @@ from glob import glob from os import environ from logging import getLogger -from typing import Optional, List -from ..sim_if import is_string_not_iterable +from typing import Dict, Iterable, Optional, List, TypeVar, Union + from ..vhdl_standard import VHDL, VHDLStandard LOGGER = getLogger(__name__) @@ -35,7 +35,10 @@ def select_vhdl_standard(vhdl_standard: Optional[str] = None) -> VHDLStandard: raise -def lower_generics(generics): +T = TypeVar("T") + + +def lower_generics(generics: Dict[str, T]) -> Dict[str, T]: """ Convert all generics names to lower case to match internal representation. @TODO Maybe warn in case of conflict. VHDL forbids this though so the user will notice anyway. @@ -43,7 +46,7 @@ def lower_generics(generics): return dict((name.lower(), value) for name, value in generics.items()) -def check_not_empty(lst, allow_empty, error_msg): +def check_not_empty(lst: List[T], allow_empty: bool, error_msg: str) -> List[T]: """ Raise ValueError if the list is empty unless allow_empty is True Returns the list @@ -53,12 +56,12 @@ def check_not_empty(lst, allow_empty, error_msg): return lst -def get_checked_file_names_from_globs(pattern, allow_empty): +def get_checked_file_names_from_globs(pattern: Union[Iterable[str], str, Path], allow_empty: bool) -> List[str]: """ Get file names from globs and check that exist """ - if is_string_not_iterable(pattern): - patterns = [pattern] + if isinstance(pattern, str): + patterns: Iterable[str] = [pattern] elif isinstance(pattern, Path): patterns = [str(pattern)] else: diff --git a/vunit/ui/library.py b/vunit/ui/library.py index 475a4c237..64ed7c35a 100644 --- a/vunit/ui/library.py +++ b/vunit/ui/library.py @@ -10,7 +10,12 @@ from pathlib import Path from fnmatch import fnmatch -from typing import Optional +from typing import Any, List, Optional, Union +from typing import TYPE_CHECKING + +from ..sim_if import OptionType +from ..test.bench_list import TestBenchList +from .preprocessor import Preprocessor from ..vhdl_standard import VHDL, VHDLStandard from ..project import Project from ..source_file import file_type_of, FILE_TYPES, VERILOG_FILE_TYPES @@ -20,179 +25,8 @@ from .testbench import TestBench from .packagefacade import PackageFacade - -class LibraryList(list): - """ - A list of :class:`.Library` - """ - - def __init__(self, libraries): - list.__init__(self, libraries) - - def get_test_benches(self, pattern="*", allow_empty=False): - """ - Get a list of test benches - - :param pattern: A wildcard pattern matching the test_bench name - :param allow_empty: To disable an error when no test benches were found - :returns: A list of :class:`.TestBench` objects - """ - results = [] - for library in self: - results += library.get_test_benches(pattern, allow_empty=True) - - return check_not_empty( - results, - allow_empty, - "No testbenches found within libraries", - ) - - def set_generic(self, name, value, allow_empty=False): - """ - Set a value of generic within all |configurations| of test benches and tests in these libraries - - :param name: The name of the generic - :param value: The value of the generic - :param allow_empty: To disable an error when no test benches were found - - :example: - - .. code-block:: python - - libs.set_generic("data_width", 16) - - .. note:: - Only affects test benches added *before* the generic is set. - """ - check_not_empty( - self.get_test_benches(allow_empty=True), - allow_empty, - "No testbenches in libraries", - ) - - for library in self: - library.set_generic(name, value, allow_empty=True) - - def set_parameter(self, name, value, allow_empty=False): - """ - Set a value of parameter within all |configurations| of test benches and tests in these libraries - - :param name: The name of the parameter - :param value: The value of the parameter - :param allow_empty: To disable an error when no test benches were found - - :example: - - .. code-block:: python - - libs.set_parameter("data_width", 16) - - .. note:: - Only affects test benches added *before* the parameter is set. - """ - check_not_empty( - self.get_test_benches(allow_empty=True), - allow_empty, - "No testbenches in libraries", - ) - - for library in self: - library.set_parameter(name, value, allow_empty=True) - - def set_sim_option(self, name, value, allow_empty=False, overwrite=True): - """ - Set simulation option within all |configurations| of test benches and tests in these libraries. - - :param name: |simulation_options| - :param value: The value of the simulation option - :param allow_empty: To disable an error when no test benches were found - :param overwrite: To overwrite the option or append to the existing value - - :example: - - .. code-block:: python - - libs.set_sim_option("ghdl.a_flags", ["--no-vital-checks"]) - - .. note:: - Only affects test benches added *before* the option is set. - """ - check_not_empty( - self.get_test_benches(allow_empty=True), - allow_empty, - "No testbenches in libraries", - ) - - for library in self: - library.set_sim_option(name, value, allow_empty=True, overwrite=overwrite) - - def get_source_files(self, pattern="*", allow_empty=False): - """ - Get a list of source files within these libraries - - :param pattern: A wildcard pattern matching either an absolute or relative path - :param allow_empty: To disable an error if no files matched the pattern - :returns: A :class:`.SourceFileList` object - """ - - results = [ - source_file for library in self for source_file in library.get_source_files(pattern, allow_empty=True) - ] - - check_not_empty( - results, - allow_empty, - f"Pattern {pattern} did not match any file", - ) - - return SourceFileList(results) - - def set_compile_option(self, name, value, allow_empty=False): - """ - Set compile option for all files within these libraries - - :param name: |compile_option| - :param value: The value of the compile option - :param allow_empty: To disable an error when no source files were found - - :example: - - .. code-block:: python - - libs.set_compile_option("ghdl.a_flags", ["--no-vital-checks"]) - - - .. note:: - Only affects files added *before* the option is set. - """ - check_not_empty( - self.get_source_files(allow_empty=True), - allow_empty, - "No source files in libraries", - ) - - for library in self: - library.set_compile_option(name, value, allow_empty=True) - - def add_compile_option(self, name, value, allow_empty=False): - """ - Add compile option to all files within these libraries - - :param name: |compile_option| - :param value: The value of the compile option - :param allow_empty: To disable an error when no source files were found - - .. note:: - Only affects files added *before* the option is set. - """ - check_not_empty( - self.get_source_files(allow_empty=True), - allow_empty, - "No source files in libraries", - ) - - for library in self: - library.add_compile_option(name, value, allow_empty=True) +if TYPE_CHECKING: + from vunit.ui import VUnit class Library(object): @@ -200,20 +34,20 @@ class Library(object): User interface of a library """ - def __init__(self, library_name, parent, project: Project, test_bench_list): + def __init__(self, library_name: str, parent: "VUnit", project: Project, test_bench_list: TestBenchList) -> None: self._library_name = library_name self._parent = parent self._project = project self._test_bench_list = test_bench_list @property - def name(self): + def name(self) -> str: """ The name of the library """ return self._library_name - def set_generic(self, name, value, allow_empty=False): + def set_generic(self, name: str, value: Any, allow_empty: bool = False) -> None: """ Set a value of generic within all |configurations| of test benches and tests this library @@ -233,7 +67,7 @@ def set_generic(self, name, value, allow_empty=False): for test_bench in self.get_test_benches(allow_empty=allow_empty): test_bench.set_generic(name.lower(), value) - def set_parameter(self, name, value, allow_empty=False): + def set_parameter(self, name: str, value: Any, allow_empty: bool = False) -> None: """ Set a value of parameter within all |configurations| of test benches and tests this library @@ -253,7 +87,7 @@ def set_parameter(self, name, value, allow_empty=False): for test_bench in self.get_test_benches(allow_empty=allow_empty): test_bench.set_generic(name, value) - def set_sim_option(self, name, value, allow_empty=False, overwrite=True): + def set_sim_option(self, name: str, value: OptionType, allow_empty: bool = False, overwrite: bool = True) -> None: """ Set simulation option within all |configurations| of test benches and tests this library @@ -274,7 +108,7 @@ def set_sim_option(self, name, value, allow_empty=False, overwrite=True): for test_bench in self.get_test_benches(allow_empty=allow_empty): test_bench.set_sim_option(name, value, overwrite) - def set_compile_option(self, name, value, allow_empty=False): + def set_compile_option(self, name: str, value: OptionType, allow_empty: bool = False) -> None: """ Set compile option for all files within the library @@ -295,7 +129,7 @@ def set_compile_option(self, name, value, allow_empty=False): for source_file in self.get_source_files(allow_empty=allow_empty): source_file.set_compile_option(name, value) - def add_compile_option(self, name, value, allow_empty=False): + def add_compile_option(self, name: str, value: OptionType, allow_empty: bool = False) -> None: """ Add compile option to all files within the library @@ -309,7 +143,7 @@ def add_compile_option(self, name, value, allow_empty=False): for source_file in self.get_source_files(allow_empty=allow_empty): source_file.add_compile_option(name, value) - def get_source_file(self, file_name): + def get_source_file(self, file_name: str) -> SourceFile: """ Get a source file within this library @@ -319,7 +153,7 @@ def get_source_file(self, file_name): """ return self._parent.get_source_file(file_name, self._library_name) - def get_source_files(self, pattern="*", allow_empty=False): + def get_source_files(self, pattern: str = "*", allow_empty: bool = False) -> SourceFileList: """ Get a list of source files within this libary @@ -331,15 +165,15 @@ def get_source_files(self, pattern="*", allow_empty=False): def add_source_files( # pylint: disable=too-many-arguments self, - pattern, - preprocessors=None, - include_dirs=None, - defines=None, - allow_empty=False, + pattern: Union[str, Path], + preprocessors: Optional[List[Preprocessor]] = None, + include_dirs: Optional[List[str]] = None, + defines: Optional[dict] = None, + allow_empty: bool = False, vhdl_standard: Optional[str] = None, - no_parse=False, - file_type=None, - ): + no_parse: bool = False, + file_type: Optional[str] = None, + ) -> SourceFileList: """ Add source files matching wildcard pattern to library @@ -361,7 +195,7 @@ def add_source_files( # pylint: disable=too-many-arguments """ return SourceFileList( - source_files=[ + [ self.add_source_file( file_name, preprocessors, @@ -377,14 +211,14 @@ def add_source_files( # pylint: disable=too-many-arguments def add_source_file( # pylint: disable=too-many-arguments self, - file_name, - preprocessors=None, - include_dirs=None, - defines=None, + file_name: Union[str, Path], + preprocessors: Optional[List[Preprocessor]] = None, + include_dirs: Optional[List[str]] = None, + defines: Optional[dict] = None, vhdl_standard: Optional[str] = None, - no_parse=False, - file_type=None, - ): + no_parse: bool = False, + file_type: Optional[str] = None, + ) -> SourceFile: """ Add source file to library @@ -435,7 +269,7 @@ def add_source_file( # pylint: disable=too-many-arguments return SourceFile(source_file, self._project, self._parent) - def package(self, name): + def package(self, name: str) -> PackageFacade: """ Get a package within the library """ @@ -449,7 +283,7 @@ def package(self, name): return PackageFacade(self._parent, self._library_name, name, design_unit) - def entity(self, name): + def entity(self, name: str) -> TestBench: """ Get an entity within the library @@ -464,7 +298,7 @@ def entity(self, name): return self.test_bench(name) - def module(self, name): + def module(self, name: str) -> TestBench: """ Get a module within the library @@ -478,7 +312,7 @@ def module(self, name): return self.test_bench(name) - def test_bench(self, name): + def test_bench(self, name: str) -> TestBench: """ Get a test bench within this library @@ -490,7 +324,7 @@ def test_bench(self, name): return TestBench(self._test_bench_list.get_test_bench(self._library_name, name), self) - def get_test_benches(self, pattern="*", allow_empty=False): + def get_test_benches(self, pattern: str = "*", allow_empty: bool = False) -> List[TestBench]: """ Get a list of test benches @@ -500,7 +334,7 @@ def get_test_benches(self, pattern="*", allow_empty=False): """ results = [] for test_bench in self._test_bench_list.get_test_benches_in_library(self._library_name): - if not fnmatch(Path(test_bench.name).resolve(), pattern): + if not fnmatch(str(Path(test_bench.name).resolve()), pattern): continue results.append(TestBench(test_bench, self)) @@ -520,3 +354,174 @@ def _which_vhdl_standard(self, vhdl_standard: Optional[str]) -> VHDLStandard: return self._project.get_library(self._library_name).vhdl_standard return VHDL.standard(vhdl_standard) + + +class LibraryList(List[Library]): + """ + A list of :class:`.Library` + """ + + def get_test_benches(self, pattern: str = "*", allow_empty: bool = False) -> List[TestBench]: + """ + Get a list of test benches + + :param pattern: A wildcard pattern matching the test_bench name + :param allow_empty: To disable an error when no test benches were found + :returns: A list of :class:`.TestBench` objects + """ + results = [] + for library in self: + results += library.get_test_benches(pattern, allow_empty=True) + + return check_not_empty( + results, + allow_empty, + "No testbenches found within libraries", + ) + + def set_generic(self, name: str, value: Any, allow_empty: bool = False) -> None: + """ + Set a value of generic within all |configurations| of test benches and tests in these libraries + + :param name: The name of the generic + :param value: The value of the generic + :param allow_empty: To disable an error when no test benches were found + + :example: + + .. code-block:: python + + libs.set_generic("data_width", 16) + + .. note:: + Only affects test benches added *before* the generic is set. + """ + check_not_empty( + self.get_test_benches(allow_empty=True), + allow_empty, + "No testbenches in libraries", + ) + + for library in self: + library.set_generic(name, value, allow_empty=True) + + def set_parameter(self, name: str, value: Any, allow_empty: bool = False) -> None: + """ + Set a value of parameter within all |configurations| of test benches and tests in these libraries + + :param name: The name of the parameter + :param value: The value of the parameter + :param allow_empty: To disable an error when no test benches were found + + :example: + + .. code-block:: python + + libs.set_parameter("data_width", 16) + + .. note:: + Only affects test benches added *before* the parameter is set. + """ + check_not_empty( + self.get_test_benches(allow_empty=True), + allow_empty, + "No testbenches in libraries", + ) + + for library in self: + library.set_parameter(name, value, allow_empty=True) + + def set_sim_option(self, name: str, value: OptionType, allow_empty: bool = False, overwrite: bool = True) -> None: + """ + Set simulation option within all |configurations| of test benches and tests in these libraries. + + :param name: |simulation_options| + :param value: The value of the simulation option + :param allow_empty: To disable an error when no test benches were found + :param overwrite: To overwrite the option or append to the existing value + + :example: + + .. code-block:: python + + libs.set_sim_option("ghdl.a_flags", ["--no-vital-checks"]) + + .. note:: + Only affects test benches added *before* the option is set. + """ + check_not_empty( + self.get_test_benches(allow_empty=True), + allow_empty, + "No testbenches in libraries", + ) + + for library in self: + library.set_sim_option(name, value, allow_empty=True, overwrite=overwrite) + + def get_source_files(self, pattern: str = "*", allow_empty: bool = False) -> SourceFileList: + """ + Get a list of source files within these libraries + + :param pattern: A wildcard pattern matching either an absolute or relative path + :param allow_empty: To disable an error if no files matched the pattern + :returns: A :class:`.SourceFileList` object + """ + + results = [ + source_file for library in self for source_file in library.get_source_files(pattern, allow_empty=True) + ] + + check_not_empty( + results, + allow_empty, + f"Pattern {pattern} did not match any file", + ) + + return SourceFileList(results) + + def set_compile_option(self, name: str, value: OptionType, allow_empty: bool = False) -> None: + """ + Set compile option for all files within these libraries + + :param name: |compile_option| + :param value: The value of the compile option + :param allow_empty: To disable an error when no source files were found + + :example: + + .. code-block:: python + + libs.set_compile_option("ghdl.a_flags", ["--no-vital-checks"]) + + + .. note:: + Only affects files added *before* the option is set. + """ + check_not_empty( + self.get_source_files(allow_empty=True), + allow_empty, + "No source files in libraries", + ) + + for library in self: + library.set_compile_option(name, value, allow_empty=True) + + def add_compile_option(self, name: str, value: OptionType, allow_empty: bool = False) -> None: + """ + Add compile option to all files within these libraries + + :param name: |compile_option| + :param value: The value of the compile option + :param allow_empty: To disable an error when no source files were found + + .. note:: + Only affects files added *before* the option is set. + """ + check_not_empty( + self.get_source_files(allow_empty=True), + allow_empty, + "No source files in libraries", + ) + + for library in self: + library.add_compile_option(name, value, allow_empty=True) diff --git a/vunit/ui/packagefacade.py b/vunit/ui/packagefacade.py index 7c6d6d04b..0bc4e7960 100644 --- a/vunit/ui/packagefacade.py +++ b/vunit/ui/packagefacade.py @@ -9,21 +9,33 @@ """ from pathlib import Path +from typing import List, Optional, Union, TYPE_CHECKING + +from vunit.design_unit import DesignUnit +from vunit.ui.source import SourceFileList from ..com import codec_generator +if TYPE_CHECKING: + from vunit.ui import VUnit + class PackageFacade(object): """ User interface of a Package """ - def __init__(self, parent, library_name, package_name, design_unit): + def __init__(self, parent: "VUnit", library_name: str, package_name: str, design_unit: DesignUnit) -> None: self._parent = parent self._library_name = library_name self._package_name = package_name self._design_unit = design_unit - def generate_codecs(self, codec_package_name=None, used_packages=None, output_file_name=None): + def generate_codecs( + self, + codec_package_name: Optional[str] = None, + used_packages: Optional[List[str]] = None, + output_file_name: Optional[Union[str, Path]] = None, + ) -> SourceFileList: """ Generates codecs for the datatypes in this Package """ diff --git a/vunit/ui/preprocessor.py b/vunit/ui/preprocessor.py index 0701a8dcb..615a5b160 100644 --- a/vunit/ui/preprocessor.py +++ b/vunit/ui/preprocessor.py @@ -22,7 +22,7 @@ def __init__(self, order: int = 0) -> None: """ self.order = order - def run(self, code: str, file_name: str) -> str: # pylint: disable=unused-argument + def run(self, code: str, file_name: str) -> str: # pylint: disable=unused-argument,no-self-use """ Preprocess code. diff --git a/vunit/ui/results.py b/vunit/ui/results.py index ea2ddfd6b..528b58c1a 100644 --- a/vunit/ui/results.py +++ b/vunit/ui/results.py @@ -9,21 +9,25 @@ """ from pathlib import Path -from typing import Dict, Union +from typing import Any, Dict, List, Optional, Union, TYPE_CHECKING from .common import TEST_OUTPUT_PATH +if TYPE_CHECKING: + from vunit.sim_if import SimulatorInterface + from vunit.test.report import TestReport + class Results(object): """ Gives access to results after running tests """ - def __init__(self, output_path, simulator_if, report): + def __init__(self, output_path: str, simulator_if: "SimulatorInterface", report: "TestReport") -> None: self._output_path = output_path self._simulator_if = simulator_if self._report = report - def merge_coverage(self, file_name, args=None): + def merge_coverage(self, file_name: str, args: Optional[List[str]] = None) -> None: """ Create a merged coverage report from the individual coverage files @@ -32,7 +36,7 @@ def merge_coverage(self, file_name, args=None): """ self._simulator_if.merge_coverage(file_name=file_name, args=args) - def get_report(self): + def get_report(self) -> "Report": """ Get a report (dictionary) of tests: status, time and output path @@ -92,7 +96,7 @@ def post_func(results): vu.main(post_run=post_func) """ - def __init__(self, test_output_path: Union[str, Path], status, time, path: Union[str, Path]): + def __init__(self, test_output_path: Union[str, Path], status: Any, time: Any, path: Union[str, Path]) -> None: self._test_output_path = Path(test_output_path) self.status = status self.time = time diff --git a/vunit/ui/source.py b/vunit/ui/source.py index 18ab1268c..468d051e9 100644 --- a/vunit/ui/source.py +++ b/vunit/ui/source.py @@ -8,58 +8,16 @@ UI classes SourceFile and SourceFileList """ -from .. import ostools - - -class SourceFileList(list): - """ - A list of :class:`.SourceFile` - """ +from typing import Iterable, List, Optional, Union, TYPE_CHECKING - def __init__(self, source_files): - list.__init__(self, source_files) - - def set_compile_option(self, name, value): - """ - Set compile option for all files in the list - - :param name: |compile_option| - :param value: The value of the compile option - - :example: - - .. code-block:: python - - files.set_compile_option("ghdl.a_flags", ["--no-vital-checks"]) - """ - for source_file in self: - source_file.set_compile_option(name, value) - - def add_compile_option(self, name, value): - """ - Add compile option to all files in the list - - :param name: |compile_option| - :param value: The value of the compile option - """ - for source_file in self: - source_file.add_compile_option(name, value) - - def add_dependency_on(self, source_file): - """ - Add manual dependency of these files on other file(s) - - :param source_file: The file(s) which this file depends on - - :example: - - .. code-block:: python +from ..project import Project +from ..sim_if import OptionType +from .. import ostools - other_file = lib.get_source_file("other_file.vhd") - files.add_dependency_on(other_file) - """ - for my_source_file in self: - my_source_file.add_dependency_on(source_file) +if TYPE_CHECKING: + from ..source_file import SourceFile as Source_File + from . import VUnit + from .library import Library class SourceFile(object): @@ -67,37 +25,40 @@ class SourceFile(object): A single file """ - def __init__(self, source_file, project, ui): + def __init__(self, source_file: "Source_File", project: Project, ui: "VUnit") -> None: self._source_file = source_file self._project = project self._ui = ui @property - def name(self): + def name(self) -> str: """ The name of the SourceFile """ return ostools.simplify_path(self._source_file.name) @property - def vhdl_standard(self): + def vhdl_standard(self) -> Optional[str]: """ The VHDL standard applicable to the file, None if not a VHDL file """ - if self._source_file.file_type == "vhdl": + # Import here to avoid circular import problems + from vunit.source_file import VHDLSourceFile # pylint: disable=import-outside-toplevel + + if isinstance(self._source_file, VHDLSourceFile): return str(self._source_file.get_vhdl_standard()) return None @property - def library(self): + def library(self) -> "Library": """ The library of the source file """ return self._ui.library(self._source_file.library.name) - def set_compile_option(self, name, value): + def set_compile_option(self, name: str, value: OptionType) -> None: """ Set compile option for this file @@ -112,7 +73,7 @@ def set_compile_option(self, name, value): """ self._source_file.set_compile_option(name, value) - def add_compile_option(self, name, value): + def add_compile_option(self, name: str, value: OptionType) -> None: """ Add compile option to this file @@ -121,7 +82,7 @@ def add_compile_option(self, name, value): """ self._source_file.add_compile_option(name, value) - def get_compile_option(self, name): + def get_compile_option(self, name: str) -> OptionType: """ Return compile option of this file @@ -129,7 +90,7 @@ def get_compile_option(self, name): """ return self._source_file.get_compile_option(name) - def add_dependency_on(self, source_file): + def add_dependency_on(self, source_file: Union[Iterable["Source_File"], "Source_File"]) -> None: """ Add manual dependency of this file other file(s) @@ -145,8 +106,56 @@ def add_dependency_on(self, source_file): if isinstance(source_file, SourceFile): private_source_file = source_file._source_file # pylint: disable=protected-access self._project.add_manual_dependency(self._source_file, depends_on=private_source_file) - elif hasattr(source_file, "__iter__"): + elif isinstance(source_file, Iterable): for element in source_file: self.add_dependency_on(element) else: raise ValueError(source_file) + + +class SourceFileList(List[SourceFile]): + """ + A list of :class:`.SourceFile` + """ + + def set_compile_option(self, name: str, value: OptionType) -> None: + """ + Set compile option for all files in the list + + :param name: |compile_option| + :param value: The value of the compile option + + :example: + + .. code-block:: python + + files.set_compile_option("ghdl.a_flags", ["--no-vital-checks"]) + """ + for source_file in self: + source_file.set_compile_option(name, value) + + def add_compile_option(self, name: str, value: OptionType) -> None: + """ + Add compile option to all files in the list + + :param name: |compile_option| + :param value: The value of the compile option + """ + for source_file in self: + source_file.add_compile_option(name, value) + + def add_dependency_on(self, source_file: "Source_File") -> None: + """ + Add manual dependency of these files on other file(s) + + :param source_file: The file(s) which this file depends on + + :example: + + .. code-block:: python + + other_file = lib.get_source_file("other_file.vhd") + files.add_dependency_on(other_file) + """ + for my_source_file in self: + my_source_file.add_dependency_on(source_file) diff --git a/vunit/ui/test.py b/vunit/ui/test.py index ddb5f7be5..d8e8045c3 100644 --- a/vunit/ui/test.py +++ b/vunit/ui/test.py @@ -8,6 +8,9 @@ UI class Test """ +from typing import Any, Callable, Dict, Optional, cast +from vunit.sim_if import OptionType +from vunit.test.bench import TestConfigurationVisitor from .common import lower_generics @@ -17,11 +20,11 @@ class Test(object): """ - def __init__(self, test_case): + def __init__(self, test_case: TestConfigurationVisitor) -> None: self._test_case = test_case @property - def name(self): + def name(self) -> str: """ :returns: the entity or module name of the test bench """ @@ -29,15 +32,15 @@ def name(self): def add_config( # pylint: disable=too-many-arguments self, - name, - generics=None, - parameters=None, - pre_config=None, - post_check=None, - sim_options=None, - attributes=None, - vhdl_configuration_name=None, - ): + name: str, + generics: Optional[dict] = None, + parameters: Optional[dict] = None, + pre_config: Optional[Callable] = None, + post_check: Optional[Callable] = None, + sim_options: Optional[Dict[str, OptionType]] = None, + attributes: Optional[dict] = None, + vhdl_configuration_name: Optional[str] = None, + ) -> None: """ Add a configuration to this test copying the default configuration. @@ -79,7 +82,7 @@ def add_config( # pylint: disable=too-many-arguments generics = {} if generics is None else generics generics = lower_generics(generics) parameters = {} if parameters is None else parameters - generics.update(parameters) + cast(dict, generics).update(parameters) attributes = {} if attributes is None else attributes self._test_case.add_config( name=name, @@ -91,7 +94,7 @@ def add_config( # pylint: disable=too-many-arguments vhdl_configuration_name=vhdl_configuration_name, ) - def set_attribute(self, name, value): + def set_attribute(self, name: str, value: Any) -> None: """ Set a value of attribute within all |configurations| of this test @@ -107,7 +110,7 @@ def set_attribute(self, name, value): """ self._test_case.set_attribute(name, value) - def set_generic(self, name, value): + def set_generic(self, name: str, value: Any) -> None: """ Set a value of generic within all |configurations| of this test @@ -123,7 +126,7 @@ def set_generic(self, name, value): """ self._test_case.set_generic(name.lower(), value) - def set_parameter(self, name, value): + def set_parameter(self, name: str, value: Any) -> None: """ Set a value of parameter within all |configurations| of this test @@ -139,7 +142,7 @@ def set_parameter(self, name, value): """ self._test_case.set_generic(name, value) - def set_vhdl_configuration_name(self, value: str): + def set_vhdl_configuration_name(self, value: str) -> None: """ Set VHDL configuration name of all |configurations| of this test @@ -148,7 +151,7 @@ def set_vhdl_configuration_name(self, value: str): """ self._test_case.set_vhdl_configuration_name(value) - def set_sim_option(self, name, value, overwrite=True): + def set_sim_option(self, name: str, value: OptionType, overwrite: bool = True) -> None: """ Set simulation option within all |configurations| of this test @@ -165,7 +168,7 @@ def set_sim_option(self, name, value, overwrite=True): """ self._test_case.set_sim_option(name, value, overwrite) - def set_pre_config(self, value): + def set_pre_config(self, value: Callable) -> None: """ Set :ref:`pre_config ` function of all |configurations| of this test @@ -173,7 +176,7 @@ def set_pre_config(self, value): """ self._test_case.set_pre_config(value) - def set_post_check(self, value): + def set_post_check(self, value: Callable) -> None: """ Set :ref:`post_check ` function of all |configurations| of this test diff --git a/vunit/ui/testbench.py b/vunit/ui/testbench.py index 9a7b7d63c..e0d18260a 100644 --- a/vunit/ui/testbench.py +++ b/vunit/ui/testbench.py @@ -9,9 +9,16 @@ """ from fnmatch import fnmatch +from typing import Any, Callable, Dict, List, Optional, cast, TYPE_CHECKING + +from ..sim_if import OptionType +from ..test.bench import TestBench as Test_Bench from .common import lower_generics from .test import Test +if TYPE_CHECKING: + from vunit.ui.library import Library + class TestBench(object): """ @@ -20,25 +27,25 @@ class TestBench(object): bench will apply that option to all test cases belonging to that test bench. """ - def __init__(self, test_bench, library): + def __init__(self, test_bench: Test_Bench, library: "Library") -> None: self._test_bench = test_bench self._library = library @property - def name(self): + def name(self) -> str: """ :returns: The entity or module name of the test bench """ return self._test_bench.name @property - def library(self): + def library(self) -> "Library": """ :returns: The library that contains this test bench """ return self._library - def set_attribute(self, name, value): + def set_attribute(self, name: str, value: str) -> None: """ Set a value of attribute within all |configurations| of this test bench or test cases within it @@ -54,7 +61,7 @@ def set_attribute(self, name, value): """ self._test_bench.set_attribute(name, value) - def set_generic(self, name, value): + def set_generic(self, name: str, value: Any) -> None: """ Set a value of generic within all |configurations| of this test bench or test cases within it @@ -70,7 +77,7 @@ def set_generic(self, name, value): """ self._test_bench.set_generic(name.lower(), value) - def set_parameter(self, name, value): + def set_parameter(self, name: str, value: Any) -> None: """ Set a value of parameter within all |configurations| of this test bench or test cases within it @@ -86,7 +93,7 @@ def set_parameter(self, name, value): """ self._test_bench.set_generic(name, value) - def set_vhdl_configuration_name(self, value: str): + def set_vhdl_configuration_name(self, value: str) -> None: """ Set VHDL configuration name of all |configurations| of this test bench or test cases within it @@ -95,7 +102,7 @@ def set_vhdl_configuration_name(self, value: str): """ self._test_bench.set_vhdl_configuration_name(value) - def set_sim_option(self, name, value, overwrite=True): + def set_sim_option(self, name: str, value: OptionType, overwrite: bool = True) -> None: """ Set simulation option within all |configurations| of this test bench or test cases within it @@ -112,7 +119,7 @@ def set_sim_option(self, name, value, overwrite=True): """ self._test_bench.set_sim_option(name, value, overwrite) - def set_pre_config(self, value): + def set_pre_config(self, value: Callable) -> None: """ Set :ref:`pre_config ` function of all |configurations| of this test bench or test cases within it @@ -121,7 +128,7 @@ def set_pre_config(self, value): """ self._test_bench.set_pre_config(value) - def set_post_check(self, value): + def set_post_check(self, value: Callable) -> None: """ Set :ref:`post_check ` function of all |configurations| of this test bench or test cases within it @@ -132,15 +139,15 @@ def set_post_check(self, value): def add_config( # pylint: disable=too-many-arguments self, - name, - generics=None, - parameters=None, - pre_config=None, - post_check=None, - sim_options=None, - attributes=None, - vhdl_configuration_name=None, - ): + name: str, + generics: Optional[Dict[str, Any]] = None, + parameters: Optional[Dict[str, Any]] = None, + pre_config: Optional[Callable] = None, + post_check: Optional[Callable] = None, + sim_options: Optional[Dict[str, OptionType]] = None, + attributes: Optional[Dict[str, Any]] = None, + vhdl_configuration_name: Optional[str] = None, + ) -> None: """ Add a configuration of this test bench or to all test cases within it by copying the default configuration. @@ -183,7 +190,7 @@ def add_config( # pylint: disable=too-many-arguments generics = {} if generics is None else generics generics = lower_generics(generics) parameters = {} if parameters is None else parameters - generics.update(parameters) + cast(dict, generics).update(parameters) attributes = {} if attributes is None else attributes self._test_bench.add_config( name=name, @@ -195,7 +202,7 @@ def add_config( # pylint: disable=too-many-arguments vhdl_configuration_name=vhdl_configuration_name, ) - def test(self, name): + def test(self, name: str) -> Test: """ Get a test within this test bench @@ -204,7 +211,7 @@ def test(self, name): """ return Test(self._test_bench.get_test_case(name)) - def get_tests(self, pattern="*"): + def get_tests(self, pattern: str = "*") -> List[Test]: """ Get a list of tests @@ -219,7 +226,7 @@ def get_tests(self, pattern="*"): results.append(Test(test_case)) return results - def scan_tests_from_file(self, file_name): + def scan_tests_from_file(self, file_name: str) -> None: """ Scan tests from another file than the one containing the test bench. Useful for when the top level test bench does not