dv-solve 0.2.3__py3-none-win_amd64.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.
- dv_solve/__init__.py +253 -0
- dv_solve/__version__.py +42 -0
- dv_solve/_resolve.py +594 -0
- dv_solve/buffer_inference.py +181 -0
- dv_solve/builder.py +451 -0
- dv_solve/bvsat.py +235 -0
- dv_solve/ctx.py +483 -0
- dv_solve/dv_solve.dll +0 -0
- dv_solve/dv_solve.lib +0 -0
- dv_solve/dv_solve_debug.dll +0 -0
- dv_solve/dv_solve_debug.lib +0 -0
- dv_solve/dv_solve_dpi.dll +0 -0
- dv_solve/dv_solve_dpi.lib +0 -0
- dv_solve/flow_constraint_store.py +114 -0
- dv_solve/icl.py +96 -0
- dv_solve/lib.py +385 -0
- dv_solve/partitioner.py +119 -0
- dv_solve/problem.py +282 -0
- dv_solve/scheduling_graph.py +293 -0
- dv_solve/share/include/dv_solve/compat/win/msvc_compat.h +130 -0
- dv_solve/share/include/dv_solve/compat/win/sys/resource.h +26 -0
- dv_solve/share/include/dv_solve/compat/win/sys/time.h +25 -0
- dv_solve/share/include/dv_solve/compat/win/unistd.h +42 -0
- dv_solve/share/include/dv_solve/dv_solve.h +407 -0
- dv_solve/share/include/dv_solve/dvs_aig.h +107 -0
- dv_solve/share/include/dv_solve/dvs_aig_cnf.h +65 -0
- dv_solve/share/include/dv_solve/dvs_alloc.h +30 -0
- dv_solve/share/include/dv_solve/dvs_arena.h +110 -0
- dv_solve/share/include/dv_solve/dvs_bbsolver.h +289 -0
- dv_solve/share/include/dv_solve/dvs_bitblast.h +102 -0
- dv_solve/share/include/dv_solve/dvs_block_alloc.h +69 -0
- dv_solve/share/include/dv_solve/dvs_builder.h +199 -0
- dv_solve/share/include/dv_solve/dvs_bvbounds.h +116 -0
- dv_solve/share/include/dv_solve/dvs_bvdom.h +138 -0
- dv_solve/share/include/dv_solve/dvs_contradiction.h +173 -0
- dv_solve/share/include/dv_solve/dvs_costguided.h +238 -0
- dv_solve/share/include/dv_solve/dvs_ctx.h +411 -0
- dv_solve/share/include/dv_solve/dvs_cube.h +44 -0
- dv_solve/share/include/dv_solve/dvs_diffcycle.h +54 -0
- dv_solve/share/include/dv_solve/dvs_dpi.h +139 -0
- dv_solve/share/include/dv_solve/dvs_explain.h +134 -0
- dv_solve/share/include/dv_solve/dvs_i128.h +157 -0
- dv_solve/share/include/dv_solve/dvs_lcg.h +304 -0
- dv_solve/share/include/dv_solve/dvs_placement.h +157 -0
- dv_solve/share/include/dv_solve/dvs_pool.h +83 -0
- dv_solve/share/include/dv_solve/dvs_problem.h +514 -0
- dv_solve/share/include/dv_solve/dvs_propagator.h +561 -0
- dv_solve/share/include/dv_solve/dvs_sat.h +226 -0
- dv_solve/share/include/dv_solve/dvs_sat_backend.h +87 -0
- dv_solve/share/include/dv_solve/dvs_search.h +144 -0
- dv_solve/share/include/dv_solve/dvs_shave.h +31 -0
- dv_solve/share/include/dv_solve/dvs_stack.h +79 -0
- dv_solve/share/include/dv_solve/dvs_stackinfo.h +39 -0
- dv_solve/share/include/dv_solve/dvs_sv.h +105 -0
- dv_solve/share/include/dv_solve/dvs_thread.h +86 -0
- dv_solve/share/include/dv_solve/dvs_trail.h +146 -0
- dv_solve/share/include/dv_solve/dvs_variable.h +79 -0
- dv_solve/share/include/dv_solve/dvs_wiremask.h +81 -0
- dv_solve/share/include/dv_solve/smt2/smt2_frontend.h +620 -0
- dv_solve/share/include/dv_solve/smt2/smt2_lexer.h +106 -0
- dv_solve/share/include/dv_solve/smt2/smt2_parser.h +90 -0
- dv_solve/share/include/dv_solve/svdpi_compat.h +60 -0
- dv_solve/share/sv/dvs_dpi_pkg.sv +74 -0
- dv_solve/share/sv/dvs_randomizer_pkg.sv +76 -0
- dv_solve/state_graph.py +249 -0
- dv_solve/stream_solver.py +304 -0
- dv_solve/structural_solver.py +297 -0
- dv_solve-0.2.3.data/scripts/dv-solve-smt2.exe +0 -0
- dv_solve-0.2.3.dist-info/DELVEWHEEL +2 -0
- dv_solve-0.2.3.dist-info/METADATA +110 -0
- dv_solve-0.2.3.dist-info/RECORD +78 -0
- dv_solve-0.2.3.dist-info/WHEEL +5 -0
- dv_solve-0.2.3.dist-info/licenses/LICENSE +201 -0
- dv_solve-0.2.3.dist-info/top_level.txt +1 -0
- dv_solve.libs/.load-order-dv_solve-0.2.3 +3 -0
- dv_solve.libs/msvcp140.dll +0 -0
- dv_solve.libs/vcruntime140.dll +0 -0
- dv_solve.libs/vcruntime140_1.dll +0 -0
dv_solve/_resolve.py
ADDED
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
"""The single source of truth for locating dv-solve's native artifacts.
|
|
2
|
+
|
|
3
|
+
Both consumers of this module must agree, or a build silently mixes two
|
|
4
|
+
installations:
|
|
5
|
+
|
|
6
|
+
* ``lib.py`` dlopen()s the core library for the Python API;
|
|
7
|
+
* ``__init__.py``'s ``get_libdirs`` / ``get_dpi_lib`` / ``get_incdirs`` /
|
|
8
|
+
``get_svdirs`` tell a C consumer what to compile and link against.
|
|
9
|
+
|
|
10
|
+
Those two used to search different places in a different order -- the loader
|
|
11
|
+
consulted ``LD_LIBRARY_PATH``, the package's ``lib/`` subdirectory, three
|
|
12
|
+
alternate build-directory names and a ``/tmp`` pytest glob that the public
|
|
13
|
+
helpers knew nothing about. A host with any of those set could therefore run
|
|
14
|
+
the solver out of one installation while linking generated C against another,
|
|
15
|
+
and the ABI mismatch surfaces as a crash inside ``dvs_solver_compile``, nowhere
|
|
16
|
+
near its cause. Hence one resolver, used by both.
|
|
17
|
+
|
|
18
|
+
THE CONTRACT
|
|
19
|
+
------------
|
|
20
|
+
|
|
21
|
+
The unit of discovery is an INSTALLATION, not an artifact. An installation is
|
|
22
|
+
one root together with the places its libraries, headers and SV sources live.
|
|
23
|
+
Candidates, best first:
|
|
24
|
+
|
|
25
|
+
1. ``DVS_SOLVER_PATH`` (or the legacy ``ZSP_SOLVER_PATH``) -- the explicit
|
|
26
|
+
override. When set it is the ONLY
|
|
27
|
+
candidate: it is selected whether or not it is usable, and anything it
|
|
28
|
+
lacks is reported as missing from it rather than supplied by a later
|
|
29
|
+
candidate. Accepts a directory holding the artifacts or an install
|
|
30
|
+
prefix with the usual ``lib/``, ``lib64/``, ``include/``, ``share/``
|
|
31
|
+
subdirectories.
|
|
32
|
+
2. The installed package: the directory holding ``__init__.py``, then its
|
|
33
|
+
``lib/`` subdirectory. This is the binary-wheel layout.
|
|
34
|
+
3. A built source checkout, one candidate per supported build-directory
|
|
35
|
+
name: ``<root>/<build>`` and its ``lib``/``lib64`` install destinations.
|
|
36
|
+
4. Each ``LD_LIBRARY_PATH`` entry -- a last-resort, library-only fallback,
|
|
37
|
+
NOT an override. It supplies no headers or SV sources.
|
|
38
|
+
|
|
39
|
+
SELECTION: the override if set, otherwise the first candidate holding a
|
|
40
|
+
loadable core library (``libdv_solve.so`` or a numerically versioned
|
|
41
|
+
``libdv_solve.so.N...``). Every artifact -- core library for the loader, core
|
|
42
|
+
library for the linker, DPI library, headers, SV sources -- is then taken
|
|
43
|
+
from the selected installation and from nowhere else. An artifact the selected
|
|
44
|
+
installation lacks is MISSING; it is never borrowed from another candidate,
|
|
45
|
+
because that is exactly how a build mixes two solver builds.
|
|
46
|
+
|
|
47
|
+
LOADING VS LINKING within the selected installation: the unversioned name is
|
|
48
|
+
preferred for both, so whenever the installation is linkable the loader opens
|
|
49
|
+
the very file ``-ldv_solve`` resolves. An installation holding only a
|
|
50
|
+
versioned library is loadable but NOT linkable; ``require_library(...,
|
|
51
|
+
linkable=True)`` then raises naming that installation, rather than letting the
|
|
52
|
+
linker fall through to a different one.
|
|
53
|
+
|
|
54
|
+
When no candidate holds a core library at all, nothing is selected. Headers
|
|
55
|
+
and SV sources may still be located (from the first candidate that has them)
|
|
56
|
+
for compile-only consumers; there is no library for them to disagree with.
|
|
57
|
+
|
|
58
|
+
Rationale for (4) being last. It used to come second, ahead of the package,
|
|
59
|
+
which meant an ambient setting -- a simulator's wrapper script, a module-load,
|
|
60
|
+
a CI runner's default -- silently redirected the loader while the public
|
|
61
|
+
helpers, which never consulted it at all, kept reporting the package. That is
|
|
62
|
+
precisely the loader/linker split this module exists to prevent. ``LD_LIBRARY_
|
|
63
|
+
PATH`` still resolves the *dependencies* of whatever library is chosen; it
|
|
64
|
+
chooses only when nothing else can. Callers who genuinely want to select an
|
|
65
|
+
out-of-tree build have ``DVS_SOLVER_PATH``, which is unambiguous about intent.
|
|
66
|
+
|
|
67
|
+
Everything here probes for the ARTIFACT, never for the directory, and an
|
|
68
|
+
artifact must be a real file: a directory named ``libdv_solve.so`` or a
|
|
69
|
+
dangling symlink is not a library (a valid symlink to a library is). A
|
|
70
|
+
configured but unbuilt CMake tree has a ``build/lib`` the moment it is
|
|
71
|
+
configured, and an unbuilt source checkout has a ``src/c`` full of headers
|
|
72
|
+
but no library; a directory existing says nothing about whether the thing you
|
|
73
|
+
need is in it.
|
|
74
|
+
"""
|
|
75
|
+
from __future__ import annotations
|
|
76
|
+
|
|
77
|
+
import os
|
|
78
|
+
import platform
|
|
79
|
+
import re
|
|
80
|
+
from typing import List, NamedTuple, Optional
|
|
81
|
+
|
|
82
|
+
#: Build-directory names a checkout may use. ``build`` is what this project's
|
|
83
|
+
#: own instructions and CI produce; the rest are long-supported aliases that
|
|
84
|
+
#: the ctypes loader has always accepted, kept so that dropping them is not a
|
|
85
|
+
#: silent regression for anyone whose tree uses one.
|
|
86
|
+
_BUILD_DIR_NAMES = ("build", "_build", "build_release", "cmake-build-release")
|
|
87
|
+
|
|
88
|
+
#: Where CMake puts libraries under a build directory. The empty string is the
|
|
89
|
+
#: pre-install link output that sits in the build root itself, which makes a
|
|
90
|
+
#: checkout that has been built but never ``cmake --install``-ed resolve.
|
|
91
|
+
_LIB_SUBDIRS = ("lib", "lib64", "")
|
|
92
|
+
|
|
93
|
+
#: A header that exists in every dv-solve include tree, used to tell a real
|
|
94
|
+
#: include directory from a directory that merely exists.
|
|
95
|
+
_SENTINEL_HEADER = "dv_solve.h"
|
|
96
|
+
|
|
97
|
+
#: Likewise for the SystemVerilog resource directory.
|
|
98
|
+
_SENTINEL_SV = "dvs_dpi_pkg.sv"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _pkg_dir() -> str:
|
|
102
|
+
return os.path.dirname(os.path.abspath(__file__))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _src_root() -> str:
|
|
106
|
+
"""``packages/dv-solve/`` when imported from a source tree (or editable
|
|
107
|
+
install). Meaningless for a wheel installed into site-packages, where it
|
|
108
|
+
points at some interpreter-internal directory -- harmless, because every
|
|
109
|
+
path built from it is then probed for an artifact that will not be there.
|
|
110
|
+
"""
|
|
111
|
+
return os.path.abspath(os.path.join(_pkg_dir(), "..", ".."))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def lib_filename(stem: str) -> str:
|
|
115
|
+
"""The platform's file name for shared library *stem* (no directory)."""
|
|
116
|
+
system = platform.system()
|
|
117
|
+
if system == "Windows":
|
|
118
|
+
return "%s.dll" % stem
|
|
119
|
+
if system == "Darwin":
|
|
120
|
+
return "lib%s.dylib" % stem
|
|
121
|
+
return "lib%s.so" % stem
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def lib_patterns(stem: str) -> List[str]:
|
|
125
|
+
"""Glob patterns for library *stem*, most-preferred first.
|
|
126
|
+
|
|
127
|
+
The unversioned name comes first because it is the only one a linker will
|
|
128
|
+
accept for ``-l<stem>``; the soname-suffixed form is loadable by dlopen but
|
|
129
|
+
not linkable, so preferring it would let the loader and the linker pick
|
|
130
|
+
different files.
|
|
131
|
+
"""
|
|
132
|
+
system = platform.system()
|
|
133
|
+
if system == "Windows":
|
|
134
|
+
return ["%s.dll" % stem, "lib%s.dll" % stem]
|
|
135
|
+
if system == "Darwin":
|
|
136
|
+
return ["lib%s.dylib" % stem, "lib%s.*.dylib" % stem]
|
|
137
|
+
return ["lib%s.so" % stem, "lib%s.so.*" % stem]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class Installation(NamedTuple):
|
|
141
|
+
"""One candidate root and where its artifacts may live.
|
|
142
|
+
|
|
143
|
+
``inc_sets`` is a list of include-directory SETS rather than directories:
|
|
144
|
+
the installed layout needs two entries (see :func:`find_incdirs`).
|
|
145
|
+
"""
|
|
146
|
+
kind: str # "override" | "package" | "checkout" | "ld_library_path"
|
|
147
|
+
root: str
|
|
148
|
+
lib_dirs: List[str]
|
|
149
|
+
inc_sets: List[List[str]]
|
|
150
|
+
sv_dirs: List[str]
|
|
151
|
+
|
|
152
|
+
def describe(self) -> str:
|
|
153
|
+
if self.kind == "override":
|
|
154
|
+
return "%s=%s" % (_override_var(), self.root)
|
|
155
|
+
if self.kind == "ld_library_path":
|
|
156
|
+
var = "PATH" if platform.system() == "Windows" else "LD_LIBRARY_PATH"
|
|
157
|
+
return "%s entry %s" % (var, self.root)
|
|
158
|
+
return "%s installation at %s" % (self.kind, self.root)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def override_root() -> Optional[str]:
|
|
162
|
+
"""The ``DVS_SOLVER_PATH`` value, or the legacy ``ZSP_SOLVER_PATH`` when
|
|
163
|
+
only that is set (zuspec-be-sw still sets the old name). ``None`` when
|
|
164
|
+
neither is set."""
|
|
165
|
+
return (os.environ.get("DVS_SOLVER_PATH")
|
|
166
|
+
or os.environ.get("ZSP_SOLVER_PATH") or None)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _override_var() -> str:
|
|
170
|
+
"""The name of the override variable in effect, for messages."""
|
|
171
|
+
if os.environ.get("DVS_SOLVER_PATH") or not os.environ.get("ZSP_SOLVER_PATH"):
|
|
172
|
+
return "DVS_SOLVER_PATH"
|
|
173
|
+
return "ZSP_SOLVER_PATH"
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _override() -> Optional[Installation]:
|
|
177
|
+
"""The ``DVS_SOLVER_PATH`` installation, or ``None`` when unset.
|
|
178
|
+
|
|
179
|
+
The variable has historically pointed straight at a directory containing
|
|
180
|
+
the library, so that stays first. A prefix-style value (one with ``lib/``,
|
|
181
|
+
``include/`` or ``share/`` underneath) also works, because a user who
|
|
182
|
+
points at a CMake install prefix reasonably expects that to be understood.
|
|
183
|
+
"""
|
|
184
|
+
root = override_root()
|
|
185
|
+
if not root:
|
|
186
|
+
return None
|
|
187
|
+
j = os.path.join
|
|
188
|
+
inc_sets = [[root]]
|
|
189
|
+
for base in (j(root, "include"), j(root, "share", "include")):
|
|
190
|
+
inc_sets.append([base, j(base, "dv_solve")])
|
|
191
|
+
return Installation(
|
|
192
|
+
"override", root,
|
|
193
|
+
lib_dirs=[root, j(root, "lib"), j(root, "lib64")],
|
|
194
|
+
inc_sets=inc_sets,
|
|
195
|
+
sv_dirs=[root, j(root, "share", "sv"), j(root, "sv")])
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _package() -> Installation:
|
|
199
|
+
pkg, j = _pkg_dir(), os.path.join
|
|
200
|
+
return Installation(
|
|
201
|
+
"package", pkg,
|
|
202
|
+
lib_dirs=[pkg, j(pkg, "lib")],
|
|
203
|
+
inc_sets=[[b, j(b, "dv_solve")]
|
|
204
|
+
for b in (j(pkg, "share", "include"), j(pkg, "include"))],
|
|
205
|
+
sv_dirs=[j(pkg, "share", "sv")])
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _checkouts() -> List[Installation]:
|
|
209
|
+
"""One candidate per build directory of a source checkout.
|
|
210
|
+
|
|
211
|
+
The checkout's flat ``src/c`` headers and ``src/sv`` sources belong to
|
|
212
|
+
every build of it; a build's own ``include``/``share/sv`` install tree
|
|
213
|
+
follows as an alternative.
|
|
214
|
+
"""
|
|
215
|
+
root, j = _src_root(), os.path.join
|
|
216
|
+
out = []
|
|
217
|
+
for name in _BUILD_DIR_NAMES:
|
|
218
|
+
bd = j(root, name)
|
|
219
|
+
out.append(Installation(
|
|
220
|
+
"checkout", bd,
|
|
221
|
+
lib_dirs=[j(bd, sub) if sub else bd for sub in _LIB_SUBDIRS],
|
|
222
|
+
inc_sets=[[j(root, "src", "c")],
|
|
223
|
+
[j(bd, "include"), j(bd, "include", "dv_solve")]],
|
|
224
|
+
sv_dirs=[j(root, "src", "sv"), j(bd, "share", "sv")]))
|
|
225
|
+
return out
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _ld_library_path_dirs() -> List[str]:
|
|
229
|
+
var = "PATH" if platform.system() == "Windows" else "LD_LIBRARY_PATH"
|
|
230
|
+
return [d for d in os.environ.get(var, "").split(os.pathsep) if d]
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def candidates() -> List[Installation]:
|
|
234
|
+
"""Every candidate installation, best first. See the module docstring."""
|
|
235
|
+
override = _override()
|
|
236
|
+
if override is not None:
|
|
237
|
+
return [override] # terminal: nothing else competes
|
|
238
|
+
out = [_package()] + _checkouts()
|
|
239
|
+
out += [Installation("ld_library_path", d, [d], [], [])
|
|
240
|
+
for d in _ld_library_path_dirs()] # fallback, never an override
|
|
241
|
+
return out
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def lib_search_dirs() -> List[str]:
|
|
245
|
+
"""Every directory that may hold a dv-solve shared library, best first."""
|
|
246
|
+
return [d for inst in candidates() for d in inst.lib_dirs]
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# ------------------------------------------------------------ file probing --
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _versioned_re(stem: str) -> Optional["re.Pattern[str]"]:
|
|
253
|
+
"""Names of a numerically versioned library -- loadable, not linkable.
|
|
254
|
+
|
|
255
|
+
Numeric suffixes only, so ``libdv_solve.so.1.debug`` or a stray
|
|
256
|
+
``libdv_solve.so.bak`` is never mistaken for the library.
|
|
257
|
+
"""
|
|
258
|
+
system = platform.system()
|
|
259
|
+
if system == "Windows":
|
|
260
|
+
return None
|
|
261
|
+
if system == "Darwin":
|
|
262
|
+
return re.compile(r"^lib%s(\.\d+)+\.dylib$" % re.escape(stem))
|
|
263
|
+
return re.compile(r"^lib%s\.so(\.\d+)+$" % re.escape(stem))
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _is_file(path: str) -> bool:
|
|
267
|
+
"""A real file, or a symlink that ends at one. ``isfile`` follows links,
|
|
268
|
+
so this rejects both directories and dangling symlinks."""
|
|
269
|
+
return os.path.isfile(path)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _unversioned_in(d: str, stem: str) -> Optional[str]:
|
|
273
|
+
p = os.path.join(d, lib_filename(stem))
|
|
274
|
+
return p if _is_file(p) else None
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _versioned_in(d: str, stem: str) -> Optional[str]:
|
|
278
|
+
rx = _versioned_re(stem)
|
|
279
|
+
if rx is None or not os.path.isdir(d):
|
|
280
|
+
return None
|
|
281
|
+
hits = sorted((n for n in os.listdir(d) if rx.match(n)
|
|
282
|
+
and _is_file(os.path.join(d, n))), key=lambda n: (len(n), n))
|
|
283
|
+
return os.path.join(d, hits[0]) if hits else None
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def library_in(inst: Installation, stem: str,
|
|
287
|
+
linkable: bool = False) -> Optional[str]:
|
|
288
|
+
"""Library *stem* within *inst* only, or ``None``.
|
|
289
|
+
|
|
290
|
+
The unversioned name is sought across ALL of the installation's library
|
|
291
|
+
directories before any versioned name is considered, so that whenever the
|
|
292
|
+
installation can be linked, the loader opens the same file the linker
|
|
293
|
+
uses.
|
|
294
|
+
"""
|
|
295
|
+
for d in inst.lib_dirs:
|
|
296
|
+
hit = _unversioned_in(d, stem)
|
|
297
|
+
if hit:
|
|
298
|
+
return hit
|
|
299
|
+
if linkable:
|
|
300
|
+
return None
|
|
301
|
+
for d in inst.lib_dirs:
|
|
302
|
+
hit = _versioned_in(d, stem)
|
|
303
|
+
if hit:
|
|
304
|
+
return hit
|
|
305
|
+
return None
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _incdirs_in(inst: Installation) -> Optional[List[str]]:
|
|
309
|
+
for candidate in inst.inc_sets:
|
|
310
|
+
for d in candidate:
|
|
311
|
+
if _is_file(os.path.join(d, _SENTINEL_HEADER)):
|
|
312
|
+
return [p for p in candidate if os.path.isdir(p)]
|
|
313
|
+
return None
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _svdirs_in(inst: Installation) -> Optional[List[str]]:
|
|
317
|
+
for d in inst.sv_dirs:
|
|
318
|
+
if _is_file(os.path.join(d, _SENTINEL_SV)):
|
|
319
|
+
return [d]
|
|
320
|
+
return None
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
# --------------------------------------------------------------- selection --
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def select_installation() -> Optional[Installation]:
|
|
327
|
+
"""The installation every artifact comes from, or ``None`` if none.
|
|
328
|
+
|
|
329
|
+
The override when set (usable or not); otherwise the first candidate
|
|
330
|
+
holding a loadable core library.
|
|
331
|
+
"""
|
|
332
|
+
cands = candidates()
|
|
333
|
+
if cands and cands[0].kind == "override":
|
|
334
|
+
return cands[0]
|
|
335
|
+
for inst in cands:
|
|
336
|
+
if library_in(inst, "dv_solve") is not None:
|
|
337
|
+
return inst
|
|
338
|
+
return None
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def find_library(stem: str, linkable: bool = False) -> Optional[str]:
|
|
342
|
+
"""Absolute path to shared library *stem* in the selected installation,
|
|
343
|
+
or ``None``.
|
|
344
|
+
|
|
345
|
+
With ``linkable=True`` only the unversioned name is accepted, because that
|
|
346
|
+
is what ``-l<stem>`` resolves. ``None`` then means the SELECTED
|
|
347
|
+
installation cannot be linked -- a later candidate is never consulted
|
|
348
|
+
instead. Use :func:`require_library` for the diagnostic.
|
|
349
|
+
"""
|
|
350
|
+
inst = select_installation()
|
|
351
|
+
return library_in(inst, stem, linkable) if inst is not None else None
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def find_incdirs() -> Optional[List[str]]:
|
|
355
|
+
"""The include set for compiling against dv-solve, or ``None``.
|
|
356
|
+
|
|
357
|
+
A set rather than a single directory because the installed layout needs
|
|
358
|
+
two entries: headers are staged under ``share/include/dv_solve/``
|
|
359
|
+
(CMake's ``DESTINATION include/dv_solve``), while dv-solve's own sources
|
|
360
|
+
and pssc's generated ``pssc_solve.c`` both use UNQUALIFIED includes
|
|
361
|
+
(``#include "dvs_ctx.h"``), which only resolve against the nested
|
|
362
|
+
directory. The base is kept alongside it for any consumer that writes
|
|
363
|
+
``dv_solve/dvs_ctx.h``.
|
|
364
|
+
|
|
365
|
+
Validated by probing for a real header: an unbuilt checkout and a wheel
|
|
366
|
+
whose data files failed to stage both leave a plausible-looking directory
|
|
367
|
+
behind, and handing that to ``gcc -I`` defers the failure to a confusing
|
|
368
|
+
``dvs_block_alloc.h: No such file or directory`` in generated code.
|
|
369
|
+
|
|
370
|
+
Taken from the selected installation only. With nothing selected (no
|
|
371
|
+
library anywhere) the first candidate holding headers answers, for
|
|
372
|
+
compile-only consumers.
|
|
373
|
+
|
|
374
|
+
The public API is ``dv_solve/dv_solve.h`` (or ``dv_solve.h`` through the
|
|
375
|
+
nested directory); the other headers are internal. They are prefixed
|
|
376
|
+
``dvs_``, but are still kept out of the backend's include set: these
|
|
377
|
+
directories are for the SOLVER translation unit only.
|
|
378
|
+
"""
|
|
379
|
+
inst = select_installation()
|
|
380
|
+
if inst is not None:
|
|
381
|
+
return _incdirs_in(inst)
|
|
382
|
+
for inst in candidates():
|
|
383
|
+
found = _incdirs_in(inst)
|
|
384
|
+
if found is not None:
|
|
385
|
+
return found
|
|
386
|
+
return None
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def find_svdirs() -> Optional[List[str]]:
|
|
390
|
+
"""The SystemVerilog package search path, or ``None``. Same selection
|
|
391
|
+
rule as :func:`find_incdirs`."""
|
|
392
|
+
inst = select_installation()
|
|
393
|
+
if inst is not None:
|
|
394
|
+
return _svdirs_in(inst)
|
|
395
|
+
for inst in candidates():
|
|
396
|
+
found = _svdirs_in(inst)
|
|
397
|
+
if found is not None:
|
|
398
|
+
return found
|
|
399
|
+
return None
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
# ------------------------------------------------------------- diagnostics --
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def describe_search() -> str:
|
|
406
|
+
"""A rendered search path, for embedding in diagnostics."""
|
|
407
|
+
return "\n".join(" - %s" % d for d in lib_search_dirs())
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def missing_artifact_error(what: str, detail: str = "") -> RuntimeError:
|
|
411
|
+
"""A uniform, actionable error for any missing native artifact."""
|
|
412
|
+
return RuntimeError(
|
|
413
|
+
"dv-solve: %s not found.%s\n"
|
|
414
|
+
"Searched:\n%s\n"
|
|
415
|
+
"Fixes:\n"
|
|
416
|
+
" - Install a binary wheel: pip install dv-solve\n"
|
|
417
|
+
" - Build from source (needs CMake + a C compiler):\n"
|
|
418
|
+
" cmake -S . -B build -G Ninja -DCMAKE_INSTALL_PREFIX=build\n"
|
|
419
|
+
" ninja -C build install\n"
|
|
420
|
+
" - Or point DVS_SOLVER_PATH at the install prefix / directory."
|
|
421
|
+
% (what, (" " + detail) if detail else "", describe_search()))
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _incomplete_error(inst: Installation, what: str, why: str) -> RuntimeError:
|
|
425
|
+
"""The selected installation lacks *what*. Never resolved by looking
|
|
426
|
+
elsewhere -- that would pair artifacts from two solver builds."""
|
|
427
|
+
fix = (" - Complete that installation, or point DVS_SOLVER_PATH at a "
|
|
428
|
+
"complete one." if inst.kind == "override" else
|
|
429
|
+
" - Complete or remove that installation, or set DVS_SOLVER_PATH "
|
|
430
|
+
"to select a complete one.")
|
|
431
|
+
return RuntimeError(
|
|
432
|
+
"dv-solve: the selected installation (%s) has no %s.%s\n"
|
|
433
|
+
"Other installations are not consulted: every artifact must come "
|
|
434
|
+
"from the installation the solver library is loaded from.\n"
|
|
435
|
+
"Fixes:\n%s" % (inst.describe(), what, (" " + why) if why else "", fix))
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def require_library(stem: str, linkable: bool = False) -> str:
|
|
439
|
+
"""Like :func:`find_library` but raises an actionable ``RuntimeError``."""
|
|
440
|
+
inst = select_installation()
|
|
441
|
+
if inst is None:
|
|
442
|
+
raise missing_artifact_error(
|
|
443
|
+
"native library (%s)" % " / ".join(lib_patterns(stem)))
|
|
444
|
+
hit = library_in(inst, stem, linkable)
|
|
445
|
+
if hit is not None:
|
|
446
|
+
return hit
|
|
447
|
+
versioned = library_in(inst, stem) if linkable else None
|
|
448
|
+
if versioned is not None:
|
|
449
|
+
raise _incomplete_error(
|
|
450
|
+
inst, "linkable %s" % lib_filename(stem),
|
|
451
|
+
"It holds %s, which can be loaded but which -l%s cannot resolve. "
|
|
452
|
+
"Add the linker name alongside it (e.g. ln -s %s %s)."
|
|
453
|
+
% (versioned, stem, os.path.basename(versioned),
|
|
454
|
+
lib_filename(stem)))
|
|
455
|
+
raise _incomplete_error(inst, lib_filename(stem), "")
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def require_incdirs() -> List[str]:
|
|
459
|
+
found = find_incdirs()
|
|
460
|
+
if found is not None:
|
|
461
|
+
return found
|
|
462
|
+
inst = select_installation()
|
|
463
|
+
if inst is None:
|
|
464
|
+
raise missing_artifact_error("C headers (%s)" % _SENTINEL_HEADER)
|
|
465
|
+
raise _incomplete_error(inst, "C headers (%s)" % _SENTINEL_HEADER, "")
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def require_svdirs() -> List[str]:
|
|
469
|
+
found = find_svdirs()
|
|
470
|
+
if found is not None:
|
|
471
|
+
return found
|
|
472
|
+
inst = select_installation()
|
|
473
|
+
if inst is None:
|
|
474
|
+
raise missing_artifact_error("SV sources (%s)" % _SENTINEL_SV)
|
|
475
|
+
raise _incomplete_error(inst, "SV sources (%s)" % _SENTINEL_SV, "")
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
# --------------------------------------------------------- dv-solve-smt2 ----
|
|
479
|
+
#
|
|
480
|
+
# The SMT-LIB2 command-line tool is statically linked against the solver, so
|
|
481
|
+
# unlike the libraries it cannot be mismatched with the installation the
|
|
482
|
+
# Python API loads: any copy is self-contained. It still follows the same
|
|
483
|
+
# order, so that the answer is the one a user of THIS package expects.
|
|
484
|
+
#
|
|
485
|
+
# 1. The override (DVS_SOLVER_PATH), alone, as for the libraries: the root
|
|
486
|
+
# itself or its bin/.
|
|
487
|
+
# 2. The installed wheel's own copy. pip puts it in the environment's
|
|
488
|
+
# scripts directory (bin/, or Scripts\ on Windows), outside the package,
|
|
489
|
+
# so it is found through the distribution's RECORD -- which works for
|
|
490
|
+
# venv, --user and system installs alike. Only a distribution whose
|
|
491
|
+
# RECORD holds THIS package's __init__.py counts, so a wheel installed
|
|
492
|
+
# elsewhere cannot answer for a source checkout.
|
|
493
|
+
# 3. A built source checkout: <build>/bin (the install destination), then
|
|
494
|
+
# <build> itself (CMake's link output).
|
|
495
|
+
# 4. PATH, as a last resort.
|
|
496
|
+
|
|
497
|
+
SMT2_NAME = "dv-solve-smt2"
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def smt2_filename() -> str:
|
|
501
|
+
"""The platform's file name for the SMT-LIB2 tool."""
|
|
502
|
+
if platform.system() == "Windows":
|
|
503
|
+
return SMT2_NAME + ".exe"
|
|
504
|
+
return SMT2_NAME
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def _is_exe(path: str) -> bool:
|
|
508
|
+
return _is_file(path) and os.access(path, os.X_OK)
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def _dist_smt2() -> Optional[str]:
|
|
512
|
+
"""The executable recorded by the distribution that owns this package."""
|
|
513
|
+
try:
|
|
514
|
+
from importlib import metadata
|
|
515
|
+
dist = metadata.distribution("dv-solve")
|
|
516
|
+
except Exception:
|
|
517
|
+
return None
|
|
518
|
+
files = dist.files or []
|
|
519
|
+
init = os.path.join(_pkg_dir(), "__init__.py")
|
|
520
|
+
owns = any(f.name == "__init__.py" and f.parent.name == "dv_solve"
|
|
521
|
+
and os.path.abspath(str(dist.locate_file(f))) == init
|
|
522
|
+
for f in files)
|
|
523
|
+
if not owns:
|
|
524
|
+
return None
|
|
525
|
+
name = smt2_filename()
|
|
526
|
+
for f in files:
|
|
527
|
+
if f.name == name:
|
|
528
|
+
p = os.path.abspath(str(dist.locate_file(f)))
|
|
529
|
+
if _is_exe(p):
|
|
530
|
+
return p
|
|
531
|
+
return None
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def smt2_search_paths() -> List[str]:
|
|
535
|
+
"""Every location probed for the SMT-LIB2 tool, best first (for
|
|
536
|
+
diagnostics; the RECORD and PATH lookups are named, not expanded)."""
|
|
537
|
+
name, j = smt2_filename(), os.path.join
|
|
538
|
+
root = override_root()
|
|
539
|
+
if root:
|
|
540
|
+
return [j(root, name), j(root, "bin", name)]
|
|
541
|
+
out = ["the dv-solve distribution's installed scripts (RECORD)"]
|
|
542
|
+
for bd in (j(_src_root(), n) for n in _BUILD_DIR_NAMES):
|
|
543
|
+
out += [j(bd, "bin", name), j(bd, name)]
|
|
544
|
+
out.append("PATH")
|
|
545
|
+
return out
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def find_smt2_exe() -> Optional[str]:
|
|
549
|
+
"""Absolute path to ``dv-solve-smt2``, or ``None``. See above for order."""
|
|
550
|
+
name, j = smt2_filename(), os.path.join
|
|
551
|
+
root = override_root()
|
|
552
|
+
if root:
|
|
553
|
+
for p in (j(root, name), j(root, "bin", name)):
|
|
554
|
+
if _is_exe(p):
|
|
555
|
+
return os.path.abspath(p)
|
|
556
|
+
return None # the override is terminal
|
|
557
|
+
hit = _dist_smt2()
|
|
558
|
+
if hit:
|
|
559
|
+
return hit
|
|
560
|
+
for bd in (j(_src_root(), n) for n in _BUILD_DIR_NAMES):
|
|
561
|
+
for p in (j(bd, "bin", name), j(bd, name)):
|
|
562
|
+
if _is_exe(p):
|
|
563
|
+
return os.path.abspath(p)
|
|
564
|
+
import shutil
|
|
565
|
+
# The full file name, not the stem: on Windows which() would otherwise
|
|
566
|
+
# append the PATHEXT spelling (".EXE") rather than the file's own.
|
|
567
|
+
hit = shutil.which(name)
|
|
568
|
+
return os.path.abspath(hit) if hit else None
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def require_smt2_exe() -> str:
|
|
572
|
+
"""Like :func:`find_smt2_exe` but raises an actionable ``RuntimeError``."""
|
|
573
|
+
hit = find_smt2_exe()
|
|
574
|
+
if hit is not None:
|
|
575
|
+
return hit
|
|
576
|
+
raise smt2_missing_error()
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def smt2_missing_error() -> RuntimeError:
|
|
580
|
+
"""The error for a missing ``dv-solve-smt2``, naming where it looked."""
|
|
581
|
+
detail = ""
|
|
582
|
+
if override_root():
|
|
583
|
+
detail = "%s is set, so no other location was searched." % _override_var()
|
|
584
|
+
return RuntimeError(
|
|
585
|
+
"dv-solve: %s not found.%s\n"
|
|
586
|
+
"Searched:\n%s\n"
|
|
587
|
+
"Fixes:\n"
|
|
588
|
+
" - Install a binary wheel: pip install dv-solve\n"
|
|
589
|
+
" - Build from source (needs CMake + a C compiler):\n"
|
|
590
|
+
" cmake -S . -B build -G Ninja -DCMAKE_INSTALL_PREFIX=build\n"
|
|
591
|
+
" ninja -C build install\n"
|
|
592
|
+
" - Or point DVS_SOLVER_PATH at a directory or prefix holding it."
|
|
593
|
+
% (smt2_filename(), (" " + detail) if detail else "",
|
|
594
|
+
"\n".join(" - %s" % p for p in smt2_search_paths())))
|