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.
- rocket_engine/__init__.py +8 -0
- rocket_engine/__main__.py +12 -0
- rocket_engine/adapters/README.md +54 -0
- rocket_engine/adapters/__init__.py +7 -0
- rocket_engine/adapters/api.py +39 -0
- rocket_engine/adapters/base.py +46 -0
- rocket_engine/adapters/cace/__init__.py +163 -0
- rocket_engine/adapters/circulax/README.md +10 -0
- rocket_engine/adapters/circulax/__init__.py +112 -0
- rocket_engine/adapters/circulax/_driver.py +190 -0
- rocket_engine/adapters/circulax/adapter.py +538 -0
- rocket_engine/adapters/circulax/results.py +291 -0
- rocket_engine/adapters/cli/README.md +34 -0
- rocket_engine/adapters/cli/__init__.py +305 -0
- rocket_engine/adapters/connectivity/__init__.py +196 -0
- rocket_engine/adapters/connectivity/results.py +214 -0
- rocket_engine/adapters/cosim/README.md +9 -0
- rocket_engine/adapters/cosim/__init__.py +1390 -0
- rocket_engine/adapters/cosim/results.py +214 -0
- rocket_engine/adapters/devsim/__init__.py +171 -0
- rocket_engine/adapters/digital_flow/README.md +9 -0
- rocket_engine/adapters/digital_flow/__init__.py +1121 -0
- rocket_engine/adapters/digital_flow/results.py +227 -0
- rocket_engine/adapters/digital_flow/state.py +462 -0
- rocket_engine/adapters/drc/README.klayout.md +7 -0
- rocket_engine/adapters/drc/README.md +7 -0
- rocket_engine/adapters/drc/__init__.py +1 -0
- rocket_engine/adapters/drc/klayout.py +952 -0
- rocket_engine/adapters/drc/magic.py +287 -0
- rocket_engine/adapters/drc/results.py +184 -0
- rocket_engine/adapters/drc/results_kl.py +188 -0
- rocket_engine/adapters/elmer/__init__.py +163 -0
- rocket_engine/adapters/envelope.py +120 -0
- rocket_engine/adapters/equivalence/__init__.py +156 -0
- rocket_engine/adapters/errors.py +181 -0
- rocket_engine/adapters/extract_magic/README.md +15 -0
- rocket_engine/adapters/extract_magic/__init__.py +425 -0
- rocket_engine/adapters/extract_magic/results.py +209 -0
- rocket_engine/adapters/formal_sby/README.md +7 -0
- rocket_engine/adapters/formal_sby/__init__.py +773 -0
- rocket_engine/adapters/formal_sby/formal_edge.py +195 -0
- rocket_engine/adapters/formal_sby/results.py +185 -0
- rocket_engine/adapters/gdsfactory/__init__.py +194 -0
- rocket_engine/adapters/gdsfactory/_driver.py +459 -0
- rocket_engine/adapters/gdsfactory/adapter.py +609 -0
- rocket_engine/adapters/gdsfactory/results.py +181 -0
- rocket_engine/adapters/hdl_sim/README.md +7 -0
- rocket_engine/adapters/hdl_sim/__init__.py +369 -0
- rocket_engine/adapters/hdl_sim/ghdl.py +155 -0
- rocket_engine/adapters/hdl_sim/results.py +178 -0
- rocket_engine/adapters/hdl_sim/verilator.py +167 -0
- rocket_engine/adapters/http/README.md +15 -0
- rocket_engine/adapters/http/__init__.py +458 -0
- rocket_engine/adapters/http/events.py +63 -0
- rocket_engine/adapters/ipc/README.md +9 -0
- rocket_engine/adapters/ipc/__init__.py +111 -0
- rocket_engine/adapters/lvs_netgen/README.md +20 -0
- rocket_engine/adapters/lvs_netgen/__init__.py +889 -0
- rocket_engine/adapters/lvs_netgen/results.py +168 -0
- rocket_engine/adapters/mcp/README.md +19 -0
- rocket_engine/adapters/mcp/__init__.py +85 -0
- rocket_engine/adapters/mcp/api-schema.json +6107 -0
- rocket_engine/adapters/mcp/pull.py +246 -0
- rocket_engine/adapters/mcp/server.py +794 -0
- rocket_engine/adapters/meep/__init__.py +167 -0
- rocket_engine/adapters/meow/README.md +7 -0
- rocket_engine/adapters/meow/__init__.py +190 -0
- rocket_engine/adapters/meow/_driver.py +227 -0
- rocket_engine/adapters/meow/_envelope.py +108 -0
- rocket_engine/adapters/meow/adapter.py +491 -0
- rocket_engine/adapters/meow/results.py +218 -0
- rocket_engine/adapters/mutation/__init__.py +192 -0
- rocket_engine/adapters/ngspice/README.md +28 -0
- rocket_engine/adapters/ngspice/__init__.py +627 -0
- rocket_engine/adapters/ngspice/results.py +215 -0
- rocket_engine/adapters/postlayout/README.md +28 -0
- rocket_engine/adapters/postlayout/__init__.py +17 -0
- rocket_engine/adapters/postlayout/adapter.py +1062 -0
- rocket_engine/adapters/postlayout/results.py +330 -0
- rocket_engine/adapters/postlayout/translate.py +71 -0
- rocket_engine/adapters/profile.py +220 -0
- rocket_engine/adapters/sax/README.md +23 -0
- rocket_engine/adapters/sax/__init__.py +516 -0
- rocket_engine/adapters/sax/_driver.py +148 -0
- rocket_engine/adapters/sax/results.py +291 -0
- rocket_engine/adapters/stdio_console.py +1151 -0
- rocket_engine/adapters/syntax/README.md +5 -0
- rocket_engine/adapters/syntax/__init__.py +366 -0
- rocket_engine/adapters/syntax/results.py +181 -0
- rocket_engine/adapters/synth_yosys/README.md +7 -0
- rocket_engine/adapters/synth_yosys/__init__.py +655 -0
- rocket_engine/adapters/synth_yosys/amaranth.py +120 -0
- rocket_engine/adapters/synth_yosys/ghdl.py +128 -0
- rocket_engine/adapters/synth_yosys/results.py +173 -0
- rocket_engine/adapters/timing_sta/README.md +9 -0
- rocket_engine/adapters/timing_sta/__init__.py +726 -0
- rocket_engine/adapters/view_sync/__init__.py +185 -0
- rocket_engine/adapters/view_sync/results.py +189 -0
- rocket_engine/adapters/vlsir/__init__.py +142 -0
- rocket_engine/adapters/xschem/__init__.py +440 -0
- rocket_engine/adapters/xschem/deck.py +266 -0
- rocket_engine/clients/__init__.py +5 -0
- rocket_engine/clients/runtime.py +255 -0
- rocket_engine/composition.py +2279 -0
- rocket_engine/execution/README.md +30 -0
- rocket_engine/execution/__init__.py +150 -0
- rocket_engine/execution/apig/__init__.py +1 -0
- rocket_engine/execution/apig/pool.py +463 -0
- rocket_engine/execution/apig/submit.py +124 -0
- rocket_engine/execution/block_runs_scope.py +479 -0
- rocket_engine/execution/coalesce.py +414 -0
- rocket_engine/execution/console/__init__.py +1 -0
- rocket_engine/execution/console/apply_session.py +318 -0
- rocket_engine/execution/console/lifecycle.py +265 -0
- rocket_engine/execution/dispatch.py +205 -0
- rocket_engine/execution/errors.py +31 -0
- rocket_engine/execution/ports.py +797 -0
- rocket_engine/execution/pyexec/__init__.py +1 -0
- rocket_engine/execution/pyexec/apply_context.py +547 -0
- rocket_engine/execution/pyexec/approvals.py +173 -0
- rocket_engine/execution/pyexec/runner.py +404 -0
- rocket_engine/execution/pyexec/support.py +464 -0
- rocket_engine/execution/python_exec.py +897 -0
- rocket_engine/execution/queue.py +85 -0
- rocket_engine/execution/replay_safe.py +601 -0
- rocket_engine/execution/routing/__init__.py +1 -0
- rocket_engine/execution/routing/calls.py +364 -0
- rocket_engine/execution/routing/namespace.py +785 -0
- rocket_engine/execution/routing/ports.py +102 -0
- rocket_engine/execution/routing/tools.py +346 -0
- rocket_engine/execution/runner.py +311 -0
- rocket_engine/execution/runtime.py +1149 -0
- rocket_engine/execution/service.py +279 -0
- rocket_engine/execution/worker.py +464 -0
- rocket_engine/interfaces/README.md +26 -0
- rocket_engine/interfaces/__init__.py +58 -0
- rocket_engine/interfaces/apig_assets.py +407 -0
- rocket_engine/interfaces/celllink.py +461 -0
- rocket_engine/interfaces/documents.py +92 -0
- rocket_engine/interfaces/envelopes.py +131 -0
- rocket_engine/interfaces/errors.py +16 -0
- rocket_engine/interfaces/layout.py +410 -0
- rocket_engine/interfaces/layout_schema.yaml +278 -0
- rocket_engine/interfaces/paths.py +223 -0
- rocket_engine/interfaces/pdks.py +550 -0
- rocket_engine/interfaces/ports.py +72 -0
- rocket_engine/interfaces/references.py +115 -0
- rocket_engine/interfaces/refs.py +85 -0
- rocket_engine/interfaces/runparams.py +304 -0
- rocket_engine/interfaces/setup.py +552 -0
- rocket_engine/interfaces/setup_catalogue.yaml +243 -0
- rocket_engine/interfaces/setup_probes.py +66 -0
- rocket_engine/interfaces/vocabulary.py +58 -0
- rocket_engine/interfaces/yaml_dump.py +31 -0
- rocket_engine/library/README.md +22 -0
- rocket_engine/library/__init__.py +13 -0
- rocket_engine/library/apig_catalog.py +371 -0
- rocket_engine/library/catalog.py +185 -0
- rocket_engine/library/errors.py +16 -0
- rocket_engine/mutate/__init__.py +776 -0
- rocket_engine/mutate/operators.py +700 -0
- rocket_engine/ops/__init__.py +1 -0
- rocket_engine/ops/deferred.py +279 -0
- rocket_engine/ops/editors.py +404 -0
- rocket_engine/ops/evidence.py +194 -0
- rocket_engine/ops/gates.py +262 -0
- rocket_engine/ops/ports.py +76 -0
- rocket_engine/ops/queries.py +417 -0
- rocket_engine/ops/runs.py +553 -0
- rocket_engine/ops/submit.py +614 -0
- rocket_engine/project/README.md +27 -0
- rocket_engine/project/__init__.py +6 -0
- rocket_engine/project/canvas/__init__.py +55 -0
- rocket_engine/project/canvas/compile.py +109 -0
- rocket_engine/project/canvas/integrate.py +48 -0
- rocket_engine/project/canvas/schema.py +241 -0
- rocket_engine/project/canvas/templates.py +46 -0
- rocket_engine/project/canvas/validate.py +603 -0
- rocket_engine/project/edit/__init__.py +5 -0
- rocket_engine/project/edit/cas.py +87 -0
- rocket_engine/project/edit/transactions.py +282 -0
- rocket_engine/project/errors.py +16 -0
- rocket_engine/project/frontend13.py +685 -0
- rocket_engine/project/geometry/__init__.py +23 -0
- rocket_engine/project/geometry/ops.py +451 -0
- rocket_engine/project/geometry/snap.py +103 -0
- rocket_engine/project/ip/__init__.py +1 -0
- rocket_engine/project/ip/blocks.py +195 -0
- rocket_engine/project/ip/entry.schema.json +78 -0
- rocket_engine/project/ip/lifecycle.py +402 -0
- rocket_engine/project/ip/verbs.py +123 -0
- rocket_engine/project/layers/__init__.py +6 -0
- rocket_engine/project/layers/bindings.py +115 -0
- rocket_engine/project/layers/catalog.py +228 -0
- rocket_engine/project/layers/presets.py +43 -0
- rocket_engine/project/ledger/__init__.py +1 -0
- rocket_engine/project/ledger/docs.py +167 -0
- rocket_engine/project/ledger/entities.py +176 -0
- rocket_engine/project/ledger/events.py +29 -0
- rocket_engine/project/ledger/runs.py +257 -0
- rocket_engine/project/ledger/shared.py +174 -0
- rocket_engine/project/ledger/snapshots.py +385 -0
- rocket_engine/project/ledger/wires.py +552 -0
- rocket_engine/project/process/__init__.py +14 -0
- rocket_engine/project/process/model.py +193 -0
- rocket_engine/project/process/recipes.py +121 -0
- rocket_engine/project/schematic/__init__.py +1 -0
- rocket_engine/project/schematic/hierarchy.py +312 -0
- rocket_engine/project/schematic/instances.py +393 -0
- rocket_engine/project/schematic/sheets.py +198 -0
- rocket_engine/project/service.py +2941 -0
- rocket_engine/project/symbols/__init__.py +1 -0
- rocket_engine/project/symbols/cells.py +324 -0
- rocket_engine/project/symbols/linkage.py +356 -0
- rocket_engine/project/symbols/symbols.py +352 -0
- rocket_engine/project/templates/__init__.py +303 -0
- rocket_engine/project/templates/errors.py +25 -0
- rocket_engine/project/testbench/__init__.py +49 -0
- rocket_engine/project/testbench/blocks.py +619 -0
- rocket_engine/project/testbench/templates.py +134 -0
- rocket_engine/stores/README.md +12 -0
- rocket_engine/stores/__init__.py +6 -0
- rocket_engine/stores/ecs.py +1508 -0
- rocket_engine/stores/layers.py +1023 -0
- rocket_engine/stores/settings.py +1188 -0
- rocket_engine/stores/wireplace.py +1324 -0
- rocket_engine/workflows/README.md +18 -0
- rocket_engine/workflows/__init__.py +53 -0
- rocket_engine/workflows/capability_plan.py +102 -0
- rocket_engine/workflows/completion.py +228 -0
- rocket_engine/workflows/connectgraph.py +734 -0
- rocket_engine/workflows/connectgraph_emit.py +336 -0
- rocket_engine/workflows/gates.py +144 -0
- rocket_engine/workflows/photonic_wire.py +76 -0
- rocket_engine/workflows/stages.py +324 -0
- rocket_engine/workflows/viewsync.py +399 -0
- rocket_engine-0.0.2b1.dist-info/METADATA +200 -0
- rocket_engine-0.0.2b1.dist-info/RECORD +241 -0
- rocket_engine-0.0.2b1.dist-info/WHEEL +4 -0
- rocket_engine-0.0.2b1.dist-info/entry_points.txt +2 -0
- rocket_engine-0.0.2b1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
"""Gdsfactory layout adapter: generate_views behind the ToolAdapter port.
|
|
2
|
+
|
|
3
|
+
Protocol, mirroring the SAX adapter: stage the views driver plus a job
|
|
4
|
+
JSON into the workdir, run `python gf_views_driver.py
|
|
5
|
+
gf_views_job.json` under a timeout, then parse the JSON envelope on
|
|
6
|
+
stdout. The verdict always comes from parsed output: an unknown cell,
|
|
7
|
+
an unreadable GDS, or an unsupported export format synthesizes an
|
|
8
|
+
error violation with file/line, and only a gdsfactory-less (or
|
|
9
|
+
PDK-less) interpreter raises missing_dependency.
|
|
10
|
+
|
|
11
|
+
Generalization law: project inputs in, identity mappings plus
|
|
12
|
+
diagnostics out. View specs (PDK, cells, params, export filenames),
|
|
13
|
+
project GDS files, and the macro pin map all ride the job — the MZI
|
|
14
|
+
smoke (`run_layout_smoke`) is the qualification fixture, and this op
|
|
15
|
+
generalizes it to any spec. GDS roundtrip readback is a diagnostic on
|
|
16
|
+
the export recipe, never the verdict. Macro pin/obstruction mappings
|
|
17
|
+
publish as workdir artifacts with provenance: a bound project pin_map
|
|
18
|
+
reconstructs them, otherwise the artifact carries the PRD boundary
|
|
19
|
+
note (import alone does not reconstruct them).
|
|
20
|
+
|
|
21
|
+
Tool resolution: an explicit job `tool_dir` containing a python
|
|
22
|
+
wins, else the engine interpreter (gdsfactory arrives via the pip
|
|
23
|
+
`layout` group). Where the tools live on disk is B10 scope.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import json
|
|
27
|
+
import subprocess
|
|
28
|
+
import sys
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
from ruamel.yaml import YAML
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
from ...execution.errors import ExecutionError
|
|
35
|
+
except ImportError:
|
|
36
|
+
from rocket_engine.execution.errors import ExecutionError
|
|
37
|
+
try:
|
|
38
|
+
from ...execution.runner import tool_roots
|
|
39
|
+
except ImportError:
|
|
40
|
+
from rocket_engine.execution.runner import tool_roots
|
|
41
|
+
try:
|
|
42
|
+
from ...interfaces import ToolAdapter
|
|
43
|
+
except ImportError:
|
|
44
|
+
from rocket_engine.interfaces import ToolAdapter
|
|
45
|
+
try:
|
|
46
|
+
from ..envelope import extract_envelope
|
|
47
|
+
except ImportError:
|
|
48
|
+
from rocket_engine.adapters.envelope import extract_envelope
|
|
49
|
+
try:
|
|
50
|
+
from ..errors import AdapterError
|
|
51
|
+
except ImportError:
|
|
52
|
+
from rocket_engine.adapters.errors import AdapterError
|
|
53
|
+
|
|
54
|
+
OPERATION = "generate_views"
|
|
55
|
+
RECIPES = {
|
|
56
|
+
"import_gds": "Import project GDS files; map cells, publish pins.",
|
|
57
|
+
"build_cells": "Build PDK cells from a project view spec.",
|
|
58
|
+
"export_gds": "Build cells and stream GDS layouts to the workdir.",
|
|
59
|
+
"export_oasis": "Build cells and stream OASIS layouts to the workdir.",
|
|
60
|
+
}
|
|
61
|
+
_PDKS = ("generic", "cspdk.si340")
|
|
62
|
+
_EXPORT_EXT = {"export_gds": ".gds", "export_oasis": ".oas"}
|
|
63
|
+
_RULES = {
|
|
64
|
+
"invalid_cell": "views.unknown_cell",
|
|
65
|
+
"unreadable_source": "views.unreadable_source",
|
|
66
|
+
"unsupported_format": "views.unsupported_format",
|
|
67
|
+
}
|
|
68
|
+
_TIMEOUT_S = 120
|
|
69
|
+
_DRIVER_NAME = "gf_views_driver.py"
|
|
70
|
+
_JOB_NAME = "gf_views_job.json"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _load_yaml(path, what):
|
|
74
|
+
try:
|
|
75
|
+
text = Path(path).read_text(encoding="utf-8")
|
|
76
|
+
except OSError as exc:
|
|
77
|
+
raise AdapterError(
|
|
78
|
+
"invalid_reference", f"{what} unreadable: {path} ({exc})."
|
|
79
|
+
) from exc
|
|
80
|
+
try:
|
|
81
|
+
doc = YAML(typ="safe").load(text)
|
|
82
|
+
except Exception as exc:
|
|
83
|
+
mark = getattr(exc, "problem_mark", None) or getattr(
|
|
84
|
+
exc, "context_mark", None
|
|
85
|
+
)
|
|
86
|
+
where = (
|
|
87
|
+
f"{path}:{mark.line + 1}:{mark.column + 1}"
|
|
88
|
+
if mark is not None
|
|
89
|
+
else path
|
|
90
|
+
)
|
|
91
|
+
raise AdapterError(
|
|
92
|
+
"invalid_reference", f"{what} is not YAML: {where} ({exc})."
|
|
93
|
+
) from exc
|
|
94
|
+
return text, doc
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _check_view_doc(doc, spec):
|
|
98
|
+
if not isinstance(doc, dict):
|
|
99
|
+
raise AdapterError(
|
|
100
|
+
"invalid_command", f"View spec is not a mapping: {spec}."
|
|
101
|
+
)
|
|
102
|
+
pdk = doc.get("pdk", "generic")
|
|
103
|
+
if pdk not in _PDKS:
|
|
104
|
+
raise AdapterError(
|
|
105
|
+
"invalid_command",
|
|
106
|
+
f"View spec pdk must be one of {', '.join(_PDKS)}, "
|
|
107
|
+
f"got {pdk!r}: {spec}.",
|
|
108
|
+
)
|
|
109
|
+
cells = doc.get("cells")
|
|
110
|
+
if not isinstance(cells, list) or not cells:
|
|
111
|
+
raise AdapterError(
|
|
112
|
+
"invalid_command", f"View spec needs a cells list: {spec}."
|
|
113
|
+
)
|
|
114
|
+
for entry in cells:
|
|
115
|
+
if not isinstance(entry, dict) or not entry.get("cell"):
|
|
116
|
+
raise AdapterError(
|
|
117
|
+
"invalid_command",
|
|
118
|
+
f"View spec cell entries need a cell name: {spec}.",
|
|
119
|
+
)
|
|
120
|
+
params = entry.get("params", {})
|
|
121
|
+
if not isinstance(params, dict):
|
|
122
|
+
raise AdapterError(
|
|
123
|
+
"invalid_command",
|
|
124
|
+
f"Cell {entry.get('cell')!r} params must be a "
|
|
125
|
+
f"mapping: {spec}.",
|
|
126
|
+
)
|
|
127
|
+
exports = doc.get("exports", [])
|
|
128
|
+
if not isinstance(exports, list):
|
|
129
|
+
raise AdapterError(
|
|
130
|
+
"invalid_command", f"View spec exports must be a list: {spec}."
|
|
131
|
+
)
|
|
132
|
+
for export in exports:
|
|
133
|
+
if not isinstance(export, dict) or not export.get("filename"):
|
|
134
|
+
raise AdapterError(
|
|
135
|
+
"invalid_command",
|
|
136
|
+
f"View spec exports need a filename: {spec}.",
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _check_pin_map(doc, path):
|
|
141
|
+
if not isinstance(doc, dict):
|
|
142
|
+
raise AdapterError(
|
|
143
|
+
"invalid_command", f"Pin map is not a mapping: {path}."
|
|
144
|
+
)
|
|
145
|
+
cells = doc.get("cells")
|
|
146
|
+
if not isinstance(cells, dict) or not cells:
|
|
147
|
+
raise AdapterError(
|
|
148
|
+
"invalid_command", f"Pin map needs a cells mapping: {path}."
|
|
149
|
+
)
|
|
150
|
+
for cell, binding in cells.items():
|
|
151
|
+
if not isinstance(binding, dict):
|
|
152
|
+
raise AdapterError(
|
|
153
|
+
"invalid_command",
|
|
154
|
+
f"Pin map cell {cell!r} must be a mapping: {path}.",
|
|
155
|
+
)
|
|
156
|
+
pins = binding.get("pins", {})
|
|
157
|
+
if not isinstance(pins, dict):
|
|
158
|
+
raise AdapterError(
|
|
159
|
+
"invalid_command",
|
|
160
|
+
f"Pin map cell {cell!r} pins must be a mapping: {path}.",
|
|
161
|
+
)
|
|
162
|
+
for name, pin in pins.items():
|
|
163
|
+
if not isinstance(pin, dict):
|
|
164
|
+
raise AdapterError(
|
|
165
|
+
"invalid_command",
|
|
166
|
+
f"Pin map pin {name!r} must be a mapping: {path}.",
|
|
167
|
+
)
|
|
168
|
+
obstructions = binding.get("obstructions", [])
|
|
169
|
+
if not isinstance(obstructions, list):
|
|
170
|
+
raise AdapterError(
|
|
171
|
+
"invalid_command",
|
|
172
|
+
f"Pin map cell {cell!r} obstructions must be a "
|
|
173
|
+
f"list: {path}.",
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _check_number_list(pair, what, where):
|
|
178
|
+
if (
|
|
179
|
+
not isinstance(pair, list)
|
|
180
|
+
or len(pair) != 2
|
|
181
|
+
or not all(
|
|
182
|
+
isinstance(v, (int, float)) and not isinstance(v, bool)
|
|
183
|
+
for v in pair
|
|
184
|
+
)
|
|
185
|
+
):
|
|
186
|
+
raise AdapterError(
|
|
187
|
+
"runner_unavailable",
|
|
188
|
+
f"Views driver result has a bad {what}: {where}.",
|
|
189
|
+
)
|
|
190
|
+
return [float(pair[0]), float(pair[1])]
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class GdsfactoryViewsAdapter(ToolAdapter):
|
|
194
|
+
"""generate_views via gdsfactory: registered layout recipes.
|
|
195
|
+
|
|
196
|
+
Serves the generate_views operation with deck.tool `gdsfactory`
|
|
197
|
+
(the only tool). Stages the views driver plus a job JSON, runs
|
|
198
|
+
it under a timeout, and parses the JSON envelope: identity
|
|
199
|
+
mappings (requested source to built/imported component),
|
|
200
|
+
diagnostics (versions, PDK, roundtrip readback), and workdir
|
|
201
|
+
artifacts (layouts plus pin/obstruction mappings).
|
|
202
|
+
|
|
203
|
+
Job keys:
|
|
204
|
+
recipe (required): Registered recipe — import_gds,
|
|
205
|
+
build_cells, export_gds, or export_oasis.
|
|
206
|
+
sources (required): Project inputs — exactly one view-spec
|
|
207
|
+
YAML for build/export recipes, one or more .gds files
|
|
208
|
+
for import_gds.
|
|
209
|
+
pin_map: Project macro pin-map YAML (import_gds only);
|
|
210
|
+
binds pins/obstructions with provenance.
|
|
211
|
+
cspdk_path: Vendored cspdk tree when the package is not
|
|
212
|
+
installed.
|
|
213
|
+
timeout_s: Layout budget in seconds (default 120).
|
|
214
|
+
tool_dir: Directory holding the layout python; the engine
|
|
215
|
+
interpreter otherwise.
|
|
216
|
+
deck: Optional mapping; deck.tool must be gdsfactory here.
|
|
217
|
+
workdir: Run directory (default ".").
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
runner: Optional isolated runner; direct subprocess when None.
|
|
221
|
+
|
|
222
|
+
Caution: Unknown cells and unreadable GDS synthesize error
|
|
223
|
+
violations with file/line — the verdict always comes from
|
|
224
|
+
parsed output, never the exit code. GDS roundtrip readback
|
|
225
|
+
is a diagnostic, never the verdict.
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
def __init__(self, runner=None):
|
|
229
|
+
self._runner = runner
|
|
230
|
+
|
|
231
|
+
def capabilities(self, target):
|
|
232
|
+
"""Report the served operation, tool, and recipes.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
target: Target the capabilities are reported for
|
|
236
|
+
(accepted, unused — one spec per job).
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
Capability mapping with operations, tool, and recipes.
|
|
240
|
+
"""
|
|
241
|
+
return {
|
|
242
|
+
"operations": [OPERATION],
|
|
243
|
+
"tool": "gdsfactory",
|
|
244
|
+
"recipes": sorted(RECIPES),
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
def preflight(self, job):
|
|
248
|
+
"""Check operation, recipe, sources, spec shape, and python.
|
|
249
|
+
|
|
250
|
+
Cheap deterministic checks first, tool resolution last, the
|
|
251
|
+
runner probe closing it out.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
job: Solver job with operation generate_views plus the
|
|
255
|
+
recipe, project sources, and timeout inputs.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Checked mapping with the resolved python, recipe, spec
|
|
259
|
+
(spec/text/doc for build/export, gds_sources plus
|
|
260
|
+
pin map for import), cspdk path, and timeout_s.
|
|
261
|
+
|
|
262
|
+
Raises:
|
|
263
|
+
AdapterError: unsupported_operation for another
|
|
264
|
+
operation, invalid_command for a bad recipe,
|
|
265
|
+
timeout, spec shape, pin map, or export filename,
|
|
266
|
+
invalid_reference unless the recipe's project
|
|
267
|
+
sources exist and parse, missing_dependency for an
|
|
268
|
+
unresolvable layout python.
|
|
269
|
+
"""
|
|
270
|
+
if job.get("operation") != OPERATION:
|
|
271
|
+
raise AdapterError(
|
|
272
|
+
"unsupported_operation",
|
|
273
|
+
f"Views handles {OPERATION}, got {job.get('operation')}.",
|
|
274
|
+
)
|
|
275
|
+
recipe = job.get("recipe")
|
|
276
|
+
if recipe not in RECIPES:
|
|
277
|
+
raise AdapterError(
|
|
278
|
+
"invalid_command",
|
|
279
|
+
f"Unknown recipe {recipe!r}; want one of "
|
|
280
|
+
f"{', '.join(sorted(RECIPES))}.",
|
|
281
|
+
)
|
|
282
|
+
timeout = job.get("timeout_s", _TIMEOUT_S)
|
|
283
|
+
if (
|
|
284
|
+
not isinstance(timeout, (int, float))
|
|
285
|
+
or isinstance(timeout, bool)
|
|
286
|
+
or not timeout > 0
|
|
287
|
+
):
|
|
288
|
+
raise AdapterError("invalid_command", f"Bad timeout_s: {timeout!r}.")
|
|
289
|
+
cspdk_path = job.get("cspdk_path")
|
|
290
|
+
if cspdk_path is not None and not Path(cspdk_path).is_dir():
|
|
291
|
+
raise AdapterError(
|
|
292
|
+
"invalid_reference", f"cspdk_path not found: {cspdk_path}."
|
|
293
|
+
)
|
|
294
|
+
pin_map = job.get("pin_map")
|
|
295
|
+
checked = {
|
|
296
|
+
"python": self._resolve(job),
|
|
297
|
+
"recipe": recipe,
|
|
298
|
+
"cspdk_path": cspdk_path,
|
|
299
|
+
"timeout_s": timeout,
|
|
300
|
+
}
|
|
301
|
+
sources = job.get("sources") or []
|
|
302
|
+
if recipe == "import_gds":
|
|
303
|
+
missing = [s for s in sources if not Path(s).is_file()]
|
|
304
|
+
if not sources or missing:
|
|
305
|
+
raise AdapterError(
|
|
306
|
+
"invalid_reference",
|
|
307
|
+
"import_gds needs project GDS files, got: "
|
|
308
|
+
f"{missing or sources}.",
|
|
309
|
+
)
|
|
310
|
+
for source in sources:
|
|
311
|
+
if Path(source).suffix.lower() != ".gds":
|
|
312
|
+
raise AdapterError(
|
|
313
|
+
"invalid_command",
|
|
314
|
+
f"import_gds needs .gds sources: {source}.",
|
|
315
|
+
)
|
|
316
|
+
checked["gds_sources"] = list(sources)
|
|
317
|
+
checked["spec"] = ""
|
|
318
|
+
checked["text"] = ""
|
|
319
|
+
checked["doc"] = None
|
|
320
|
+
if pin_map is None:
|
|
321
|
+
checked["pin_map_path"] = None
|
|
322
|
+
checked["pin_map_doc"] = None
|
|
323
|
+
else:
|
|
324
|
+
if not Path(pin_map).is_file():
|
|
325
|
+
raise AdapterError(
|
|
326
|
+
"invalid_reference",
|
|
327
|
+
f"Pin map not found: {pin_map}.",
|
|
328
|
+
)
|
|
329
|
+
pin_text, pin_doc = _load_yaml(pin_map, "Pin map")
|
|
330
|
+
_check_pin_map(pin_doc, pin_map)
|
|
331
|
+
checked["pin_map_path"] = pin_map
|
|
332
|
+
checked["pin_map_doc"] = pin_doc
|
|
333
|
+
checked["pin_map_text"] = pin_text
|
|
334
|
+
else:
|
|
335
|
+
if pin_map is not None:
|
|
336
|
+
raise AdapterError(
|
|
337
|
+
"invalid_command",
|
|
338
|
+
f"pin_map applies to import_gds, not {recipe}.",
|
|
339
|
+
)
|
|
340
|
+
missing = [s for s in sources if not Path(s).is_file()]
|
|
341
|
+
if len(sources) != 1 or missing:
|
|
342
|
+
raise AdapterError(
|
|
343
|
+
"invalid_reference",
|
|
344
|
+
"generate_views needs one view spec, got: "
|
|
345
|
+
f"{missing or sources}.",
|
|
346
|
+
)
|
|
347
|
+
spec = sources[0]
|
|
348
|
+
text, doc = _load_yaml(spec, "View spec")
|
|
349
|
+
_check_view_doc(doc, spec)
|
|
350
|
+
if recipe in _EXPORT_EXT:
|
|
351
|
+
want = _EXPORT_EXT[recipe]
|
|
352
|
+
for export in doc.get("exports") or []:
|
|
353
|
+
if not str(export["filename"]).endswith(want):
|
|
354
|
+
raise AdapterError(
|
|
355
|
+
"invalid_command",
|
|
356
|
+
f"{recipe} needs {want} filenames: "
|
|
357
|
+
f"{export['filename']}.",
|
|
358
|
+
)
|
|
359
|
+
checked["spec"] = spec
|
|
360
|
+
checked["text"] = text
|
|
361
|
+
checked["doc"] = doc
|
|
362
|
+
checked["gds_sources"] = []
|
|
363
|
+
checked["pin_map_path"] = None
|
|
364
|
+
checked["pin_map_doc"] = None
|
|
365
|
+
if self._runner is not None:
|
|
366
|
+
try:
|
|
367
|
+
self._runner.preflight()
|
|
368
|
+
except ExecutionError as exc:
|
|
369
|
+
raise AdapterError(exc.code, str(exc)) from exc
|
|
370
|
+
return checked
|
|
371
|
+
|
|
372
|
+
def execute(self, job):
|
|
373
|
+
"""Stage the driver plus job JSON, then run the recipe.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
job: Solver job with operation generate_views plus the
|
|
377
|
+
recipe, project sources, and timeout inputs.
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
Native output with exit_code, stdout/stderr, recipe,
|
|
381
|
+
spec, spec_text, and sources.
|
|
382
|
+
|
|
383
|
+
Raises:
|
|
384
|
+
AdapterError: invalid_reference for a missing workdir,
|
|
385
|
+
runner_unavailable on a recipe timeout, plus the
|
|
386
|
+
preflight codes (execute re-checks first).
|
|
387
|
+
"""
|
|
388
|
+
checked = self.preflight(job)
|
|
389
|
+
workdir = job.get("workdir", ".")
|
|
390
|
+
if not Path(workdir).is_dir():
|
|
391
|
+
raise AdapterError(
|
|
392
|
+
"invalid_reference", f"Workdir not found: {workdir}."
|
|
393
|
+
)
|
|
394
|
+
workdir_path = Path(workdir)
|
|
395
|
+
driver = Path(__file__).with_name("_driver.py").read_text(
|
|
396
|
+
encoding="utf-8"
|
|
397
|
+
)
|
|
398
|
+
(workdir_path / _DRIVER_NAME).write_text(driver, encoding="utf-8")
|
|
399
|
+
(workdir_path / _JOB_NAME).write_text(
|
|
400
|
+
json.dumps(
|
|
401
|
+
{
|
|
402
|
+
"recipe": checked["recipe"],
|
|
403
|
+
"spec": checked["spec"],
|
|
404
|
+
"text": checked["text"],
|
|
405
|
+
"doc": checked["doc"],
|
|
406
|
+
"gds_sources": checked["gds_sources"],
|
|
407
|
+
"pin_map_path": checked["pin_map_path"],
|
|
408
|
+
"pin_map_doc": checked["pin_map_doc"],
|
|
409
|
+
"cspdk_path": checked["cspdk_path"],
|
|
410
|
+
}
|
|
411
|
+
),
|
|
412
|
+
encoding="utf-8",
|
|
413
|
+
)
|
|
414
|
+
roots = tool_roots([checked["python"]])
|
|
415
|
+
proc = self._run(
|
|
416
|
+
[checked["python"], _DRIVER_NAME, _JOB_NAME],
|
|
417
|
+
workdir,
|
|
418
|
+
checked["timeout_s"],
|
|
419
|
+
roots,
|
|
420
|
+
)
|
|
421
|
+
return {
|
|
422
|
+
"exit_code": proc.returncode,
|
|
423
|
+
"stdout": proc.stdout,
|
|
424
|
+
"stderr": proc.stderr,
|
|
425
|
+
"recipe": checked["recipe"],
|
|
426
|
+
"spec": checked["spec"],
|
|
427
|
+
"spec_text": checked["text"],
|
|
428
|
+
"sources": list(job.get("sources") or []),
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
def parse(self, native_output):
|
|
432
|
+
"""Parse the driver envelope into mappings and diagnostics.
|
|
433
|
+
|
|
434
|
+
Unknown cells, unreadable GDS, and unsupported formats
|
|
435
|
+
synthesize one error violation with file/line rather than
|
|
436
|
+
parsing as clean; roundtrip readback stays a diagnostic.
|
|
437
|
+
|
|
438
|
+
Args:
|
|
439
|
+
native_output: Native output with the driver stdout
|
|
440
|
+
envelope, recipe, spec, spec_text, and sources.
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
{total, violations[], measurements[], mappings[],
|
|
444
|
+
diagnostics{}, artifacts[],
|
|
445
|
+
gdsfactory_version} with identity mappings, per-cell
|
|
446
|
+
polygon/port/area measurements, and workdir
|
|
447
|
+
artifact paths.
|
|
448
|
+
|
|
449
|
+
Raises:
|
|
450
|
+
AdapterError: missing_dependency when the layout python
|
|
451
|
+
has no gdsfactory or PDK, runner_unavailable when
|
|
452
|
+
the driver printed no JSON envelope or no result.
|
|
453
|
+
"""
|
|
454
|
+
spec = native_output.get("spec") or ""
|
|
455
|
+
if not spec:
|
|
456
|
+
sources = native_output.get("sources") or []
|
|
457
|
+
spec = sources[0] if sources else ""
|
|
458
|
+
envelope = extract_envelope(
|
|
459
|
+
native_output.get("stdout", ""), tool="views"
|
|
460
|
+
)
|
|
461
|
+
if not envelope["ok"]:
|
|
462
|
+
error = envelope.get("error") or {}
|
|
463
|
+
code = error.get("code")
|
|
464
|
+
message = error.get("message", "views run failed")
|
|
465
|
+
if code == "missing_dependency":
|
|
466
|
+
raise AdapterError("missing_dependency", message)
|
|
467
|
+
violation = {
|
|
468
|
+
"rule": _RULES.get(code, "views.failed"),
|
|
469
|
+
"severity": "error",
|
|
470
|
+
"message": message,
|
|
471
|
+
"file": spec,
|
|
472
|
+
}
|
|
473
|
+
if error.get("line") is not None:
|
|
474
|
+
violation["line"] = error["line"]
|
|
475
|
+
return {
|
|
476
|
+
"total": 1,
|
|
477
|
+
"violations": [violation],
|
|
478
|
+
"measurements": [],
|
|
479
|
+
"mappings": [],
|
|
480
|
+
"diagnostics": {
|
|
481
|
+
"recipe": native_output.get("recipe", "")
|
|
482
|
+
},
|
|
483
|
+
"artifacts": [],
|
|
484
|
+
"gdsfactory_version": envelope.get("gdsfactory_version"),
|
|
485
|
+
}
|
|
486
|
+
result = envelope.get("result") or {}
|
|
487
|
+
mappings = result.get("mappings")
|
|
488
|
+
artifacts = result.get("artifacts")
|
|
489
|
+
diagnostics = result.get("diagnostics")
|
|
490
|
+
if (
|
|
491
|
+
not isinstance(mappings, list)
|
|
492
|
+
or not mappings
|
|
493
|
+
or not isinstance(artifacts, list)
|
|
494
|
+
or not isinstance(diagnostics, dict)
|
|
495
|
+
):
|
|
496
|
+
raise AdapterError(
|
|
497
|
+
"runner_unavailable", "views driver envelope has no result."
|
|
498
|
+
)
|
|
499
|
+
measurements = []
|
|
500
|
+
for mapping in mappings:
|
|
501
|
+
if not isinstance(mapping, dict):
|
|
502
|
+
raise AdapterError(
|
|
503
|
+
"runner_unavailable",
|
|
504
|
+
"views driver envelope has no result.",
|
|
505
|
+
)
|
|
506
|
+
try:
|
|
507
|
+
cell = mapping["component"]
|
|
508
|
+
ports = mapping["ports"]
|
|
509
|
+
polygons = mapping["n_polygons"]
|
|
510
|
+
bbox = mapping["bbox_um"]
|
|
511
|
+
except KeyError:
|
|
512
|
+
raise AdapterError(
|
|
513
|
+
"runner_unavailable",
|
|
514
|
+
"views driver envelope has no result.",
|
|
515
|
+
) from None
|
|
516
|
+
if (
|
|
517
|
+
not isinstance(cell, str)
|
|
518
|
+
or not isinstance(ports, list)
|
|
519
|
+
or not isinstance(polygons, int)
|
|
520
|
+
or isinstance(polygons, bool)
|
|
521
|
+
or not isinstance(bbox, list)
|
|
522
|
+
or len(bbox) != 2
|
|
523
|
+
):
|
|
524
|
+
raise AdapterError(
|
|
525
|
+
"runner_unavailable",
|
|
526
|
+
"views driver envelope has no result.",
|
|
527
|
+
)
|
|
528
|
+
lower = _check_number_list(bbox[0], "bbox", cell)
|
|
529
|
+
upper = _check_number_list(bbox[1], "bbox", cell)
|
|
530
|
+
area = (upper[0] - lower[0]) * (upper[1] - lower[1])
|
|
531
|
+
measurements.append(
|
|
532
|
+
{
|
|
533
|
+
"name": f"polygons({cell})",
|
|
534
|
+
"value": polygons,
|
|
535
|
+
"unit": "count",
|
|
536
|
+
}
|
|
537
|
+
)
|
|
538
|
+
measurements.append(
|
|
539
|
+
{
|
|
540
|
+
"name": f"ports({cell})",
|
|
541
|
+
"value": len(ports),
|
|
542
|
+
"unit": "count",
|
|
543
|
+
}
|
|
544
|
+
)
|
|
545
|
+
measurements.append(
|
|
546
|
+
{
|
|
547
|
+
"name": f"bbox_area_um2({cell})",
|
|
548
|
+
"value": area,
|
|
549
|
+
"unit": "um2",
|
|
550
|
+
}
|
|
551
|
+
)
|
|
552
|
+
for artifact in artifacts:
|
|
553
|
+
if (
|
|
554
|
+
not isinstance(artifact, dict)
|
|
555
|
+
or not isinstance(artifact.get("kind"), str)
|
|
556
|
+
or not isinstance(artifact.get("path"), str)
|
|
557
|
+
):
|
|
558
|
+
raise AdapterError(
|
|
559
|
+
"runner_unavailable",
|
|
560
|
+
"views driver envelope has no result.",
|
|
561
|
+
)
|
|
562
|
+
return {
|
|
563
|
+
"total": 0,
|
|
564
|
+
"violations": [],
|
|
565
|
+
"measurements": measurements,
|
|
566
|
+
"mappings": mappings,
|
|
567
|
+
"diagnostics": {
|
|
568
|
+
"recipe": result.get("recipe", ""),
|
|
569
|
+
"active_pdk": result.get("active_pdk", ""),
|
|
570
|
+
"cspdk_version": result.get("cspdk_version"),
|
|
571
|
+
"roundtrip_match": diagnostics.get("roundtrip_match"),
|
|
572
|
+
"notes": list(diagnostics.get("notes") or []),
|
|
573
|
+
},
|
|
574
|
+
"artifacts": artifacts,
|
|
575
|
+
"gdsfactory_version": envelope.get("gdsfactory_version"),
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
@staticmethod
|
|
579
|
+
def _resolve(job):
|
|
580
|
+
tool_dir = job.get("tool_dir")
|
|
581
|
+
if tool_dir:
|
|
582
|
+
for name in ("python3", "python"):
|
|
583
|
+
candidate = Path(tool_dir) / name
|
|
584
|
+
if candidate.is_file():
|
|
585
|
+
return str(candidate)
|
|
586
|
+
raise AdapterError(
|
|
587
|
+
"missing_dependency", f"python not found in {tool_dir}."
|
|
588
|
+
)
|
|
589
|
+
return sys.executable
|
|
590
|
+
|
|
591
|
+
def _run(self, argv, workdir, timeout_s, extra_binds=()):
|
|
592
|
+
if self._runner is not None:
|
|
593
|
+
try:
|
|
594
|
+
return self._runner.run(argv, workdir, timeout_s, extra_binds)
|
|
595
|
+
except ExecutionError as exc:
|
|
596
|
+
raise AdapterError(exc.code, str(exc)) from exc
|
|
597
|
+
try:
|
|
598
|
+
return subprocess.run(
|
|
599
|
+
argv,
|
|
600
|
+
capture_output=True,
|
|
601
|
+
text=True,
|
|
602
|
+
timeout=timeout_s,
|
|
603
|
+
cwd=workdir,
|
|
604
|
+
)
|
|
605
|
+
except subprocess.TimeoutExpired as exc:
|
|
606
|
+
raise AdapterError(
|
|
607
|
+
"runner_unavailable",
|
|
608
|
+
f"views recipe timed out after {timeout_s}s.",
|
|
609
|
+
) from exc
|