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,459 @@
1
+ """Staged generate_views driver: job JSON in, result envelope JSON on stdout.
2
+
3
+ The adapter copies this file into the run workdir next to
4
+ `gf_views_job.json` and executes `python gf_views_driver.py
5
+ gf_views_job.json`. One marker-delimited JSON envelope is printed to stdout
6
+ (ROCKETEDA_ENVELOPE_BEGIN/END); chatter outside the markers is
7
+ tolerated and stays in logs.
8
+
9
+ Every recipe consumes project inputs from the job: view-spec cells +
10
+ params, project GDS paths, and the optional project pin map. Nothing
11
+ is hardcoded — the PDK, the cell list, the export filenames, and the
12
+ macro pin/obstruction bindings all ride the job.
13
+
14
+ Envelope: `{"ok": true, "gdsfactory_version, "result": {recipe,
15
+ active_pdk, cspdk_version, mappings[], artifacts[], diagnostics}}` or
16
+ `{"ok": false, "error": {code, message, token, line}}`. Exit 0 on
17
+ success, 2 on a bad cell/spec/source/format, 3 when gdsfactory or the
18
+ named PDK is not importable. Anything else explodes with a traceback
19
+ and exit 1, which parse() reports as runner_unavailable rather than
20
+ mistaking a tool crash for layout.
21
+ """
22
+
23
+ import json
24
+ import sys
25
+ from pathlib import Path
26
+
27
+ _ENVELOPE_BEGIN = "ROCKETEDA_ENVELOPE_BEGIN"
28
+ _ENVELOPE_END = "ROCKETEDA_ENVELOPE_END"
29
+
30
+ _BOUNDARY_NOTE = (
31
+ "GDS import alone does not reconstruct macro pins/obstructions "
32
+ "(PRD boundary note); bind a project pin_map to publish them."
33
+ )
34
+ _BUILD_NOTE = (
35
+ "Cell builds carry no macro obstructions; bind a project "
36
+ "pin_map at import to publish them."
37
+ )
38
+
39
+
40
+ def _fail(code, message, token, line):
41
+ print(_ENVELOPE_BEGIN)
42
+ print(
43
+ json.dumps(
44
+ {
45
+ "ok": False,
46
+ "error": {
47
+ "code": code,
48
+ "message": message,
49
+ "token": token,
50
+ "line": line,
51
+ },
52
+ }
53
+ )
54
+ )
55
+ print(_ENVELOPE_END)
56
+
57
+
58
+ def _find_line(text, token):
59
+ if not token:
60
+ return None
61
+ for number, line in enumerate((text or "").splitlines(), 1):
62
+ if token in line:
63
+ return number
64
+ return None
65
+
66
+
67
+ def _polygons(comp):
68
+ return sum(len(polys) for polys in comp.get_polygons().values())
69
+
70
+
71
+ def _bbox(comp):
72
+ box = comp.bbox()
73
+ return [
74
+ [float(box.left), float(box.bottom)],
75
+ [float(box.right), float(box.top)],
76
+ ]
77
+
78
+
79
+ def _ports(comp):
80
+ try:
81
+ ports = list(comp.ports)
82
+ except Exception:
83
+ return [], []
84
+ names = []
85
+ details = []
86
+ for port in ports:
87
+ try:
88
+ name = port.name
89
+ except Exception:
90
+ continue
91
+ names.append(name)
92
+ try:
93
+ center = port.center
94
+ middle = [float(center[0]), float(center[1])]
95
+ except Exception:
96
+ middle = None
97
+ try:
98
+ layer = str(port.layer)
99
+ except Exception:
100
+ layer = None
101
+ details.append(
102
+ {"name": name, "center_um": middle, "layer": layer}
103
+ )
104
+ return sorted(names), details
105
+
106
+
107
+ def _mapping(source, comp):
108
+ names, _ = _ports(comp)
109
+ return {
110
+ "source": source,
111
+ "component": comp.name,
112
+ "ports": names,
113
+ "n_polygons": _polygons(comp),
114
+ "bbox_um": _bbox(comp),
115
+ }
116
+
117
+
118
+ def _write_json(path, payload):
119
+ Path(path).write_text(json.dumps(payload, indent=2), encoding="utf-8")
120
+ return str(Path(path).resolve())
121
+
122
+
123
+ def _pins_artifact(comp, bound, fallback):
124
+ _, details = _ports(comp)
125
+ if bound is None:
126
+ return {
127
+ "cell": comp.name,
128
+ "source": fallback["source"],
129
+ "reconstructed": fallback["reconstructed"],
130
+ "boundary_note": fallback["boundary_note"],
131
+ "pins": details,
132
+ }
133
+ pins = [
134
+ {
135
+ "name": name,
136
+ "layer": dict(spec).get("layer"),
137
+ "x_um": dict(spec).get("x_um"),
138
+ "y_um": dict(spec).get("y_um"),
139
+ }
140
+ for name, spec in (bound.get("pins") or {}).items()
141
+ ]
142
+ return {
143
+ "cell": comp.name,
144
+ "source": "project-pin-map",
145
+ "reconstructed": True,
146
+ "boundary_note": None,
147
+ "pins": pins,
148
+ }
149
+
150
+
151
+ def _obstructions_artifact(comp, bound, fallback):
152
+ if bound is None:
153
+ return {
154
+ "cell": comp.name,
155
+ "source": fallback["source"],
156
+ "reconstructed": fallback["reconstructed"],
157
+ "boundary_note": fallback["boundary_note"],
158
+ "obstructions": [],
159
+ }
160
+ return {
161
+ "cell": comp.name,
162
+ "source": "project-pin-map",
163
+ "reconstructed": True,
164
+ "boundary_note": None,
165
+ "obstructions": list(bound.get("obstructions") or []),
166
+ }
167
+
168
+
169
+ def _publish(comp, bound, artifacts, fallback):
170
+ pins_path = _write_json(
171
+ f"{comp.name}.pins.json",
172
+ _pins_artifact(comp, bound, fallback["pins"]),
173
+ )
174
+ obs_path = _write_json(
175
+ f"{comp.name}.obstructions.json",
176
+ _obstructions_artifact(comp, bound, fallback["obstructions"]),
177
+ )
178
+ artifacts.append(
179
+ {"kind": "pins", "cell": comp.name, "path": pins_path}
180
+ )
181
+ artifacts.append(
182
+ {"kind": "obstructions", "cell": comp.name, "path": obs_path}
183
+ )
184
+
185
+
186
+ def _activate_cspdk(gf, cspdk_path):
187
+ if cspdk_path is not None:
188
+ root = str(Path(cspdk_path).expanduser())
189
+ if root not in sys.path:
190
+ sys.path.insert(0, root)
191
+ try:
192
+ import cspdk
193
+ from cspdk.si340 import activate_pdk
194
+ except ImportError as exc:
195
+ raise _MissingCspdk(str(exc)) from exc
196
+ activate_pdk()
197
+ from gdsfactory.pdk import get_active_pdk
198
+
199
+ active = get_active_pdk()
200
+ return active.name, getattr(cspdk, "__version__", "unknown")
201
+
202
+
203
+ def _activate(doc, cspdk_path):
204
+ import gdsfactory as gf
205
+
206
+ if doc is None:
207
+ # Import path carries no view spec, but gf.import_gds still
208
+ # needs an active PDK. The project tree wins where the
209
+ # adapter carried it; otherwise the generic PDK keeps the
210
+ # read independent of build/export PDK selection.
211
+ if cspdk_path is not None:
212
+ name, version = _activate_cspdk(gf, cspdk_path)
213
+ return gf, name, version
214
+ gf.gpdk.PDK.activate()
215
+ return gf, "generic", None
216
+ pdk = doc.get("pdk", "generic")
217
+ if pdk == "generic":
218
+ gf.gpdk.PDK.activate()
219
+ return gf, "generic", None
220
+ name, version = _activate_cspdk(gf, cspdk_path)
221
+ return gf, name, version
222
+
223
+
224
+ class _MissingCspdk(Exception):
225
+ pass
226
+
227
+
228
+ def _build_cell(gf, entry):
229
+ from gdsfactory.pdk import get_active_pdk
230
+
231
+ active = get_active_pdk()
232
+ cell = entry.get("cell")
233
+ try:
234
+ factory = active.cells[cell]
235
+ except KeyError:
236
+ raise _BadCell(
237
+ f"Unknown cell {cell!r} in PDK {active.name!r}.", cell
238
+ ) from None
239
+ params = entry.get("params") or {}
240
+ try:
241
+ return factory(**params)
242
+ except TypeError as exc:
243
+ raise _BadCell(
244
+ f"Bad params for cell {cell!r}: {exc}.", cell
245
+ ) from exc
246
+
247
+
248
+ class _BadCell(Exception):
249
+ def __init__(self, message, token):
250
+ super().__init__(message)
251
+ self.token = token
252
+
253
+
254
+ def _export_name(exports, index, component, extension):
255
+ if isinstance(exports, list) and index < len(exports):
256
+ name = (exports[index] or {}).get("filename")
257
+ if isinstance(name, str) and name:
258
+ return name
259
+ return f"{component}{extension}"
260
+
261
+
262
+ _BUILT_FALLBACK = {
263
+ "pins": {
264
+ "source": "built-ports",
265
+ "reconstructed": True,
266
+ "boundary_note": None,
267
+ },
268
+ "obstructions": {
269
+ "source": "built-ports",
270
+ "reconstructed": False,
271
+ "boundary_note": _BUILD_NOTE,
272
+ },
273
+ }
274
+
275
+ _IMPORTED_FALLBACK = {
276
+ "pins": {
277
+ "source": "gds-ports",
278
+ "reconstructed": False,
279
+ "boundary_note": _BOUNDARY_NOTE,
280
+ },
281
+ "obstructions": {
282
+ "source": "gds-ports",
283
+ "reconstructed": False,
284
+ "boundary_note": _BOUNDARY_NOTE,
285
+ },
286
+ }
287
+
288
+
289
+ def _run_build(gf, doc):
290
+ mappings = []
291
+ artifacts = []
292
+ for entry in doc["cells"]:
293
+ comp = _build_cell(gf, entry)
294
+ mappings.append(_mapping(entry["cell"], comp))
295
+ _publish(comp, None, artifacts, _BUILT_FALLBACK)
296
+ return mappings, artifacts, None, []
297
+
298
+
299
+ def _run_export(gf, doc, extension, writer):
300
+ mappings = []
301
+ artifacts = []
302
+ notes = []
303
+ matches = []
304
+ exports = doc.get("exports") or []
305
+ for index, entry in enumerate(doc["cells"]):
306
+ comp = _build_cell(gf, entry)
307
+ mappings.append(_mapping(entry["cell"], comp))
308
+ _publish(comp, None, artifacts, _BUILT_FALLBACK)
309
+ filename = _export_name(exports, index, comp.name, extension)
310
+ writer(comp, filename)
311
+ path = str(Path(filename).resolve())
312
+ artifacts.append(
313
+ {"kind": "layout", "cell": comp.name, "path": path}
314
+ )
315
+ if extension == ".gds":
316
+ mirror = gf.import_gds(filename)
317
+ match = (
318
+ mirror.name == comp.name
319
+ and _polygons(mirror) == _polygons(comp)
320
+ and _bbox(mirror) == _bbox(comp)
321
+ )
322
+ matches.append(match)
323
+ notes.append(f"readback {filename}: match={match}.")
324
+ else:
325
+ notes.append(
326
+ f"wrote {filename} ({Path(filename).stat().st_size} "
327
+ "bytes, no readback probe for oasis)."
328
+ )
329
+ roundtrip = all(matches) if matches else None
330
+ return mappings, artifacts, roundtrip, notes
331
+
332
+
333
+ def _run_import(gf, job):
334
+ mappings = []
335
+ artifacts = []
336
+ pin_cells = (job.get("pin_map_doc") or {}).get("cells") or {}
337
+ for source in job.get("gds_sources") or []:
338
+ try:
339
+ comp = gf.import_gds(source)
340
+ except Exception as exc:
341
+ raise _BadSource(
342
+ f"Cannot import GDS {source}: {exc}.", source
343
+ ) from exc
344
+ mappings.append(_mapping(Path(source).name, comp))
345
+ _publish(comp, pin_cells.get(comp.name), artifacts,
346
+ _IMPORTED_FALLBACK)
347
+ if not mappings:
348
+ raise _BadSource("Import found no GDS sources.", None)
349
+ return mappings, artifacts, None, []
350
+
351
+
352
+ class _BadSource(Exception):
353
+ def __init__(self, message, token):
354
+ super().__init__(message)
355
+ self.token = token
356
+
357
+
358
+ def main(argv):
359
+ """Run one staged views job: job JSON in, envelope on stdout.
360
+
361
+ Args:
362
+ argv: Argument vector; argv[1] is the staged job JSON path.
363
+
364
+ Returns:
365
+ Process exit code: 0 on success, 2 on a bad
366
+ cell/spec/source/format, 3 when gdsfactory or the named
367
+ PDK is not importable.
368
+
369
+ Raises:
370
+ ValueError: Bad recipe or bad pin-map reference (raised and
371
+ caught inside main into the error envelope — never
372
+ escapes).
373
+ """
374
+ with open(argv[1], encoding="utf-8") as handle:
375
+ job = json.load(handle)
376
+ text = job.get("text", "")
377
+ doc = job.get("doc")
378
+ try:
379
+ gf, active_pdk, cspdk_ver = _activate(doc, job.get("cspdk_path"))
380
+ except ImportError as exc:
381
+ _fail(
382
+ "missing_dependency",
383
+ f"gdsfactory is not importable: {exc} "
384
+ "(uv sync).",
385
+ None,
386
+ None,
387
+ )
388
+ return 3
389
+ except _MissingCspdk as exc:
390
+ _fail(
391
+ "missing_dependency",
392
+ f"cspdk.si340 is not importable: {exc} "
393
+ "(scripts/fetch_fixtures.py --only cspdk).",
394
+ None,
395
+ None,
396
+ )
397
+ return 3
398
+ try:
399
+ recipe = job.get("recipe")
400
+ if recipe == "build_cells":
401
+ mappings, artifacts, roundtrip, notes = _run_build(gf, doc)
402
+ elif recipe == "export_gds":
403
+ mappings, artifacts, roundtrip, notes = _run_export(
404
+ gf, doc, ".gds", lambda comp, name: comp.write_gds(name)
405
+ )
406
+ elif recipe == "export_oasis":
407
+ writer = getattr(gf.Component, "write_oas", None)
408
+ if writer is None:
409
+ raise ValueError(
410
+ "OASIS export needs Component.write_oas, absent "
411
+ f"in gdsfactory {getattr(gf, '__version__', '?')}."
412
+ )
413
+ mappings, artifacts, roundtrip, notes = _run_export(
414
+ gf, doc, ".oas", lambda comp, name: comp.write_oas(name)
415
+ )
416
+ elif recipe == "import_gds":
417
+ mappings, artifacts, roundtrip, notes = _run_import(gf, job)
418
+ else:
419
+ raise ValueError(f"Unknown recipe: {recipe!r}.")
420
+ except _BadCell as exc:
421
+ _fail("invalid_cell", str(exc), exc.token,
422
+ _find_line(text, exc.token))
423
+ return 2
424
+ except _BadSource as exc:
425
+ _fail("unreadable_source", str(exc), exc.token,
426
+ _find_line(text, exc.token))
427
+ return 2
428
+ except ValueError as exc:
429
+ _fail("unsupported_format", str(exc), recipe,
430
+ _find_line(text, recipe))
431
+ return 2
432
+ print(_ENVELOPE_BEGIN)
433
+ print(
434
+ json.dumps(
435
+ {
436
+ "ok": True,
437
+ "gdsfactory_version": getattr(
438
+ gf, "__version__", "unknown"
439
+ ),
440
+ "result": {
441
+ "recipe": recipe,
442
+ "active_pdk": active_pdk,
443
+ "cspdk_version": cspdk_ver,
444
+ "mappings": mappings,
445
+ "artifacts": artifacts,
446
+ "diagnostics": {
447
+ "roundtrip_match": roundtrip,
448
+ "notes": notes,
449
+ },
450
+ },
451
+ }
452
+ )
453
+ )
454
+ print(_ENVELOPE_END)
455
+ return 0
456
+
457
+
458
+ if __name__ == "__main__":
459
+ sys.exit(main(sys.argv))