vunit-python-bridge 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vunit_python_bridge/__init__.py +92 -0
- vunit_python_bridge/bin/vunit_python_bridge-cp310-win_amd64.dll +0 -0
- vunit_python_bridge/bin/vunit_python_bridge-cp311-win_amd64.dll +0 -0
- vunit_python_bridge/bin/vunit_python_bridge-cp312-win_amd64.dll +0 -0
- vunit_python_bridge/bin/vunit_python_bridge-cp313-win_amd64.dll +0 -0
- vunit_python_bridge/bin/vunit_python_bridge-cp314-win_amd64.dll +0 -0
- vunit_python_bridge/bridge.py +171 -0
- vunit_python_bridge/foreign_application.py +90 -0
- vunit_python_bridge/hdl/src/python_bridge_pkg.vhd.in +91 -0
- vunit_python_bridge/hdl/src/python_context.vhd +13 -0
- vunit_python_bridge/hdl/src/python_ffi_pkg_bridge.vhd +507 -0
- vunit_python_bridge/hdl/src/python_pkg.vhd +1087 -0
- vunit_python_bridge/hdl/src/python_pkg_vhpi.vhd +270 -0
- vunit_python_bridge/hdl/tools/generate_python_pkg.py +386 -0
- vunit_python_bridge/hdl/tools/python_pkg.vhd.in +765 -0
- vunit_python_bridge/native/arguments.c +191 -0
- vunit_python_bridge/native/bridge.h +117 -0
- vunit_python_bridge/native/config.c +201 -0
- vunit_python_bridge/native/error.c +120 -0
- vunit_python_bridge/native/fli.c +98 -0
- vunit_python_bridge/native/interpreter.c +271 -0
- vunit_python_bridge/native/operations.c +239 -0
- vunit_python_bridge/native/vhpi/python_pkg.c +125 -0
- vunit_python_bridge/native/vhpi/python_pkg.h +31 -0
- vunit_python_bridge/native/vhpi/python_pkg_vhpi.c +366 -0
- vunit_python_bridge/native_library.py +455 -0
- vunit_python_bridge/runtime.py +645 -0
- vunit_python_bridge/simulator_hooks.py +141 -0
- vunit_python_bridge/vunit_pkg.toml +17 -0
- vunit_python_bridge-0.1.0.dist-info/METADATA +116 -0
- vunit_python_bridge-0.1.0.dist-info/RECORD +34 -0
- vunit_python_bridge-0.1.0.dist-info/WHEEL +5 -0
- vunit_python_bridge-0.1.0.dist-info/licenses/LICENSE +373 -0
- vunit_python_bridge-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
3
|
+
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2014-2026, Lars Asplund lars.anders.asplund@gmail.com
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
The native bridge library: compiled and cached on Linux, prebuilt DLLs on Windows, compiled
|
|
10
|
+
with gcc on Windows for Questa/ModelSim and when a DLL is missing.
|
|
11
|
+
|
|
12
|
+
This module only depends on the standard library so that tools/build_python_bridge.py
|
|
13
|
+
can load it without VUnit's dependencies.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import hashlib
|
|
17
|
+
import os
|
|
18
|
+
import platform
|
|
19
|
+
import shlex
|
|
20
|
+
import shutil
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
import sysconfig
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Dict, List, Optional, Tuple
|
|
26
|
+
|
|
27
|
+
PACKAGE_PATH = Path(__file__).parent.resolve()
|
|
28
|
+
# C sources of the bridge library
|
|
29
|
+
NATIVE_PATH = PACKAGE_PATH / "native"
|
|
30
|
+
# Prebuilt Windows DLLs, included in releases
|
|
31
|
+
BINARY_PATH = PACKAGE_PATH / "bin"
|
|
32
|
+
# Front end only compiled into the FLI variant of the library
|
|
33
|
+
FLI_SOURCE_NAME = "fli.c"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PythonBridgeError(RuntimeError):
|
|
37
|
+
"""
|
|
38
|
+
The Python bridge cannot be set up in this environment, e.g. due to a missing prerequisite.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def check_python_build() -> None:
|
|
43
|
+
"""
|
|
44
|
+
Reject Python builds that the bridge is known not to support.
|
|
45
|
+
"""
|
|
46
|
+
if sysconfig.get_config_var("Py_GIL_DISABLED"):
|
|
47
|
+
raise PythonBridgeError(
|
|
48
|
+
"VHDL Python support does not work with free-threaded CPython builds "
|
|
49
|
+
f"({sys.executable}). Use a regular (GIL) CPython build."
|
|
50
|
+
)
|
|
51
|
+
if sys.implementation.name != "cpython":
|
|
52
|
+
raise PythonBridgeError(f"VHDL Python support requires CPython, not {sys.implementation.name}")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def prepare_library(root: Path, simulator_prefix: Optional[Path] = None) -> Path:
|
|
56
|
+
"""
|
|
57
|
+
Path of the bridge library for the running Python, built or selected under root.
|
|
58
|
+
|
|
59
|
+
:param simulator_prefix: Executable directory of a simulator calling the bridge through
|
|
60
|
+
the FLI (Questa/ModelSim). The FLI variant of the library, which
|
|
61
|
+
also contains native/fli.c, is then built against its headers.
|
|
62
|
+
None selects the VHPIDIRECT variant used by NVC and GHDL.
|
|
63
|
+
"""
|
|
64
|
+
if sys.platform == "win32":
|
|
65
|
+
add_python_dll_to_path()
|
|
66
|
+
if simulator_prefix is not None:
|
|
67
|
+
return _build_windows_library(root, simulator_prefix)
|
|
68
|
+
return _prepare_windows_library(root)
|
|
69
|
+
return _prepare_posix_library(root, simulator_prefix)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def windows_dll_name() -> str:
|
|
73
|
+
"""
|
|
74
|
+
File name of the prebuilt bridge DLL for the running Python.
|
|
75
|
+
"""
|
|
76
|
+
return f"vunit_python_bridge-cp{sys.version_info[0]}{sys.version_info[1]}-win_amd64.dll"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def windows_python_dll() -> str:
|
|
80
|
+
"""
|
|
81
|
+
Path of the Python DLL of the running interpreter.
|
|
82
|
+
"""
|
|
83
|
+
import ctypes # pylint: disable=import-outside-toplevel
|
|
84
|
+
|
|
85
|
+
dll_handle = getattr(sys, "dllhandle", None)
|
|
86
|
+
if dll_handle is None:
|
|
87
|
+
raise PythonBridgeError(
|
|
88
|
+
f"VHDL Python support requires a CPython build using a Python DLL ({sys.executable} has none)"
|
|
89
|
+
)
|
|
90
|
+
buffer = ctypes.create_unicode_buffer(32768)
|
|
91
|
+
kernel32 = ctypes.windll.kernel32 # type: ignore[attr-defined]
|
|
92
|
+
length = kernel32.GetModuleFileNameW(ctypes.c_void_p(dll_handle), buffer, len(buffer))
|
|
93
|
+
if length == 0:
|
|
94
|
+
raise PythonBridgeError("Failed to determine the path of the Python DLL")
|
|
95
|
+
return buffer.value
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _check_windows_python() -> None:
|
|
99
|
+
"""
|
|
100
|
+
Reject a Python that is not a 64-bit x86 (win-amd64) build.
|
|
101
|
+
"""
|
|
102
|
+
if sysconfig.get_platform() != "win-amd64":
|
|
103
|
+
raise PythonBridgeError(
|
|
104
|
+
"VHDL Python support on Windows requires a 64-bit (x86-64) python.org style CPython, "
|
|
105
|
+
f"{sys.executable} is built for {sysconfig.get_platform()}"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# Machine field values of the PE (Windows executable) header
|
|
110
|
+
_PE_MACHINE_X64 = 0x8664
|
|
111
|
+
_PE_MACHINE_NAMES = {_PE_MACHINE_X64: "64-bit x86 (x64)", 0x014C: "32-bit x86", 0xAA64: "ARM64"}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def pe_machine(path: Path) -> Optional[int]:
|
|
115
|
+
"""
|
|
116
|
+
The machine type in the PE header of a Windows executable, or None if it has no readable one.
|
|
117
|
+
"""
|
|
118
|
+
try:
|
|
119
|
+
with open(path, "rb") as fptr:
|
|
120
|
+
header = fptr.read(4096)
|
|
121
|
+
except OSError:
|
|
122
|
+
return None
|
|
123
|
+
offset = int.from_bytes(header[0x3C:0x40], "little")
|
|
124
|
+
if header[:2] != b"MZ" or header[offset : offset + 4] != b"PE\0\0":
|
|
125
|
+
return None
|
|
126
|
+
return int.from_bytes(header[offset + 4 : offset + 6], "little")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def check_windows_64bit_simulator(simulator_name: str, simulator_prefix: Optional[str]) -> None:
|
|
130
|
+
"""
|
|
131
|
+
On Windows, reject a Python or simulator that is not 64-bit x86. The bridge library and the
|
|
132
|
+
Python DLL it embeds are loaded into the simulator process, so all three must match, and
|
|
133
|
+
only 64-bit Python is supported. A mismatch otherwise fails late with an obscure load error.
|
|
134
|
+
A simulator executable that cannot be read is not checked.
|
|
135
|
+
"""
|
|
136
|
+
if sys.platform != "win32":
|
|
137
|
+
return
|
|
138
|
+
_check_windows_python()
|
|
139
|
+
if simulator_prefix is None:
|
|
140
|
+
return
|
|
141
|
+
# NVC and GHDL name their executable, which VUnit lets the NVC and GHDL environment variables
|
|
142
|
+
# override, the Questa/ModelSim and Aldec interfaces use vsim
|
|
143
|
+
name = os.environ.get(simulator_name.upper(), simulator_name) if simulator_name in ("nvc", "ghdl") else "vsim"
|
|
144
|
+
executable = Path(simulator_prefix) / (name if name.lower().endswith(".exe") else f"{name}.exe")
|
|
145
|
+
machine = pe_machine(executable)
|
|
146
|
+
if machine is not None and machine != _PE_MACHINE_X64:
|
|
147
|
+
kind = _PE_MACHINE_NAMES.get(machine, f"machine type 0x{machine:04x}")
|
|
148
|
+
raise PythonBridgeError(
|
|
149
|
+
f"VHDL Python support requires a 64-bit (x64) simulator, but {executable!s} is {kind}. "
|
|
150
|
+
"Use a 64-bit installation of the simulator."
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _prepare_windows_library(root: Path) -> Path:
|
|
155
|
+
"""
|
|
156
|
+
Select the prebuilt DLL for the running Python, or build the library with gcc when the
|
|
157
|
+
installation has none (e.g. a VUnit package built without the DLLs).
|
|
158
|
+
"""
|
|
159
|
+
_check_windows_python()
|
|
160
|
+
if hasattr(sys, "gettotalrefcount"):
|
|
161
|
+
raise PythonBridgeError("VHDL Python support does not provide bridge DLLs for debug builds of CPython")
|
|
162
|
+
|
|
163
|
+
name = windows_dll_name()
|
|
164
|
+
source = BINARY_PATH / name
|
|
165
|
+
if not source.is_file():
|
|
166
|
+
return _build_windows_library(root)
|
|
167
|
+
data = source.read_bytes()
|
|
168
|
+
directory = root / f"cp{sys.version_info[0]}{sys.version_info[1]}-win_amd64-{hashlib.sha256(data).hexdigest()[:12]}"
|
|
169
|
+
target = directory / "vunit_python_bridge.dll"
|
|
170
|
+
if not (target.is_file() and target.read_bytes() == data):
|
|
171
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
172
|
+
tmp = target.with_name(target.name + f".{os.getpid()}.tmp")
|
|
173
|
+
tmp.write_bytes(data)
|
|
174
|
+
os.replace(tmp, target)
|
|
175
|
+
return target
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _python_library() -> Path:
|
|
179
|
+
"""
|
|
180
|
+
The shared libpython of the running interpreter.
|
|
181
|
+
|
|
182
|
+
Regular --enable-shared builds name it in LDLIBRARY (libpython3.X.so, .dylib); macOS
|
|
183
|
+
framework builds name the framework binary instead (Python.framework/Versions/3.X/Python).
|
|
184
|
+
"""
|
|
185
|
+
ldlibrary = sysconfig.get_config_var("LDLIBRARY") or ""
|
|
186
|
+
framework = sysconfig.get_config_var("PYTHONFRAMEWORK") or ""
|
|
187
|
+
version = f"{sys.version_info[0]}.{sys.version_info[1]}{getattr(sys, 'abiflags', '')}"
|
|
188
|
+
shared = bool(sysconfig.get_config_var("Py_ENABLE_SHARED")) or bool(framework)
|
|
189
|
+
if not shared or not (framework or ldlibrary.endswith((".so", ".dylib")) or ".so." in ldlibrary):
|
|
190
|
+
raise PythonBridgeError(
|
|
191
|
+
f"VHDL Python support requires a CPython built with a shared Python library (--enable-shared). "
|
|
192
|
+
f"{sys.executable} links Python statically "
|
|
193
|
+
f"(LDLIBRARY={ldlibrary!r}, Py_ENABLE_SHARED={sysconfig.get_config_var('Py_ENABLE_SHARED')!r})."
|
|
194
|
+
)
|
|
195
|
+
prefix = Path(sys.base_prefix)
|
|
196
|
+
candidates = [Path(sysconfig.get_config_var("LIBDIR") or "") / ldlibrary]
|
|
197
|
+
# Relocated installations (e.g. uv/python-build-standalone) may report a
|
|
198
|
+
# build-time LIBDIR, fall back to the installation prefix.
|
|
199
|
+
candidates.append(prefix / "lib" / ldlibrary)
|
|
200
|
+
if sys.platform == "darwin":
|
|
201
|
+
candidates.append(prefix / "lib" / f"libpython{version}.dylib")
|
|
202
|
+
if framework:
|
|
203
|
+
# The framework binary: the prefix of a framework build is Versions/3.X
|
|
204
|
+
candidates.append(prefix / framework)
|
|
205
|
+
framework_prefix = sysconfig.get_config_var("PYTHONFRAMEWORKPREFIX") or ""
|
|
206
|
+
candidates.append(Path(framework_prefix) / f"{framework}.framework" / "Versions" / version / framework)
|
|
207
|
+
for candidate in candidates:
|
|
208
|
+
if candidate.is_file():
|
|
209
|
+
return candidate.resolve()
|
|
210
|
+
raise PythonBridgeError(
|
|
211
|
+
f"Failed to find the shared Python library {ldlibrary} of {sys.executable} "
|
|
212
|
+
f"(searched {', '.join(str(path) for path in candidates)})"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _include_dirs(candidates: Optional[List[str]] = None) -> List[str]:
|
|
217
|
+
"""
|
|
218
|
+
Include directories of the Python development headers, by default the ones sysconfig names.
|
|
219
|
+
"""
|
|
220
|
+
result: List[str] = []
|
|
221
|
+
for path in candidates or [sysconfig.get_paths().get(name, "") for name in ("include", "platinclude")]:
|
|
222
|
+
if path and path not in result:
|
|
223
|
+
result.append(path)
|
|
224
|
+
for path in result:
|
|
225
|
+
if (Path(path) / "Python.h").is_file():
|
|
226
|
+
return result
|
|
227
|
+
raise PythonBridgeError(
|
|
228
|
+
f"VHDL Python support needs the Python development headers (Python.h) of {sys.executable} "
|
|
229
|
+
f"to build the Python bridge library, but they were not found in {', '.join(result)}. "
|
|
230
|
+
"Install the development package of your Python (e.g. python3-dev)."
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _compiler() -> List[str]:
|
|
235
|
+
"""
|
|
236
|
+
The C compiler command: CC, the compiler Python was built with, or cc/gcc/clang.
|
|
237
|
+
"""
|
|
238
|
+
if os.environ.get("CC"):
|
|
239
|
+
return shlex.split(os.environ["CC"])
|
|
240
|
+
configured = shlex.split(sysconfig.get_config_var("CC") or "")
|
|
241
|
+
if configured and shutil.which(configured[0]):
|
|
242
|
+
return configured[:1]
|
|
243
|
+
for name in ("cc", "gcc", "clang"):
|
|
244
|
+
if shutil.which(name):
|
|
245
|
+
return [name]
|
|
246
|
+
raise PythonBridgeError(
|
|
247
|
+
"VHDL Python support needs a C compiler (cc, gcc or clang) to build the Python bridge library. "
|
|
248
|
+
"Install one or set the CC environment variable."
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def bridge_sources(fli: bool = False) -> List[Path]:
|
|
253
|
+
"""
|
|
254
|
+
The C source files of the bridge library. fli adds the FLI front end.
|
|
255
|
+
"""
|
|
256
|
+
sources = sorted(item for item in NATIVE_PATH.glob("*.c") if item.name != FLI_SOURCE_NAME)
|
|
257
|
+
if fli:
|
|
258
|
+
sources.append(NATIVE_PATH / FLI_SOURCE_NAME)
|
|
259
|
+
return sources
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _fli_include_dir(simulator_prefix: Path) -> str:
|
|
263
|
+
"""
|
|
264
|
+
Directory of mti.h, next to the executable directory of the simulator.
|
|
265
|
+
"""
|
|
266
|
+
include = Path(simulator_prefix).resolve().parent / "include"
|
|
267
|
+
if not (include / "mti.h").is_file():
|
|
268
|
+
raise PythonBridgeError(
|
|
269
|
+
f"VHDL Python support needs the FLI header mti.h of the simulator to build the Python "
|
|
270
|
+
f"bridge library, but it was not found in {include!s}"
|
|
271
|
+
)
|
|
272
|
+
return str(include)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _source_fingerprint() -> str:
|
|
276
|
+
"""
|
|
277
|
+
Hash of all bridge sources and headers.
|
|
278
|
+
"""
|
|
279
|
+
digest = hashlib.sha256()
|
|
280
|
+
for path in sorted(NATIVE_PATH.glob("*.[ch]")):
|
|
281
|
+
digest.update(path.name.encode("utf-8") + b"\0" + path.read_bytes() + b"\0")
|
|
282
|
+
return digest.hexdigest()
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _posix_cache_directory(root: Path, python_library: Path, include_dirs: List[str]) -> Path:
|
|
286
|
+
"""
|
|
287
|
+
Cache directory of a build: everything it depends on hashed into its name. The
|
|
288
|
+
include directories cover the FLI variant, whose simulator headers are among them.
|
|
289
|
+
"""
|
|
290
|
+
key_items = [
|
|
291
|
+
_source_fingerprint(),
|
|
292
|
+
sys.platform,
|
|
293
|
+
platform.machine(),
|
|
294
|
+
" ".join(platform.libc_ver()),
|
|
295
|
+
sys.version,
|
|
296
|
+
str(sysconfig.get_config_var("SOABI")),
|
|
297
|
+
str(sysconfig.get_config_var("Py_GIL_DISABLED")),
|
|
298
|
+
str(python_library),
|
|
299
|
+
" ".join(include_dirs),
|
|
300
|
+
]
|
|
301
|
+
key = hashlib.sha256("\n".join(key_items).encode("utf-8")).hexdigest()[:16]
|
|
302
|
+
tag = sysconfig.get_config_var("SOABI") or f"cp{sys.version_info[0]}{sys.version_info[1]}"
|
|
303
|
+
return root / f"{tag}-{key}"
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _prepare_posix_library(root: Path, simulator_prefix: Optional[Path] = None) -> Path:
|
|
307
|
+
"""
|
|
308
|
+
Compile the bridge for the running Python, reusing a cached build when possible.
|
|
309
|
+
|
|
310
|
+
The FLI variant, selected with simulator_prefix, differs only in the added front end,
|
|
311
|
+
the added include directory and the file name, so both variants share this cache: the
|
|
312
|
+
simulator prefix is part of the key through its include directory.
|
|
313
|
+
"""
|
|
314
|
+
fli = simulator_prefix is not None
|
|
315
|
+
python_library = _python_library()
|
|
316
|
+
include_dirs = _include_dirs()
|
|
317
|
+
if simulator_prefix is not None:
|
|
318
|
+
include_dirs = include_dirs + [_fli_include_dir(simulator_prefix)]
|
|
319
|
+
|
|
320
|
+
name = "libvunit_python_bridge_fli.so" if fli else "libvunit_python_bridge.so"
|
|
321
|
+
directory = _posix_cache_directory(root, python_library, include_dirs)
|
|
322
|
+
library_file = directory / name
|
|
323
|
+
if library_file.is_file():
|
|
324
|
+
return library_file
|
|
325
|
+
|
|
326
|
+
compiler = _compiler()
|
|
327
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
328
|
+
tmp = directory / f"{name}.{os.getpid()}.tmp"
|
|
329
|
+
cmd = (
|
|
330
|
+
compiler
|
|
331
|
+
+ ["-shared", "-fPIC", "-O2", "-fvisibility=hidden"]
|
|
332
|
+
+ [f"-I{path}" for path in include_dirs]
|
|
333
|
+
+ [str(path) for path in bridge_sources(fli=fli)]
|
|
334
|
+
+ ["-o", str(tmp)]
|
|
335
|
+
+ [str(python_library), f"-Wl,-rpath,{python_library.parent!s}"]
|
|
336
|
+
+ ([f"-Wl,-install_name,@rpath/{name}"] if sys.platform == "darwin" else [f"-Wl,-soname,{name}"])
|
|
337
|
+
+ (["-ldl"] if sys.platform.startswith("linux") else [])
|
|
338
|
+
)
|
|
339
|
+
return compile_library(cmd, tmp, library_file)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def compile_library(cmd: List[str], tmp: Path, library_file: Path, env: Optional[Dict[str, str]] = None) -> Path:
|
|
343
|
+
"""
|
|
344
|
+
Run a compiler command building tmp and move the result to library_file, failing with the
|
|
345
|
+
compiler output.
|
|
346
|
+
"""
|
|
347
|
+
try:
|
|
348
|
+
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False, env=env)
|
|
349
|
+
except OSError as exc:
|
|
350
|
+
raise PythonBridgeError(f"Failed to run the C compiler {cmd[0]!r}: {exc}") from exc
|
|
351
|
+
if proc.returncode != 0:
|
|
352
|
+
tmp.unlink(missing_ok=True)
|
|
353
|
+
raise PythonBridgeError(
|
|
354
|
+
"Failed to build the Python bridge library:\n"
|
|
355
|
+
+ " ".join(shlex.quote(item) for item in cmd)
|
|
356
|
+
+ "\n"
|
|
357
|
+
+ proc.stdout.decode(errors="replace")
|
|
358
|
+
)
|
|
359
|
+
if tmp != library_file:
|
|
360
|
+
os.replace(tmp, library_file)
|
|
361
|
+
return library_file
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def windows_gcc(simulator_prefix: Optional[Path] = None) -> Optional[List[str]]:
|
|
365
|
+
"""
|
|
366
|
+
The C compiler command for builds on Windows, or None: CC, else the MinGW gcc bundled with
|
|
367
|
+
the simulator (Questa/ModelSim: gcc-*-mingw64*, Riviera-PRO: mingw), else gcc on PATH.
|
|
368
|
+
Not every installation bundles a gcc, e.g. Questa on Windows often does not.
|
|
369
|
+
"""
|
|
370
|
+
if os.environ.get("CC"):
|
|
371
|
+
return shlex.split(os.environ["CC"])
|
|
372
|
+
if simulator_prefix is not None:
|
|
373
|
+
install = Path(simulator_prefix).resolve().parent
|
|
374
|
+
bundled = sorted(install.glob("gcc*mingw64*/bin/gcc.exe")) + sorted(install.glob("mingw*/bin/gcc.exe"))
|
|
375
|
+
if bundled:
|
|
376
|
+
return [str(bundled[0])]
|
|
377
|
+
gcc = shutil.which("gcc")
|
|
378
|
+
return [gcc] if gcc else None
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def python_dev_paths() -> Tuple[str, Path]:
|
|
382
|
+
"""
|
|
383
|
+
The directory of Python.h and of the Python library to link against, for the Python running
|
|
384
|
+
VUnit. Both belong to the base installation, also when VUnit runs in a virtual environment,
|
|
385
|
+
which has neither.
|
|
386
|
+
"""
|
|
387
|
+
include = next(
|
|
388
|
+
path
|
|
389
|
+
for path in _include_dirs([sysconfig.get_paths()["include"], str(Path(sys.base_prefix) / "include")])
|
|
390
|
+
if (Path(path) / "Python.h").is_file()
|
|
391
|
+
)
|
|
392
|
+
if sys.platform == "win32":
|
|
393
|
+
return include, Path(sys.base_prefix) / "libs"
|
|
394
|
+
return include, Path(sysconfig.get_config_var("LIBDIR") or Path(sys.base_prefix) / "lib")
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def add_python_dll_to_path() -> None:
|
|
398
|
+
"""
|
|
399
|
+
Libraries built with gcc on Windows import the Python DLL directly (the prebuilt DLLs load it
|
|
400
|
+
themselves), so the simulator processes VUnit starts must find it through PATH.
|
|
401
|
+
"""
|
|
402
|
+
# ponytail: changes PATH of the whole VUnit process, only for its children to find pythonXY.dll
|
|
403
|
+
directory = str(Path(windows_python_dll()).parent)
|
|
404
|
+
paths = os.environ.get("PATH", "").split(os.pathsep)
|
|
405
|
+
if directory not in paths:
|
|
406
|
+
os.environ["PATH"] = os.pathsep.join([item for item in paths if item] + [directory])
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _build_windows_library(root: Path, simulator_prefix: Optional[Path] = None) -> Path:
|
|
410
|
+
"""
|
|
411
|
+
Build the library on Windows with gcc (see windows_gcc) against the headers and the import
|
|
412
|
+
library of the Python running VUnit, reusing a cached build like the POSIX one.
|
|
413
|
+
|
|
414
|
+
With simulator_prefix, the FLI variant for Questa/ModelSim: the prebuilt DLLs cannot be used
|
|
415
|
+
for it, since they are MSVC builds without the FLI front end, which has to be linked against
|
|
416
|
+
the simulator's own libmtipli. Without, the VHPIDIRECT variant for NVC and GHDL, built only
|
|
417
|
+
when there is no prebuilt DLL.
|
|
418
|
+
|
|
419
|
+
Untested: this environment has no Windows installation.
|
|
420
|
+
"""
|
|
421
|
+
fli = simulator_prefix is not None
|
|
422
|
+
compiler = windows_gcc(simulator_prefix)
|
|
423
|
+
if compiler is None:
|
|
424
|
+
bundled = ""
|
|
425
|
+
if simulator_prefix is not None:
|
|
426
|
+
bundled = f"the simulator installation ({Path(simulator_prefix).resolve().parent!s}) bundles none and "
|
|
427
|
+
raise PythonBridgeError(
|
|
428
|
+
"VHDL Python support on Windows needs gcc (MinGW-w64) to build the Python bridge library, but "
|
|
429
|
+
f"{bundled}there is no gcc on PATH. Install MinGW-w64 and put its bin directory on PATH, "
|
|
430
|
+
"or set the CC environment variable."
|
|
431
|
+
)
|
|
432
|
+
python_include, python_libs = python_dev_paths()
|
|
433
|
+
include_dirs = [python_include] + ([_fli_include_dir(simulator_prefix)] if simulator_prefix is not None else [])
|
|
434
|
+
|
|
435
|
+
name = "vunit_python_bridge_fli.dll" if fli else "vunit_python_bridge.dll"
|
|
436
|
+
key_items = [_source_fingerprint(), sys.version, sys.base_prefix, str(simulator_prefix), " ".join(compiler)]
|
|
437
|
+
key = hashlib.sha256("\n".join(key_items).encode("utf-8")).hexdigest()[:16]
|
|
438
|
+
directory = root / f"cp{sys.version_info[0]}{sys.version_info[1]}-win_amd64-{'fli' if fli else 'gcc'}-{key}"
|
|
439
|
+
library_file = directory / name
|
|
440
|
+
if library_file.is_file():
|
|
441
|
+
return library_file
|
|
442
|
+
|
|
443
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
444
|
+
tmp = directory / f"{name}.{os.getpid()}.tmp"
|
|
445
|
+
cmd = (
|
|
446
|
+
compiler
|
|
447
|
+
+ ["-shared", "-m64", "-O2", "-D__USE_MINGW_ANSI_STDIO=1"]
|
|
448
|
+
+ (["-freg-struct-return"] if fli else [])
|
|
449
|
+
+ [f"-I{path}" for path in include_dirs]
|
|
450
|
+
+ [str(path) for path in bridge_sources(fli=fli)]
|
|
451
|
+
+ ["-o", str(tmp)]
|
|
452
|
+
+ [f"-L{python_libs!s}", f"-lpython{sys.version_info[0]}{sys.version_info[1]}"]
|
|
453
|
+
+ ([f"-L{Path(simulator_prefix).resolve()!s}", "-lmtipli"] if simulator_prefix is not None else [])
|
|
454
|
+
)
|
|
455
|
+
return compile_library(cmd, tmp, library_file)
|