gdsdiff 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.
- gdsdiff/__init__.py +148 -0
- gdsdiff/__main__.py +7 -0
- gdsdiff/_environment.py +12 -0
- gdsdiff/_version.py +34 -0
- gdsdiff/acceptance_evidence.py +530 -0
- gdsdiff/api.py +2955 -0
- gdsdiff/backends/__init__.py +26 -0
- gdsdiff/backends/base.py +77 -0
- gdsdiff/backends/boundary_loop_ctypes.py +191 -0
- gdsdiff/backends/cpu.py +20 -0
- gdsdiff/backends/cpu_ctypes.py +255 -0
- gdsdiff/backends/cuda.py +40 -0
- gdsdiff/backends/cuda_ctypes.py +1297 -0
- gdsdiff/backends/exact.py +424 -0
- gdsdiff/backends/geometry_ctypes.py +252 -0
- gdsdiff/backends/identity.py +53 -0
- gdsdiff/backends/metal.py +198 -0
- gdsdiff/backends/metal_ctypes.py +866 -0
- gdsdiff/backends/polygon_extract_ctypes.py +233 -0
- gdsdiff/backends/structural_ctypes.py +130 -0
- gdsdiff/boundary_loops.py +616 -0
- gdsdiff/cache.py +544 -0
- gdsdiff/canonicalize.py +176 -0
- gdsdiff/cli.py +352 -0
- gdsdiff/config.py +257 -0
- gdsdiff/cuda_setup.py +174 -0
- gdsdiff/design_history.py +65 -0
- gdsdiff/diagnostics.py +42 -0
- gdsdiff/diff_gds.py +66 -0
- gdsdiff/exact_diff_markdown.py +979 -0
- gdsdiff/exact_oracle.py +649 -0
- gdsdiff/extract.py +376 -0
- gdsdiff/gds_metadata.py +43 -0
- gdsdiff/geometry.py +112 -0
- gdsdiff/grid.py +143 -0
- gdsdiff/hierarchy.py +215 -0
- gdsdiff/hierarchy_extract.py +263 -0
- gdsdiff/inventory.py +153 -0
- gdsdiff/multiprocessing_support.py +39 -0
- gdsdiff/native.py +135 -0
- gdsdiff/native_cache.py +265 -0
- gdsdiff/native_src/cpu_prefilter.cpp +349 -0
- gdsdiff/native_src/gds_boundary_loops.cpp +505 -0
- gdsdiff/native_src/gds_geometry_scan.cpp +601 -0
- gdsdiff/native_src/gds_polygon_extract.cpp +594 -0
- gdsdiff/native_src/gds_structural_scan.cpp +335 -0
- gdsdiff/native_src/gdsdiff/CMakeLists.txt +74 -0
- gdsdiff/native_src/gdsdiff/core.cpp +191 -0
- gdsdiff/native_src/gdsdiff/core.hpp +96 -0
- gdsdiff/native_src/gdsdiff/cuda_assignment.cu +304 -0
- gdsdiff/native_src/gdsdiff/cuda_assignment.cuh +33 -0
- gdsdiff/native_src/gdsdiff/cuda_candidates.cu +133 -0
- gdsdiff/native_src/gdsdiff/cuda_candidates.cuh +17 -0
- gdsdiff/native_src/gdsdiff/cuda_ctypes.cu +4528 -0
- gdsdiff/native_src/gdsdiff/cuda_memory.cu +194 -0
- gdsdiff/native_src/gdsdiff/cuda_memory.cuh +34 -0
- gdsdiff/native_src/gdsdiff/metal_ctypes.mm +2791 -0
- gdsdiff/native_src/gdsdiff/python_bindings.cpp +21 -0
- gdsdiff/native_src/gdsdiff/test_core.cpp +93 -0
- gdsdiff/native_src/gdsdiff/test_cuda_assignment.cu +109 -0
- gdsdiff/native_src/gdsdiff/test_cuda_candidates.cu +94 -0
- gdsdiff/native_src/gdsdiff/test_cuda_memory.cu +97 -0
- gdsdiff/policy.py +305 -0
- gdsdiff/polygon_components.py +860 -0
- gdsdiff/profiles.py +152 -0
- gdsdiff/py.typed +1 -0
- gdsdiff/rect_coverage.py +384 -0
- gdsdiff/report.py +354 -0
- gdsdiff/schemas/gdsdiff_report-v1.schema.json +92 -0
- gdsdiff/semantics.py +75 -0
- gdsdiff/source_edge_diff.py +1120 -0
- gdsdiff/spatial.py +71 -0
- gdsdiff/structural_guard.py +161 -0
- gdsdiff/surplus_coverage.py +255 -0
- gdsdiff/synthetic_suite.py +1643 -0
- gdsdiff/templates.py +709 -0
- gdsdiff/tiling.py +153 -0
- gdsdiff/windowed_exact.py +270 -0
- gdsdiff/windows.py +184 -0
- gdsdiff-0.1.0.dist-info/METADATA +135 -0
- gdsdiff-0.1.0.dist-info/RECORD +85 -0
- gdsdiff-0.1.0.dist-info/WHEEL +5 -0
- gdsdiff-0.1.0.dist-info/entry_points.txt +4 -0
- gdsdiff-0.1.0.dist-info/licenses/LICENSE +674 -0
- gdsdiff-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ctypes
|
|
4
|
+
import subprocess
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from functools import lru_cache
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from ..config import LayerSpec, parse_layer_selector
|
|
12
|
+
from ..geometry import LayerTable, PolygonBuffer
|
|
13
|
+
from ..native_cache import ensure_cached_native_library
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PolygonExtractCtypesUnavailable(RuntimeError):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class _CPolygonExtractResult(ctypes.Structure):
|
|
21
|
+
_fields_ = [
|
|
22
|
+
("polygon_count", ctypes.c_uint64),
|
|
23
|
+
("vertex_count", ctypes.c_uint64),
|
|
24
|
+
("vertices_xy", ctypes.POINTER(ctypes.c_int64)),
|
|
25
|
+
("polygon_offsets", ctypes.POINTER(ctypes.c_uint64)),
|
|
26
|
+
("layers", ctypes.POINTER(ctypes.c_int32)),
|
|
27
|
+
("datatypes", ctypes.POINTER(ctypes.c_int32)),
|
|
28
|
+
("bboxes", ctypes.POINTER(ctypes.c_int64)),
|
|
29
|
+
("flags", ctypes.POINTER(ctypes.c_uint32)),
|
|
30
|
+
("source_ids", ctypes.POINTER(ctypes.c_uint64)),
|
|
31
|
+
("hashes", ctypes.POINTER(ctypes.c_uint64)),
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class NativePolygonExtraction:
|
|
37
|
+
path: Path
|
|
38
|
+
top_name: str | None
|
|
39
|
+
buffer: PolygonBuffer
|
|
40
|
+
native_parse_s: float | None = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def extract_gds_polygons_native(
|
|
44
|
+
path: str | Path,
|
|
45
|
+
*,
|
|
46
|
+
top_name: str | None = None,
|
|
47
|
+
layers: str | LayerSpec | tuple[str | tuple[int, int] | LayerSpec, ...] = "all",
|
|
48
|
+
) -> NativePolygonExtraction:
|
|
49
|
+
path = Path(path)
|
|
50
|
+
library = _load_library()
|
|
51
|
+
result = _CPolygonExtractResult()
|
|
52
|
+
top_bytes = top_name.encode("utf-8") if top_name else None
|
|
53
|
+
status = library.gds_polygon_extract(str(path).encode("utf-8"), top_bytes, ctypes.byref(result))
|
|
54
|
+
if status != 0:
|
|
55
|
+
message = library.gds_polygon_extract_last_error()
|
|
56
|
+
raise PolygonExtractCtypesUnavailable(message.decode("utf-8") if message else "native polygon extraction failed")
|
|
57
|
+
try:
|
|
58
|
+
buffer = _buffer_from_result(result, layers=layers)
|
|
59
|
+
return NativePolygonExtraction(path=path, top_name=top_name, buffer=buffer)
|
|
60
|
+
finally:
|
|
61
|
+
library.gds_polygon_extract_free(ctypes.byref(result))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def polygon_extract_ctypes_available() -> bool:
|
|
65
|
+
try:
|
|
66
|
+
_load_library()
|
|
67
|
+
except PolygonExtractCtypesUnavailable:
|
|
68
|
+
return False
|
|
69
|
+
return True
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _buffer_from_result(
|
|
73
|
+
result: _CPolygonExtractResult,
|
|
74
|
+
*,
|
|
75
|
+
layers: str | LayerSpec | tuple[str | tuple[int, int] | LayerSpec, ...],
|
|
76
|
+
) -> PolygonBuffer:
|
|
77
|
+
polygon_count = int(result.polygon_count)
|
|
78
|
+
vertex_count = int(result.vertex_count)
|
|
79
|
+
vertices = _copy_array(result.vertices_xy, vertex_count * 2, np.int64).reshape((vertex_count, 2))
|
|
80
|
+
offsets = _copy_array(result.polygon_offsets, polygon_count + 1, np.uint64)
|
|
81
|
+
raw_layers = _copy_array(result.layers, polygon_count, np.int32)
|
|
82
|
+
raw_datatypes = _copy_array(result.datatypes, polygon_count, np.int32)
|
|
83
|
+
bboxes = _copy_array(result.bboxes, polygon_count * 4, np.int64).reshape((polygon_count, 4))
|
|
84
|
+
flags = _copy_array(result.flags, polygon_count, np.uint32)
|
|
85
|
+
source_ids = _copy_array(result.source_ids, polygon_count, np.uint64)
|
|
86
|
+
selected = _selected_layers(layers)
|
|
87
|
+
hashes = _copy_array(result.hashes, polygon_count * 2, np.uint64).reshape((polygon_count, 2))
|
|
88
|
+
if selected is not None:
|
|
89
|
+
keep = _selected_mask(raw_layers, raw_datatypes, selected)
|
|
90
|
+
if not np.all(keep):
|
|
91
|
+
vertices, offsets, raw_layers, raw_datatypes, bboxes, flags, source_ids, hashes = _filter_polygons(
|
|
92
|
+
vertices,
|
|
93
|
+
offsets,
|
|
94
|
+
raw_layers,
|
|
95
|
+
raw_datatypes,
|
|
96
|
+
bboxes,
|
|
97
|
+
flags,
|
|
98
|
+
source_ids,
|
|
99
|
+
hashes,
|
|
100
|
+
keep,
|
|
101
|
+
)
|
|
102
|
+
table, layer_ids = _build_layer_table(raw_layers, raw_datatypes)
|
|
103
|
+
return PolygonBuffer(
|
|
104
|
+
vertices_xy=vertices.astype(np.int64, copy=False),
|
|
105
|
+
polygon_offsets=offsets.astype(np.uint64, copy=False),
|
|
106
|
+
layer_ids=layer_ids,
|
|
107
|
+
bboxes=bboxes.astype(np.int64, copy=False),
|
|
108
|
+
flags=flags.astype(np.uint32, copy=False),
|
|
109
|
+
source_ids=source_ids.astype(np.uint64, copy=False),
|
|
110
|
+
layer_table=table,
|
|
111
|
+
polygon_hashes=hashes.astype(np.uint64, copy=False),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _selected_layers(layers) -> tuple[LayerSpec, ...] | None:
|
|
116
|
+
if layers == "all":
|
|
117
|
+
return None
|
|
118
|
+
parsed = parse_layer_selector(layers)
|
|
119
|
+
if parsed == "all":
|
|
120
|
+
return None
|
|
121
|
+
return parsed
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _selected_mask(raw_layers: np.ndarray, raw_datatypes: np.ndarray, selected: tuple[LayerSpec, ...]) -> np.ndarray:
|
|
125
|
+
keep = np.zeros(raw_layers.shape, dtype=bool)
|
|
126
|
+
for layer in selected:
|
|
127
|
+
keep |= (raw_layers == layer.layer) & (raw_datatypes == layer.datatype)
|
|
128
|
+
return keep
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _build_layer_table(raw_layers: np.ndarray, raw_datatypes: np.ndarray) -> tuple[LayerTable, np.ndarray]:
|
|
132
|
+
if len(raw_layers) == 0:
|
|
133
|
+
return LayerTable(()), np.empty((0,), dtype=np.uint32)
|
|
134
|
+
pairs = np.column_stack((raw_layers, raw_datatypes)).astype(np.int32, copy=False)
|
|
135
|
+
unique_pairs = np.unique(pairs, axis=0)
|
|
136
|
+
table = LayerTable(tuple(LayerSpec(int(layer), int(datatype)) for layer, datatype in unique_pairs))
|
|
137
|
+
layer_ids = np.empty(len(raw_layers), dtype=np.uint32)
|
|
138
|
+
for index, (layer, datatype) in enumerate(unique_pairs):
|
|
139
|
+
layer_ids[(raw_layers == layer) & (raw_datatypes == datatype)] = index
|
|
140
|
+
return table, layer_ids
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _filter_polygons(
|
|
144
|
+
vertices: np.ndarray,
|
|
145
|
+
offsets: np.ndarray,
|
|
146
|
+
raw_layers: np.ndarray,
|
|
147
|
+
raw_datatypes: np.ndarray,
|
|
148
|
+
bboxes: np.ndarray,
|
|
149
|
+
flags: np.ndarray,
|
|
150
|
+
source_ids: np.ndarray,
|
|
151
|
+
hashes: np.ndarray,
|
|
152
|
+
keep: np.ndarray,
|
|
153
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
154
|
+
kept_indices = np.nonzero(keep)[0]
|
|
155
|
+
new_vertices: list[np.ndarray] = []
|
|
156
|
+
new_offsets = [0]
|
|
157
|
+
for index in kept_indices:
|
|
158
|
+
start = int(offsets[index])
|
|
159
|
+
stop = int(offsets[index + 1])
|
|
160
|
+
part = vertices[start:stop]
|
|
161
|
+
new_vertices.append(part)
|
|
162
|
+
new_offsets.append(new_offsets[-1] + len(part))
|
|
163
|
+
return (
|
|
164
|
+
np.vstack(new_vertices).astype(np.int64, copy=False) if new_vertices else np.empty((0, 2), dtype=np.int64),
|
|
165
|
+
np.asarray(new_offsets, dtype=np.uint64),
|
|
166
|
+
raw_layers[keep].astype(np.int32, copy=False),
|
|
167
|
+
raw_datatypes[keep].astype(np.int32, copy=False),
|
|
168
|
+
bboxes[keep].astype(np.int64, copy=False),
|
|
169
|
+
flags[keep].astype(np.uint32, copy=False),
|
|
170
|
+
source_ids[keep].astype(np.uint64, copy=False),
|
|
171
|
+
hashes[keep].astype(np.uint64, copy=False),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _copy_array(pointer, count: int, dtype) -> np.ndarray:
|
|
176
|
+
if count == 0:
|
|
177
|
+
return np.empty((0,), dtype=dtype)
|
|
178
|
+
if not pointer:
|
|
179
|
+
raise PolygonExtractCtypesUnavailable("native polygon extraction returned a null array")
|
|
180
|
+
ctype = np.ctypeslib.as_ctypes_type(dtype)
|
|
181
|
+
array = ctypes.cast(pointer, ctypes.POINTER(ctype * count)).contents
|
|
182
|
+
return np.frombuffer(array, dtype=dtype).copy()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@lru_cache(maxsize=1)
|
|
186
|
+
def _load_library() -> ctypes.CDLL:
|
|
187
|
+
path = _library_path()
|
|
188
|
+
library = ctypes.CDLL(str(path))
|
|
189
|
+
library.gds_polygon_extract.argtypes = [
|
|
190
|
+
ctypes.c_char_p,
|
|
191
|
+
ctypes.c_char_p,
|
|
192
|
+
ctypes.POINTER(_CPolygonExtractResult),
|
|
193
|
+
]
|
|
194
|
+
library.gds_polygon_extract.restype = ctypes.c_int
|
|
195
|
+
library.gds_polygon_extract_free.argtypes = [ctypes.POINTER(_CPolygonExtractResult)]
|
|
196
|
+
library.gds_polygon_extract_free.restype = None
|
|
197
|
+
library.gds_polygon_extract_last_error.argtypes = []
|
|
198
|
+
library.gds_polygon_extract_last_error.restype = ctypes.c_char_p
|
|
199
|
+
return library
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _build_library(path: Path) -> None:
|
|
203
|
+
source = _source_path()
|
|
204
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
205
|
+
command = [
|
|
206
|
+
"g++",
|
|
207
|
+
"-std=c++17",
|
|
208
|
+
"-O3",
|
|
209
|
+
"-shared",
|
|
210
|
+
"-fPIC",
|
|
211
|
+
str(source),
|
|
212
|
+
"-o",
|
|
213
|
+
str(path),
|
|
214
|
+
]
|
|
215
|
+
try:
|
|
216
|
+
completed = subprocess.run(command, check=False, text=True, capture_output=True)
|
|
217
|
+
except FileNotFoundError as exc:
|
|
218
|
+
raise PolygonExtractCtypesUnavailable("g++ is not available") from exc
|
|
219
|
+
if completed.returncode != 0:
|
|
220
|
+
raise PolygonExtractCtypesUnavailable((completed.stderr or completed.stdout).strip() or "g++ failed")
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _library_path() -> Path:
|
|
224
|
+
return ensure_cached_native_library(
|
|
225
|
+
"_gds_polygon_extract.so",
|
|
226
|
+
sources=(_source_path(),),
|
|
227
|
+
build_key=("g++", "-std=c++17", "-O3", "-shared", "-fPIC"),
|
|
228
|
+
builder=_build_library,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _source_path() -> Path:
|
|
233
|
+
return Path(__file__).resolve().parents[1] / "native_src" / "gds_polygon_extract.cpp"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ctypes
|
|
4
|
+
import subprocess
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from functools import lru_cache
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from ..native_cache import ensure_cached_native_library
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class StructuralCtypesUnavailable(RuntimeError):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _CStructuralResult(ctypes.Structure):
|
|
17
|
+
_fields_ = [
|
|
18
|
+
("flattened_shape_count", ctypes.c_uint64),
|
|
19
|
+
("cell_count", ctypes.c_uint64),
|
|
20
|
+
("reachable_cell_count", ctypes.c_uint64),
|
|
21
|
+
("structural_hash", ctypes.c_uint64),
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class NativeStructuralSummary:
|
|
27
|
+
path: Path
|
|
28
|
+
flattened_shape_count: int
|
|
29
|
+
cell_count: int
|
|
30
|
+
reachable_cell_count: int
|
|
31
|
+
structural_hash: int
|
|
32
|
+
|
|
33
|
+
def to_json(self) -> dict[str, object]:
|
|
34
|
+
return {
|
|
35
|
+
"path": str(self.path),
|
|
36
|
+
"flattened_shape_count": self.flattened_shape_count,
|
|
37
|
+
"cell_count": self.cell_count,
|
|
38
|
+
"reachable_cell_count": self.reachable_cell_count,
|
|
39
|
+
"structural_hash": f"{self.structural_hash:016x}",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def summarize_gds_structure_native(
|
|
44
|
+
path: str | Path,
|
|
45
|
+
*,
|
|
46
|
+
top_name: str | None = None,
|
|
47
|
+
counts_only: bool = False,
|
|
48
|
+
) -> NativeStructuralSummary:
|
|
49
|
+
library = _load_library()
|
|
50
|
+
result = _CStructuralResult()
|
|
51
|
+
top_bytes = top_name.encode("utf-8") if top_name else None
|
|
52
|
+
function = library.gds_structural_summary_counts_only if counts_only else library.gds_structural_summary
|
|
53
|
+
status = function(
|
|
54
|
+
str(path).encode("utf-8"),
|
|
55
|
+
top_bytes,
|
|
56
|
+
ctypes.byref(result),
|
|
57
|
+
)
|
|
58
|
+
if status != 0:
|
|
59
|
+
message = library.gds_structural_last_error()
|
|
60
|
+
raise StructuralCtypesUnavailable(message.decode("utf-8") if message else "native structural scan failed")
|
|
61
|
+
return NativeStructuralSummary(
|
|
62
|
+
path=Path(path),
|
|
63
|
+
flattened_shape_count=int(result.flattened_shape_count),
|
|
64
|
+
cell_count=int(result.cell_count),
|
|
65
|
+
reachable_cell_count=int(result.reachable_cell_count),
|
|
66
|
+
structural_hash=int(result.structural_hash),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def structural_ctypes_available() -> bool:
|
|
71
|
+
try:
|
|
72
|
+
_load_library()
|
|
73
|
+
except StructuralCtypesUnavailable:
|
|
74
|
+
return False
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@lru_cache(maxsize=1)
|
|
79
|
+
def _load_library() -> ctypes.CDLL:
|
|
80
|
+
path = _library_path()
|
|
81
|
+
library = ctypes.CDLL(str(path))
|
|
82
|
+
library.gds_structural_summary.argtypes = [
|
|
83
|
+
ctypes.c_char_p,
|
|
84
|
+
ctypes.c_char_p,
|
|
85
|
+
ctypes.POINTER(_CStructuralResult),
|
|
86
|
+
]
|
|
87
|
+
library.gds_structural_summary.restype = ctypes.c_int
|
|
88
|
+
library.gds_structural_summary_counts_only.argtypes = [
|
|
89
|
+
ctypes.c_char_p,
|
|
90
|
+
ctypes.c_char_p,
|
|
91
|
+
ctypes.POINTER(_CStructuralResult),
|
|
92
|
+
]
|
|
93
|
+
library.gds_structural_summary_counts_only.restype = ctypes.c_int
|
|
94
|
+
library.gds_structural_last_error.argtypes = []
|
|
95
|
+
library.gds_structural_last_error.restype = ctypes.c_char_p
|
|
96
|
+
return library
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _build_library(path: Path) -> None:
|
|
100
|
+
source = _source_path()
|
|
101
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
102
|
+
command = [
|
|
103
|
+
"g++",
|
|
104
|
+
"-std=c++17",
|
|
105
|
+
"-O3",
|
|
106
|
+
"-shared",
|
|
107
|
+
"-fPIC",
|
|
108
|
+
str(source),
|
|
109
|
+
"-o",
|
|
110
|
+
str(path),
|
|
111
|
+
]
|
|
112
|
+
try:
|
|
113
|
+
completed = subprocess.run(command, check=False, text=True, capture_output=True)
|
|
114
|
+
except FileNotFoundError as exc:
|
|
115
|
+
raise StructuralCtypesUnavailable("g++ is not available") from exc
|
|
116
|
+
if completed.returncode != 0:
|
|
117
|
+
raise StructuralCtypesUnavailable((completed.stderr or completed.stdout).strip() or "g++ failed")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _library_path() -> Path:
|
|
121
|
+
return ensure_cached_native_library(
|
|
122
|
+
"_gds_structural_scan.so",
|
|
123
|
+
sources=(_source_path(),),
|
|
124
|
+
build_key=("g++", "-std=c++17", "-O3", "-shared", "-fPIC"),
|
|
125
|
+
builder=_build_library,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _source_path() -> Path:
|
|
130
|
+
return Path(__file__).resolve().parents[1] / "native_src" / "gds_structural_scan.cpp"
|