siliconcompiler 0.33.2__py3-none-any.whl → 0.34.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- siliconcompiler/__init__.py +2 -0
- siliconcompiler/_metadata.py +1 -1
- siliconcompiler/apps/sc_issue.py +5 -3
- siliconcompiler/apps/sc_remote.py +0 -17
- siliconcompiler/checklist.py +1 -1
- siliconcompiler/core.py +34 -47
- siliconcompiler/dependencyschema.py +392 -0
- siliconcompiler/design.py +664 -0
- siliconcompiler/flowgraph.py +32 -1
- siliconcompiler/package/__init__.py +383 -223
- siliconcompiler/package/git.py +75 -77
- siliconcompiler/package/github.py +70 -97
- siliconcompiler/package/https.py +77 -93
- siliconcompiler/packageschema.py +260 -0
- siliconcompiler/pdk.py +2 -2
- siliconcompiler/remote/client.py +15 -3
- siliconcompiler/report/dashboard/cli/board.py +1 -1
- siliconcompiler/scheduler/__init__.py +3 -1382
- siliconcompiler/scheduler/docker.py +268 -0
- siliconcompiler/scheduler/run_node.py +10 -16
- siliconcompiler/scheduler/scheduler.py +308 -0
- siliconcompiler/scheduler/schedulernode.py +934 -0
- siliconcompiler/scheduler/slurm.py +147 -163
- siliconcompiler/scheduler/taskscheduler.py +39 -52
- siliconcompiler/schema/__init__.py +3 -3
- siliconcompiler/schema/baseschema.py +234 -10
- siliconcompiler/schema/editableschema.py +4 -0
- siliconcompiler/schema/journal.py +210 -0
- siliconcompiler/schema/namedschema.py +31 -2
- siliconcompiler/schema/parameter.py +14 -1
- siliconcompiler/schema/parametervalue.py +1 -34
- siliconcompiler/schema/schema_cfg.py +210 -349
- siliconcompiler/tool.py +61 -20
- siliconcompiler/tools/builtin/concatenate.py +2 -2
- siliconcompiler/tools/builtin/verify.py +1 -2
- siliconcompiler/tools/openroad/scripts/common/procs.tcl +27 -25
- siliconcompiler/tools/vpr/route.py +69 -0
- siliconcompiler/toolscripts/_tools.json +4 -4
- siliconcompiler/utils/__init__.py +2 -23
- siliconcompiler/utils/flowgraph.py +5 -5
- siliconcompiler/utils/logging.py +2 -1
- {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.0.dist-info}/METADATA +4 -3
- {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.0.dist-info}/RECORD +47 -42
- siliconcompiler/scheduler/docker_runner.py +0 -254
- siliconcompiler/schema/journalingschema.py +0 -242
- {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.0.dist-info}/WHEEL +0 -0
- {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.0.dist-info}/entry_points.txt +0 -0
- {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.0.dist-info}/licenses/LICENSE +0 -0
- {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.0.dist-info}/top_level.txt +0 -0
|
@@ -1,1384 +1,5 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import os
|
|
3
|
-
import re
|
|
4
|
-
import shutil
|
|
5
|
-
import sys
|
|
6
|
-
from logging.handlers import QueueHandler
|
|
7
|
-
from siliconcompiler import sc_open
|
|
8
|
-
from siliconcompiler import utils
|
|
9
|
-
from siliconcompiler.remote import Client
|
|
10
|
-
from siliconcompiler import Schema
|
|
11
|
-
from siliconcompiler.schema import JournalingSchema
|
|
12
|
-
from siliconcompiler.record import RecordTime, RecordTool
|
|
13
|
-
from siliconcompiler import NodeStatus, SiliconCompilerError
|
|
14
|
-
from siliconcompiler.tools._common import input_file_node_name
|
|
15
|
-
import lambdapdk
|
|
16
|
-
from siliconcompiler.tools._common import get_tool_task, record_metric
|
|
17
|
-
from siliconcompiler.scheduler import send_messages
|
|
18
|
-
from siliconcompiler.flowgraph import RuntimeFlowgraph
|
|
19
1
|
from siliconcompiler.scheduler.taskscheduler import TaskScheduler
|
|
20
2
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
###############################################################################
|
|
27
|
-
class SiliconCompilerTimeout(Exception):
|
|
28
|
-
''' Minimal Exception wrapper used to raise sc timeout errors.
|
|
29
|
-
'''
|
|
30
|
-
def __init__(self, message):
|
|
31
|
-
super(Exception, self).__init__(message)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def run(chip):
|
|
35
|
-
'''
|
|
36
|
-
See :meth:`~siliconcompiler.core.Chip.run` for detailed documentation.
|
|
37
|
-
'''
|
|
38
|
-
|
|
39
|
-
_check_display(chip)
|
|
40
|
-
|
|
41
|
-
# Check required settings before attempting run()
|
|
42
|
-
for key in (['option', 'flow'], ):
|
|
43
|
-
if chip.get(*key) is None:
|
|
44
|
-
raise SiliconCompilerError(
|
|
45
|
-
f"{key} must be set before calling run()",
|
|
46
|
-
chip=chip)
|
|
47
|
-
|
|
48
|
-
org_jobname = chip.get('option', 'jobname')
|
|
49
|
-
_increment_job_name(chip)
|
|
50
|
-
|
|
51
|
-
# Re-init logger to include run info after setting up flowgraph.
|
|
52
|
-
chip._init_logger(in_run=True)
|
|
53
|
-
if chip._dash and not chip._dash.is_running():
|
|
54
|
-
chip._dash.open_dashboard()
|
|
55
|
-
|
|
56
|
-
# Check if flowgraph is complete and valid
|
|
57
|
-
flow = chip.get('option', 'flow')
|
|
58
|
-
if not chip.schema.get("flowgraph", flow, field="schema").validate(logger=chip.logger):
|
|
59
|
-
raise SiliconCompilerError(
|
|
60
|
-
f"{flow} flowgraph contains errors and cannot be run.",
|
|
61
|
-
chip=chip)
|
|
62
|
-
if not RuntimeFlowgraph.validate(
|
|
63
|
-
chip.schema.get("flowgraph", flow, field="schema"),
|
|
64
|
-
from_steps=chip.get('option', 'from'),
|
|
65
|
-
to_steps=chip.get('option', 'to'),
|
|
66
|
-
prune_nodes=chip.get('option', 'prune'),
|
|
67
|
-
logger=chip.logger):
|
|
68
|
-
raise SiliconCompilerError(
|
|
69
|
-
f"{flow} flowgraph contains errors and cannot be run.",
|
|
70
|
-
chip=chip)
|
|
71
|
-
|
|
72
|
-
copy_old_run_dir(chip, org_jobname)
|
|
73
|
-
clean_build_dir(chip)
|
|
74
|
-
|
|
75
|
-
runtime = RuntimeFlowgraph(
|
|
76
|
-
chip.schema.get("flowgraph", flow, field='schema'),
|
|
77
|
-
from_steps=chip.get('option', 'from'),
|
|
78
|
-
to_steps=chip.get('option', 'to'),
|
|
79
|
-
prune_nodes=chip.get('option', 'prune'))
|
|
80
|
-
|
|
81
|
-
_reset_flow_nodes(chip, flow, runtime.get_nodes())
|
|
82
|
-
chip.schema.get("record", field='schema').record_python_packages()
|
|
83
|
-
|
|
84
|
-
if chip.get('option', 'remote'):
|
|
85
|
-
client = Client(chip)
|
|
86
|
-
client.run()
|
|
87
|
-
else:
|
|
88
|
-
_local_process(chip, flow)
|
|
89
|
-
|
|
90
|
-
# Merge cfgs from last executed tasks, and write out a final manifest.
|
|
91
|
-
_finalize_run(chip)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
###########################################################################
|
|
95
|
-
def _finalize_run(chip):
|
|
96
|
-
'''
|
|
97
|
-
Helper function to finalize a job run after it completes:
|
|
98
|
-
* Clear any -arg_step/-arg_index values in case only one node was run.
|
|
99
|
-
* Store this run in the Schema's 'history' field.
|
|
100
|
-
* Write out a final JSON manifest containing the full results and history.
|
|
101
|
-
'''
|
|
102
|
-
|
|
103
|
-
# Clear scratchpad args since these are checked on run() entry
|
|
104
|
-
chip.set('arg', 'step', None, clobber=True)
|
|
105
|
-
chip.set('arg', 'index', None, clobber=True)
|
|
106
|
-
|
|
107
|
-
# Store run in history
|
|
108
|
-
chip.schema.record_history()
|
|
109
|
-
|
|
110
|
-
# Storing manifest in job root directory
|
|
111
|
-
filepath = os.path.join(chip.getworkdir(), f"{chip.design}.pkg.json")
|
|
112
|
-
chip.write_manifest(filepath)
|
|
113
|
-
|
|
114
|
-
send_messages.send(chip, 'summary', None, None)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def _increment_job_name(chip):
|
|
118
|
-
'''
|
|
119
|
-
Auto-update jobname if ['option', 'jobincr'] is True
|
|
120
|
-
Do this before initializing logger so that it picks up correct jobname
|
|
121
|
-
'''
|
|
122
|
-
if not chip.get('option', 'clean'):
|
|
123
|
-
return
|
|
124
|
-
if chip.get('option', 'jobincr'):
|
|
125
|
-
workdir = chip.getworkdir()
|
|
126
|
-
if os.path.isdir(workdir):
|
|
127
|
-
# Strip off digits following jobname, if any
|
|
128
|
-
stem = chip.get('option', 'jobname').rstrip('0123456789')
|
|
129
|
-
|
|
130
|
-
designdir = os.path.dirname(workdir)
|
|
131
|
-
jobid = 0
|
|
132
|
-
for job in os.listdir(designdir):
|
|
133
|
-
m = re.match(stem + r'(\d+)', job)
|
|
134
|
-
if m:
|
|
135
|
-
jobid = max(jobid, int(m.group(1)))
|
|
136
|
-
chip.set('option', 'jobname', f'{stem}{jobid + 1}')
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
###########################################################################
|
|
140
|
-
def _check_display(chip):
|
|
141
|
-
'''
|
|
142
|
-
Automatically disable display for Linux systems without desktop environment
|
|
143
|
-
'''
|
|
144
|
-
if not chip.get('option', 'nodisplay') and sys.platform == 'linux' \
|
|
145
|
-
and 'DISPLAY' not in os.environ and 'WAYLAND_DISPLAY' not in os.environ:
|
|
146
|
-
chip.logger.warning('Environment variable $DISPLAY or $WAYLAND_DISPLAY not set')
|
|
147
|
-
chip.logger.warning("Setting ['option', 'nodisplay'] to True")
|
|
148
|
-
chip.set('option', 'nodisplay', True)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def _local_process(chip, flow):
|
|
152
|
-
from_nodes = []
|
|
153
|
-
extra_setup_nodes = {}
|
|
154
|
-
|
|
155
|
-
chip.schema = JournalingSchema(chip.schema)
|
|
156
|
-
chip.schema.start_journal()
|
|
157
|
-
|
|
158
|
-
if chip.get('option', 'clean') or not chip.get('option', 'from'):
|
|
159
|
-
load_nodes = list(chip.schema.get("flowgraph", flow, field="schema").get_nodes())
|
|
160
|
-
else:
|
|
161
|
-
for step in chip.get('option', 'from'):
|
|
162
|
-
from_nodes.extend(
|
|
163
|
-
[(step, index) for index in chip.getkeys('flowgraph', flow, step)])
|
|
164
|
-
|
|
165
|
-
runtime = RuntimeFlowgraph(
|
|
166
|
-
chip.schema.get("flowgraph", flow, field="schema"),
|
|
167
|
-
to_steps=chip.get('option', 'from'),
|
|
168
|
-
prune_nodes=chip.get('option', 'prune'))
|
|
169
|
-
load_nodes = list(runtime.get_nodes())
|
|
170
|
-
|
|
171
|
-
for node_level in chip.schema.get("flowgraph", flow, field="schema").get_execution_order():
|
|
172
|
-
for step, index in node_level:
|
|
173
|
-
if (step, index) not in load_nodes:
|
|
174
|
-
continue
|
|
175
|
-
if (step, index) in from_nodes:
|
|
176
|
-
continue
|
|
177
|
-
|
|
178
|
-
manifest = os.path.join(chip.getworkdir(step=step, index=index),
|
|
179
|
-
'outputs',
|
|
180
|
-
f'{chip.design}.pkg.json')
|
|
181
|
-
if os.path.exists(manifest):
|
|
182
|
-
# ensure we setup these nodes again
|
|
183
|
-
try:
|
|
184
|
-
journal = JournalingSchema(Schema())
|
|
185
|
-
journal.read_manifest(manifest)
|
|
186
|
-
extra_setup_nodes[(step, index)] = journal
|
|
187
|
-
except Exception:
|
|
188
|
-
pass
|
|
189
|
-
|
|
190
|
-
runtimeflow = RuntimeFlowgraph(
|
|
191
|
-
chip.schema.get("flowgraph", flow, field="schema"),
|
|
192
|
-
from_steps=chip.get('option', 'from'),
|
|
193
|
-
to_steps=chip.get('option', 'to'),
|
|
194
|
-
prune_nodes=chip.get('option', 'prune'))
|
|
195
|
-
|
|
196
|
-
# Setup tools for all nodes to run.
|
|
197
|
-
nodes = list(runtimeflow.get_nodes())
|
|
198
|
-
all_setup_nodes = nodes + load_nodes + list(extra_setup_nodes.keys())
|
|
199
|
-
for layer_nodes in chip.schema.get("flowgraph", flow, field="schema").get_execution_order():
|
|
200
|
-
for step, index in layer_nodes:
|
|
201
|
-
if (step, index) in all_setup_nodes:
|
|
202
|
-
node_kept = _setup_node(chip, step, index)
|
|
203
|
-
if not node_kept and (step, index) in extra_setup_nodes:
|
|
204
|
-
del extra_setup_nodes[(step, index)]
|
|
205
|
-
if (step, index) in extra_setup_nodes:
|
|
206
|
-
schema = extra_setup_nodes[(step, index)]
|
|
207
|
-
node_status = None
|
|
208
|
-
try:
|
|
209
|
-
node_status = schema.get('record', 'status', step=step, index=index)
|
|
210
|
-
except: # noqa E722
|
|
211
|
-
pass
|
|
212
|
-
if node_status:
|
|
213
|
-
chip.schema.get("record", field='schema').set('status', node_status,
|
|
214
|
-
step=step, index=index)
|
|
215
|
-
|
|
216
|
-
def mark_pending(step, index):
|
|
217
|
-
chip.schema.get("record", field='schema').set('status', NodeStatus.PENDING,
|
|
218
|
-
step=step, index=index)
|
|
219
|
-
for next_step, next_index in runtimeflow.get_nodes_starting_at(step, index):
|
|
220
|
-
if chip.get('record', 'status', step=next_step, index=next_index) == \
|
|
221
|
-
NodeStatus.SKIPPED:
|
|
222
|
-
continue
|
|
223
|
-
|
|
224
|
-
# Mark following steps as pending
|
|
225
|
-
chip.schema.get("record", field='schema').set('status', NodeStatus.PENDING,
|
|
226
|
-
step=next_step, index=next_index)
|
|
227
|
-
|
|
228
|
-
# Check if nodes have been modified from previous data
|
|
229
|
-
for layer_nodes in chip.schema.get("flowgraph", flow, field="schema").get_execution_order():
|
|
230
|
-
for step, index in layer_nodes:
|
|
231
|
-
# Only look at successful nodes
|
|
232
|
-
if chip.get('record', 'status', step=step, index=index) not in \
|
|
233
|
-
(NodeStatus.SUCCESS, NodeStatus.SKIPPED):
|
|
234
|
-
continue
|
|
235
|
-
|
|
236
|
-
if (step, index) in runtimeflow.get_nodes() and \
|
|
237
|
-
not check_node_inputs(chip, step, index):
|
|
238
|
-
# change failing nodes to pending
|
|
239
|
-
mark_pending(step, index)
|
|
240
|
-
elif (step, index) in extra_setup_nodes:
|
|
241
|
-
# import old information
|
|
242
|
-
chip.schema.import_journal(schema=extra_setup_nodes[(step, index)])
|
|
243
|
-
|
|
244
|
-
# Ensure pending nodes cause following nodes to be run
|
|
245
|
-
for step, index in nodes:
|
|
246
|
-
if chip.get('record', 'status', step=step, index=index) in \
|
|
247
|
-
(NodeStatus.PENDING, NodeStatus.ERROR):
|
|
248
|
-
mark_pending(step, index)
|
|
249
|
-
|
|
250
|
-
# Clean nodes marked pending
|
|
251
|
-
for step, index in nodes:
|
|
252
|
-
if chip.get('record', 'status', step=step, index=index) == NodeStatus.PENDING:
|
|
253
|
-
clean_node_dir(chip, step, index)
|
|
254
|
-
|
|
255
|
-
chip.write_manifest(os.path.join(chip.getworkdir(), f"{chip.get('design')}.pkg.json"))
|
|
256
|
-
chip.schema.stop_journal()
|
|
257
|
-
chip.schema = chip.schema.get_base_schema()
|
|
258
|
-
|
|
259
|
-
# Check validity of setup
|
|
260
|
-
chip.logger.info("Checking manifest before running.")
|
|
261
|
-
check_ok = chip.check_manifest()
|
|
262
|
-
|
|
263
|
-
# Check if there were errors before proceeding with run
|
|
264
|
-
if not check_ok:
|
|
265
|
-
raise SiliconCompilerError('Manifest check failed. See previous errors.', chip=chip)
|
|
266
|
-
|
|
267
|
-
if chip._error:
|
|
268
|
-
raise SiliconCompilerError(
|
|
269
|
-
'Implementation errors encountered. See previous errors.',
|
|
270
|
-
chip=chip)
|
|
271
|
-
|
|
272
|
-
task_scheduler = TaskScheduler(chip)
|
|
273
|
-
task_scheduler.run()
|
|
274
|
-
|
|
275
|
-
_check_nodes_status(chip, flow)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
###########################################################################
|
|
279
|
-
def _setup_node(chip, step, index, flow=None):
|
|
280
|
-
preset_step = chip.get('arg', 'step')
|
|
281
|
-
preset_index = chip.get('arg', 'index')
|
|
282
|
-
preset_flow = chip.get('option', 'flow')
|
|
283
|
-
|
|
284
|
-
if flow:
|
|
285
|
-
chip.set('option', 'flow', flow)
|
|
286
|
-
|
|
287
|
-
chip.set('arg', 'step', step)
|
|
288
|
-
chip.set('arg', 'index', index)
|
|
289
|
-
tool, task = get_tool_task(chip, step, index, flow=flow)
|
|
290
|
-
|
|
291
|
-
task_class = chip.get("tool", tool, field="schema")
|
|
292
|
-
task_class.set_runtime(chip)
|
|
293
|
-
|
|
294
|
-
# Run node setup.
|
|
295
|
-
chip.logger.info(f'Setting up node {step}{index} with {tool}/{task}')
|
|
296
|
-
setup_ret = None
|
|
297
|
-
try:
|
|
298
|
-
setup_ret = task_class.setup()
|
|
299
|
-
except Exception as e:
|
|
300
|
-
chip.logger.error(f'Failed to run setup() for {tool}/{task}')
|
|
301
|
-
raise e
|
|
302
|
-
|
|
303
|
-
task_class.set_runtime(None)
|
|
304
|
-
|
|
305
|
-
# Need to restore step/index, otherwise we will skip setting up other indices.
|
|
306
|
-
chip.set('option', 'flow', preset_flow)
|
|
307
|
-
chip.set('arg', 'step', preset_step)
|
|
308
|
-
chip.set('arg', 'index', preset_index)
|
|
309
|
-
|
|
310
|
-
if setup_ret is not None:
|
|
311
|
-
chip.logger.warning(f'Removing {step}{index} due to {setup_ret}')
|
|
312
|
-
chip.schema.get("record", field='schema').set('status', NodeStatus.SKIPPED,
|
|
313
|
-
step=step, index=index)
|
|
314
|
-
|
|
315
|
-
return False
|
|
316
|
-
|
|
317
|
-
return True
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
###########################################################################
|
|
321
|
-
def _runtask(chip, flow, step, index, exec_func, pipe=None, queue=None, replay=False):
|
|
322
|
-
'''
|
|
323
|
-
Private per node run method called by run().
|
|
324
|
-
|
|
325
|
-
The method takes in a step string and index string to indicate what
|
|
326
|
-
to run.
|
|
327
|
-
|
|
328
|
-
Note that since _runtask occurs in its own process with a separate
|
|
329
|
-
address space, any changes made to the `self` object will not
|
|
330
|
-
be reflected in the parent. We rely on reading/writing the chip manifest
|
|
331
|
-
to the filesystem to communicate updates between processes.
|
|
332
|
-
'''
|
|
333
|
-
|
|
334
|
-
chip._init_codecs()
|
|
335
|
-
|
|
336
|
-
chip._init_logger(step, index, in_run=True)
|
|
337
|
-
if queue:
|
|
338
|
-
chip.logger.removeHandler(chip.logger._console)
|
|
339
|
-
chip.logger._console = QueueHandler(queue)
|
|
340
|
-
chip.logger.addHandler(chip.logger._console)
|
|
341
|
-
chip._init_logger_formats()
|
|
342
|
-
|
|
343
|
-
chip.set('arg', 'step', step, clobber=True)
|
|
344
|
-
chip.set('arg', 'index', index, clobber=True)
|
|
345
|
-
|
|
346
|
-
chip.schema = JournalingSchema(chip.schema)
|
|
347
|
-
chip.schema.start_journal()
|
|
348
|
-
|
|
349
|
-
# Make record of sc version and machine
|
|
350
|
-
chip.schema.get("record", field='schema').record_version(step, index)
|
|
351
|
-
# Record user information if enabled
|
|
352
|
-
if chip.get('option', 'track', step=step, index=index):
|
|
353
|
-
chip.schema.get("record", field='schema').record_userinformation(step, index)
|
|
354
|
-
|
|
355
|
-
# Start wall timer
|
|
356
|
-
chip.schema.get("record", field='schema').record_time(step, index, RecordTime.START)
|
|
357
|
-
|
|
358
|
-
workdir = _setup_workdir(chip, step, index, replay)
|
|
359
|
-
cwd = os.getcwd()
|
|
360
|
-
os.chdir(workdir)
|
|
361
|
-
|
|
362
|
-
chip._add_file_logger(os.path.join(workdir, f'sc_{step}{index}.log'))
|
|
363
|
-
|
|
364
|
-
try:
|
|
365
|
-
_setupnode(chip, flow, step, index, replay)
|
|
366
|
-
|
|
367
|
-
exec_func(chip, step, index, replay)
|
|
368
|
-
except Exception as e:
|
|
369
|
-
utils.print_traceback(chip.logger, e)
|
|
370
|
-
_haltstep(chip, chip.get('option', 'flow'), step, index)
|
|
371
|
-
|
|
372
|
-
# return to original directory
|
|
373
|
-
os.chdir(cwd)
|
|
374
|
-
chip.schema.stop_journal()
|
|
375
|
-
chip.schema = chip.schema.get_base_schema()
|
|
376
|
-
|
|
377
|
-
if pipe:
|
|
378
|
-
pipe.send(chip._packages)
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
###########################################################################
|
|
382
|
-
def _haltstep(chip, flow, step, index, log=True):
|
|
383
|
-
chip.schema.get("record", field='schema').set('status', NodeStatus.ERROR,
|
|
384
|
-
step=step, index=index)
|
|
385
|
-
chip.write_manifest(os.path.join("outputs", f"{chip.get('design')}.pkg.json"))
|
|
386
|
-
|
|
387
|
-
if log:
|
|
388
|
-
chip.logger.error(f"Halting step '{step}' index '{index}' due to errors.")
|
|
389
|
-
send_messages.send(chip, "fail", step, index)
|
|
390
|
-
sys.exit(1)
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
def _setupnode(chip, flow, step, index, replay):
|
|
394
|
-
_hash_files(chip, step, index, setup=True)
|
|
395
|
-
|
|
396
|
-
# Select the inputs to this node
|
|
397
|
-
_select_inputs(chip, step, index)
|
|
398
|
-
|
|
399
|
-
# Write manifest prior to step running into inputs
|
|
400
|
-
chip.write_manifest(f'inputs/{chip.get("design")}.pkg.json')
|
|
401
|
-
|
|
402
|
-
# Forward data
|
|
403
|
-
_copy_previous_steps_output_data(chip, step, index, replay)
|
|
404
|
-
|
|
405
|
-
# Check manifest
|
|
406
|
-
if not _check_manifest_dynamic(chip, step, index):
|
|
407
|
-
chip.logger.error("Fatal error in check_manifest()! See previous errors.")
|
|
408
|
-
_haltstep(chip, flow, step, index)
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
###########################################################################
|
|
412
|
-
def _setup_workdir(chip, step, index, replay):
|
|
413
|
-
workdir = chip.getworkdir(step=step, index=index)
|
|
414
|
-
|
|
415
|
-
if os.path.isdir(workdir) and not replay:
|
|
416
|
-
shutil.rmtree(workdir)
|
|
417
|
-
os.makedirs(workdir, exist_ok=True)
|
|
418
|
-
os.makedirs(os.path.join(workdir, 'inputs'), exist_ok=True)
|
|
419
|
-
os.makedirs(os.path.join(workdir, 'outputs'), exist_ok=True)
|
|
420
|
-
os.makedirs(os.path.join(workdir, 'reports'), exist_ok=True)
|
|
421
|
-
return workdir
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
def _select_inputs(chip, step, index, trial=False):
|
|
425
|
-
|
|
426
|
-
flow = chip.get('option', 'flow')
|
|
427
|
-
tool, _ = get_tool_task(chip, step, index, flow)
|
|
428
|
-
|
|
429
|
-
task_class = chip.get("tool", tool, field="schema")
|
|
430
|
-
task_class.set_runtime(chip, step=step, index=index)
|
|
431
|
-
|
|
432
|
-
log_level = chip.logger.level
|
|
433
|
-
if trial:
|
|
434
|
-
chip.logger.setLevel(logging.CRITICAL)
|
|
435
|
-
|
|
436
|
-
sel_inputs = task_class.select_input_nodes()
|
|
437
|
-
|
|
438
|
-
if trial:
|
|
439
|
-
chip.logger.setLevel(log_level)
|
|
440
|
-
|
|
441
|
-
if (step, index) not in chip.schema.get("flowgraph", flow, field="schema").get_entry_nodes() \
|
|
442
|
-
and not sel_inputs:
|
|
443
|
-
chip.logger.error(f'No inputs selected after running {tool}')
|
|
444
|
-
_haltstep(chip, flow, step, index)
|
|
445
|
-
|
|
446
|
-
if not trial:
|
|
447
|
-
chip.schema.get("record", field='schema').set('inputnode', sel_inputs,
|
|
448
|
-
step=step, index=index)
|
|
449
|
-
|
|
450
|
-
return sel_inputs
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
def copy_output_file(chip, outfile, folder='inputs'):
|
|
454
|
-
design = chip.get('design')
|
|
455
|
-
|
|
456
|
-
if outfile.is_file() or outfile.is_symlink():
|
|
457
|
-
if outfile.name == f'{design}.pkg.json':
|
|
458
|
-
return
|
|
459
|
-
utils.link_symlink_copy(outfile.path, f'{folder}/{outfile.name}')
|
|
460
|
-
elif outfile.is_dir():
|
|
461
|
-
shutil.copytree(outfile.path,
|
|
462
|
-
f'{folder}/{outfile.name}',
|
|
463
|
-
dirs_exist_ok=True,
|
|
464
|
-
copy_function=utils.link_symlink_copy)
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
def forward_output_files(chip, step, index):
|
|
468
|
-
for in_step, in_index in chip.get('record', 'inputnode', step=step, index=index):
|
|
469
|
-
in_workdir = chip.getworkdir(step=in_step, index=in_index)
|
|
470
|
-
for outfile in os.scandir(f"{in_workdir}/outputs"):
|
|
471
|
-
copy_output_file(chip, outfile, folder='outputs')
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
def _copy_previous_steps_output_data(chip, step, index, replay):
|
|
475
|
-
'''
|
|
476
|
-
Copy (link) output data from previous steps
|
|
477
|
-
'''
|
|
478
|
-
|
|
479
|
-
flow = chip.get('option', 'flow')
|
|
480
|
-
|
|
481
|
-
flow_schema = chip.schema.get("flowgraph", flow, field="schema")
|
|
482
|
-
runtime = RuntimeFlowgraph(
|
|
483
|
-
flow_schema,
|
|
484
|
-
from_steps=set([step for step, _ in flow_schema.get_entry_nodes()]),
|
|
485
|
-
prune_nodes=chip.get('option', 'prune'))
|
|
486
|
-
|
|
487
|
-
if not runtime.get_node_inputs(step, index, record=chip.schema.get("record", field="schema")):
|
|
488
|
-
all_inputs = []
|
|
489
|
-
elif not chip.get('record', 'inputnode', step=step, index=index):
|
|
490
|
-
all_inputs = runtime.get_node_inputs(step, index,
|
|
491
|
-
record=chip.schema.get("record", field="schema"))
|
|
492
|
-
else:
|
|
493
|
-
all_inputs = chip.get('record', 'inputnode', step=step, index=index)
|
|
494
|
-
|
|
495
|
-
strict = chip.get('option', 'strict')
|
|
496
|
-
tool, task = get_tool_task(chip, step, index)
|
|
497
|
-
in_files = chip.get('tool', tool, 'task', task, 'input', step=step, index=index)
|
|
498
|
-
for in_step, in_index in all_inputs:
|
|
499
|
-
if chip.get('record', 'status', step=in_step, index=in_index) == NodeStatus.ERROR:
|
|
500
|
-
chip.logger.error(f'Halting step due to previous error in {in_step}{in_index}')
|
|
501
|
-
_haltstep(chip, flow, step, index)
|
|
502
|
-
|
|
503
|
-
# Skip copying pkg.json files here, since we write the current chip
|
|
504
|
-
# configuration into inputs/{design}.pkg.json earlier in _runstep.
|
|
505
|
-
if not replay:
|
|
506
|
-
in_workdir = chip.getworkdir(step=in_step, index=in_index)
|
|
507
|
-
output_dir = os.path.join(in_workdir, "outputs")
|
|
508
|
-
|
|
509
|
-
if not os.path.isdir(output_dir):
|
|
510
|
-
chip.logger.error(
|
|
511
|
-
f'Unable to locate outputs directory for {in_step}{in_index}: {output_dir}')
|
|
512
|
-
_haltstep(chip, flow, step, index)
|
|
513
|
-
|
|
514
|
-
for outfile in os.scandir(output_dir):
|
|
515
|
-
new_name = input_file_node_name(outfile.name, in_step, in_index)
|
|
516
|
-
if strict:
|
|
517
|
-
if outfile.name not in in_files and new_name not in in_files:
|
|
518
|
-
continue
|
|
519
|
-
|
|
520
|
-
copy_output_file(chip, outfile)
|
|
521
|
-
|
|
522
|
-
if new_name in in_files:
|
|
523
|
-
# perform rename
|
|
524
|
-
os.rename(f'inputs/{outfile.name}', f'inputs/{new_name}')
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
############################################################################
|
|
528
|
-
# Chip helper Functions
|
|
529
|
-
############################################################################
|
|
530
|
-
def _check_logfile(chip, step, index, quiet=False, run_func=None):
|
|
531
|
-
'''
|
|
532
|
-
Check log file (must be after post-process)
|
|
533
|
-
'''
|
|
534
|
-
if run_func is None:
|
|
535
|
-
tool, task = get_tool_task(chip, step, index)
|
|
536
|
-
|
|
537
|
-
log_file = os.path.join(chip.getworkdir(step=step, index=index), f'{step}.log')
|
|
538
|
-
matches = check_logfile(chip, step=step, index=index,
|
|
539
|
-
display=not quiet,
|
|
540
|
-
logfile=log_file)
|
|
541
|
-
if 'errors' in matches:
|
|
542
|
-
errors = chip.get('metric', 'errors', step=step, index=index)
|
|
543
|
-
if errors is None:
|
|
544
|
-
errors = 0
|
|
545
|
-
errors += matches['errors']
|
|
546
|
-
|
|
547
|
-
sources = [f'{step}.log']
|
|
548
|
-
if chip.valid('tool', tool, 'task', task, 'regex', 'errors'):
|
|
549
|
-
if chip.get('tool', tool, 'task', task, 'regex', 'errors',
|
|
550
|
-
step=step, index=index):
|
|
551
|
-
sources.append(f'{step}.errors')
|
|
552
|
-
|
|
553
|
-
record_metric(chip, step, index, 'errors', errors, sources)
|
|
554
|
-
if 'warnings' in matches:
|
|
555
|
-
warnings = chip.get('metric', 'warnings', step=step, index=index)
|
|
556
|
-
if warnings is None:
|
|
557
|
-
warnings = 0
|
|
558
|
-
warnings += matches['warnings']
|
|
559
|
-
|
|
560
|
-
sources = [f'{step}.log']
|
|
561
|
-
if chip.valid('tool', tool, 'task', task, 'regex', 'warnings'):
|
|
562
|
-
if chip.get('tool', tool, 'task', task, 'regex', 'warnings',
|
|
563
|
-
step=step, index=index):
|
|
564
|
-
sources.append(f'{step}.warnings')
|
|
565
|
-
|
|
566
|
-
record_metric(chip, step, index, 'warnings', warnings, sources)
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
def _executenode(chip, step, index, replay):
|
|
570
|
-
workdir = chip.getworkdir(step=step, index=index)
|
|
571
|
-
flow = chip.get('option', 'flow')
|
|
572
|
-
tool, task = get_tool_task(chip, step, index, flow)
|
|
573
|
-
|
|
574
|
-
task_class = chip.get("tool", tool, field="schema")
|
|
575
|
-
task_class.set_runtime(chip)
|
|
576
|
-
|
|
577
|
-
chip.logger.info(f'Running in {workdir}')
|
|
578
|
-
|
|
579
|
-
try:
|
|
580
|
-
task_class.pre_process()
|
|
581
|
-
except Exception as e:
|
|
582
|
-
chip.logger.error(f"Pre-processing failed for '{tool}/{task}'.")
|
|
583
|
-
utils.print_traceback(chip.logger, e)
|
|
584
|
-
raise e
|
|
585
|
-
|
|
586
|
-
if chip.get('record', 'status', step=step, index=index) == NodeStatus.SKIPPED:
|
|
587
|
-
# copy inputs to outputs and skip execution
|
|
588
|
-
forward_output_files(chip, step, index)
|
|
589
|
-
|
|
590
|
-
send_messages.send(chip, "skipped", step, index)
|
|
591
|
-
else:
|
|
592
|
-
org_env = os.environ.copy()
|
|
593
|
-
os.environ.update(task_class.get_runtime_environmental_variables())
|
|
594
|
-
|
|
595
|
-
toolpath = task_class.get_exe()
|
|
596
|
-
version = task_class.get_exe_version()
|
|
597
|
-
|
|
598
|
-
if not chip.get('option', 'novercheck', step=step, index=index):
|
|
599
|
-
if not task_class.check_exe_version(version):
|
|
600
|
-
_haltstep(chip, flow, step, index)
|
|
601
|
-
|
|
602
|
-
if version:
|
|
603
|
-
chip.schema.get("record", field='schema').record_tool(
|
|
604
|
-
step, index, version, RecordTool.VERSION)
|
|
605
|
-
|
|
606
|
-
if toolpath:
|
|
607
|
-
chip.schema.get("record", field='schema').record_tool(
|
|
608
|
-
step, index, toolpath, RecordTool.PATH)
|
|
609
|
-
|
|
610
|
-
send_messages.send(chip, "begin", step, index)
|
|
611
|
-
|
|
612
|
-
try:
|
|
613
|
-
if not replay:
|
|
614
|
-
task_class.generate_replay_script(
|
|
615
|
-
os.path.join(workdir, "replay.sh"),
|
|
616
|
-
workdir)
|
|
617
|
-
ret_code = task_class.run_task(
|
|
618
|
-
workdir,
|
|
619
|
-
chip.get('option', 'quiet', step=step, index=index),
|
|
620
|
-
chip.get('option', 'loglevel', step=step, index=index),
|
|
621
|
-
chip.get('option', 'breakpoint', step=step, index=index),
|
|
622
|
-
chip.get('option', 'nice', step=step, index=index),
|
|
623
|
-
chip.get('option', 'timeout', step=step, index=index))
|
|
624
|
-
except Exception as e:
|
|
625
|
-
raise e
|
|
626
|
-
|
|
627
|
-
os.environ.clear()
|
|
628
|
-
os.environ.update(org_env)
|
|
629
|
-
|
|
630
|
-
if ret_code != 0:
|
|
631
|
-
msg = f'Command failed with code {ret_code}.'
|
|
632
|
-
logfile = f"{step}.log"
|
|
633
|
-
if os.path.exists(logfile):
|
|
634
|
-
if chip.get('option', 'quiet', step=step, index=index):
|
|
635
|
-
# Print last N lines of log when in quiet mode
|
|
636
|
-
with sc_open(logfile) as logfd:
|
|
637
|
-
loglines = logfd.read().splitlines()
|
|
638
|
-
for logline in loglines[-_failed_log_lines:]:
|
|
639
|
-
chip.logger.error(logline)
|
|
640
|
-
# No log file for pure-Python tools.
|
|
641
|
-
msg += f' See log file {os.path.abspath(logfile)}'
|
|
642
|
-
chip.logger.warning(msg)
|
|
643
|
-
chip._error = True
|
|
644
|
-
|
|
645
|
-
try:
|
|
646
|
-
task_class.post_process()
|
|
647
|
-
except Exception as e:
|
|
648
|
-
chip.logger.error(f"Post-processing failed for '{tool}/{task}'.")
|
|
649
|
-
utils.print_traceback(chip.logger, e)
|
|
650
|
-
chip._error = True
|
|
651
|
-
|
|
652
|
-
_finalizenode(chip, step, index, replay)
|
|
653
|
-
|
|
654
|
-
send_messages.send(chip, "end", step, index)
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
def _hash_files(chip, step, index, setup=False):
|
|
658
|
-
if chip._error:
|
|
659
|
-
return
|
|
660
|
-
|
|
661
|
-
flow = chip.get('option', 'flow')
|
|
662
|
-
tool, task = get_tool_task(chip, step, index, flow)
|
|
663
|
-
if chip.get('option', 'hash'):
|
|
664
|
-
if not setup:
|
|
665
|
-
# hash all outputs
|
|
666
|
-
chip.hash_files('tool', tool, 'task', task, 'output',
|
|
667
|
-
step=step, index=index, check=False, verbose=False)
|
|
668
|
-
else:
|
|
669
|
-
for task_key in ('refdir', 'prescript', 'postscript', 'script'):
|
|
670
|
-
chip.hash_files('tool', tool, 'task', task, task_key,
|
|
671
|
-
step=step, index=index, check=False,
|
|
672
|
-
allow_cache=True, verbose=False)
|
|
673
|
-
|
|
674
|
-
# hash all requirements
|
|
675
|
-
for item in set(chip.get('tool', tool, 'task', task, 'require', step=step, index=index)):
|
|
676
|
-
args = item.split(',')
|
|
677
|
-
sc_type = chip.get(*args, field='type')
|
|
678
|
-
if 'file' in sc_type or 'dir' in sc_type:
|
|
679
|
-
if chip.get(*args, field='pernode').is_never():
|
|
680
|
-
if not setup:
|
|
681
|
-
if chip.get(*args, field='filehash'):
|
|
682
|
-
continue
|
|
683
|
-
chip.hash_files(*args, check=False, allow_cache=True, verbose=False)
|
|
684
|
-
else:
|
|
685
|
-
if not setup:
|
|
686
|
-
if chip.get(*args, field='filehash', step=step, index=index):
|
|
687
|
-
continue
|
|
688
|
-
chip.hash_files(*args, step=step, index=index,
|
|
689
|
-
check=False, allow_cache=True, verbose=False)
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
def _finalizenode(chip, step, index, replay):
|
|
693
|
-
if chip.schema.is_journaling() and any(
|
|
694
|
-
[record["type"] == "get" for record in chip.schema.get_journal()]):
|
|
695
|
-
assert_required_accesses(chip, step, index)
|
|
696
|
-
|
|
697
|
-
flow = chip.get('option', 'flow')
|
|
698
|
-
tool, task = get_tool_task(chip, step, index, flow)
|
|
699
|
-
quiet = (
|
|
700
|
-
chip.get('option', 'quiet', step=step, index=index) and not
|
|
701
|
-
chip.get('option', 'breakpoint', step=step, index=index)
|
|
702
|
-
)
|
|
703
|
-
|
|
704
|
-
is_skipped = chip.get('record', 'status', step=step, index=index) == NodeStatus.SKIPPED
|
|
705
|
-
|
|
706
|
-
if not is_skipped:
|
|
707
|
-
_check_logfile(chip, step, index, quiet, None)
|
|
708
|
-
|
|
709
|
-
# Report metrics
|
|
710
|
-
for metric in ['errors', 'warnings']:
|
|
711
|
-
val = chip.get('metric', metric, step=step, index=index)
|
|
712
|
-
if val is not None:
|
|
713
|
-
chip.logger.info(f'Number of {metric}: {val}')
|
|
714
|
-
|
|
715
|
-
_hash_files(chip, step, index)
|
|
716
|
-
|
|
717
|
-
# Capture wall runtime and cpu cores
|
|
718
|
-
end_time = chip.schema.get("record", field='schema').record_time(step, index, RecordTime.END)
|
|
719
|
-
|
|
720
|
-
walltime = end_time - chip.schema.get("record", field='schema').get_recorded_time(
|
|
721
|
-
step, index, RecordTime.START)
|
|
722
|
-
record_metric(chip, step, index, 'tasktime', walltime,
|
|
723
|
-
source=None, source_unit='s')
|
|
724
|
-
|
|
725
|
-
chip.schema.get("metric", field='schema').record_totaltime(
|
|
726
|
-
step, index,
|
|
727
|
-
chip.schema.get("flowgraph", flow, field='schema'),
|
|
728
|
-
chip.schema.get("record", field='schema'))
|
|
729
|
-
chip.logger.info(f"Finished task in {round(walltime, 2)}s")
|
|
730
|
-
|
|
731
|
-
# Save a successful manifest
|
|
732
|
-
if not is_skipped:
|
|
733
|
-
chip.schema.get("record", field='schema').set('status', NodeStatus.SUCCESS,
|
|
734
|
-
step=step, index=index)
|
|
735
|
-
|
|
736
|
-
chip.write_manifest(os.path.join("outputs", f"{chip.get('design')}.pkg.json"))
|
|
737
|
-
|
|
738
|
-
if chip._error and not replay:
|
|
739
|
-
_make_testcase(chip, step, index)
|
|
740
|
-
|
|
741
|
-
# Stop if there are errors
|
|
742
|
-
errors = chip.get('metric', 'errors', step=step, index=index)
|
|
743
|
-
if errors and not chip.get('option', 'continue', step=step, index=index):
|
|
744
|
-
# TODO: should we warn if errors is not set?
|
|
745
|
-
chip.logger.error(f'{tool} reported {errors} errors during {step}{index}')
|
|
746
|
-
_haltstep(chip, flow, step, index)
|
|
747
|
-
|
|
748
|
-
if chip._error:
|
|
749
|
-
_haltstep(chip, flow, step, index)
|
|
750
|
-
|
|
751
|
-
if chip.get('option', 'strict'):
|
|
752
|
-
assert_output_files(chip, step, index)
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
def _make_testcase(chip, step, index):
|
|
756
|
-
# Import here to avoid circular import
|
|
757
|
-
from siliconcompiler.utils.issue import generate_testcase
|
|
758
|
-
|
|
759
|
-
generate_testcase(
|
|
760
|
-
chip,
|
|
761
|
-
step,
|
|
762
|
-
index,
|
|
763
|
-
archive_directory=chip.getworkdir(),
|
|
764
|
-
include_pdks=False,
|
|
765
|
-
include_specific_pdks=lambdapdk.get_pdks(),
|
|
766
|
-
include_libraries=False,
|
|
767
|
-
include_specific_libraries=lambdapdk.get_libs(),
|
|
768
|
-
hash_files=chip.get('option', 'hash'),
|
|
769
|
-
verbose_collect=False)
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
def assert_output_files(chip, step, index):
|
|
773
|
-
flow = chip.get('option', 'flow')
|
|
774
|
-
tool, task = get_tool_task(chip, step, index, flow)
|
|
775
|
-
|
|
776
|
-
if tool == 'builtin':
|
|
777
|
-
return
|
|
778
|
-
|
|
779
|
-
outputs = os.listdir(f'{chip.getworkdir(step=step, index=index)}/outputs')
|
|
780
|
-
outputs.remove(f'{chip.design}.pkg.json')
|
|
781
|
-
|
|
782
|
-
output_files = chip.get('tool', tool, 'task', task, 'output',
|
|
783
|
-
step=step, index=index)
|
|
784
|
-
|
|
785
|
-
if set(outputs) != set(output_files):
|
|
786
|
-
raise SiliconCompilerError(
|
|
787
|
-
f'Output files set {output_files} for {step}{index} does not match generated '
|
|
788
|
-
f'outputs: {outputs}',
|
|
789
|
-
chip=chip)
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
def assert_required_accesses(chip, step, index):
|
|
793
|
-
flow = chip.get('option', 'flow')
|
|
794
|
-
jobname = chip.get('option', 'jobname')
|
|
795
|
-
tool, task = get_tool_task(chip, step, index, flow)
|
|
796
|
-
|
|
797
|
-
if tool == 'builtin':
|
|
798
|
-
return
|
|
799
|
-
|
|
800
|
-
gets = set([tuple(record["key"]) for record in chip.schema.get_journal()
|
|
801
|
-
if record["type"] == "get"])
|
|
802
|
-
logfile = os.path.join(
|
|
803
|
-
chip.getworkdir(jobname=jobname, step=step, index=index),
|
|
804
|
-
f'{step}.log')
|
|
805
|
-
|
|
806
|
-
with sc_open(logfile) as f:
|
|
807
|
-
for line in f:
|
|
808
|
-
if line.startswith(Schema._RECORD_ACCESS_IDENTIFIER):
|
|
809
|
-
key = line[len(Schema._RECORD_ACCESS_IDENTIFIER):].strip().split(',')
|
|
810
|
-
if chip.valid(*key, check_complete=True):
|
|
811
|
-
gets.add(tuple(key))
|
|
812
|
-
|
|
813
|
-
def get_value(*key):
|
|
814
|
-
if chip.get(*key, field='pernode').is_never():
|
|
815
|
-
return chip.get(*key)
|
|
816
|
-
else:
|
|
817
|
-
return chip.get(*key, step=step, index=index)
|
|
818
|
-
|
|
819
|
-
getkeys = set()
|
|
820
|
-
# Remove keys with empty values
|
|
821
|
-
for key in set(sorted(gets)):
|
|
822
|
-
if get_value(*key):
|
|
823
|
-
getkeys.add(key)
|
|
824
|
-
|
|
825
|
-
# Remove keys that dont matter
|
|
826
|
-
exempt = [
|
|
827
|
-
('design',),
|
|
828
|
-
('arg', 'step'), ('arg', 'index'),
|
|
829
|
-
('option', 'jobname'), ('option', 'flow'), ('option', 'strict'), ('option', 'builddir'),
|
|
830
|
-
('option', 'quiet'),
|
|
831
|
-
('tool', tool, 'exe'),
|
|
832
|
-
('tool', tool, 'task', task, 'require'),
|
|
833
|
-
('tool', tool, 'task', task, 'threads'),
|
|
834
|
-
('flowgraph', flow, step, index, 'tool'), ('flowgraph', flow, step, index, 'task'),
|
|
835
|
-
('flowgraph', flow, step, index, 'taskmodule')]
|
|
836
|
-
for key in chip.getkeys('metric'):
|
|
837
|
-
exempt.append(('metric', key))
|
|
838
|
-
for key in chip.getkeys('tool', tool, 'task', task, 'report'):
|
|
839
|
-
exempt.append(('tool', tool, 'task', task, 'report', key))
|
|
840
|
-
|
|
841
|
-
required = set(
|
|
842
|
-
[tuple(key.split(',')) for key in chip.get('tool', tool, 'task', task, 'require',
|
|
843
|
-
step=step, index=index)])
|
|
844
|
-
|
|
845
|
-
for key in set(exempt):
|
|
846
|
-
if key in getkeys:
|
|
847
|
-
getkeys.remove(key)
|
|
848
|
-
if key in required:
|
|
849
|
-
required.remove(key)
|
|
850
|
-
|
|
851
|
-
excess_require = required.difference(getkeys)
|
|
852
|
-
if True:
|
|
853
|
-
for key in sorted(excess_require):
|
|
854
|
-
chip.logger.error(f"{step}{index} does not require requirement: {','.join(key)}")
|
|
855
|
-
missing_require = getkeys.difference(required)
|
|
856
|
-
for key in sorted(missing_require):
|
|
857
|
-
chip.logger.error(f"{step}{index} has an unexpressed requirement: "
|
|
858
|
-
f"{','.join(key)} = {get_value(*key)}")
|
|
859
|
-
|
|
860
|
-
if missing_require:
|
|
861
|
-
raise SiliconCompilerError(
|
|
862
|
-
f'Requirements for {step}{index} does not match access list: '
|
|
863
|
-
f'{", ".join([",".join(key) for key in sorted(missing_require)])}',
|
|
864
|
-
chip=chip)
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
def _reset_flow_nodes(chip, flow, nodes_to_execute):
|
|
868
|
-
# Reset flowgraph/records/metrics by probing build directory. We need
|
|
869
|
-
# to set values to None for steps we may re-run so that merging
|
|
870
|
-
# manifests from _runtask() actually updates values.
|
|
871
|
-
|
|
872
|
-
def clear_node(step, index):
|
|
873
|
-
# Reset metrics and records
|
|
874
|
-
chip.schema.get("metric", field='schema').clear(step, index)
|
|
875
|
-
for metric in chip.getkeys('metric'):
|
|
876
|
-
_clear_metric(chip, step, index, metric)
|
|
877
|
-
|
|
878
|
-
chip.schema.get("record", field='schema').clear(
|
|
879
|
-
step, index, keep=['remoteid', 'status', 'pythonpackage'])
|
|
880
|
-
|
|
881
|
-
# Mark all nodes as pending
|
|
882
|
-
for step, index in chip.schema.get("flowgraph", flow, field="schema").get_nodes():
|
|
883
|
-
chip.schema.get("record", field='schema').set('status', NodeStatus.PENDING,
|
|
884
|
-
step=step, index=index)
|
|
885
|
-
|
|
886
|
-
should_resume = not chip.get('option', 'clean')
|
|
887
|
-
for step, index in chip.schema.get("flowgraph", flow, field="schema").get_nodes():
|
|
888
|
-
stepdir = chip.getworkdir(step=step, index=index)
|
|
889
|
-
cfg = f"{stepdir}/outputs/{chip.get('design')}.pkg.json"
|
|
890
|
-
|
|
891
|
-
if not os.path.isdir(stepdir) or (
|
|
892
|
-
(step, index) in nodes_to_execute and not should_resume):
|
|
893
|
-
# If stepdir doesn't exist, we need to re-run this task. If
|
|
894
|
-
# we're not running with -resume, we also re-run anything
|
|
895
|
-
# in the nodes to execute.
|
|
896
|
-
clear_node(step, index)
|
|
897
|
-
elif os.path.isfile(cfg):
|
|
898
|
-
try:
|
|
899
|
-
old_status = Schema(manifest=cfg).get('record', 'status', step=step, index=index)
|
|
900
|
-
if old_status:
|
|
901
|
-
chip.schema.get("record", field='schema').set('status', old_status,
|
|
902
|
-
step=step, index=index)
|
|
903
|
-
except Exception:
|
|
904
|
-
# unable to load so leave it default
|
|
905
|
-
pass
|
|
906
|
-
else:
|
|
907
|
-
chip.schema.get("record", field='schema').set('status', NodeStatus.ERROR,
|
|
908
|
-
step=step, index=index)
|
|
909
|
-
|
|
910
|
-
for step in chip.getkeys('flowgraph', flow):
|
|
911
|
-
all_indices_failed = True
|
|
912
|
-
for index in chip.getkeys('flowgraph', flow, step):
|
|
913
|
-
if chip.get('record', 'status', step=step, index=index) == NodeStatus.SUCCESS:
|
|
914
|
-
all_indices_failed = False
|
|
915
|
-
|
|
916
|
-
if should_resume and all_indices_failed:
|
|
917
|
-
# When running with -resume, we re-run any step in flowgraph that
|
|
918
|
-
# had all indices fail.
|
|
919
|
-
for index in chip.getkeys('flowgraph', flow, step):
|
|
920
|
-
if (step, index) in nodes_to_execute:
|
|
921
|
-
clear_node(step, index)
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
def _check_nodes_status(chip, flow):
|
|
925
|
-
flowgraph = chip.schema.get("flowgraph", flow, field="schema")
|
|
926
|
-
runtime = RuntimeFlowgraph(
|
|
927
|
-
flowgraph,
|
|
928
|
-
from_steps=chip.get('option', 'from'),
|
|
929
|
-
to_steps=chip.get('option', 'to'),
|
|
930
|
-
prune_nodes=chip.get('option', 'prune'))
|
|
931
|
-
runtime_no_prune = RuntimeFlowgraph(
|
|
932
|
-
flowgraph,
|
|
933
|
-
from_steps=chip.get('option', 'from'),
|
|
934
|
-
to_steps=chip.get('option', 'to'))
|
|
935
|
-
|
|
936
|
-
all_steps = [step for step, index in runtime_no_prune.get_exit_nodes()
|
|
937
|
-
if (step, index) not in chip.get('option', 'prune')]
|
|
938
|
-
complete_steps = [step for step, _ in runtime.get_completed_nodes(
|
|
939
|
-
record=chip.schema.get("record", field='schema'))]
|
|
940
|
-
|
|
941
|
-
unreached = set(all_steps).difference(complete_steps)
|
|
942
|
-
|
|
943
|
-
if unreached:
|
|
944
|
-
raise SiliconCompilerError(
|
|
945
|
-
f'These final steps could not be reached: {",".join(sorted(unreached))}', chip=chip)
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
def get_check_node_keys(chip, step, index):
|
|
949
|
-
tool, task = get_tool_task(chip, step, index)
|
|
950
|
-
|
|
951
|
-
# Collect keys to check for changes
|
|
952
|
-
required = chip.get('tool', tool, 'task', task, 'require', step=step, index=index)
|
|
953
|
-
|
|
954
|
-
tool_task_key = ('tool', tool, 'task', task)
|
|
955
|
-
for key in ('option', 'threads', 'prescript', 'postscript', 'refdir', 'script',):
|
|
956
|
-
required.append(",".join([*tool_task_key, key]))
|
|
957
|
-
|
|
958
|
-
for env_key in chip.getkeys(*tool_task_key, 'env'):
|
|
959
|
-
required.append(",".join([*tool_task_key, 'env', env_key]))
|
|
960
|
-
|
|
961
|
-
return set(sorted(required))
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
def check_node_inputs(chip, step, index):
|
|
965
|
-
from siliconcompiler import Chip # import here to avoid circular import
|
|
966
|
-
|
|
967
|
-
if chip.get('option', 'clean'):
|
|
968
|
-
return True
|
|
969
|
-
|
|
970
|
-
def get_file_time(path):
|
|
971
|
-
times = [os.path.getmtime(path)]
|
|
972
|
-
if os.path.isdir(path):
|
|
973
|
-
for path_root, _, files in os.walk(path):
|
|
974
|
-
for path_end in files:
|
|
975
|
-
times.append(os.path.getmtime(os.path.join(path_root, path_end)))
|
|
976
|
-
|
|
977
|
-
return max(times)
|
|
978
|
-
|
|
979
|
-
# Load previous manifest
|
|
980
|
-
input_manifest = None
|
|
981
|
-
in_cfg = f"{chip.getworkdir(step=step, index=index)}/inputs/{chip.design}.pkg.json"
|
|
982
|
-
if os.path.exists(in_cfg):
|
|
983
|
-
input_manifest_time = get_file_time(in_cfg)
|
|
984
|
-
input_manifest = Schema(manifest=in_cfg, logger=chip.logger)
|
|
985
|
-
|
|
986
|
-
if not input_manifest:
|
|
987
|
-
# No manifest found so assume okay
|
|
988
|
-
return True
|
|
989
|
-
|
|
990
|
-
flow = chip.get('option', 'flow')
|
|
991
|
-
input_flow = input_manifest.get('option', 'flow')
|
|
992
|
-
|
|
993
|
-
# Assume modified if flow does not match
|
|
994
|
-
if flow != input_flow:
|
|
995
|
-
return False
|
|
996
|
-
|
|
997
|
-
input_chip = Chip('<>')
|
|
998
|
-
input_chip.schema = input_manifest
|
|
999
|
-
# Copy over useful information from chip
|
|
1000
|
-
input_chip.logger = chip.logger
|
|
1001
|
-
input_chip._packages = chip._packages
|
|
1002
|
-
|
|
1003
|
-
tool, task = get_tool_task(chip, step, index)
|
|
1004
|
-
input_tool, input_task = get_tool_task(input_chip, step, index)
|
|
1005
|
-
|
|
1006
|
-
# Assume modified if tool or task does not match
|
|
1007
|
-
if tool != input_tool or task != input_task:
|
|
1008
|
-
return False
|
|
1009
|
-
|
|
1010
|
-
# Check if inputs changed
|
|
1011
|
-
new_inputs = set(_select_inputs(chip, step, index, trial=True))
|
|
1012
|
-
if set(input_chip.get('record', 'inputnode', step=step, index=index)) != new_inputs:
|
|
1013
|
-
chip.logger.warning(f'inputs to {step}{index} has been modified from previous run')
|
|
1014
|
-
return False
|
|
1015
|
-
|
|
1016
|
-
# Collect keys to check for changes
|
|
1017
|
-
required = get_check_node_keys(chip, step, index)
|
|
1018
|
-
required.update(get_check_node_keys(input_chip, step, index))
|
|
1019
|
-
|
|
1020
|
-
def print_warning(key, extra=None):
|
|
1021
|
-
if extra:
|
|
1022
|
-
chip.logger.warning(f'[{",".join(key)}] ({extra}) in {step}{index} has been modified '
|
|
1023
|
-
'from previous run')
|
|
1024
|
-
else:
|
|
1025
|
-
chip.logger.warning(f'[{",".join(key)}] in {step}{index} has been modified '
|
|
1026
|
-
'from previous run')
|
|
1027
|
-
|
|
1028
|
-
# Check if keys have been modified
|
|
1029
|
-
for check_key in required:
|
|
1030
|
-
key = check_key.split(',')
|
|
1031
|
-
|
|
1032
|
-
if not chip.valid(*key) or not input_chip.valid(*key):
|
|
1033
|
-
print_warning(key)
|
|
1034
|
-
return False
|
|
1035
|
-
|
|
1036
|
-
check_step = step
|
|
1037
|
-
check_index = index
|
|
1038
|
-
if chip.get(*key, field='pernode').is_never():
|
|
1039
|
-
check_step = None
|
|
1040
|
-
check_index = None
|
|
1041
|
-
|
|
1042
|
-
sc_type = chip.get(*key, field='type')
|
|
1043
|
-
if 'file' in sc_type or 'dir' in sc_type:
|
|
1044
|
-
if chip.get('option', 'hash') and input_chip.get('option', 'hash'):
|
|
1045
|
-
check_hash = chip.hash_files(*key, update=False, check=False,
|
|
1046
|
-
verbose=False, allow_cache=True,
|
|
1047
|
-
step=check_step, index=check_index)
|
|
1048
|
-
prev_hash = input_chip.get(*key, field='filehash',
|
|
1049
|
-
step=check_step, index=check_index)
|
|
1050
|
-
|
|
1051
|
-
if check_hash != prev_hash:
|
|
1052
|
-
print_warning(key, "file hash")
|
|
1053
|
-
return False
|
|
1054
|
-
else:
|
|
1055
|
-
# check timestamps on current files
|
|
1056
|
-
for check_file in chip.find_files(*key, step=check_step, index=check_index):
|
|
1057
|
-
if get_file_time(check_file) > input_manifest_time:
|
|
1058
|
-
print_warning(key, "timestamp")
|
|
1059
|
-
return False
|
|
1060
|
-
|
|
1061
|
-
# check values
|
|
1062
|
-
for field in ('value', 'package'):
|
|
1063
|
-
check_val = chip.get(*key, field=field, step=check_step, index=check_index)
|
|
1064
|
-
prev_val = input_chip.get(*key, field=field, step=check_step, index=check_index)
|
|
1065
|
-
|
|
1066
|
-
if check_val != prev_val:
|
|
1067
|
-
print_warning(key)
|
|
1068
|
-
return False
|
|
1069
|
-
else:
|
|
1070
|
-
check_val = chip.get(*key, step=check_step, index=check_index)
|
|
1071
|
-
prev_val = input_chip.get(*key, step=check_step, index=check_index)
|
|
1072
|
-
|
|
1073
|
-
if check_val != prev_val:
|
|
1074
|
-
print_warning(key)
|
|
1075
|
-
return False
|
|
1076
|
-
|
|
1077
|
-
return True
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
###########################################################################
|
|
1081
|
-
def check_logfile(chip, jobname=None, step=None, index='0',
|
|
1082
|
-
logfile=None, display=True):
|
|
1083
|
-
'''
|
|
1084
|
-
Checks logfile for patterns found in the 'regex' parameter.
|
|
1085
|
-
|
|
1086
|
-
Reads the content of the task's log file and compares the content found
|
|
1087
|
-
with the task's 'regex' parameter. The matches are stored in the file
|
|
1088
|
-
'<step>.<suffix>' in the current directory. The matches are logged
|
|
1089
|
-
if display is set to True.
|
|
1090
|
-
|
|
1091
|
-
Args:
|
|
1092
|
-
jobname (str): Job directory name. If None, :keypath:`option, jobname` is used.
|
|
1093
|
-
step (str): Task step name ('syn', 'place', etc). If None, :keypath:`arg, step` is used.
|
|
1094
|
-
index (str): Task index. Default value is 0. If None, :keypath:`arg, index` is used.
|
|
1095
|
-
logfile (str): Path to logfile. If None, the default task logfile is used.
|
|
1096
|
-
display (bool): If True, logs matches.
|
|
1097
|
-
|
|
1098
|
-
Returns:
|
|
1099
|
-
Dictionary mapping suffixes to number of matches for that suffix's
|
|
1100
|
-
regex.
|
|
1101
|
-
|
|
1102
|
-
Examples:
|
|
1103
|
-
>>> chip.check_logfile(step='place')
|
|
1104
|
-
Searches for regex matches in the place logfile.
|
|
1105
|
-
'''
|
|
1106
|
-
|
|
1107
|
-
# Using manifest to get defaults
|
|
1108
|
-
|
|
1109
|
-
flow = chip.get('option', 'flow')
|
|
1110
|
-
|
|
1111
|
-
if jobname is None:
|
|
1112
|
-
jobname = chip.get('option', 'jobname')
|
|
1113
|
-
if step is None:
|
|
1114
|
-
step = chip.get('arg', 'step')
|
|
1115
|
-
if step is None:
|
|
1116
|
-
raise ValueError("Must provide 'step' or set ['arg', 'step']")
|
|
1117
|
-
if index is None:
|
|
1118
|
-
index = chip.get('arg', 'index')
|
|
1119
|
-
if index is None:
|
|
1120
|
-
raise ValueError("Must provide 'index' or set ['arg', 'index']")
|
|
1121
|
-
if logfile is None:
|
|
1122
|
-
logfile = os.path.join(chip.getworkdir(jobname=jobname, step=step, index=index),
|
|
1123
|
-
f'{step}.log')
|
|
1124
|
-
|
|
1125
|
-
tool, task = get_tool_task(chip, step, index, flow=flow)
|
|
1126
|
-
|
|
1127
|
-
# Creating local dictionary (for speed)
|
|
1128
|
-
# chip.get is slow
|
|
1129
|
-
checks = {}
|
|
1130
|
-
matches = {}
|
|
1131
|
-
for suffix in chip.getkeys('tool', tool, 'task', task, 'regex'):
|
|
1132
|
-
regexes = chip.get('tool', tool, 'task', task, 'regex', suffix, step=step, index=index)
|
|
1133
|
-
if not regexes:
|
|
1134
|
-
continue
|
|
1135
|
-
|
|
1136
|
-
checks[suffix] = {}
|
|
1137
|
-
checks[suffix]['report'] = open(f"{step}.{suffix}", "w")
|
|
1138
|
-
checks[suffix]['args'] = regexes
|
|
1139
|
-
matches[suffix] = 0
|
|
1140
|
-
|
|
1141
|
-
# Order suffixes as follows: [..., 'warnings', 'errors']
|
|
1142
|
-
ordered_suffixes = list(filter(lambda key:
|
|
1143
|
-
key not in ['warnings', 'errors'], checks.keys()))
|
|
1144
|
-
if 'warnings' in checks:
|
|
1145
|
-
ordered_suffixes.append('warnings')
|
|
1146
|
-
if 'errors' in checks:
|
|
1147
|
-
ordered_suffixes.append('errors')
|
|
1148
|
-
|
|
1149
|
-
# Looping through patterns for each line
|
|
1150
|
-
with sc_open(logfile) as f:
|
|
1151
|
-
line_count = sum(1 for _ in f)
|
|
1152
|
-
right_align = len(str(line_count))
|
|
1153
|
-
for suffix in ordered_suffixes:
|
|
1154
|
-
# Start at the beginning of file again
|
|
1155
|
-
f.seek(0)
|
|
1156
|
-
for num, line in enumerate(f, start=1):
|
|
1157
|
-
string = line
|
|
1158
|
-
for item in checks[suffix]['args']:
|
|
1159
|
-
if string is None:
|
|
1160
|
-
break
|
|
1161
|
-
else:
|
|
1162
|
-
string = utils.grep(chip, item, string)
|
|
1163
|
-
if string is not None:
|
|
1164
|
-
matches[suffix] += 1
|
|
1165
|
-
# always print to file
|
|
1166
|
-
line_with_num = f'{num: >{right_align}}: {string.strip()}'
|
|
1167
|
-
print(line_with_num, file=checks[suffix]['report'])
|
|
1168
|
-
# selectively print to display
|
|
1169
|
-
if display:
|
|
1170
|
-
if suffix == 'errors':
|
|
1171
|
-
chip.logger.error(line_with_num)
|
|
1172
|
-
elif suffix == 'warnings':
|
|
1173
|
-
chip.logger.warning(line_with_num)
|
|
1174
|
-
else:
|
|
1175
|
-
chip.logger.info(f'{suffix}: {line_with_num}')
|
|
1176
|
-
|
|
1177
|
-
for suffix in ordered_suffixes:
|
|
1178
|
-
checks[suffix]['report'].close()
|
|
1179
|
-
|
|
1180
|
-
return matches
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
def copy_old_run_dir(chip, org_jobname):
|
|
1184
|
-
from_nodes = []
|
|
1185
|
-
flow = chip.get('option', 'flow')
|
|
1186
|
-
for step in chip.get('option', 'from'):
|
|
1187
|
-
from_nodes.extend(
|
|
1188
|
-
[(step, index) for index in chip.getkeys('flowgraph', flow, step)])
|
|
1189
|
-
from_nodes = set(from_nodes)
|
|
1190
|
-
if not from_nodes:
|
|
1191
|
-
# Nothing to do
|
|
1192
|
-
return
|
|
1193
|
-
|
|
1194
|
-
if org_jobname == chip.get('option', 'jobname'):
|
|
1195
|
-
return
|
|
1196
|
-
|
|
1197
|
-
# Copy nodes forward
|
|
1198
|
-
runtime = RuntimeFlowgraph(
|
|
1199
|
-
chip.schema.get("flowgraph", flow, field="schema"),
|
|
1200
|
-
to_steps=chip.get('option', 'from'),
|
|
1201
|
-
prune_nodes=chip.get('option', 'prune'))
|
|
1202
|
-
org_nodes = set(runtime.get_nodes())
|
|
1203
|
-
|
|
1204
|
-
copy_nodes = org_nodes.difference(from_nodes)
|
|
1205
|
-
|
|
1206
|
-
def copy_files(from_path, to_path):
|
|
1207
|
-
shutil.copytree(from_path, to_path,
|
|
1208
|
-
dirs_exist_ok=True,
|
|
1209
|
-
copy_function=utils.link_copy)
|
|
1210
|
-
|
|
1211
|
-
for step, index in copy_nodes:
|
|
1212
|
-
copy_from = chip.getworkdir(jobname=org_jobname, step=step, index=index)
|
|
1213
|
-
copy_to = chip.getworkdir(step=step, index=index)
|
|
1214
|
-
|
|
1215
|
-
if not os.path.exists(copy_from):
|
|
1216
|
-
continue
|
|
1217
|
-
|
|
1218
|
-
chip.logger.info(f'Importing {step}{index} from {org_jobname}')
|
|
1219
|
-
copy_files(copy_from, copy_to)
|
|
1220
|
-
|
|
1221
|
-
# Copy collect directory
|
|
1222
|
-
copy_from = chip._getcollectdir(jobname=org_jobname)
|
|
1223
|
-
copy_to = chip._getcollectdir()
|
|
1224
|
-
if os.path.exists(copy_from):
|
|
1225
|
-
copy_files(copy_from, copy_to)
|
|
1226
|
-
|
|
1227
|
-
# Modify manifests to correct jobname
|
|
1228
|
-
for step, index in copy_nodes:
|
|
1229
|
-
tool, _ = get_tool_task(chip, step, index)
|
|
1230
|
-
task_class = chip.get("tool", tool, field="schema")
|
|
1231
|
-
|
|
1232
|
-
# rewrite replay files
|
|
1233
|
-
replay_file = f'{chip.getworkdir(step=step, index=index)}/replay.sh'
|
|
1234
|
-
if os.path.exists(replay_file):
|
|
1235
|
-
# delete file as it might be a hard link
|
|
1236
|
-
os.remove(replay_file)
|
|
1237
|
-
chip.set('arg', 'step', step)
|
|
1238
|
-
chip.set('arg', 'index', index)
|
|
1239
|
-
task_class.set_runtime(chip, step=step, index=index)
|
|
1240
|
-
task_class.generate_replay_script(replay_file, chip.getworkdir(step=step, index=index))
|
|
1241
|
-
task_class.set_runtime(None)
|
|
1242
|
-
chip.unset('arg', 'step')
|
|
1243
|
-
chip.unset('arg', 'index')
|
|
1244
|
-
|
|
1245
|
-
for io in ('inputs', 'outputs'):
|
|
1246
|
-
manifest = f'{chip.getworkdir(step=step, index=index)}/{io}/{chip.design}.pkg.json'
|
|
1247
|
-
if os.path.exists(manifest):
|
|
1248
|
-
schema = Schema(manifest=manifest)
|
|
1249
|
-
# delete file as it might be a hard link
|
|
1250
|
-
os.remove(manifest)
|
|
1251
|
-
schema.set('option', 'jobname', chip.get('option', 'jobname'))
|
|
1252
|
-
schema.write_manifest(manifest)
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
def clean_node_dir(chip, step, index):
|
|
1256
|
-
node_dir = chip.getworkdir(step=step, index=index)
|
|
1257
|
-
if os.path.isdir(node_dir):
|
|
1258
|
-
shutil.rmtree(node_dir)
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
def clean_build_dir(chip):
|
|
1262
|
-
if chip.get('record', 'remoteid'):
|
|
1263
|
-
return
|
|
1264
|
-
|
|
1265
|
-
if chip.get('arg', 'step'):
|
|
1266
|
-
return
|
|
1267
|
-
|
|
1268
|
-
if chip.get('option', 'clean') and not chip.get('option', 'from'):
|
|
1269
|
-
# If no step or nodes to start from were specified, the whole flow is being run
|
|
1270
|
-
# start-to-finish. Delete the build dir to clear stale results.
|
|
1271
|
-
cur_job_dir = chip.getworkdir()
|
|
1272
|
-
if os.path.isdir(cur_job_dir):
|
|
1273
|
-
shutil.rmtree(cur_job_dir)
|
|
1274
|
-
|
|
1275
|
-
return
|
|
1276
|
-
|
|
1277
|
-
if chip.get('option', 'from'):
|
|
1278
|
-
runtime = RuntimeFlowgraph(
|
|
1279
|
-
chip.schema.get("flowgraph", chip.get('option', 'flow'), field='schema'),
|
|
1280
|
-
from_steps=chip.get('option', 'from'),
|
|
1281
|
-
to_steps=chip.get('option', 'to'),
|
|
1282
|
-
prune_nodes=chip.get('option', 'prune'))
|
|
1283
|
-
# Remove stale outputs that will be rerun
|
|
1284
|
-
for step, index in runtime.get_nodes():
|
|
1285
|
-
clean_node_dir(chip, step, index)
|
|
1286
|
-
|
|
1287
|
-
all_nodes = set(chip.schema.get("flowgraph", chip.get('option', 'flow'),
|
|
1288
|
-
field="schema").get_nodes())
|
|
1289
|
-
old_nodes = __collect_nodes_in_workdir(chip)
|
|
1290
|
-
node_mismatch = old_nodes.difference(all_nodes)
|
|
1291
|
-
if node_mismatch:
|
|
1292
|
-
# flow has different structure so clear whole
|
|
1293
|
-
cur_job_dir = chip.getworkdir()
|
|
1294
|
-
shutil.rmtree(cur_job_dir)
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
def __collect_nodes_in_workdir(chip):
|
|
1298
|
-
workdir = chip.getworkdir()
|
|
1299
|
-
if not os.path.isdir(workdir):
|
|
1300
|
-
return set()
|
|
1301
|
-
|
|
1302
|
-
collect_dir = chip._getcollectdir()
|
|
1303
|
-
|
|
1304
|
-
nodes = []
|
|
1305
|
-
for step in os.listdir(workdir):
|
|
1306
|
-
step_dir = os.path.join(workdir, step)
|
|
1307
|
-
|
|
1308
|
-
if step_dir == collect_dir:
|
|
1309
|
-
continue
|
|
1310
|
-
|
|
1311
|
-
if not os.path.isdir(step_dir):
|
|
1312
|
-
continue
|
|
1313
|
-
|
|
1314
|
-
for index in os.listdir(step_dir):
|
|
1315
|
-
if os.path.isdir(os.path.join(step_dir, index)):
|
|
1316
|
-
nodes.append((step, index))
|
|
1317
|
-
|
|
1318
|
-
return set(nodes)
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
###########################################################################
|
|
1322
|
-
def _check_manifest_dynamic(chip, step, index):
|
|
1323
|
-
'''Runtime checks called from _runtask().
|
|
1324
|
-
|
|
1325
|
-
- Make sure expected inputs exist.
|
|
1326
|
-
- Make sure all required filepaths resolve correctly.
|
|
1327
|
-
'''
|
|
1328
|
-
error = False
|
|
1329
|
-
|
|
1330
|
-
flow = chip.get('option', 'flow')
|
|
1331
|
-
tool, task = get_tool_task(chip, step, index, flow=flow)
|
|
1332
|
-
|
|
1333
|
-
required_inputs = chip.get('tool', tool, 'task', task, 'input', step=step, index=index)
|
|
1334
|
-
input_dir = os.path.join(chip.getworkdir(step=step, index=index), 'inputs')
|
|
1335
|
-
for filename in required_inputs:
|
|
1336
|
-
path = os.path.join(input_dir, filename)
|
|
1337
|
-
if not os.path.exists(path):
|
|
1338
|
-
chip.logger.error(f'Required input {filename} not received for {step}{index}.')
|
|
1339
|
-
error = True
|
|
1340
|
-
|
|
1341
|
-
all_required = chip.get('tool', tool, 'task', task, 'require', step=step, index=index)
|
|
1342
|
-
for item in all_required:
|
|
1343
|
-
keypath = item.split(',')
|
|
1344
|
-
if not chip.valid(*keypath):
|
|
1345
|
-
chip.logger.error(f'Cannot resolve required keypath {keypath}.')
|
|
1346
|
-
error = True
|
|
1347
|
-
else:
|
|
1348
|
-
paramtype = chip.get(*keypath, field='type')
|
|
1349
|
-
is_perstep = not chip.get(*keypath, field='pernode').is_never()
|
|
1350
|
-
if ('file' in paramtype) or ('dir' in paramtype):
|
|
1351
|
-
for val, check_step, check_index in chip.schema.get(*keypath,
|
|
1352
|
-
field=None).getvalues():
|
|
1353
|
-
if is_perstep:
|
|
1354
|
-
if check_step is None:
|
|
1355
|
-
check_step = Schema.GLOBAL_KEY
|
|
1356
|
-
if check_index is None:
|
|
1357
|
-
check_index = Schema.GLOBAL_KEY
|
|
1358
|
-
abspath = chip.find_files(*keypath,
|
|
1359
|
-
missing_ok=True,
|
|
1360
|
-
step=check_step, index=check_index)
|
|
1361
|
-
unresolved_paths = val
|
|
1362
|
-
if not isinstance(abspath, list):
|
|
1363
|
-
abspath = [abspath]
|
|
1364
|
-
unresolved_paths = [unresolved_paths]
|
|
1365
|
-
for i, path in enumerate(abspath):
|
|
1366
|
-
if path is None:
|
|
1367
|
-
unresolved_path = unresolved_paths[i]
|
|
1368
|
-
chip.logger.error(f'Cannot resolve path {unresolved_path} in '
|
|
1369
|
-
f'required file keypath {keypath}.')
|
|
1370
|
-
error = True
|
|
1371
|
-
|
|
1372
|
-
return not error
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
#######################################
|
|
1376
|
-
def _clear_metric(chip, step, index, metric):
|
|
1377
|
-
'''
|
|
1378
|
-
Helper function to clear metrics records
|
|
1379
|
-
'''
|
|
1380
|
-
|
|
1381
|
-
flow = chip.get('option', 'flow')
|
|
1382
|
-
tool, task = get_tool_task(chip, step, index, flow=flow)
|
|
1383
|
-
|
|
1384
|
-
chip.unset('tool', tool, 'task', task, 'report', metric, step=step, index=index)
|
|
3
|
+
__all__ = [
|
|
4
|
+
"TaskScheduler"
|
|
5
|
+
]
|