Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 4 additions & 17 deletions examples/embedded_python/tb_example.vhd
Original file line number Diff line number Diff line change
Expand Up @@ -71,29 +71,16 @@ begin
variable coefficients : real_vector(0 to 2);
variable table : integer_vector_ptr_t;

-- The Tcl/Tk installation of the Python installation, see tcl_installation.py
procedure set_tcl_installation is
begin
exec("from os import environ");
exec("from sys import prefix");
exec("from pathlib import Path");
exec("old_environ = environ");
exec(
"if (Path(prefix) / 'lib' / 'tcl8.6').exists():" +
" environ['TCL_LIBRARY'] = str(Path(prefix) / 'lib' / 'tcl8.6')" +
"else:" +
" environ['TCL_LIBRARY'] = str(Path(prefix) / 'tcl' / 'tcl8.6')"
);
exec(
"if (Path(prefix) / 'lib' / 'tk8.6').exists():" +
" environ['TK_LIBRARY'] = str(Path(prefix) / 'lib' / 'tk8.6')" +
"else:" +
" environ['TK_LIBRARY'] = str(Path(prefix) / 'tcl' / 'tk8.6')"
);
import_module_from_file(join(tb_path(runner_cfg), "tcl_installation.py"), "tcl_installation");
exec("tcl_installation.set_tcl_installation()");
end;

procedure unset_tcl_installation is
begin
exec("environ = old_environ");
exec("tcl_installation.unset_tcl_installation()");
end;

procedure query_if(expr : boolean; check_result : check_result_t) is
Expand Down
44 changes: 44 additions & 0 deletions examples/embedded_python/tcl_installation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
#
# Copyright (c) 2014-2026, Lars Asplund lars.anders.asplund@gmail.com

"""
The Tcl/Tk installation for the tests using tkinter (PySimpleGUI dialogs, Matplotlib windows),
imported from VHDL with import_module_from_file.

A simulator with a Tcl of its own, like Riviera-PRO, can make tkinter look for the Tcl/Tk script
libraries of that Tcl rather than those of the Python installation. set_tcl_installation points
TCL_LIBRARY and TK_LIBRARY at the libraries of the Python installation, of whatever Tcl version it
has, when it has them in its lib (Linux, macOS) or tcl (Windows) directory. Otherwise it leaves
them alone, for Tcl to find its libraries itself.
"""

import os
import sys
from pathlib import Path

# The variable, the directory name pattern and a script every such directory has
LIBRARIES = [("TCL_LIBRARY", "tcl[0-9]*", "init.tcl"), ("TK_LIBRARY", "tk[0-9]*", "tk.tcl")]

_saved = {}


def set_tcl_installation():
for name, pattern, script in LIBRARIES:
_saved.setdefault(name, os.environ.get(name))
for directory in (Path(sys.base_prefix) / "lib", Path(sys.base_prefix) / "tcl"):
found = [path for path in sorted(directory.glob(pattern), reverse=True) if (path / script).is_file()]
if found:
os.environ[name] = str(found[0])
break


def unset_tcl_installation():
for name, value in _saved.items():
if value is None:
os.environ.pop(name, None)
else:
os.environ[name] = value
_saved.clear()
3 changes: 3 additions & 0 deletions src/vunit_python_bridge/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def setup(context):
# pylint: disable=import-outside-toplevel
from .bridge import setup as setup_bridge
from .foreign_application import setup_vhpi_application
from .native_library import add_python_libraries_to_library_path
from . import simulator_hooks

simulator_name = context.simulator_name
Expand All @@ -73,6 +74,8 @@ def setup(context):
f"it has no foreign language interface for {simulator_name}"
)

add_python_libraries_to_library_path()

if interface == "VHPI":
# Riviera-PRO/Active-HDL, the simulators served by the VHPI application
context.add_source_files(context.library.name, [VHDL_PATH / "python_pkg_vhpi.vhd"])
Expand Down
19 changes: 19 additions & 0 deletions src/vunit_python_bridge/native_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,25 @@ def add_python_dll_to_path() -> None:
os.environ["PATH"] = os.pathsep.join([item for item in paths if item] + [directory])


def add_python_libraries_to_library_path() -> None:
"""
Standalone builds of Python (python-build-standalone) keep the shared libraries their extension
modules load, Tcl/Tk for tkinter, next to libpython. On Linux only the python executable finds
them, through its RPATH, not a simulator embedding the interpreter. They have no SONAME either,
so loading them from Python first does not help: the simulator processes VUnit starts must find
them through LD_LIBRARY_PATH.
"""
# ponytail: changes LD_LIBRARY_PATH of the whole VUnit process, only for its children to find Tcl/Tk
if not sys.platform.startswith("linux"):
return
directory = Path(sys.base_prefix) / "lib"
if not any(directory.glob("libtcl*.so")):
return
paths = os.environ.get("LD_LIBRARY_PATH", "").split(os.pathsep)
if str(directory) not in paths:
os.environ["LD_LIBRARY_PATH"] = os.pathsep.join([item for item in paths if item] + [str(directory)])


def _build_windows_library(root: Path, simulator_prefix: Optional[Path] = None) -> Path:
"""
Build the library on Windows with gcc (see windows_gcc) against the headers and the import
Expand Down
26 changes: 26 additions & 0 deletions tests/test_python_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,32 @@ def test_add_python_dll_to_path_appends_once(self):
native_library.add_python_dll_to_path()
self.assertEqual(os.environ["PATH"], os.pathsep.join(["first", "pydir"]))

def test_add_python_libraries_to_library_path_only_for_a_python_with_its_own_tcl(self):
with create_tempdir() as tempdir:
lib = tempdir / "lib"
lib.mkdir()
with (
mock.patch("sys.platform", "linux"),
mock.patch("sys.base_prefix", str(tempdir)),
mock.patch.dict(os.environ, {"LD_LIBRARY_PATH": "first"}),
):
# A distribution Python: Tcl/Tk are in the system library directories
native_library.add_python_libraries_to_library_path()
self.assertEqual(os.environ["LD_LIBRARY_PATH"], "first")
# A standalone build: Tcl/Tk next to libpython, added once
(lib / "libtcl9.0.so").touch()
native_library.add_python_libraries_to_library_path()
native_library.add_python_libraries_to_library_path()
self.assertEqual(os.environ["LD_LIBRARY_PATH"], os.pathsep.join(["first", str(lib)]))
for platform in ("darwin", "win32"):
with (
mock.patch("sys.platform", platform),
mock.patch("sys.base_prefix", str(tempdir)),
mock.patch.dict(os.environ, {"LD_LIBRARY_PATH": "first"}),
):
native_library.add_python_libraries_to_library_path()
self.assertEqual(os.environ["LD_LIBRARY_PATH"], "first", platform)


class TestWindows64Bit(unittest.TestCase):
"""
Expand Down
Loading