rocket-engine 0.0.2b1__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.
Files changed (241) hide show
  1. rocket_engine/__init__.py +8 -0
  2. rocket_engine/__main__.py +12 -0
  3. rocket_engine/adapters/README.md +54 -0
  4. rocket_engine/adapters/__init__.py +7 -0
  5. rocket_engine/adapters/api.py +39 -0
  6. rocket_engine/adapters/base.py +46 -0
  7. rocket_engine/adapters/cace/__init__.py +163 -0
  8. rocket_engine/adapters/circulax/README.md +10 -0
  9. rocket_engine/adapters/circulax/__init__.py +112 -0
  10. rocket_engine/adapters/circulax/_driver.py +190 -0
  11. rocket_engine/adapters/circulax/adapter.py +538 -0
  12. rocket_engine/adapters/circulax/results.py +291 -0
  13. rocket_engine/adapters/cli/README.md +34 -0
  14. rocket_engine/adapters/cli/__init__.py +305 -0
  15. rocket_engine/adapters/connectivity/__init__.py +196 -0
  16. rocket_engine/adapters/connectivity/results.py +214 -0
  17. rocket_engine/adapters/cosim/README.md +9 -0
  18. rocket_engine/adapters/cosim/__init__.py +1390 -0
  19. rocket_engine/adapters/cosim/results.py +214 -0
  20. rocket_engine/adapters/devsim/__init__.py +171 -0
  21. rocket_engine/adapters/digital_flow/README.md +9 -0
  22. rocket_engine/adapters/digital_flow/__init__.py +1121 -0
  23. rocket_engine/adapters/digital_flow/results.py +227 -0
  24. rocket_engine/adapters/digital_flow/state.py +462 -0
  25. rocket_engine/adapters/drc/README.klayout.md +7 -0
  26. rocket_engine/adapters/drc/README.md +7 -0
  27. rocket_engine/adapters/drc/__init__.py +1 -0
  28. rocket_engine/adapters/drc/klayout.py +952 -0
  29. rocket_engine/adapters/drc/magic.py +287 -0
  30. rocket_engine/adapters/drc/results.py +184 -0
  31. rocket_engine/adapters/drc/results_kl.py +188 -0
  32. rocket_engine/adapters/elmer/__init__.py +163 -0
  33. rocket_engine/adapters/envelope.py +120 -0
  34. rocket_engine/adapters/equivalence/__init__.py +156 -0
  35. rocket_engine/adapters/errors.py +181 -0
  36. rocket_engine/adapters/extract_magic/README.md +15 -0
  37. rocket_engine/adapters/extract_magic/__init__.py +425 -0
  38. rocket_engine/adapters/extract_magic/results.py +209 -0
  39. rocket_engine/adapters/formal_sby/README.md +7 -0
  40. rocket_engine/adapters/formal_sby/__init__.py +773 -0
  41. rocket_engine/adapters/formal_sby/formal_edge.py +195 -0
  42. rocket_engine/adapters/formal_sby/results.py +185 -0
  43. rocket_engine/adapters/gdsfactory/__init__.py +194 -0
  44. rocket_engine/adapters/gdsfactory/_driver.py +459 -0
  45. rocket_engine/adapters/gdsfactory/adapter.py +609 -0
  46. rocket_engine/adapters/gdsfactory/results.py +181 -0
  47. rocket_engine/adapters/hdl_sim/README.md +7 -0
  48. rocket_engine/adapters/hdl_sim/__init__.py +369 -0
  49. rocket_engine/adapters/hdl_sim/ghdl.py +155 -0
  50. rocket_engine/adapters/hdl_sim/results.py +178 -0
  51. rocket_engine/adapters/hdl_sim/verilator.py +167 -0
  52. rocket_engine/adapters/http/README.md +15 -0
  53. rocket_engine/adapters/http/__init__.py +458 -0
  54. rocket_engine/adapters/http/events.py +63 -0
  55. rocket_engine/adapters/ipc/README.md +9 -0
  56. rocket_engine/adapters/ipc/__init__.py +111 -0
  57. rocket_engine/adapters/lvs_netgen/README.md +20 -0
  58. rocket_engine/adapters/lvs_netgen/__init__.py +889 -0
  59. rocket_engine/adapters/lvs_netgen/results.py +168 -0
  60. rocket_engine/adapters/mcp/README.md +19 -0
  61. rocket_engine/adapters/mcp/__init__.py +85 -0
  62. rocket_engine/adapters/mcp/api-schema.json +6107 -0
  63. rocket_engine/adapters/mcp/pull.py +246 -0
  64. rocket_engine/adapters/mcp/server.py +794 -0
  65. rocket_engine/adapters/meep/__init__.py +167 -0
  66. rocket_engine/adapters/meow/README.md +7 -0
  67. rocket_engine/adapters/meow/__init__.py +190 -0
  68. rocket_engine/adapters/meow/_driver.py +227 -0
  69. rocket_engine/adapters/meow/_envelope.py +108 -0
  70. rocket_engine/adapters/meow/adapter.py +491 -0
  71. rocket_engine/adapters/meow/results.py +218 -0
  72. rocket_engine/adapters/mutation/__init__.py +192 -0
  73. rocket_engine/adapters/ngspice/README.md +28 -0
  74. rocket_engine/adapters/ngspice/__init__.py +627 -0
  75. rocket_engine/adapters/ngspice/results.py +215 -0
  76. rocket_engine/adapters/postlayout/README.md +28 -0
  77. rocket_engine/adapters/postlayout/__init__.py +17 -0
  78. rocket_engine/adapters/postlayout/adapter.py +1062 -0
  79. rocket_engine/adapters/postlayout/results.py +330 -0
  80. rocket_engine/adapters/postlayout/translate.py +71 -0
  81. rocket_engine/adapters/profile.py +220 -0
  82. rocket_engine/adapters/sax/README.md +23 -0
  83. rocket_engine/adapters/sax/__init__.py +516 -0
  84. rocket_engine/adapters/sax/_driver.py +148 -0
  85. rocket_engine/adapters/sax/results.py +291 -0
  86. rocket_engine/adapters/stdio_console.py +1151 -0
  87. rocket_engine/adapters/syntax/README.md +5 -0
  88. rocket_engine/adapters/syntax/__init__.py +366 -0
  89. rocket_engine/adapters/syntax/results.py +181 -0
  90. rocket_engine/adapters/synth_yosys/README.md +7 -0
  91. rocket_engine/adapters/synth_yosys/__init__.py +655 -0
  92. rocket_engine/adapters/synth_yosys/amaranth.py +120 -0
  93. rocket_engine/adapters/synth_yosys/ghdl.py +128 -0
  94. rocket_engine/adapters/synth_yosys/results.py +173 -0
  95. rocket_engine/adapters/timing_sta/README.md +9 -0
  96. rocket_engine/adapters/timing_sta/__init__.py +726 -0
  97. rocket_engine/adapters/view_sync/__init__.py +185 -0
  98. rocket_engine/adapters/view_sync/results.py +189 -0
  99. rocket_engine/adapters/vlsir/__init__.py +142 -0
  100. rocket_engine/adapters/xschem/__init__.py +440 -0
  101. rocket_engine/adapters/xschem/deck.py +266 -0
  102. rocket_engine/clients/__init__.py +5 -0
  103. rocket_engine/clients/runtime.py +255 -0
  104. rocket_engine/composition.py +2279 -0
  105. rocket_engine/execution/README.md +30 -0
  106. rocket_engine/execution/__init__.py +150 -0
  107. rocket_engine/execution/apig/__init__.py +1 -0
  108. rocket_engine/execution/apig/pool.py +463 -0
  109. rocket_engine/execution/apig/submit.py +124 -0
  110. rocket_engine/execution/block_runs_scope.py +479 -0
  111. rocket_engine/execution/coalesce.py +414 -0
  112. rocket_engine/execution/console/__init__.py +1 -0
  113. rocket_engine/execution/console/apply_session.py +318 -0
  114. rocket_engine/execution/console/lifecycle.py +265 -0
  115. rocket_engine/execution/dispatch.py +205 -0
  116. rocket_engine/execution/errors.py +31 -0
  117. rocket_engine/execution/ports.py +797 -0
  118. rocket_engine/execution/pyexec/__init__.py +1 -0
  119. rocket_engine/execution/pyexec/apply_context.py +547 -0
  120. rocket_engine/execution/pyexec/approvals.py +173 -0
  121. rocket_engine/execution/pyexec/runner.py +404 -0
  122. rocket_engine/execution/pyexec/support.py +464 -0
  123. rocket_engine/execution/python_exec.py +897 -0
  124. rocket_engine/execution/queue.py +85 -0
  125. rocket_engine/execution/replay_safe.py +601 -0
  126. rocket_engine/execution/routing/__init__.py +1 -0
  127. rocket_engine/execution/routing/calls.py +364 -0
  128. rocket_engine/execution/routing/namespace.py +785 -0
  129. rocket_engine/execution/routing/ports.py +102 -0
  130. rocket_engine/execution/routing/tools.py +346 -0
  131. rocket_engine/execution/runner.py +311 -0
  132. rocket_engine/execution/runtime.py +1149 -0
  133. rocket_engine/execution/service.py +279 -0
  134. rocket_engine/execution/worker.py +464 -0
  135. rocket_engine/interfaces/README.md +26 -0
  136. rocket_engine/interfaces/__init__.py +58 -0
  137. rocket_engine/interfaces/apig_assets.py +407 -0
  138. rocket_engine/interfaces/celllink.py +461 -0
  139. rocket_engine/interfaces/documents.py +92 -0
  140. rocket_engine/interfaces/envelopes.py +131 -0
  141. rocket_engine/interfaces/errors.py +16 -0
  142. rocket_engine/interfaces/layout.py +410 -0
  143. rocket_engine/interfaces/layout_schema.yaml +278 -0
  144. rocket_engine/interfaces/paths.py +223 -0
  145. rocket_engine/interfaces/pdks.py +550 -0
  146. rocket_engine/interfaces/ports.py +72 -0
  147. rocket_engine/interfaces/references.py +115 -0
  148. rocket_engine/interfaces/refs.py +85 -0
  149. rocket_engine/interfaces/runparams.py +304 -0
  150. rocket_engine/interfaces/setup.py +552 -0
  151. rocket_engine/interfaces/setup_catalogue.yaml +243 -0
  152. rocket_engine/interfaces/setup_probes.py +66 -0
  153. rocket_engine/interfaces/vocabulary.py +58 -0
  154. rocket_engine/interfaces/yaml_dump.py +31 -0
  155. rocket_engine/library/README.md +22 -0
  156. rocket_engine/library/__init__.py +13 -0
  157. rocket_engine/library/apig_catalog.py +371 -0
  158. rocket_engine/library/catalog.py +185 -0
  159. rocket_engine/library/errors.py +16 -0
  160. rocket_engine/mutate/__init__.py +776 -0
  161. rocket_engine/mutate/operators.py +700 -0
  162. rocket_engine/ops/__init__.py +1 -0
  163. rocket_engine/ops/deferred.py +279 -0
  164. rocket_engine/ops/editors.py +404 -0
  165. rocket_engine/ops/evidence.py +194 -0
  166. rocket_engine/ops/gates.py +262 -0
  167. rocket_engine/ops/ports.py +76 -0
  168. rocket_engine/ops/queries.py +417 -0
  169. rocket_engine/ops/runs.py +553 -0
  170. rocket_engine/ops/submit.py +614 -0
  171. rocket_engine/project/README.md +27 -0
  172. rocket_engine/project/__init__.py +6 -0
  173. rocket_engine/project/canvas/__init__.py +55 -0
  174. rocket_engine/project/canvas/compile.py +109 -0
  175. rocket_engine/project/canvas/integrate.py +48 -0
  176. rocket_engine/project/canvas/schema.py +241 -0
  177. rocket_engine/project/canvas/templates.py +46 -0
  178. rocket_engine/project/canvas/validate.py +603 -0
  179. rocket_engine/project/edit/__init__.py +5 -0
  180. rocket_engine/project/edit/cas.py +87 -0
  181. rocket_engine/project/edit/transactions.py +282 -0
  182. rocket_engine/project/errors.py +16 -0
  183. rocket_engine/project/frontend13.py +685 -0
  184. rocket_engine/project/geometry/__init__.py +23 -0
  185. rocket_engine/project/geometry/ops.py +451 -0
  186. rocket_engine/project/geometry/snap.py +103 -0
  187. rocket_engine/project/ip/__init__.py +1 -0
  188. rocket_engine/project/ip/blocks.py +195 -0
  189. rocket_engine/project/ip/entry.schema.json +78 -0
  190. rocket_engine/project/ip/lifecycle.py +402 -0
  191. rocket_engine/project/ip/verbs.py +123 -0
  192. rocket_engine/project/layers/__init__.py +6 -0
  193. rocket_engine/project/layers/bindings.py +115 -0
  194. rocket_engine/project/layers/catalog.py +228 -0
  195. rocket_engine/project/layers/presets.py +43 -0
  196. rocket_engine/project/ledger/__init__.py +1 -0
  197. rocket_engine/project/ledger/docs.py +167 -0
  198. rocket_engine/project/ledger/entities.py +176 -0
  199. rocket_engine/project/ledger/events.py +29 -0
  200. rocket_engine/project/ledger/runs.py +257 -0
  201. rocket_engine/project/ledger/shared.py +174 -0
  202. rocket_engine/project/ledger/snapshots.py +385 -0
  203. rocket_engine/project/ledger/wires.py +552 -0
  204. rocket_engine/project/process/__init__.py +14 -0
  205. rocket_engine/project/process/model.py +193 -0
  206. rocket_engine/project/process/recipes.py +121 -0
  207. rocket_engine/project/schematic/__init__.py +1 -0
  208. rocket_engine/project/schematic/hierarchy.py +312 -0
  209. rocket_engine/project/schematic/instances.py +393 -0
  210. rocket_engine/project/schematic/sheets.py +198 -0
  211. rocket_engine/project/service.py +2941 -0
  212. rocket_engine/project/symbols/__init__.py +1 -0
  213. rocket_engine/project/symbols/cells.py +324 -0
  214. rocket_engine/project/symbols/linkage.py +356 -0
  215. rocket_engine/project/symbols/symbols.py +352 -0
  216. rocket_engine/project/templates/__init__.py +303 -0
  217. rocket_engine/project/templates/errors.py +25 -0
  218. rocket_engine/project/testbench/__init__.py +49 -0
  219. rocket_engine/project/testbench/blocks.py +619 -0
  220. rocket_engine/project/testbench/templates.py +134 -0
  221. rocket_engine/stores/README.md +12 -0
  222. rocket_engine/stores/__init__.py +6 -0
  223. rocket_engine/stores/ecs.py +1508 -0
  224. rocket_engine/stores/layers.py +1023 -0
  225. rocket_engine/stores/settings.py +1188 -0
  226. rocket_engine/stores/wireplace.py +1324 -0
  227. rocket_engine/workflows/README.md +18 -0
  228. rocket_engine/workflows/__init__.py +53 -0
  229. rocket_engine/workflows/capability_plan.py +102 -0
  230. rocket_engine/workflows/completion.py +228 -0
  231. rocket_engine/workflows/connectgraph.py +734 -0
  232. rocket_engine/workflows/connectgraph_emit.py +336 -0
  233. rocket_engine/workflows/gates.py +144 -0
  234. rocket_engine/workflows/photonic_wire.py +76 -0
  235. rocket_engine/workflows/stages.py +324 -0
  236. rocket_engine/workflows/viewsync.py +399 -0
  237. rocket_engine-0.0.2b1.dist-info/METADATA +200 -0
  238. rocket_engine-0.0.2b1.dist-info/RECORD +241 -0
  239. rocket_engine-0.0.2b1.dist-info/WHEEL +4 -0
  240. rocket_engine-0.0.2b1.dist-info/entry_points.txt +2 -0
  241. rocket_engine-0.0.2b1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,440 @@
1
+ """Xschem schematic deck adapter (DECK-XSCHEM): netlist, then sim.
2
+
3
+ Protocol, verified against XSCHEM V3.4.8 on this host: batch
4
+ netlist is `xschem -x -q --rcfile <rc> -s -n <sch> -o <dir>
5
+ -N <name>` (add `-f` for a flat netlist, `-S` when the schematic
6
+ owns the sim, `--command/--script` for scripted flows). Project
7
+ rcfiles may hardcode `XSCHEM_SHAREDIR` to a path that does not
8
+ exist on the run host (a fixture xschemrc pointed at
9
+ `/usr/share/xschem` while the install lives under
10
+ `/usr/local`); the batch then exits 0 with no output and only a
11
+ `Tcl_AppInit() err` line in the log — silent-fail. The adapter
12
+ wraps the project rcfile: it pins `XSCHEM_SHAREDIR` to the real
13
+ install around the project source and re-pins after it, detects
14
+ the init error loudly, and refuses a missing netlist output
15
+ instead of fake-greening. `DISPLAY` is scrubbed so a dead remote
16
+ display can never pop (or hang) a GUI. Netgen-style: the verdict
17
+ always comes from artifacts and logs, never the exit code; a
18
+ missing binary fails with the PROV remedy. `** sch_path` /
19
+ `** sym_path` headers embed the checkout path, so netlists
20
+ normalize to basenames before any byte-compare.
21
+
22
+ Engine wiring (vocabulary operation, composition/MCP
23
+ registration) belongs to the owning surface; this file staffs
24
+ the solver with operation `run_xschem`.
25
+ """
26
+
27
+ import re
28
+ import shutil
29
+ import subprocess
30
+ from pathlib import Path
31
+
32
+ from ...execution.errors import ExecutionError
33
+ from ...interfaces import ToolAdapter
34
+ from ...interfaces.runparams import MAX_TIMEOUT_S, MIN_TIMEOUT_S
35
+ from ..errors import AdapterError
36
+
37
+ OPERATION = "run_xschem"
38
+ _TIMEOUT_S = 120
39
+ _INIT_ERR_RE = re.compile(r"Tcl_AppInit\(\) err|cannot find .*xschem\.tcl")
40
+ _VERSION_RE = re.compile(r"XSCHEM V3")
41
+ _SCH_PATH_RE = re.compile(r"^(\*\* (?:sch_path|sym_path):\s*).*[/\\]([^/\\\s]+)\s*$", re.M)
42
+
43
+
44
+ def _budget_or_raise(job):
45
+ """Job solver budget: validated, or the adapter default.
46
+
47
+ Args:
48
+ job: Solver job, maybe with a `timeout_s`.
49
+
50
+ Returns:
51
+ Budget seconds for every `_run` call.
52
+
53
+ Raises:
54
+ AdapterError: invalid_command for a boolean,
55
+ non-number, or out-of-bounds budget.
56
+ """
57
+ timeout = job.get("timeout_s", _TIMEOUT_S)
58
+ if (
59
+ isinstance(timeout, bool)
60
+ or not isinstance(timeout, (int, float))
61
+ or not MIN_TIMEOUT_S <= timeout <= MAX_TIMEOUT_S
62
+ ):
63
+ raise AdapterError(
64
+ "invalid_command", f"Bad timeout_s: {timeout!r}."
65
+ )
66
+ return timeout
67
+
68
+
69
+ def normalize_netlist(text):
70
+ """Strip checkout paths from xschem path headers.
71
+
72
+ Args:
73
+ text: Raw netlist text with `** sch_path` / `** sym_path`
74
+ headers carrying absolute checkout paths.
75
+
76
+ Returns:
77
+ Netlist text with those headers reduced to basenames,
78
+ byte-comparable across checkouts.
79
+ """
80
+ return _SCH_PATH_RE.sub(r"\1\2", text)
81
+
82
+
83
+ def resolve_xschem(xschem_bin=None):
84
+ """Path to a usable xschem binary, or None.
85
+
86
+ Args:
87
+ xschem_bin: Explicit binary path, or None for PATH.
88
+
89
+ Returns:
90
+ Binary path, or None when missing or not a file.
91
+ """
92
+ if xschem_bin is not None:
93
+ return xschem_bin if Path(xschem_bin).is_file() else None
94
+ return shutil.which("xschem")
95
+
96
+
97
+ def sharedir_for(binary):
98
+ """Install sharedir next to the binary, or None when absent.
99
+
100
+ Args:
101
+ binary: Resolved xschem binary path.
102
+
103
+ Returns:
104
+ `<bindir>/../share/xschem` when it holds `xschem.tcl`,
105
+ else None.
106
+ """
107
+ candidate = Path(binary).resolve().parent.parent / "share" / "xschem"
108
+ if (candidate / "xschem.tcl").is_file():
109
+ return str(candidate)
110
+ return None
111
+
112
+
113
+ class XschemAdapter(ToolAdapter):
114
+ """run_xschem via the xschem batch netlister.
115
+
116
+ Serves the run_xschem operation with deck.tool `xschem` (the
117
+ only tool). Netlists a schematic to SPICE with a pinned
118
+ rcfile and explicit outputs; optional `-f` flat mode, `-S`
119
+ schematic-owned sim, and `--command`/`--script` seams.
120
+
121
+ Job keys:
122
+ sch_file (required): Schematic file under netlist.
123
+ rcfile: Project rcfile path (wrapped to pin
124
+ XSCHEM_SHAREDIR; absent means the install default).
125
+ output_name: Netlist file name (default
126
+ `<stem>.spice`).
127
+ flat: Flat netlist (`-f`) when true.
128
+ sim: Run the schematic-owned sim (`-S`) when true.
129
+ command: Extra `--command` Tcl snippet.
130
+ script: Extra `--script` Tcl file path.
131
+ workdir: Run directory (default ".").
132
+
133
+ Args:
134
+ runner: Optional isolated runner; direct subprocess when None.
135
+ xschem_bin: Explicit xschem binary; PATH when None.
136
+
137
+ Caution: xschem exits 0 on silent init failure and on
138
+ netlist errors alike — the verdict always comes from the
139
+ output file and the log, never the exit code.
140
+ """
141
+
142
+ def __init__(self, runner=None, xschem_bin=None):
143
+ self._runner = runner
144
+ self._xschem_bin = xschem_bin
145
+
146
+ def capabilities(self, target):
147
+ """Report the served operation, tool, and modes.
148
+
149
+ Args:
150
+ target: Target the capabilities are reported for
151
+ (accepted, unused — one schematic per job).
152
+
153
+ Returns:
154
+ Capability mapping with operations, tool, and modes.
155
+ """
156
+ return {
157
+ "operations": [OPERATION],
158
+ "tool": "xschem",
159
+ "modes": ["netlist", "sim"],
160
+ }
161
+
162
+ def _assert_version(self, banner):
163
+ """Reject non-V3 xschem banners loudly.
164
+
165
+ Args:
166
+ banner: Combined stdout/stderr of `xschem -v`.
167
+
168
+ Raises:
169
+ AdapterError: missing_dependency when the banner is
170
+ not the V3 family this adapter is verified on.
171
+ """
172
+ if not _VERSION_RE.search(banner or ""):
173
+ raise AdapterError(
174
+ "missing_dependency",
175
+ f"xschem V3 required, got banner: {(banner or '').strip()[:200]!r} "
176
+ "(PROV: install xschem V3, see docs/tickets/PROV-DEFERRED.md).",
177
+ )
178
+
179
+ def preflight(self, job):
180
+ """Check operation, files, binary, version, and sharedir.
181
+
182
+ Cheap deterministic checks first (operation, files),
183
+ machine-dependent ones last (binary, version, sharedir).
184
+
185
+ Args:
186
+ job: Solver job with operation run_xschem plus
187
+ sch_file and deck inputs.
188
+
189
+ Returns:
190
+ Checked mapping with sch_file, binary, sharedir,
191
+ output_name, flags, and timeout.
192
+
193
+ Raises:
194
+ AdapterError: unsupported_operation for another
195
+ operation, invalid_reference for a missing
196
+ schematic/rcfile/script, missing_dependency for
197
+ a missing binary, a non-V3 banner, or an
198
+ unresolvable sharedir.
199
+ """
200
+ if job.get("operation") != OPERATION:
201
+ raise AdapterError(
202
+ "unsupported_operation",
203
+ f"xschem handles {OPERATION}, got {job.get('operation')}.",
204
+ )
205
+ timeout = _budget_or_raise(job)
206
+ sch_file = job.get("sch_file")
207
+ if not sch_file or not Path(sch_file).is_file():
208
+ raise AdapterError(
209
+ "invalid_reference", f"Schematic file not found: {sch_file}."
210
+ )
211
+ for key in ("rcfile", "script"):
212
+ path = job.get(key)
213
+ if path is not None and not Path(path).is_file():
214
+ raise AdapterError(
215
+ "invalid_reference", f"xschem {key} not found: {path}."
216
+ )
217
+ binary = resolve_xschem(self._xschem_bin)
218
+ if binary is None:
219
+ raise AdapterError(
220
+ "missing_dependency",
221
+ "xschem binary not found "
222
+ "(PROV: install xschem V3, see docs/tickets/PROV-DEFERRED.md).",
223
+ )
224
+ banner = self._version_banner(binary, timeout)
225
+ self._assert_version(banner)
226
+ sharedir = sharedir_for(binary)
227
+ if sharedir is None:
228
+ raise AdapterError(
229
+ "missing_dependency",
230
+ f"xschem sharedir unresolvable next to {binary} "
231
+ "(expected <bindir>/../share/xschem/xschem.tcl).",
232
+ )
233
+ output_name = job.get("output_name") or (Path(sch_file).stem + ".spice")
234
+ if not isinstance(output_name, str) or not output_name or "/" in output_name:
235
+ raise AdapterError(
236
+ "invalid_command", f"Bad xschem output_name: {output_name!r}."
237
+ )
238
+ return {
239
+ "sch_file": sch_file,
240
+ "rcfile": job.get("rcfile"),
241
+ "script": job.get("script"),
242
+ "command": job.get("command"),
243
+ "binary": binary,
244
+ "sharedir": sharedir,
245
+ "output_name": output_name,
246
+ "flat": bool(job.get("flat")),
247
+ "sim": bool(job.get("sim")),
248
+ "timeout_s": timeout,
249
+ }
250
+
251
+ def execute(self, job):
252
+ """Wrap the rcfile, netlist, and read back the output.
253
+
254
+ Args:
255
+ job: Solver job with operation run_xschem plus
256
+ sch_file and deck inputs.
257
+
258
+ Returns:
259
+ Native output with exit_code, stdout/stderr/log,
260
+ netlist file/text (raw and normalized), and flags.
261
+
262
+ Raises:
263
+ AdapterError: invalid_reference for a missing
264
+ workdir, invalid_command for a bad command
265
+ snippet, missing_dependency on silent init
266
+ failure, runner_unavailable when no netlist
267
+ output appears, plus the preflight codes
268
+ (execute re-checks first).
269
+ """
270
+ checked = self.preflight(job)
271
+ workdir = job.get("workdir", ".")
272
+ if not Path(workdir).is_dir():
273
+ raise AdapterError(
274
+ "invalid_reference", f"Workdir not found: {workdir}."
275
+ )
276
+ command = checked.get("command")
277
+ if command is not None and not isinstance(command, str):
278
+ raise AdapterError(
279
+ "invalid_command", f"Bad xschem command: {command!r}."
280
+ )
281
+ outdir = Path(workdir) / "xschem_out"
282
+ outdir.mkdir(exist_ok=True)
283
+ wrapper = Path(workdir) / "xschem_wrapper.rc"
284
+ lines = [f"set XSCHEM_SHAREDIR {{{checked['sharedir']}}}"]
285
+ if checked.get("rcfile") is not None:
286
+ lines.append(f"source {{{checked['rcfile']}}}")
287
+ lines.append(f"set XSCHEM_SHAREDIR {{{checked['sharedir']}}}")
288
+ wrapper.write_text("\n".join(lines) + "\n", encoding="utf-8")
289
+ argv = [
290
+ checked["binary"],
291
+ "-x",
292
+ "-q",
293
+ "--rcfile",
294
+ str(wrapper),
295
+ "-s",
296
+ "-n",
297
+ checked["sch_file"],
298
+ "-o",
299
+ str(outdir),
300
+ "-N",
301
+ checked["output_name"],
302
+ ]
303
+ if checked["flat"]:
304
+ argv.append("-f")
305
+ if checked["sim"]:
306
+ argv.append("-S")
307
+ if command:
308
+ argv.extend(["--command", command])
309
+ if checked.get("script") is not None:
310
+ argv.extend(["--script", checked["script"]])
311
+ log_file = Path(workdir) / "xschem.log"
312
+ proc = self._run(argv, workdir, timeout=checked["timeout_s"])
313
+ log_file.write_text(
314
+ (proc.stdout or "") + (proc.stderr or ""), encoding="utf-8"
315
+ )
316
+ log_text = log_file.read_text(encoding="utf-8")
317
+ if _INIT_ERR_RE.search(log_text):
318
+ raise AdapterError(
319
+ "missing_dependency",
320
+ "xschem init failed (xschem.tcl unloadable; sharedir "
321
+ f"{checked['sharedir']!r}); see xschem.log "
322
+ "(PROV: install xschem V3, see docs/tickets/PROV-DEFERRED.md).",
323
+ )
324
+ netlist_file = outdir / checked["output_name"]
325
+ if not netlist_file.is_file():
326
+ raise AdapterError(
327
+ "runner_unavailable",
328
+ f"xschem produced no netlist: {(proc.stderr or '')[:500]}.",
329
+ )
330
+ raw = netlist_file.read_text(encoding="utf-8")
331
+ return {
332
+ "exit_code": proc.returncode,
333
+ "stdout": proc.stdout,
334
+ "stderr": proc.stderr,
335
+ "log_file": str(log_file),
336
+ "log_text": log_text,
337
+ "netlist_file": str(netlist_file),
338
+ "netlist_text": raw,
339
+ "normalized_text": normalize_netlist(raw),
340
+ "flat": checked["flat"],
341
+ "sim": checked["sim"],
342
+ }
343
+
344
+ def parse(self, native_output):
345
+ """Count netlist elements; sim mode surfaces sim failures.
346
+
347
+ Args:
348
+ native_output: Native output with the netlist text
349
+ and log from execute.
350
+
351
+ Returns:
352
+ {total, violations[]} with element count and any
353
+ xschem/sim failures as violations.
354
+
355
+ Raises:
356
+ AdapterError: runner_unavailable when the output has
357
+ no netlist text.
358
+ """
359
+ raw = native_output.get("netlist_text")
360
+ if raw is None:
361
+ netlist_file = native_output.get("netlist_file")
362
+ if not netlist_file or not Path(netlist_file).is_file():
363
+ raise AdapterError(
364
+ "runner_unavailable", "xschem run has no netlist."
365
+ )
366
+ raw = Path(netlist_file).read_text(encoding="utf-8")
367
+ elements = [
368
+ line
369
+ for line in raw.splitlines()
370
+ if line.strip() and not line.strip().startswith("*")
371
+ ]
372
+ violations = []
373
+ if native_output.get("sim"):
374
+ log_text = native_output.get("log_text", "")
375
+ for line in log_text.splitlines():
376
+ if re.search(r"error|failed|no simulation", line, re.IGNORECASE):
377
+ violations.append(
378
+ {
379
+ "rule": "xschem_sim_fail",
380
+ "message": line.strip()[:300],
381
+ "severity": "error",
382
+ }
383
+ )
384
+ break
385
+ return {"total": len(elements), "violations": violations}
386
+
387
+ def _version_banner(self, binary, timeout):
388
+ """Combined stdout/stderr of `xschem -v` for the V3 assert.
389
+
390
+ Args:
391
+ binary: Resolved xschem binary path.
392
+ timeout: Budget seconds for the probe.
393
+
394
+ Returns:
395
+ Banner text (empty when the probe cannot run — the
396
+ version assert then refuses).
397
+ """
398
+ try:
399
+ proc = self._run([binary, "-v"], ".", timeout=timeout)
400
+ except AdapterError:
401
+ return ""
402
+ return (proc.stdout or "") + (proc.stderr or "")
403
+
404
+ def _run(self, argv, workdir, timeout=_TIMEOUT_S):
405
+ """Run xschem with DISPLAY scrubbed (forced text mode).
406
+
407
+ Args:
408
+ argv: xschem argument vector.
409
+ workdir: Run directory.
410
+ timeout: Budget seconds for the run.
411
+
412
+ Returns:
413
+ Completed process with xschem stdout/stderr.
414
+
415
+ Raises:
416
+ AdapterError: runner_unavailable on timeout, plus
417
+ converted runner errors when injected.
418
+ """
419
+ if self._runner is not None:
420
+ try:
421
+ return self._runner.run(argv, workdir, timeout)
422
+ except ExecutionError as exc:
423
+ raise AdapterError(exc.code, str(exc)) from exc
424
+ import os
425
+
426
+ env = dict(os.environ)
427
+ env.pop("DISPLAY", None)
428
+ try:
429
+ return subprocess.run(
430
+ argv,
431
+ capture_output=True,
432
+ text=True,
433
+ timeout=timeout,
434
+ cwd=workdir,
435
+ env=env,
436
+ )
437
+ except subprocess.TimeoutExpired as exc:
438
+ raise AdapterError(
439
+ "runner_unavailable", f"xschem timed out after {timeout}s."
440
+ ) from exc