siliconcompiler 0.34.1__py3-none-any.whl → 0.34.3__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.
- siliconcompiler/__init__.py +23 -4
- siliconcompiler/__main__.py +1 -7
- siliconcompiler/_metadata.py +1 -1
- siliconcompiler/apps/_common.py +104 -23
- siliconcompiler/apps/sc.py +4 -8
- siliconcompiler/apps/sc_dashboard.py +6 -4
- siliconcompiler/apps/sc_install.py +10 -6
- siliconcompiler/apps/sc_issue.py +7 -5
- siliconcompiler/apps/sc_remote.py +1 -1
- siliconcompiler/apps/sc_server.py +9 -14
- siliconcompiler/apps/sc_show.py +7 -6
- siliconcompiler/apps/smake.py +130 -94
- siliconcompiler/apps/utils/replay.py +4 -7
- siliconcompiler/apps/utils/summarize.py +3 -5
- siliconcompiler/asic.py +420 -0
- siliconcompiler/checklist.py +25 -2
- siliconcompiler/cmdlineschema.py +534 -0
- siliconcompiler/constraints/__init__.py +17 -0
- siliconcompiler/constraints/asic_component.py +378 -0
- siliconcompiler/constraints/asic_floorplan.py +449 -0
- siliconcompiler/constraints/asic_pins.py +489 -0
- siliconcompiler/constraints/asic_timing.py +517 -0
- siliconcompiler/core.py +10 -35
- siliconcompiler/data/templates/tcl/manifest.tcl.j2 +8 -0
- siliconcompiler/dependencyschema.py +96 -202
- siliconcompiler/design.py +327 -241
- siliconcompiler/filesetschema.py +250 -0
- siliconcompiler/flowgraph.py +298 -106
- siliconcompiler/fpga.py +124 -1
- siliconcompiler/library.py +331 -0
- siliconcompiler/metric.py +327 -92
- siliconcompiler/metrics/__init__.py +7 -0
- siliconcompiler/metrics/asic.py +245 -0
- siliconcompiler/metrics/fpga.py +220 -0
- siliconcompiler/package/__init__.py +391 -67
- siliconcompiler/package/git.py +92 -16
- siliconcompiler/package/github.py +114 -22
- siliconcompiler/package/https.py +79 -16
- siliconcompiler/packageschema.py +341 -16
- siliconcompiler/pathschema.py +255 -0
- siliconcompiler/pdk.py +566 -1
- siliconcompiler/project.py +1460 -0
- siliconcompiler/record.py +38 -1
- siliconcompiler/remote/__init__.py +5 -2
- siliconcompiler/remote/client.py +11 -6
- siliconcompiler/remote/schema.py +5 -23
- siliconcompiler/remote/server.py +41 -54
- siliconcompiler/report/__init__.py +3 -3
- siliconcompiler/report/dashboard/__init__.py +48 -14
- siliconcompiler/report/dashboard/cli/__init__.py +99 -21
- siliconcompiler/report/dashboard/cli/board.py +364 -179
- siliconcompiler/report/dashboard/web/__init__.py +90 -12
- siliconcompiler/report/dashboard/web/components/__init__.py +219 -240
- siliconcompiler/report/dashboard/web/components/flowgraph.py +49 -26
- siliconcompiler/report/dashboard/web/components/graph.py +139 -100
- siliconcompiler/report/dashboard/web/layouts/__init__.py +29 -1
- siliconcompiler/report/dashboard/web/layouts/_common.py +38 -2
- siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph.py +39 -26
- siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph_node_tab.py +50 -50
- siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph_sac_tabs.py +49 -46
- siliconcompiler/report/dashboard/web/state.py +141 -14
- siliconcompiler/report/dashboard/web/utils/__init__.py +79 -16
- siliconcompiler/report/dashboard/web/utils/file_utils.py +74 -11
- siliconcompiler/report/dashboard/web/viewer.py +25 -1
- siliconcompiler/report/report.py +5 -2
- siliconcompiler/report/summary_image.py +29 -11
- siliconcompiler/scheduler/__init__.py +9 -1
- siliconcompiler/scheduler/docker.py +81 -4
- siliconcompiler/scheduler/run_node.py +37 -20
- siliconcompiler/scheduler/scheduler.py +211 -36
- siliconcompiler/scheduler/schedulernode.py +394 -60
- siliconcompiler/scheduler/send_messages.py +77 -29
- siliconcompiler/scheduler/slurm.py +76 -12
- siliconcompiler/scheduler/taskscheduler.py +142 -21
- siliconcompiler/schema/__init__.py +0 -4
- siliconcompiler/schema/baseschema.py +338 -59
- siliconcompiler/schema/editableschema.py +14 -6
- siliconcompiler/schema/journal.py +28 -17
- siliconcompiler/schema/namedschema.py +22 -14
- siliconcompiler/schema/parameter.py +89 -28
- siliconcompiler/schema/parametertype.py +2 -0
- siliconcompiler/schema/parametervalue.py +258 -15
- siliconcompiler/schema/safeschema.py +25 -2
- siliconcompiler/schema/schema_cfg.py +23 -19
- siliconcompiler/schema/utils.py +2 -2
- siliconcompiler/schema_obj.py +24 -5
- siliconcompiler/tool.py +1131 -265
- siliconcompiler/tools/bambu/__init__.py +41 -0
- siliconcompiler/tools/builtin/concatenate.py +2 -2
- siliconcompiler/tools/builtin/minimum.py +2 -1
- siliconcompiler/tools/builtin/mux.py +2 -1
- siliconcompiler/tools/builtin/nop.py +2 -1
- siliconcompiler/tools/builtin/verify.py +2 -1
- siliconcompiler/tools/klayout/__init__.py +95 -0
- siliconcompiler/tools/openroad/__init__.py +289 -0
- siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +3 -0
- siliconcompiler/tools/openroad/scripts/apr/sc_detailed_route.tcl +7 -2
- siliconcompiler/tools/openroad/scripts/apr/sc_global_route.tcl +8 -4
- siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +9 -5
- siliconcompiler/tools/openroad/scripts/common/write_images.tcl +5 -1
- siliconcompiler/tools/slang/__init__.py +1 -1
- siliconcompiler/tools/slang/elaborate.py +2 -1
- siliconcompiler/tools/vivado/scripts/sc_run.tcl +1 -1
- siliconcompiler/tools/vivado/scripts/sc_syn_fpga.tcl +8 -1
- siliconcompiler/tools/vivado/syn_fpga.py +6 -0
- siliconcompiler/tools/vivado/vivado.py +35 -2
- siliconcompiler/tools/vpr/__init__.py +150 -0
- siliconcompiler/tools/yosys/__init__.py +369 -1
- siliconcompiler/tools/yosys/scripts/procs.tcl +0 -1
- siliconcompiler/toolscripts/_tools.json +5 -10
- siliconcompiler/utils/__init__.py +66 -0
- siliconcompiler/utils/flowgraph.py +2 -2
- siliconcompiler/utils/issue.py +2 -1
- siliconcompiler/utils/logging.py +14 -0
- siliconcompiler/utils/multiprocessing.py +256 -0
- siliconcompiler/utils/showtools.py +10 -0
- {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/METADATA +6 -6
- {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/RECORD +122 -115
- {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/entry_points.txt +3 -0
- siliconcompiler/schema/cmdlineschema.py +0 -250
- siliconcompiler/schema/packageschema.py +0 -101
- siliconcompiler/toolscripts/rhel8/install-slang.sh +0 -40
- siliconcompiler/toolscripts/rhel9/install-slang.sh +0 -40
- siliconcompiler/toolscripts/ubuntu20/install-slang.sh +0 -47
- siliconcompiler/toolscripts/ubuntu22/install-slang.sh +0 -37
- siliconcompiler/toolscripts/ubuntu24/install-slang.sh +0 -37
- {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/WHEEL +0 -0
- {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/licenses/LICENSE +0 -0
- {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/top_level.txt +0 -0
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
# SC dependencies outside of its directory, since it may be used by tool drivers
|
|
5
5
|
# that have isolated Python environments.
|
|
6
6
|
|
|
7
|
+
import contextlib
|
|
7
8
|
import copy
|
|
9
|
+
import importlib
|
|
10
|
+
import logging
|
|
8
11
|
|
|
9
12
|
try:
|
|
10
13
|
import gzip
|
|
@@ -21,7 +24,10 @@ except ModuleNotFoundError:
|
|
|
21
24
|
|
|
22
25
|
import os.path
|
|
23
26
|
|
|
24
|
-
from
|
|
27
|
+
from functools import cache
|
|
28
|
+
from typing import Dict, Type, Tuple, Union, Set, Callable, List
|
|
29
|
+
|
|
30
|
+
from .parameter import Parameter, NodeValue
|
|
25
31
|
from .journal import Journal
|
|
26
32
|
|
|
27
33
|
|
|
@@ -36,8 +42,100 @@ class BaseSchema:
|
|
|
36
42
|
self.__default = None
|
|
37
43
|
self.__journal = Journal()
|
|
38
44
|
self.__parent = self
|
|
45
|
+
self.__active = None
|
|
46
|
+
self.__key = None
|
|
39
47
|
|
|
40
|
-
|
|
48
|
+
@property
|
|
49
|
+
def _keypath(self):
|
|
50
|
+
'''
|
|
51
|
+
Returns the key to the current section of the schema
|
|
52
|
+
'''
|
|
53
|
+
if self.__parent is self:
|
|
54
|
+
return tuple()
|
|
55
|
+
return tuple([*self.__parent._keypath, self.__key])
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
@cache
|
|
59
|
+
def __get_child_classes() -> Dict[str, Type["BaseSchema"]]:
|
|
60
|
+
"""
|
|
61
|
+
Returns all known subclasses of BaseSchema
|
|
62
|
+
"""
|
|
63
|
+
def recurse(cls):
|
|
64
|
+
subclss = set()
|
|
65
|
+
subclss.add(cls)
|
|
66
|
+
for subcls in cls.__subclasses__():
|
|
67
|
+
subclss.update(recurse(subcls))
|
|
68
|
+
return subclss
|
|
69
|
+
|
|
70
|
+
# Resolve true base
|
|
71
|
+
cls_mapping = {}
|
|
72
|
+
for cls in recurse(BaseSchema):
|
|
73
|
+
try:
|
|
74
|
+
cls_mapping.setdefault(cls._getdict_type(), set()).add(cls)
|
|
75
|
+
except NotImplementedError:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
# Build lookup table
|
|
79
|
+
cls_map = {}
|
|
80
|
+
for cls_type, clss in cls_mapping.items():
|
|
81
|
+
for cls in clss:
|
|
82
|
+
cls_map[f"{cls.__module__}/{cls.__name__}"] = cls
|
|
83
|
+
|
|
84
|
+
if len(clss) > 1:
|
|
85
|
+
found = False
|
|
86
|
+
for cls in clss:
|
|
87
|
+
if cls.__name__ == cls_type:
|
|
88
|
+
cls_map[cls_type] = cls
|
|
89
|
+
found = True
|
|
90
|
+
break
|
|
91
|
+
if not found:
|
|
92
|
+
raise RuntimeError(f"fatal error at: {cls_type}")
|
|
93
|
+
else:
|
|
94
|
+
cls_map[cls_type] = list(clss)[0]
|
|
95
|
+
return cls_map
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
@cache
|
|
99
|
+
def __load_schema_class(cls_name: str) -> Type["BaseSchema"]:
|
|
100
|
+
"""
|
|
101
|
+
Load a schema class from a string
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
module_name, cls_name = cls_name.split("/")
|
|
105
|
+
except (ValueError, AttributeError):
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
module = importlib.import_module(module_name)
|
|
110
|
+
except (ImportError, ModuleNotFoundError, SyntaxError):
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
cls = getattr(module, cls_name, None)
|
|
114
|
+
if not cls:
|
|
115
|
+
return None
|
|
116
|
+
if not issubclass(cls, BaseSchema):
|
|
117
|
+
raise TypeError(f"{cls_name} must be a BaseSchema type")
|
|
118
|
+
return cls
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def __process_meta_section(meta: Dict[str, str]) -> Type["BaseSchema"]:
|
|
122
|
+
"""
|
|
123
|
+
Handle __meta__ section of the schema by loading the appropriate class
|
|
124
|
+
"""
|
|
125
|
+
cls_map = BaseSchema.__get_child_classes()
|
|
126
|
+
|
|
127
|
+
# Lookup object, use class first, then type
|
|
128
|
+
cls_name = meta.get("class", None)
|
|
129
|
+
cls = None
|
|
130
|
+
if cls_name:
|
|
131
|
+
cls = cls_map.get(cls_name, None)
|
|
132
|
+
if not cls:
|
|
133
|
+
cls = BaseSchema.__load_schema_class(cls_name)
|
|
134
|
+
if not cls:
|
|
135
|
+
cls = cls_map.get(meta.get("sctype", None), None)
|
|
136
|
+
return cls
|
|
137
|
+
|
|
138
|
+
def _from_dict(self, manifest: Dict, keypath: Tuple[str], version: str = None):
|
|
41
139
|
'''
|
|
42
140
|
Decodes a dictionary into a schema object
|
|
43
141
|
|
|
@@ -52,6 +150,10 @@ class BaseSchema:
|
|
|
52
150
|
|
|
53
151
|
if "__journal__" in manifest:
|
|
54
152
|
self.__journal.from_dict(manifest["__journal__"])
|
|
153
|
+
del manifest["__journal__"]
|
|
154
|
+
|
|
155
|
+
if "__meta__" in manifest:
|
|
156
|
+
del manifest["__meta__"]
|
|
55
157
|
|
|
56
158
|
if self.__default:
|
|
57
159
|
data = manifest.get("default", None)
|
|
@@ -62,9 +164,25 @@ class BaseSchema:
|
|
|
62
164
|
|
|
63
165
|
for key, data in manifest.items():
|
|
64
166
|
obj = self.__manifest.get(key, None)
|
|
167
|
+
if not obj and isinstance(data, dict) and "__meta__" in data:
|
|
168
|
+
# Lookup object, use class first, then type
|
|
169
|
+
cls = BaseSchema.__process_meta_section(data["__meta__"])
|
|
170
|
+
if cls is BaseSchema and self.__default:
|
|
171
|
+
# Use default when BaseSchema is the class
|
|
172
|
+
obj = self.__default.copy(key=keypath + [key])
|
|
173
|
+
self.__manifest[key] = obj
|
|
174
|
+
elif cls:
|
|
175
|
+
# Create object and connect to schema
|
|
176
|
+
obj = cls()
|
|
177
|
+
obj.__parent = self
|
|
178
|
+
obj.__key = key
|
|
179
|
+
self.__manifest[key] = obj
|
|
180
|
+
|
|
181
|
+
# Use default if it is available
|
|
65
182
|
if not obj and self.__default:
|
|
66
|
-
obj = self.__default.copy()
|
|
183
|
+
obj = self.__default.copy(key=keypath + [key])
|
|
67
184
|
self.__manifest[key] = obj
|
|
185
|
+
|
|
68
186
|
if obj:
|
|
69
187
|
obj._from_dict(data, keypath + [key], version=version)
|
|
70
188
|
handled.add(key)
|
|
@@ -75,7 +193,7 @@ class BaseSchema:
|
|
|
75
193
|
|
|
76
194
|
# Manifest methods
|
|
77
195
|
@classmethod
|
|
78
|
-
def from_manifest(cls, filepath=None, cfg=None):
|
|
196
|
+
def from_manifest(cls, filepath: str = None, cfg: Dict = None) -> "BaseSchema":
|
|
79
197
|
'''
|
|
80
198
|
Create a new schema based on the provided source files.
|
|
81
199
|
|
|
@@ -89,15 +207,24 @@ class BaseSchema:
|
|
|
89
207
|
if not filepath and cfg is None:
|
|
90
208
|
raise RuntimeError("filepath or dictionary is required")
|
|
91
209
|
|
|
92
|
-
schema = cls()
|
|
93
210
|
if filepath:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
211
|
+
cfg = BaseSchema._read_manifest(filepath)
|
|
212
|
+
|
|
213
|
+
new_cls = None
|
|
214
|
+
if "__meta__" in cfg:
|
|
215
|
+
# Determine correct class
|
|
216
|
+
new_cls = BaseSchema.__process_meta_section(cfg["__meta__"])
|
|
217
|
+
if new_cls:
|
|
218
|
+
schema = new_cls()
|
|
219
|
+
else:
|
|
220
|
+
schema = cls()
|
|
221
|
+
|
|
222
|
+
schema._from_dict(cfg, [])
|
|
223
|
+
|
|
97
224
|
return schema
|
|
98
225
|
|
|
99
226
|
@staticmethod
|
|
100
|
-
def __open_file(filepath, is_read=True):
|
|
227
|
+
def __open_file(filepath: str, is_read: bool = True):
|
|
101
228
|
_, ext = os.path.splitext(filepath)
|
|
102
229
|
if ext.lower() == ".gz":
|
|
103
230
|
if not _has_gzip:
|
|
@@ -105,7 +232,27 @@ class BaseSchema:
|
|
|
105
232
|
return gzip.open(filepath, mode="rt" if is_read else "wt", encoding="utf-8")
|
|
106
233
|
return open(filepath, mode="r" if is_read else "w", encoding="utf-8")
|
|
107
234
|
|
|
108
|
-
def
|
|
235
|
+
def __format_key(self, *key: str):
|
|
236
|
+
return f"[{','.join([*self._keypath, *key])}]"
|
|
237
|
+
|
|
238
|
+
@staticmethod
|
|
239
|
+
def _read_manifest(filepath: str):
|
|
240
|
+
"""
|
|
241
|
+
Reads a manifest from disk and returns dictionary.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
filename (path): Path to a manifest file to be loaded.
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
fin = BaseSchema.__open_file(filepath)
|
|
248
|
+
try:
|
|
249
|
+
manifest = json.loads(fin.read())
|
|
250
|
+
finally:
|
|
251
|
+
fin.close()
|
|
252
|
+
|
|
253
|
+
return manifest
|
|
254
|
+
|
|
255
|
+
def read_manifest(self, filepath: str):
|
|
109
256
|
"""
|
|
110
257
|
Reads a manifest from disk and replaces the current data with the data in the file.
|
|
111
258
|
|
|
@@ -117,13 +264,9 @@ class BaseSchema:
|
|
|
117
264
|
Loads the file mychip.json into the current Schema object.
|
|
118
265
|
"""
|
|
119
266
|
|
|
120
|
-
|
|
121
|
-
manifest = json.loads(fin.read())
|
|
122
|
-
fin.close()
|
|
123
|
-
|
|
124
|
-
self._from_dict(manifest, [])
|
|
267
|
+
self._from_dict(BaseSchema._read_manifest(filepath), [])
|
|
125
268
|
|
|
126
|
-
def write_manifest(self, filepath):
|
|
269
|
+
def write_manifest(self, filepath: str):
|
|
127
270
|
'''
|
|
128
271
|
Writes the manifest to a file.
|
|
129
272
|
|
|
@@ -147,11 +290,11 @@ class BaseSchema:
|
|
|
147
290
|
|
|
148
291
|
# Accessor methods
|
|
149
292
|
def __search(self,
|
|
150
|
-
*keypath,
|
|
151
|
-
insert_defaults=False,
|
|
152
|
-
use_default=False,
|
|
153
|
-
require_leaf=True,
|
|
154
|
-
complete_path=None):
|
|
293
|
+
*keypath: str,
|
|
294
|
+
insert_defaults: bool = False,
|
|
295
|
+
use_default: bool = False,
|
|
296
|
+
require_leaf: bool = True,
|
|
297
|
+
complete_path: bool = None) -> Union["BaseSchema", Parameter]:
|
|
155
298
|
if len(keypath) == 0:
|
|
156
299
|
if require_leaf:
|
|
157
300
|
raise KeyError
|
|
@@ -189,7 +332,8 @@ class BaseSchema:
|
|
|
189
332
|
complete_path=complete_path)
|
|
190
333
|
return key_param
|
|
191
334
|
|
|
192
|
-
def get(self, *keypath, field='value',
|
|
335
|
+
def get(self, *keypath: str, field: str = 'value',
|
|
336
|
+
step: str = None, index: Union[int, str] = None):
|
|
193
337
|
"""
|
|
194
338
|
Returns a parameter field from the schema.
|
|
195
339
|
|
|
@@ -216,21 +360,23 @@ class BaseSchema:
|
|
|
216
360
|
|
|
217
361
|
try:
|
|
218
362
|
require_leaf = True
|
|
363
|
+
insert_defaults = False
|
|
219
364
|
if field == 'schema':
|
|
220
365
|
require_leaf = False
|
|
366
|
+
insert_defaults = True
|
|
221
367
|
param = self.__search(
|
|
222
368
|
*keypath,
|
|
223
|
-
insert_defaults=
|
|
369
|
+
insert_defaults=insert_defaults,
|
|
224
370
|
use_default=True,
|
|
225
371
|
require_leaf=require_leaf)
|
|
226
372
|
if field == 'schema':
|
|
227
373
|
if isinstance(param, Parameter):
|
|
228
|
-
raise ValueError(f"
|
|
374
|
+
raise ValueError(f"{self.__format_key(*keypath)} is a complete keypath")
|
|
229
375
|
self.__journal.record("get", keypath, field=field, step=step, index=index)
|
|
230
376
|
param.__journal = self.__journal.get_child(*keypath)
|
|
231
377
|
return param
|
|
232
378
|
except KeyError:
|
|
233
|
-
raise KeyError(f"
|
|
379
|
+
raise KeyError(f"{self.__format_key(*keypath)} is not a valid keypath")
|
|
234
380
|
if field is None:
|
|
235
381
|
return param
|
|
236
382
|
|
|
@@ -239,11 +385,12 @@ class BaseSchema:
|
|
|
239
385
|
self.__journal.record("get", keypath, field=field, step=step, index=index)
|
|
240
386
|
return get_ret
|
|
241
387
|
except Exception as e:
|
|
242
|
-
new_msg = f"error while accessing
|
|
388
|
+
new_msg = f"error while accessing {self.__format_key(*keypath)}: {e.args[0]}"
|
|
243
389
|
e.args = (new_msg, *e.args[1:])
|
|
244
390
|
raise e
|
|
245
391
|
|
|
246
|
-
def set(self, *args, field='value', clobber=True,
|
|
392
|
+
def set(self, *args: str, field: str = 'value', clobber: bool = True,
|
|
393
|
+
step: str = None, index: Union[int, str] = None):
|
|
247
394
|
'''
|
|
248
395
|
Sets a schema parameter field.
|
|
249
396
|
|
|
@@ -273,7 +420,7 @@ class BaseSchema:
|
|
|
273
420
|
try:
|
|
274
421
|
param = self.__search(*keypath, insert_defaults=True)
|
|
275
422
|
except KeyError:
|
|
276
|
-
raise KeyError(f"
|
|
423
|
+
raise KeyError(f"{self.__format_key(*keypath)} is not a valid keypath")
|
|
277
424
|
|
|
278
425
|
try:
|
|
279
426
|
set_ret = param.set(value, field=field, clobber=clobber,
|
|
@@ -281,13 +428,15 @@ class BaseSchema:
|
|
|
281
428
|
if set_ret:
|
|
282
429
|
self.__journal.record("set", keypath, value=value, field=field,
|
|
283
430
|
step=step, index=index)
|
|
431
|
+
self.__process_active(param, set_ret)
|
|
284
432
|
return set_ret
|
|
285
433
|
except Exception as e:
|
|
286
|
-
new_msg = f"error while setting
|
|
434
|
+
new_msg = f"error while setting {self.__format_key(*keypath)}: {e.args[0]}"
|
|
287
435
|
e.args = (new_msg, *e.args[1:])
|
|
288
436
|
raise e
|
|
289
437
|
|
|
290
|
-
def add(self, *args, field='value',
|
|
438
|
+
def add(self, *args: str, field: str = 'value',
|
|
439
|
+
step: str = None, index: Union[int, str] = None):
|
|
291
440
|
'''
|
|
292
441
|
Adds item(s) to a schema parameter list.
|
|
293
442
|
|
|
@@ -316,20 +465,21 @@ class BaseSchema:
|
|
|
316
465
|
try:
|
|
317
466
|
param = self.__search(*keypath, insert_defaults=True)
|
|
318
467
|
except KeyError:
|
|
319
|
-
raise KeyError(f"
|
|
468
|
+
raise KeyError(f"{self.__format_key(*keypath)} is not a valid keypath")
|
|
320
469
|
|
|
321
470
|
try:
|
|
322
471
|
add_ret = param.add(value, field=field, step=step, index=index)
|
|
323
472
|
if add_ret:
|
|
324
473
|
self.__journal.record("add", keypath, value=value, field=field,
|
|
325
474
|
step=step, index=index)
|
|
475
|
+
self.__process_active(param, add_ret)
|
|
326
476
|
return add_ret
|
|
327
477
|
except Exception as e:
|
|
328
|
-
new_msg = f"error while adding to
|
|
478
|
+
new_msg = f"error while adding to {self.__format_key(*keypath)}: {e.args[0]}"
|
|
329
479
|
e.args = (new_msg, *e.args[1:])
|
|
330
480
|
raise e
|
|
331
481
|
|
|
332
|
-
def unset(self, *keypath, step=None, index=None):
|
|
482
|
+
def unset(self, *keypath: str, step: str = None, index: Union[int, str] = None):
|
|
333
483
|
'''
|
|
334
484
|
Unsets a schema parameter.
|
|
335
485
|
|
|
@@ -358,17 +508,17 @@ class BaseSchema:
|
|
|
358
508
|
try:
|
|
359
509
|
param = self.__search(*keypath, use_default=True)
|
|
360
510
|
except KeyError:
|
|
361
|
-
raise KeyError(f"
|
|
511
|
+
raise KeyError(f"{self.__format_key(*keypath)} is not a valid keypath")
|
|
362
512
|
|
|
363
513
|
try:
|
|
364
514
|
param.unset(step=step, index=index)
|
|
365
515
|
self.__journal.record("unset", keypath, step=step, index=index)
|
|
366
516
|
except Exception as e:
|
|
367
|
-
new_msg = f"error while unsetting
|
|
517
|
+
new_msg = f"error while unsetting {self.__format_key(*keypath)}: {e.args[0]}"
|
|
368
518
|
e.args = (new_msg, *e.args[1:])
|
|
369
519
|
raise e
|
|
370
520
|
|
|
371
|
-
def remove(self, *keypath):
|
|
521
|
+
def remove(self, *keypath: str):
|
|
372
522
|
'''
|
|
373
523
|
Remove a schema parameter and its subparameters.
|
|
374
524
|
|
|
@@ -384,7 +534,7 @@ class BaseSchema:
|
|
|
384
534
|
try:
|
|
385
535
|
key_param = self.__search(*search_path, require_leaf=False)
|
|
386
536
|
except KeyError:
|
|
387
|
-
raise KeyError(f"
|
|
537
|
+
raise KeyError(f"{self.__format_key(*keypath)} is not a valid keypath")
|
|
388
538
|
|
|
389
539
|
if removal_key not in key_param.__manifest:
|
|
390
540
|
return
|
|
@@ -398,7 +548,8 @@ class BaseSchema:
|
|
|
398
548
|
del key_param.__manifest[removal_key]
|
|
399
549
|
self.__journal.record("remove", keypath)
|
|
400
550
|
|
|
401
|
-
def valid(self, *keypath, default_valid=False,
|
|
551
|
+
def valid(self, *keypath: str, default_valid: bool = False,
|
|
552
|
+
check_complete: bool = False) -> bool:
|
|
402
553
|
"""
|
|
403
554
|
Checks validity of a keypath.
|
|
404
555
|
|
|
@@ -432,7 +583,7 @@ class BaseSchema:
|
|
|
432
583
|
return isinstance(param, Parameter)
|
|
433
584
|
return True
|
|
434
585
|
|
|
435
|
-
def getkeys(self, *keypath):
|
|
586
|
+
def getkeys(self, *keypath: str) -> Tuple[str]:
|
|
436
587
|
"""
|
|
437
588
|
Returns a tuple of schema dictionary keys.
|
|
438
589
|
|
|
@@ -454,15 +605,15 @@ class BaseSchema:
|
|
|
454
605
|
try:
|
|
455
606
|
key_param = self.__search(*keypath, require_leaf=False)
|
|
456
607
|
except KeyError:
|
|
457
|
-
raise KeyError(f"
|
|
608
|
+
raise KeyError(f"{self.__format_key(*keypath)} is not a valid keypath")
|
|
458
609
|
if isinstance(key_param, Parameter):
|
|
459
610
|
return tuple()
|
|
460
611
|
else:
|
|
461
612
|
key_param = self
|
|
462
613
|
|
|
463
|
-
return tuple(key_param.__manifest.keys())
|
|
614
|
+
return tuple(sorted(key_param.__manifest.keys()))
|
|
464
615
|
|
|
465
|
-
def allkeys(self, *keypath, include_default=True):
|
|
616
|
+
def allkeys(self, *keypath: str, include_default: bool = True) -> Set[Tuple[str]]:
|
|
466
617
|
'''
|
|
467
618
|
Returns all keypaths in the schema as a set of tuples.
|
|
468
619
|
|
|
@@ -491,7 +642,16 @@ class BaseSchema:
|
|
|
491
642
|
add(keys, key, item)
|
|
492
643
|
return set(keys)
|
|
493
644
|
|
|
494
|
-
|
|
645
|
+
@classmethod
|
|
646
|
+
def _getdict_type(cls) -> str:
|
|
647
|
+
"""
|
|
648
|
+
Returns the meta data for getdict
|
|
649
|
+
"""
|
|
650
|
+
|
|
651
|
+
return "BaseSchema"
|
|
652
|
+
|
|
653
|
+
def getdict(self, *keypath: str, include_default: bool = True,
|
|
654
|
+
values_only: bool = False) -> Dict:
|
|
495
655
|
"""
|
|
496
656
|
Returns a schema dictionary.
|
|
497
657
|
|
|
@@ -534,10 +694,19 @@ class BaseSchema:
|
|
|
534
694
|
if not values_only and self.__journal.has_journaling():
|
|
535
695
|
manifest["__journal__"] = self.__journal.get()
|
|
536
696
|
|
|
697
|
+
if not values_only and self.__class__ is not BaseSchema:
|
|
698
|
+
manifest["__meta__"] = {
|
|
699
|
+
"class": f"{self.__class__.__module__}/{self.__class__.__name__}"
|
|
700
|
+
}
|
|
701
|
+
try:
|
|
702
|
+
manifest["__meta__"]["sctype"] = self._getdict_type()
|
|
703
|
+
except NotImplementedError:
|
|
704
|
+
pass
|
|
705
|
+
|
|
537
706
|
return manifest
|
|
538
707
|
|
|
539
708
|
# Utility functions
|
|
540
|
-
def copy(self, key=None):
|
|
709
|
+
def copy(self, key: Tuple[str] = None):
|
|
541
710
|
"""
|
|
542
711
|
Returns a copy of this schema.
|
|
543
712
|
|
|
@@ -555,10 +724,38 @@ class BaseSchema:
|
|
|
555
724
|
else:
|
|
556
725
|
schema_copy.__parent = schema_copy
|
|
557
726
|
|
|
727
|
+
if key:
|
|
728
|
+
schema_copy.__key = key[-1]
|
|
729
|
+
|
|
558
730
|
return schema_copy
|
|
559
731
|
|
|
560
|
-
def
|
|
561
|
-
|
|
732
|
+
def _find_files_search_paths(self, keypath: Tuple[str], step: str, index: str):
|
|
733
|
+
"""
|
|
734
|
+
Returns a list of paths to search during find files.
|
|
735
|
+
|
|
736
|
+
Args:
|
|
737
|
+
keypath (str): final component of keypath
|
|
738
|
+
step (str): Step name.
|
|
739
|
+
index (str): Index name.
|
|
740
|
+
"""
|
|
741
|
+
return []
|
|
742
|
+
|
|
743
|
+
def _find_files_dataroot_resolvers(self):
|
|
744
|
+
"""
|
|
745
|
+
Returns a dictionary of path resolevrs data directory handling for find_files
|
|
746
|
+
|
|
747
|
+
Returns:
|
|
748
|
+
dictionary of str to resolver mapping
|
|
749
|
+
"""
|
|
750
|
+
if self.__parent is self:
|
|
751
|
+
return {}
|
|
752
|
+
return self.__parent._find_files_dataroot_resolvers()
|
|
753
|
+
|
|
754
|
+
def find_files(self, *keypath: str, missing_ok: bool = False,
|
|
755
|
+
step: str = None, index: Union[int, str] = None,
|
|
756
|
+
packages: Dict[str, Union[str, Callable]] = None,
|
|
757
|
+
collection_dir: str = None,
|
|
758
|
+
cwd: str = None) -> Union[str, List[str], Set[str]]:
|
|
562
759
|
"""
|
|
563
760
|
Returns absolute paths to files or directories based on the keypath
|
|
564
761
|
provided.
|
|
@@ -592,10 +789,13 @@ class BaseSchema:
|
|
|
592
789
|
the schema.
|
|
593
790
|
"""
|
|
594
791
|
|
|
595
|
-
|
|
792
|
+
base_schema = self.get(*keypath[0:-1], field="schema")
|
|
793
|
+
|
|
794
|
+
param = base_schema.get(keypath[-1], field=None)
|
|
596
795
|
paramtype = param.get(field='type')
|
|
597
796
|
if 'file' not in paramtype and 'dir' not in paramtype:
|
|
598
|
-
raise TypeError(
|
|
797
|
+
raise TypeError(
|
|
798
|
+
f'Cannot find files on {self.__format_key(*keypath)}, must be a path type')
|
|
599
799
|
|
|
600
800
|
paths = param.get(field=None, step=step, index=index)
|
|
601
801
|
|
|
@@ -615,23 +815,26 @@ class BaseSchema:
|
|
|
615
815
|
cwd = os.getcwd()
|
|
616
816
|
|
|
617
817
|
if packages is None:
|
|
618
|
-
packages =
|
|
818
|
+
packages = base_schema._find_files_dataroot_resolvers()
|
|
619
819
|
|
|
620
820
|
resolved_paths = []
|
|
821
|
+
root_search_paths = base_schema._find_files_search_paths(keypath[-1], step, index)
|
|
621
822
|
for path in paths:
|
|
622
|
-
search_paths =
|
|
823
|
+
search_paths = root_search_paths.copy()
|
|
623
824
|
|
|
624
825
|
package = path.get(field="package")
|
|
625
826
|
if package:
|
|
626
827
|
if package not in packages:
|
|
627
|
-
raise ValueError(f"Resolver for {package} not provided"
|
|
828
|
+
raise ValueError(f"Resolver for {package} not provided: "
|
|
829
|
+
f"{self.__format_key(*keypath)}")
|
|
628
830
|
package_path = packages[package]
|
|
629
831
|
if isinstance(package_path, str):
|
|
630
832
|
search_paths.append(os.path.abspath(package_path))
|
|
631
833
|
elif callable(package_path):
|
|
632
834
|
search_paths.append(package_path())
|
|
633
835
|
else:
|
|
634
|
-
raise TypeError(f"Resolver for {package} is not a recognized type"
|
|
836
|
+
raise TypeError(f"Resolver for {package} is not a recognized type: "
|
|
837
|
+
f"{self.__format_key(*keypath)}")
|
|
635
838
|
else:
|
|
636
839
|
if cwd:
|
|
637
840
|
search_paths.append(os.path.abspath(cwd))
|
|
@@ -644,10 +847,11 @@ class BaseSchema:
|
|
|
644
847
|
if not missing_ok:
|
|
645
848
|
if package:
|
|
646
849
|
raise FileNotFoundError(
|
|
647
|
-
f'Could not find "{path.get()}" in {package}
|
|
850
|
+
f'Could not find "{path.get()}" in {package} '
|
|
851
|
+
f'{self.__format_key(*keypath)}')
|
|
648
852
|
else:
|
|
649
853
|
raise FileNotFoundError(
|
|
650
|
-
f'Could not find "{path.get()}"
|
|
854
|
+
f'Could not find "{path.get()}" {self.__format_key(*keypath)}')
|
|
651
855
|
resolved_paths.append(resolved_path)
|
|
652
856
|
|
|
653
857
|
if not is_list:
|
|
@@ -656,8 +860,11 @@ class BaseSchema:
|
|
|
656
860
|
return resolved_paths[0]
|
|
657
861
|
return resolved_paths
|
|
658
862
|
|
|
659
|
-
def check_filepaths(self, ignore_keys=
|
|
660
|
-
|
|
863
|
+
def check_filepaths(self, ignore_keys: bool = None,
|
|
864
|
+
logger: logging.Logger = None,
|
|
865
|
+
packages: Dict[str, Union[str, Callable]] = None,
|
|
866
|
+
collection_dir: str = None,
|
|
867
|
+
cwd: str = None) -> bool:
|
|
661
868
|
'''
|
|
662
869
|
Verifies that paths to all files in manifest are valid.
|
|
663
870
|
|
|
@@ -717,12 +924,12 @@ class BaseSchema:
|
|
|
717
924
|
else:
|
|
718
925
|
node_indicator = f" ({step}/{index})"
|
|
719
926
|
|
|
720
|
-
logger.error(f"Parameter
|
|
721
|
-
f"{check_file} is invalid")
|
|
927
|
+
logger.error(f"Parameter {self.__format_key(*keypath)}{node_indicator} "
|
|
928
|
+
f"path {check_file} is invalid")
|
|
722
929
|
|
|
723
930
|
return not error
|
|
724
931
|
|
|
725
|
-
def _parent(self, root=False):
|
|
932
|
+
def _parent(self, root: bool = False) -> "BaseSchema":
|
|
726
933
|
'''
|
|
727
934
|
Returns the parent of this schema section, if root is true the root parent
|
|
728
935
|
will be returned.
|
|
@@ -735,3 +942,75 @@ class BaseSchema:
|
|
|
735
942
|
if self.__parent is self:
|
|
736
943
|
return self
|
|
737
944
|
return self.__parent._parent(root=root)
|
|
945
|
+
|
|
946
|
+
@contextlib.contextmanager
|
|
947
|
+
def _active(self, **kwargs):
|
|
948
|
+
'''
|
|
949
|
+
Use this context to temporarily set additional fields in :meth:`.set` and :meth:`.add`.
|
|
950
|
+
Additional fields can be specified which can be accessed by :meth:`._get_active`.
|
|
951
|
+
|
|
952
|
+
Args:
|
|
953
|
+
kwargs (dict of str): keyword arguments that are used for setting values
|
|
954
|
+
|
|
955
|
+
Example:
|
|
956
|
+
>>> with schema._active(package="lambdalib"):
|
|
957
|
+
... schema.set("file", "top.v")
|
|
958
|
+
Sets the file to top.v and associates lambdalib as the package.
|
|
959
|
+
'''
|
|
960
|
+
if self.__active:
|
|
961
|
+
orig_active = self.__active.copy()
|
|
962
|
+
else:
|
|
963
|
+
orig_active = None
|
|
964
|
+
|
|
965
|
+
if self.__active is None:
|
|
966
|
+
self.__active = {}
|
|
967
|
+
|
|
968
|
+
self.__active.update(kwargs)
|
|
969
|
+
try:
|
|
970
|
+
yield
|
|
971
|
+
finally:
|
|
972
|
+
self.__active = orig_active
|
|
973
|
+
|
|
974
|
+
def _get_active(self, field: str, defvalue=None):
|
|
975
|
+
'''
|
|
976
|
+
Get the value of a specific field.
|
|
977
|
+
|
|
978
|
+
Args:
|
|
979
|
+
field (str): if None, return the current active dictionary,
|
|
980
|
+
otherwise the value, if the field is not present, defvalue is returned.
|
|
981
|
+
defvalue (any): value to return if the field is not present.
|
|
982
|
+
'''
|
|
983
|
+
if self.__active is None:
|
|
984
|
+
return defvalue
|
|
985
|
+
|
|
986
|
+
if field is None:
|
|
987
|
+
return self.__active.copy()
|
|
988
|
+
|
|
989
|
+
return self.__active.get(field, defvalue)
|
|
990
|
+
|
|
991
|
+
def __process_active(self, param, nodevalues):
|
|
992
|
+
if not self.__active:
|
|
993
|
+
return
|
|
994
|
+
|
|
995
|
+
if not isinstance(nodevalues, (list, set, tuple)):
|
|
996
|
+
# Make everything a list
|
|
997
|
+
nodevalues = [nodevalues]
|
|
998
|
+
|
|
999
|
+
if not all([isinstance(v, NodeValue) for v in nodevalues]):
|
|
1000
|
+
nodevalues = []
|
|
1001
|
+
nodevalues_fields = []
|
|
1002
|
+
else:
|
|
1003
|
+
nodevalues_fields = nodevalues[0].fields
|
|
1004
|
+
|
|
1005
|
+
key_fields = ("copy", "lock")
|
|
1006
|
+
|
|
1007
|
+
for field, value in self.__active.items():
|
|
1008
|
+
if field in key_fields:
|
|
1009
|
+
param.set(value, field=field)
|
|
1010
|
+
continue
|
|
1011
|
+
|
|
1012
|
+
if field not in nodevalues_fields:
|
|
1013
|
+
continue
|
|
1014
|
+
|
|
1015
|
+
for param in nodevalues:
|
|
1016
|
+
param.set(value, field=field)
|