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.
Files changed (49) hide show
  1. siliconcompiler/__init__.py +2 -0
  2. siliconcompiler/_metadata.py +1 -1
  3. siliconcompiler/apps/sc_issue.py +5 -3
  4. siliconcompiler/apps/sc_remote.py +0 -17
  5. siliconcompiler/checklist.py +1 -1
  6. siliconcompiler/core.py +34 -47
  7. siliconcompiler/dependencyschema.py +392 -0
  8. siliconcompiler/design.py +664 -0
  9. siliconcompiler/flowgraph.py +32 -1
  10. siliconcompiler/package/__init__.py +383 -223
  11. siliconcompiler/package/git.py +75 -77
  12. siliconcompiler/package/github.py +70 -97
  13. siliconcompiler/package/https.py +77 -93
  14. siliconcompiler/packageschema.py +260 -0
  15. siliconcompiler/pdk.py +2 -2
  16. siliconcompiler/remote/client.py +15 -3
  17. siliconcompiler/report/dashboard/cli/board.py +1 -1
  18. siliconcompiler/scheduler/__init__.py +3 -1382
  19. siliconcompiler/scheduler/docker.py +268 -0
  20. siliconcompiler/scheduler/run_node.py +10 -16
  21. siliconcompiler/scheduler/scheduler.py +308 -0
  22. siliconcompiler/scheduler/schedulernode.py +934 -0
  23. siliconcompiler/scheduler/slurm.py +147 -163
  24. siliconcompiler/scheduler/taskscheduler.py +39 -52
  25. siliconcompiler/schema/__init__.py +3 -3
  26. siliconcompiler/schema/baseschema.py +234 -10
  27. siliconcompiler/schema/editableschema.py +4 -0
  28. siliconcompiler/schema/journal.py +210 -0
  29. siliconcompiler/schema/namedschema.py +31 -2
  30. siliconcompiler/schema/parameter.py +14 -1
  31. siliconcompiler/schema/parametervalue.py +1 -34
  32. siliconcompiler/schema/schema_cfg.py +210 -349
  33. siliconcompiler/tool.py +61 -20
  34. siliconcompiler/tools/builtin/concatenate.py +2 -2
  35. siliconcompiler/tools/builtin/verify.py +1 -2
  36. siliconcompiler/tools/openroad/scripts/common/procs.tcl +27 -25
  37. siliconcompiler/tools/vpr/route.py +69 -0
  38. siliconcompiler/toolscripts/_tools.json +4 -4
  39. siliconcompiler/utils/__init__.py +2 -23
  40. siliconcompiler/utils/flowgraph.py +5 -5
  41. siliconcompiler/utils/logging.py +2 -1
  42. {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.0.dist-info}/METADATA +4 -3
  43. {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.0.dist-info}/RECORD +47 -42
  44. siliconcompiler/scheduler/docker_runner.py +0 -254
  45. siliconcompiler/schema/journalingschema.py +0 -242
  46. {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.0.dist-info}/WHEEL +0 -0
  47. {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.0.dist-info}/entry_points.txt +0 -0
  48. {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.0.dist-info}/licenses/LICENSE +0 -0
  49. {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
- # Max lines to print from failed node log
23
- _failed_log_lines = 20
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
+ ]