diff --git a/docs/news.d/1234.bugfix.rst b/docs/news.d/1234.bugfix.rst new file mode 100644 index 000000000..bc5eb0180 --- /dev/null +++ b/docs/news.d/1234.bugfix.rst @@ -0,0 +1 @@ +The simulator hooks registered by the packages of a VUnit object are no longer lost when another VUnit object is created. diff --git a/docs/vunit_packages.rst b/docs/vunit_packages.rst index 87b3c770e..f7549538a 100644 --- a/docs/vunit_packages.rst +++ b/docs/vunit_packages.rst @@ -188,7 +188,9 @@ and use these dependencies. Use ``context.register_simulator_hooks(simulator_name, ...)`` to register functions that supply these settings for a particular simulator. Hooks receive the simulator interface, so they can adapt their results to its -configuration. They apply only to the project in which they are registered. +configuration. They apply only to the project in which they are registered, +that is to the tests of the VUnit object the package is added to. Creating +another VUnit object neither removes them nor gives them to the new object. The following example builds a native library and registers hooks to load it with NVC or link it with GHDL: diff --git a/tests/unit/test_builtins.py b/tests/unit/test_builtins.py index abc33ec56..d289f53a3 100644 --- a/tests/unit/test_builtins.py +++ b/tests/unit/test_builtins.py @@ -20,6 +20,7 @@ from vunit.vhdl_standard import VHDL, VHDLStandard from vunit.project import Project from vunit.sim_if import hooks +from vunit.sim_if.hooks import SimulatorHooks from tests.common import create_tempdir from contextlib import contextmanager from importlib.machinery import ModuleSpec @@ -64,6 +65,7 @@ def add_library(name): self.vu._output_path = "output_path" self.vu._run_script_path = Path("run.py") + self.vu._simulator_hooks = SimulatorHooks() self.builtins = Builtins(self.vu, VHDLStandard("2002"), None) @@ -728,8 +730,6 @@ def setup(context): self.assertEqual(context.simulator_backend, backend) def test_setup_function_registers_simulator_hooks(self): - self.addCleanup(hooks.clear_hooks) - with ( create_tempdir() as tempdir, pkg_env(tempdir), @@ -760,6 +760,7 @@ def setup(context): simulator_interface = mock.Mock() simulator_interface.name = "ghdl" + simulator_interface.simulator_hooks = self.vu._simulator_hooks self.assertEqual(hooks.get_flags(simulator_interface, "elab_flags"), ["-Wl,-lfoo"]) self.assertEqual(hooks.get_flags(simulator_interface, "run_flags"), ["--load=foo"]) self.assertEqual(hooks.get_flags(simulator_interface, "process_flags"), ["-noautoldlibpath"]) diff --git a/tests/unit/test_sim_if_hooks.py b/tests/unit/test_sim_if_hooks.py index 22a56f4f9..d00695a74 100644 --- a/tests/unit/test_sim_if_hooks.py +++ b/tests/unit/test_sim_if_hooks.py @@ -15,7 +15,8 @@ from vunit import VUnit from vunit.project import Project -from vunit.sim_if import hooks +from vunit.sim_if import SimulatorInterface, hooks +from vunit.sim_if.hooks import SimulatorHooks from vunit.sim_if.activehdl import ActiveHDLInterface from vunit.sim_if.ghdl import GHDLInterface from vunit.sim_if.modelsim import ModelSimInterface @@ -51,14 +52,10 @@ class TestSimulatorHooks(unittest.TestCase): """ def setUp(self): - hooks.clear_hooks() + self.hooks = SimulatorHooks() - def tearDown(self): - hooks.clear_hooks() - - @staticmethod - def _simulator(name): - return SimpleNamespace(name=name) + def _simulator(self, name): + return SimpleNamespace(name=name, simulator_hooks=self.hooks) def test_no_hooks_by_default(self): simulator = self._simulator("ghdl") @@ -69,7 +66,7 @@ def test_no_hooks_by_default(self): def test_calls_hooks_with_the_simulator_interface(self): simulator = self._simulator("ghdl") - hooks.register_hooks( + self.hooks.register( "ghdl", elab_flags=lambda interface: [f"-Wl,-l{interface.name}"], run_flags=lambda interface: ["--load"], @@ -85,54 +82,89 @@ def test_calls_hooks_with_the_simulator_interface(self): def test_combines_hooks_in_registration_order(self): simulator = self._simulator("ghdl") for kind in FLAG_KINDS: - hooks.register_hooks("ghdl", **{kind: lambda interface: ["first"]}) - hooks.register_hooks("ghdl", **{kind: lambda interface: ["second"]}) + self.hooks.register("ghdl", **{kind: lambda interface: ["first"]}) + self.hooks.register("ghdl", **{kind: lambda interface: ["second"]}) self.assertEqual(hooks.get_flags(simulator, kind), ["first", "second"]) def test_hooks_are_specific_to_a_simulator(self): for kind in FLAG_KINDS: - hooks.register_hooks("ghdl", **{kind: lambda interface: ["--load"]}) + self.hooks.register("ghdl", **{kind: lambda interface: ["--load"]}) self.assertEqual(hooks.get_flags(self._simulator("ghdl"), kind), ["--load"]) self.assertEqual(hooks.get_flags(self._simulator("nvc"), kind), []) def test_copies_the_environment_when_there_is_none(self): simulator = self._simulator("ghdl") - hooks.register_hooks("ghdl", run_env=lambda interface, env: dict(env, FOO="1")) + self.hooks.register("ghdl", run_env=lambda interface, env: dict(env, FOO="1")) env = hooks.get_run_env(simulator) self.assertEqual(env["FOO"], "1") self.assertIn("PATH", env) - def test_clears_hooks(self): - simulator = self._simulator("ghdl") - hooks.register_hooks("ghdl", elab_flags=lambda interface: ["--load"]) - hooks.clear_hooks() - - self.assertEqual(hooks.get_flags(simulator, "elab_flags"), []) - def test_raises_if_hook_is_not_callable(self): with self.assertRaisesRegex(ValueError, "run_flags hook for simulator ghdl is not callable."): - hooks.register_hooks("ghdl", run_flags="--load") + self.hooks.register("ghdl", run_flags="--load") with self.assertRaisesRegex(ValueError, "process_flags hook for simulator ghdl is not callable."): - hooks.register_hooks("ghdl", process_flags="-noautoldlibpath") + self.hooks.register("ghdl", process_flags="-noautoldlibpath") def test_raises_if_simulator_name_is_invalid(self): with self.assertRaisesRegex(ValueError, "Simulator name must be a non-empty string."): - hooks.register_hooks("", run_flags=lambda interface: []) + self.hooks.register("", run_flags=lambda interface: []) - def test_hooks_are_cleared_when_a_vunit_object_is_created(self): - hooks.register_hooks("ghdl", elab_flags=lambda interface: ["--load"]) + def test_no_hooks_for_an_interface_not_created_by_a_vunit_object(self): + self.hooks.register("ghdl", elab_flags=lambda interface: ["--load"]) + simulator = SimpleNamespace(name="ghdl") + + self.assertEqual(hooks.get_flags(simulator, "elab_flags"), []) + self.assertEqual(hooks.get_run_env(simulator, {"FOO": "1"}), {"FOO": "1"}) - with ( - create_tempdir() as tempdir, - mock.patch("vunit.sim_if.factory.SIMULATOR_FACTORY.select_simulator", new=lambda: None), - ): - VUnit.from_argv(argv=["--output-path=%s" % tempdir]) - self.assertEqual(hooks.get_flags(self._simulator("ghdl"), "elab_flags"), []) +class _FakeSimulator(SimulatorInterface): + """ + A simulator class creating a plain interface + """ + + name = "ghdl" + + @classmethod + def from_args(cls, args, output_path): # pylint: disable=unused-argument + return SimpleNamespace(name=cls.name) + + +class TestSimulatorHooksOfVUnitObjects(unittest.TestCase): + """ + Test that the hooks belong to the VUnit object of the packages registering them + """ + + @staticmethod + def _vunit(output_path): + with mock.patch("vunit.sim_if.factory.SIMULATOR_FACTORY.select_simulator", new=lambda: _FakeSimulator): + return VUnit.from_argv(argv=[f"--output-path={output_path!s}"]) + + def test_simulator_interface_gets_the_hooks_of_its_vunit_object(self): + with create_tempdir() as tempdir: + vu = self._vunit(tempdir) + vu._simulator_hooks.register( # pylint: disable=protected-access + "ghdl", elab_flags=lambda interface: ["--load"] + ) + simulator_if = vu._create_simulator_if() # pylint: disable=protected-access + + self.assertEqual(hooks.get_flags(simulator_if, "elab_flags"), ["--load"]) + + def test_hooks_are_kept_when_another_vunit_object_is_created(self): + with create_tempdir() as tempdir: + first = self._vunit(Path(tempdir) / "first") + first._simulator_hooks.register( # pylint: disable=protected-access + "ghdl", elab_flags=lambda interface: ["--load"] + ) + second = self._vunit(Path(tempdir) / "second") + first_if = first._create_simulator_if() # pylint: disable=protected-access + second_if = second._create_simulator_if() # pylint: disable=protected-access + + self.assertEqual(hooks.get_flags(first_if, "elab_flags"), ["--load"]) + self.assertEqual(hooks.get_flags(second_if, "elab_flags"), []) class TestSimulatorHooksAreUsed(unittest.TestCase): @@ -141,10 +173,7 @@ class TestSimulatorHooksAreUsed(unittest.TestCase): """ def setUp(self): - hooks.clear_hooks() - - def tearDown(self): - hooks.clear_hooks() + self.hooks = SimulatorHooks() @staticmethod def _project(): @@ -154,7 +183,7 @@ def _project(): @mock.patch.object(GHDLInterface, "determine_version", return_value=5.0) def test_ghdl_uses_hooks(self, determine_version): - hooks.register_hooks( + self.hooks.register( "ghdl", elab_flags=lambda interface: ["-Wl,-lfoo"], run_flags=lambda interface: ["--load=foo"], @@ -162,6 +191,7 @@ def test_ghdl_uses_hooks(self, determine_version): ) simif = GHDLInterface(prefix="prefix", output_path="") + simif.simulator_hooks = self.hooks simif._project = self._project() # pylint: disable=protected-access simif._vhdl_standard = VHDL.standard("2008") # pylint: disable=protected-access @@ -182,7 +212,7 @@ def test_ghdl_uses_hooks(self, determine_version): @mock.patch.object(NVCInterface, "determine_version", return_value=(1, 16)) def test_nvc_uses_hooks(self, determine_version): - hooks.register_hooks( + self.hooks.register( "nvc", elab_flags=lambda interface: ["--jit"], run_flags=lambda interface: ["--load=foo"], @@ -190,6 +220,7 @@ def test_nvc_uses_hooks(self, determine_version): ) simif = NVCInterface(prefix="prefix", output_path="", num_threads=1) + simif.simulator_hooks = self.hooks simif._project = self._project() # pylint: disable=protected-access simif._vhdl_standard = VHDL.standard("2008") # pylint: disable=protected-access @@ -207,26 +238,26 @@ def test_nvc_uses_hooks(self, determine_version): self.assertEqual(process.call_args[1]["env"]["FOO"], "1") def test_modelsim_uses_hooks(self): - hooks.register_hooks( + self.hooks.register( "modelsim", elab_flags=lambda interface: ["-foo"], run_flags=lambda interface: ["-bar"], ) - simif = SimpleNamespace(name=ModelSimInterface.name, _gui=False) + simif = SimpleNamespace(name=ModelSimInterface.name, _gui=False, simulator_hooks=self.hooks) self.assertEqual(ModelSimInterface._vopt_extra_args(simif, make_config()), "-foo") self.assertEqual(ModelSimInterface._vsim_extra_args(simif, make_config()), "-bar") def test_rivierapro_uses_hooks(self): - hooks.register_hooks("rivierapro", run_flags=lambda interface: ["-bar"]) + self.hooks.register("rivierapro", run_flags=lambda interface: ["-bar"]) - simif = SimpleNamespace(name=RivieraProInterface.name, _gui=False) + simif = SimpleNamespace(name=RivieraProInterface.name, _gui=False, simulator_hooks=self.hooks) self.assertEqual(RivieraProInterface._vsim_extra_args(simif, make_config()), "-bar") def test_activehdl_uses_hooks(self): - hooks.register_hooks("activehdl", run_flags=lambda interface: ["-bar"]) + self.hooks.register("activehdl", run_flags=lambda interface: ["-bar"]) - simif = SimpleNamespace(name=ActiveHDLInterface.name, _gui=False) + simif = SimpleNamespace(name=ActiveHDLInterface.name, _gui=False, simulator_hooks=self.hooks) self.assertEqual(ActiveHDLInterface._vsim_extra_args(simif, make_config()), "-bar") @@ -252,10 +283,7 @@ class TestVsimProcessHooks(unittest.TestCase): """ def setUp(self): - hooks.clear_hooks() - - def tearDown(self): - hooks.clear_hooks() + self.hooks = SimulatorHooks() def test_no_process_hooks_by_default(self): with ( @@ -263,6 +291,7 @@ def test_no_process_hooks_by_default(self): mock.patch("vunit.sim_if.vsim_simulator_mixin.Process", autospec=True) as process, ): simif = _VsimSimulator(str(Path(tempdir) / "modelsim.ini"), persistent=True) + simif.simulator_hooks = self.hooks simif._persistent_shell._create_process(1) # pylint: disable=protected-access self.assertNotIn("-noautoldlibpath", process.call_args[0][0]) self.assertEqual(process.call_args[1]["env"], {"BASE": "1"}) @@ -271,7 +300,7 @@ def test_no_process_hooks_by_default(self): self.assertIsNone(process.call_args[1]["env"]) def test_persistent_vsim_process_uses_hooks(self): - hooks.register_hooks( + self.hooks.register( "modelsim", process_flags=lambda interface: ["-noautoldlibpath"], run_env=lambda interface, env: dict(env, FOO="1"), @@ -282,6 +311,7 @@ def test_persistent_vsim_process_uses_hooks(self): mock.patch("vunit.sim_if.vsim_simulator_mixin.Process", autospec=True) as process, ): simif = _VsimSimulator(str(Path(tempdir) / "modelsim.ini"), persistent=True) + simif.simulator_hooks = self.hooks simif._persistent_shell._create_process(1) # pylint: disable=protected-access args = process.call_args[0][0] @@ -289,7 +319,7 @@ def test_persistent_vsim_process_uses_hooks(self): self.assertEqual(process.call_args[1]["env"], {"BASE": "1", "FOO": "1"}) def test_batch_vsim_process_uses_hooks(self): - hooks.register_hooks( + self.hooks.register( "modelsim", process_flags=lambda interface: ["-noautoldlibpath"], run_env=lambda interface, env: dict(env, FOO="1"), @@ -300,6 +330,7 @@ def test_batch_vsim_process_uses_hooks(self): mock.patch("vunit.sim_if.vsim_simulator_mixin.Process", autospec=True) as process, ): simif = _VsimSimulator(str(Path(tempdir) / "modelsim.ini"), persistent=False) + simif.simulator_hooks = self.hooks simif._run_batch_file(str(Path(tempdir) / "batch.do")) # pylint: disable=protected-access args = process.call_args[0][0] @@ -307,13 +338,14 @@ def test_batch_vsim_process_uses_hooks(self): self.assertEqual(process.call_args[1]["env"]["FOO"], "1") def test_process_flags_are_specific_to_a_simulator(self): - hooks.register_hooks("rivierapro", process_flags=lambda interface: ["-noautoldlibpath"]) + self.hooks.register("rivierapro", process_flags=lambda interface: ["-noautoldlibpath"]) with ( create_tempdir() as tempdir, mock.patch("vunit.sim_if.vsim_simulator_mixin.Process", autospec=True) as process, ): simif = _VsimSimulator(str(Path(tempdir) / "modelsim.ini"), persistent=False) + simif.simulator_hooks = self.hooks simif._run_batch_file(str(Path(tempdir) / "batch.do")) # pylint: disable=protected-access self.assertNotIn("-noautoldlibpath", process.call_args[0][0]) diff --git a/vunit/package_context.py b/vunit/package_context.py index e8b05d1bb..da93fec78 100644 --- a/vunit/package_context.py +++ b/vunit/package_context.py @@ -12,7 +12,7 @@ from pathlib import Path from typing import Any, Optional -from vunit.sim_if.hooks import register_hooks +from vunit.sim_if.hooks import EnvHook, FlagsHook from vunit.vhdl_standard import VHDLStandard @@ -43,8 +43,36 @@ class PackageContext: simulator_class: Any _vunit_obj: Any - #: Register hooks extending what a simulator does for a test, see :ref:`vunit_packages`. - register_simulator_hooks = staticmethod(register_hooks) + def register_simulator_hooks( + self, + simulator_name: str, + *, + elab_flags: Optional[FlagsHook] = None, + run_flags: Optional[FlagsHook] = None, + process_flags: Optional[FlagsHook] = None, + run_env: Optional[EnvHook] = None, + ) -> None: + """ + Register hooks extending what a simulator does for a test, see :ref:`vunit_packages`. The + hooks apply to the tests of the VUnit object the package is added to. + + :param simulator_name: The name of the simulator the hooks apply to, for example "ghdl". + :param elab_flags: A ``elab_flags(simulator_interface)`` function returning extra flags for + the elaboration of a test. + :param run_flags: A ``run_flags(simulator_interface)`` function returning extra flags for the + simulation of a test. + :param process_flags: A ``process_flags(simulator_interface)`` function returning extra flags + for the simulator process VUnit starts. + :param run_env: A ``run_env(simulator_interface, env)`` function returning the environment + of the simulation of a test, given the environment it would otherwise have. + """ + self._vunit_obj._simulator_hooks.register( # pylint: disable=protected-access + simulator_name, + elab_flags=elab_flags, + run_flags=run_flags, + process_flags=process_flags, + run_env=run_env, + ) @property def simulator_name(self) -> Optional[str]: diff --git a/vunit/sim_if/hooks.py b/vunit/sim_if/hooks.py index a11fffed4..7aa6528ab 100644 --- a/vunit/sim_if/hooks.py +++ b/vunit/sim_if/hooks.py @@ -5,15 +5,16 @@ # Copyright (c) 2014-2026, Lars Asplund lars.anders.asplund@gmail.com """ -Registry of simulator hooks. +Simulator hooks. A hook is a callable provided by a VUnit package to extend what a simulator interface does for a test. The hooks are registered per simulator name and are called by the simulator interfaces with the interface instance such that a hook can base its result on how the simulator was configured. -The registry is cleared when a new VUnit object is created, that is the hooks registered for -a project do not leak into the next one. +Every VUnit object has a registry of its own, holding the hooks registered by its packages, +and gives it to the simulator interface it creates. The hooks of a project therefore neither +leak into another VUnit object nor are lost when another VUnit object is created. """ from os import environ @@ -22,75 +23,109 @@ FlagsHook = Callable[..., List[str]] EnvHook = Callable[..., Dict[str, str]] -# The hooks registered for a simulator name and a kind of hook -_HOOKS: Dict[Tuple[str, str], List[Callable]] = {} - -def register_hooks( - simulator_name: str, - *, - elab_flags: Optional[FlagsHook] = None, - run_flags: Optional[FlagsHook] = None, - process_flags: Optional[FlagsHook] = None, - run_env: Optional[EnvHook] = None, -) -> None: +class SimulatorHooks: """ - Register hooks for a simulator. - - :param simulator_name: The name of the simulator the hooks apply to, for example "ghdl". - :param elab_flags: A ``elab_flags(simulator_interface)`` function returning extra flags for - the elaboration of a test. Simulators without a separate elaboration step - do not call this hook. - :param run_flags: A ``run_flags(simulator_interface)`` function returning extra flags for the - simulation of a test. - :param process_flags: A ``process_flags(simulator_interface)`` function returning extra flags for - the simulator process VUnit starts. Only simulators driving the simulation - from a separate process, that is the vsim based ones, call this hook. - :param run_env: A ``run_env(simulator_interface, env)`` function returning the environment - of the simulation of a test, given the environment it would otherwise have. + The hooks registered by the packages of a VUnit object. """ - if not isinstance(simulator_name, str) or not simulator_name: - raise ValueError("Simulator name must be a non-empty string.") - - new_hooks = {"elab_flags": elab_flags, "run_flags": run_flags, "process_flags": process_flags, "run_env": run_env} - for kind, hook in new_hooks.items(): - if hook is not None and not callable(hook): - raise ValueError(f"{kind} hook for simulator {simulator_name} is not callable.") - - for kind, hook in new_hooks.items(): - if hook is not None: - _HOOKS.setdefault((simulator_name, kind), []).append(hook) - -def clear_hooks() -> None: + def __init__(self): + # The hooks registered for a simulator name and a kind of hook + self._hooks: Dict[Tuple[str, str], List[Callable]] = {} + + def register( + self, + simulator_name: str, + *, + elab_flags: Optional[FlagsHook] = None, + run_flags: Optional[FlagsHook] = None, + process_flags: Optional[FlagsHook] = None, + run_env: Optional[EnvHook] = None, + ) -> None: + """ + Register hooks for a simulator. + + :param simulator_name: The name of the simulator the hooks apply to, for example "ghdl". + :param elab_flags: A ``elab_flags(simulator_interface)`` function returning extra flags for + the elaboration of a test. Simulators without a separate elaboration step + do not call this hook. + :param run_flags: A ``run_flags(simulator_interface)`` function returning extra flags for the + simulation of a test. + :param process_flags: A ``process_flags(simulator_interface)`` function returning extra flags + for the simulator process VUnit starts. Only simulators driving the + simulation from a separate process, that is the vsim based ones, call + this hook. + :param run_env: A ``run_env(simulator_interface, env)`` function returning the environment + of the simulation of a test, given the environment it would otherwise have. + """ + if not isinstance(simulator_name, str) or not simulator_name: + raise ValueError("Simulator name must be a non-empty string.") + + new_hooks = { + "elab_flags": elab_flags, + "run_flags": run_flags, + "process_flags": process_flags, + "run_env": run_env, + } + for kind, hook in new_hooks.items(): + if hook is not None and not callable(hook): + raise ValueError(f"{kind} hook for simulator {simulator_name} is not callable.") + + for kind, hook in new_hooks.items(): + if hook is not None: + self._hooks.setdefault((simulator_name, kind), []).append(hook) + + def get_flags(self, simulator_interface, kind: str) -> List[str]: + """ + Return the extra flags provided by the hooks of the simulator of a kind, that is + "elab_flags", "run_flags" or "process_flags". + """ + return [ + flag for hook in self._hooks.get((simulator_interface.name, kind), []) for flag in hook(simulator_interface) + ] + + def get_run_env(self, simulator_interface, env: Optional[Dict[str, str]] = None) -> Optional[Dict[str, str]]: + """ + Return the simulation environment transformed by the hooks of the simulator. + + The environment is returned unchanged if there are no hooks. None means that the + simulation inherits the environment of VUnit, in which case a copy of that environment is + given to the hooks. + """ + hooks = self._hooks.get((simulator_interface.name, "run_env"), []) + if not hooks: + return env + + result = environ.copy() if env is None else env + for hook in hooks: + result = hook(simulator_interface, result) + + return result + + +# The hooks of a simulator interface not created by a VUnit object +_NO_HOOKS = SimulatorHooks() + + +def _hooks_of(simulator_interface) -> SimulatorHooks: """ - Remove all registered hooks. + The hooks given to a simulator interface by the VUnit object that created it. """ - _HOOKS.clear() + hooks = getattr(simulator_interface, "simulator_hooks", None) + return hooks if isinstance(hooks, SimulatorHooks) else _NO_HOOKS def get_flags(simulator_interface, kind: str) -> List[str]: """ - Return the extra flags provided by the hooks of the simulator of a kind, that is + Return the extra flags provided by the hooks of the simulator interface of a kind, that is "elab_flags", "run_flags" or "process_flags". """ - return [flag for hook in _HOOKS.get((simulator_interface.name, kind), []) for flag in hook(simulator_interface)] + return _hooks_of(simulator_interface).get_flags(simulator_interface, kind) def get_run_env(simulator_interface, env: Optional[Dict[str, str]] = None) -> Optional[Dict[str, str]]: """ - Return the simulation environment transformed by the hooks of the simulator. - - The environment is returned unchanged if there are no hooks. None means that the simulation - inherits the environment of VUnit, in which case a copy of that environment is given to the - hooks. + Return the simulation environment transformed by the hooks of the simulator interface, see + :meth:`SimulatorHooks.get_run_env`. """ - hooks = _HOOKS.get((simulator_interface.name, "run_env"), []) - if not hooks: - return env - - result = environ.copy() if env is None else env - for hook in hooks: - result = hook(simulator_interface, result) - - return result + return _hooks_of(simulator_interface).get_run_env(simulator_interface, env) diff --git a/vunit/sim_if/vsim_simulator_mixin.py b/vunit/sim_if/vsim_simulator_mixin.py index 6048c113a..d52aac6f1 100644 --- a/vunit/sim_if/vsim_simulator_mixin.py +++ b/vunit/sim_if/vsim_simulator_mixin.py @@ -11,6 +11,7 @@ import sys import os +import weakref from pathlib import Path from ..ostools import write_file, Process from ..test.suites import get_result_file_name @@ -30,10 +31,12 @@ def __init__(self, prefix, persistent, sim_cfg_file_name): self._sim_cfg_file_name = sim_cfg_file_name prefix = self._prefix # Avoid circular dependency inhibiting process destruction - env = hooks.get_run_env(self, self.get_env()) - process_flags = hooks.get_flags(self, "process_flags") + # Likewise a weak reference. The hooks are asked when a process is created rather than + # here, since the VUnit object gives its hooks to the interface after creating it. + simulator_if = weakref.ref(self) def create_process(ident): + simulator = simulator_if() return Process( [ str(Path(prefix) / "vsim"), @@ -43,9 +46,9 @@ def create_process(ident): "-do", str((Path(__file__).parent / "tcl_read_eval_loop.tcl").resolve()), ] - + process_flags, + + hooks.get_flags(simulator, "process_flags"), cwd=str(Path(sim_cfg_file_name).parent), - env=env, + env=hooks.get_run_env(simulator, simulator.get_env()), ) if persistent: diff --git a/vunit/ui/__init__.py b/vunit/ui/__init__.py index 28943a3c5..17503a6c8 100644 --- a/vunit/ui/__init__.py +++ b/vunit/ui/__init__.py @@ -28,7 +28,7 @@ from ..vunit_cli import VUnitCLI from ..sim_if.factory import SIMULATOR_FACTORY from ..sim_if import SimulatorInterface -from ..sim_if.hooks import clear_hooks as clear_simulator_hooks +from ..sim_if.hooks import SimulatorHooks from ..color_printer import COLOR_PRINTER, NO_COLOR_PRINTER from ..project import Project @@ -154,8 +154,8 @@ def __init__( self._output_path = str(Path(args.output_path).resolve()) self._run_script_path = _find_run_script_path() - # Simulator hooks are registered by the packages of this project - clear_simulator_hooks() + # The simulator hooks registered by the packages of this project + self._simulator_hooks = SimulatorHooks() if args.no_color: self._printer = NO_COLOR_PRINTER @@ -861,7 +861,9 @@ def _create_simulator_if(self): if not Path(self._simulator_output_path).exists(): os.makedirs(self._simulator_output_path) - return self._simulator_class.from_args(args=self._args, output_path=self._simulator_output_path) + simulator_if = self._simulator_class.from_args(args=self._args, output_path=self._simulator_output_path) + simulator_if.simulator_hooks = self._simulator_hooks + return simulator_if def _get_test_history(self, simulator_if): """