siliconcompiler 0.30.0__py3-none-any.whl → 0.31.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 (69) hide show
  1. siliconcompiler/_metadata.py +1 -1
  2. siliconcompiler/apps/sc_install.py +7 -3
  3. siliconcompiler/apps/sc_remote.py +1 -3
  4. siliconcompiler/core.py +23 -8
  5. siliconcompiler/flowgraph.py +11 -23
  6. siliconcompiler/package.py +1 -1
  7. siliconcompiler/remote/schema.py +9 -8
  8. siliconcompiler/report/report.py +4 -3
  9. siliconcompiler/scheduler/__init__.py +109 -104
  10. siliconcompiler/scheduler/docker_runner.py +1 -1
  11. siliconcompiler/scheduler/send_messages.py +1 -1
  12. siliconcompiler/schema/schema_cfg.py +367 -357
  13. siliconcompiler/schema/schema_obj.py +32 -18
  14. siliconcompiler/schema/utils.py +19 -0
  15. siliconcompiler/sphinx_ext/schemagen.py +3 -1
  16. siliconcompiler/templates/replay/replay.sh.j2 +92 -0
  17. siliconcompiler/tools/_common/__init__.py +8 -2
  18. siliconcompiler/tools/_common/asic.py +1 -1
  19. siliconcompiler/tools/klayout/export.py +5 -0
  20. siliconcompiler/tools/klayout/klayout.py +18 -1
  21. siliconcompiler/tools/klayout/klayout_export.py +4 -1
  22. siliconcompiler/tools/klayout/klayout_operations.py +5 -2
  23. siliconcompiler/tools/klayout/klayout_utils.py +23 -0
  24. siliconcompiler/tools/klayout/operations.py +5 -0
  25. siliconcompiler/tools/magic/magic.py +1 -1
  26. siliconcompiler/tools/openroad/_apr.py +14 -3
  27. siliconcompiler/tools/openroad/antenna_repair.py +2 -1
  28. siliconcompiler/tools/openroad/clock_tree_synthesis.py +2 -1
  29. siliconcompiler/tools/openroad/detailed_placement.py +2 -1
  30. siliconcompiler/tools/openroad/detailed_route.py +8 -0
  31. siliconcompiler/tools/openroad/fillercell_insertion.py +2 -1
  32. siliconcompiler/tools/openroad/global_placement.py +2 -1
  33. siliconcompiler/tools/openroad/pin_placement.py +2 -1
  34. siliconcompiler/tools/openroad/repair_design.py +2 -1
  35. siliconcompiler/tools/openroad/repair_timing.py +2 -1
  36. siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +6 -0
  37. siliconcompiler/tools/openroad/scripts/apr/sc_clock_tree_synthesis.tcl +1 -0
  38. siliconcompiler/tools/openroad/scripts/apr/sc_detailed_route.tcl +8 -0
  39. siliconcompiler/tools/openroad/scripts/apr/sc_global_placement.tcl +1 -0
  40. siliconcompiler/tools/openroad/scripts/apr/sc_global_route.tcl +2 -0
  41. siliconcompiler/tools/openroad/scripts/apr/sc_macro_placement.tcl +5 -0
  42. siliconcompiler/tools/openroad/scripts/apr/sc_power_grid.tcl +1 -0
  43. siliconcompiler/tools/openroad/scripts/apr/sc_repair_design.tcl +1 -0
  44. siliconcompiler/tools/openroad/scripts/apr/sc_repair_timing.tcl +3 -0
  45. siliconcompiler/tools/openroad/scripts/common/procs.tcl +29 -12
  46. siliconcompiler/tools/openroad/scripts/common/reports.tcl +15 -0
  47. siliconcompiler/tools/openroad/scripts/common/write_images.tcl +28 -0
  48. siliconcompiler/tools/yosys/__init__.py +7 -0
  49. siliconcompiler/tools/yosys/sc_syn.tcl +33 -24
  50. siliconcompiler/tools/yosys/syn_asic.py +27 -0
  51. siliconcompiler/tools/yosys/syn_asic.tcl +27 -0
  52. siliconcompiler/toolscripts/_tools.json +14 -2
  53. siliconcompiler/toolscripts/rhel8/install-yosys-moosic.sh +17 -0
  54. siliconcompiler/toolscripts/rhel8/install-yosys-slang.sh +22 -0
  55. siliconcompiler/toolscripts/rhel9/install-yosys-moosic.sh +17 -0
  56. siliconcompiler/toolscripts/rhel9/install-yosys-slang.sh +22 -0
  57. siliconcompiler/toolscripts/ubuntu20/install-yosys-moosic.sh +17 -0
  58. siliconcompiler/toolscripts/ubuntu20/install-yosys-slang.sh +22 -0
  59. siliconcompiler/toolscripts/ubuntu22/install-yosys-moosic.sh +17 -0
  60. siliconcompiler/toolscripts/ubuntu22/install-yosys-slang.sh +22 -0
  61. siliconcompiler/toolscripts/ubuntu24/install-yosys-moosic.sh +17 -0
  62. siliconcompiler/toolscripts/ubuntu24/install-yosys-slang.sh +22 -0
  63. siliconcompiler/utils/__init__.py +33 -5
  64. {siliconcompiler-0.30.0.dist-info → siliconcompiler-0.31.0.dist-info}/METADATA +7 -7
  65. {siliconcompiler-0.30.0.dist-info → siliconcompiler-0.31.0.dist-info}/RECORD +69 -58
  66. {siliconcompiler-0.30.0.dist-info → siliconcompiler-0.31.0.dist-info}/WHEEL +1 -1
  67. {siliconcompiler-0.30.0.dist-info → siliconcompiler-0.31.0.dist-info}/LICENSE +0 -0
  68. {siliconcompiler-0.30.0.dist-info → siliconcompiler-0.31.0.dist-info}/entry_points.txt +0 -0
  69. {siliconcompiler-0.30.0.dist-info → siliconcompiler-0.31.0.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,4 @@
1
1
  import contextlib
2
- import copy
3
2
  import distro
4
3
  import getpass
5
4
  import multiprocessing
@@ -27,7 +26,7 @@ from siliconcompiler.scheduler import slurm
27
26
  from siliconcompiler.scheduler import docker_runner
28
27
  from siliconcompiler import NodeStatus, SiliconCompilerError
29
28
  from siliconcompiler.flowgraph import _get_flowgraph_nodes, _get_flowgraph_execution_order, \
30
- _get_pruned_node_inputs, _get_flowgraph_node_inputs, _get_flowgraph_entry_nodes, \
29
+ _get_pruned_node_inputs, _get_flowgraph_entry_nodes, \
31
30
  _unreachable_steps_to_execute, _nodes_to_execute, \
32
31
  get_nodes_from, nodes_to_execute, _check_flowgraph
33
32
  from siliconcompiler.tools._common import input_file_node_name
@@ -99,13 +98,6 @@ def run(chip):
99
98
  _reset_flow_nodes(chip, flow, nodes_to_execute(chip, flow))
100
99
  __record_packages(chip)
101
100
 
102
- # Save current environment
103
- environment = copy.deepcopy(os.environ)
104
- # Set env variables
105
- for envvar in chip.getkeys('option', 'env'):
106
- val = chip.get('option', 'env', envvar)
107
- os.environ[envvar] = val
108
-
109
101
  if chip.get('option', 'remote'):
110
102
  client = Client(chip)
111
103
  client.run()
@@ -113,23 +105,18 @@ def run(chip):
113
105
  _local_process(chip, flow)
114
106
 
115
107
  # Merge cfgs from last executed tasks, and write out a final manifest.
116
- _finalize_run(chip, environment)
108
+ _finalize_run(chip)
117
109
 
118
110
 
119
111
  ###########################################################################
120
- def _finalize_run(chip, environment):
112
+ def _finalize_run(chip):
121
113
  '''
122
114
  Helper function to finalize a job run after it completes:
123
- * Restore any environment variable changes made during the run.
124
115
  * Clear any -arg_step/-arg_index values in case only one node was run.
125
116
  * Store this run in the Schema's 'history' field.
126
117
  * Write out a final JSON manifest containing the full results and history.
127
118
  '''
128
119
 
129
- # Restore environment
130
- os.environ.clear()
131
- os.environ.update(environment)
132
-
133
120
  # Clear scratchpad args since these are checked on run() entry
134
121
  chip.set('arg', 'step', None, clobber=True)
135
122
  chip.set('arg', 'index', None, clobber=True)
@@ -502,10 +489,13 @@ def _haltstep(chip, flow, step, index, log=True):
502
489
  def _setupnode(chip, flow, step, index, replay):
503
490
  _hash_files(chip, step, index, setup=True)
504
491
 
492
+ # Select the inputs to this node
493
+ _select_inputs(chip, step, index)
494
+
505
495
  # Write manifest prior to step running into inputs
506
496
  chip.write_manifest(f'inputs/{chip.get("design")}.pkg.json')
507
497
 
508
- _select_inputs(chip, step, index)
498
+ # Forward data
509
499
  _copy_previous_steps_output_data(chip, step, index, replay)
510
500
 
511
501
  # Check manifest
@@ -541,7 +531,7 @@ def _setup_workdir(chip, step, index, replay):
541
531
  return workdir
542
532
 
543
533
 
544
- def _select_inputs(chip, step, index):
534
+ def _select_inputs(chip, step, index, trial=False):
545
535
 
546
536
  flow = chip.get('option', 'flow')
547
537
  tool, _ = get_tool_task(chip, step, index, flow)
@@ -551,15 +541,24 @@ def _select_inputs(chip, step, index):
551
541
  '_select_inputs',
552
542
  None)
553
543
  if select_inputs:
544
+ log_handlers = None
545
+ if trial:
546
+ log_handlers = chip.logger.handlers.copy()
547
+ chip.logger.handlers.clear()
554
548
  sel_inputs = select_inputs(chip, step, index)
549
+ if log_handlers:
550
+ chip.logger.handlers = log_handlers
555
551
  else:
556
- sel_inputs = _get_flowgraph_node_inputs(chip, flow, (step, index))
552
+ sel_inputs = _get_pruned_node_inputs(chip, flow, (step, index))
557
553
 
558
554
  if (step, index) not in _get_flowgraph_entry_nodes(chip, flow) and not sel_inputs:
559
555
  chip.logger.error(f'No inputs selected after running {tool}')
560
556
  _haltstep(chip, flow, step, index)
561
557
 
562
- chip.set('record', 'inputnode', sel_inputs, step=step, index=index)
558
+ if not trial:
559
+ chip.set('record', 'inputnode', sel_inputs, step=step, index=index)
560
+
561
+ return sel_inputs
563
562
 
564
563
 
565
564
  def copy_output_file(chip, outfile, folder='inputs'):
@@ -608,8 +607,14 @@ def _copy_previous_steps_output_data(chip, step, index, replay):
608
607
  # configuration into inputs/{design}.pkg.json earlier in _runstep.
609
608
  if not replay:
610
609
  in_workdir = chip.getworkdir(step=in_step, index=in_index)
610
+ output_dir = os.path.join(in_workdir, "outputs")
611
+
612
+ if not os.path.isdir(output_dir):
613
+ chip.logger.error(
614
+ f'Unable to locate outputs directory for {in_step}{in_index}: {output_dir}')
615
+ _haltstep(chip, flow, step, index)
611
616
 
612
- for outfile in os.scandir(f"{in_workdir}/outputs"):
617
+ for outfile in os.scandir(output_dir):
613
618
  new_name = input_file_node_name(outfile.name, in_step, in_index)
614
619
  if strict:
615
620
  if outfile.name not in in_files and new_name not in in_files:
@@ -649,13 +654,36 @@ def _getexe(chip, tool, step, index):
649
654
  syspath = os.getenv('PATH', os.defpath)
650
655
  if path:
651
656
  # Prepend 'path' schema var to system path
652
- syspath = utils._resolve_env_vars(chip, path) + os.pathsep + syspath
657
+ syspath = utils._resolve_env_vars(chip, path, step, index) + os.pathsep + syspath
653
658
 
654
659
  fullexe = shutil.which(exe, path=syspath)
655
660
 
656
661
  return fullexe
657
662
 
658
663
 
664
+ def _get_run_env_vars(chip, tool, task, step, index, include_path):
665
+ envvars = utils.get_env_vars(chip, step, index)
666
+ for item in chip.getkeys('tool', tool, 'licenseserver'):
667
+ license_file = chip.get('tool', tool, 'licenseserver', item, step=step, index=index)
668
+ if license_file:
669
+ envvars[item] = ':'.join(license_file)
670
+
671
+ if include_path:
672
+ path = chip.get('tool', tool, 'path', step=step, index=index)
673
+ if path:
674
+ envvars['PATH'] = path + os.pathsep + os.environ['PATH']
675
+ else:
676
+ envvars['PATH'] = os.environ['PATH']
677
+
678
+ # Forward additional variables
679
+ for var in ('LD_LIBRARY_PATH',):
680
+ val = os.getenv(var, None)
681
+ if val:
682
+ envvars[var] = val
683
+
684
+ return envvars
685
+
686
+
659
687
  #######################################
660
688
  def _makecmd(chip, tool, task, step, index, script_name='replay.sh', include_path=True):
661
689
  '''
@@ -707,32 +735,6 @@ def _makecmd(chip, tool, task, step, index, script_name='replay.sh', include_pat
707
735
  chip.logger.error(f'Failed to get runtime options for {tool}/{task}')
708
736
  raise e
709
737
 
710
- envvars = {}
711
- for key in chip.getkeys('option', 'env'):
712
- envvars[key] = chip.get('option', 'env', key)
713
- for item in chip.getkeys('tool', tool, 'licenseserver'):
714
- license_file = chip.get('tool', tool, 'licenseserver', item, step=step, index=index)
715
- if license_file:
716
- envvars[item] = ':'.join(license_file)
717
-
718
- if include_path:
719
- path = chip.get('tool', tool, 'path', step=step, index=index)
720
- if path:
721
- envvars['PATH'] = path + os.pathsep + os.environ['PATH']
722
- else:
723
- envvars['PATH'] = os.environ['PATH']
724
-
725
- # Forward additional variables
726
- for var in ('LD_LIBRARY_PATH',):
727
- val = os.getenv(var, None)
728
- if val:
729
- envvars[var] = val
730
-
731
- for key in chip.getkeys('tool', tool, 'task', task, 'env'):
732
- val = chip.get('tool', tool, 'task', task, 'env', key, step=step, index=index)
733
- if val:
734
- envvars[key] = val
735
-
736
738
  # Separate variables to be able to display nice name of executable
737
739
  cmd = os.path.basename(cmdlist[0])
738
740
  cmd_args = cmdlist[1:]
@@ -747,19 +749,23 @@ def _makecmd(chip, tool, task, step, index, script_name='replay.sh', include_pat
747
749
 
748
750
  # create replay file
749
751
  with open(script_name, 'w') as f:
750
- print('#!/usr/bin/env bash', file=f)
751
-
752
- envvar_cmd = 'export'
753
- for key, val in envvars.items():
754
- print(f'{envvar_cmd} {key}="{val}"', file=f)
755
-
756
752
  # Ensure execution runs from the same directory
753
+ replay_opts = {}
757
754
  work_dir = chip.getworkdir(step=step, index=index)
758
755
  if chip._relative_path:
759
756
  work_dir = os.path.relpath(work_dir, chip._relative_path)
760
- print(f'cd {work_dir}', file=f)
761
-
762
- format_cmd = [chip.get('tool', tool, 'exe')]
757
+ replay_opts["work_dir"] = work_dir
758
+ replay_opts["exports"] = _get_run_env_vars(chip,
759
+ tool, task,
760
+ step, index,
761
+ include_path=include_path)
762
+ replay_opts["executable"] = chip.get('tool', tool, 'exe')
763
+
764
+ vswitch = chip.get('tool', tool, 'vswitch')
765
+ if vswitch:
766
+ replay_opts["version_flag"] = " ".join(vswitch)
767
+
768
+ format_cmd = [replay_opts["executable"]]
763
769
  arg_test = re.compile(r'^[-+]')
764
770
  file_test = re.compile(r'^[/]')
765
771
  for cmdarg in cmd_args:
@@ -775,7 +781,11 @@ def _makecmd(chip, tool, task, step, index, script_name='replay.sh', include_pat
775
781
  format_cmd.append(cmdarg)
776
782
  else:
777
783
  format_cmd[-1] += f' {cmdarg}'
778
- print(" \\\n ".join(format_cmd), file=f)
784
+
785
+ replay_opts["cmds"] = format_cmd
786
+
787
+ f.write(utils.get_file_template("replay/replay.sh.j2").render(replay_opts))
788
+ f.write("\n")
779
789
 
780
790
  os.chmod(script_name, 0o755)
781
791
 
@@ -830,6 +840,7 @@ def _run_executable_or_builtin(chip, step, index, version, toolpath, workdir, ru
830
840
 
831
841
  # TODO: Currently no memory usage tracking in breakpoints, builtins, or unexpected errors.
832
842
  max_mem_bytes = 0
843
+ cpu_start = time.time()
833
844
 
834
845
  stdout_file, stderr_file = __get_stdio(chip, tool, task, flow, step, index)
835
846
  is_stdout_log = chip.get('tool', tool, 'task', task, 'stdout', 'destination',
@@ -881,7 +892,10 @@ def _run_executable_or_builtin(chip, step, index, version, toolpath, workdir, ru
881
892
 
882
893
  ##################
883
894
  # Make record of tool options
884
- __record_tool(chip, step, index, version, toolpath, cmd_args)
895
+ if cmd_args is not None:
896
+ chip.set('record', 'toolargs',
897
+ ' '.join(f'"{arg}"' if ' ' in arg else arg for arg in cmd_args),
898
+ step=step, index=index)
885
899
 
886
900
  chip.logger.info('%s', printable_cmd)
887
901
  timeout = chip.get('option', 'timeout', step=step, index=index)
@@ -1003,8 +1017,15 @@ def _run_executable_or_builtin(chip, step, index, version, toolpath, workdir, ru
1003
1017
  chip.logger.warning(msg)
1004
1018
  chip._error = True
1005
1019
 
1020
+ # Capture cpu runtime
1021
+ record_metric(chip, step, index, 'exetime', round((time.time() - cpu_start), 2),
1022
+ source=None,
1023
+ source_unit='s')
1024
+
1006
1025
  # Capture memory usage
1007
- record_metric(chip, step, index, 'memory', max_mem_bytes, source=None, source_unit='B')
1026
+ record_metric(chip, step, index, 'memory', max_mem_bytes,
1027
+ source=None,
1028
+ source_unit='B')
1008
1029
 
1009
1030
 
1010
1031
  def _post_process(chip, step, index):
@@ -1074,26 +1095,26 @@ def _executenode(chip, step, index, replay):
1074
1095
 
1075
1096
  send_messages.send(chip, "skipped", step, index)
1076
1097
  else:
1077
- _set_env_vars(chip, step, index)
1098
+ org_env = _set_env_vars(chip, step, index)
1078
1099
 
1079
1100
  run_func = getattr(chip._get_task_module(step, index, flow=flow), 'run', None)
1080
- (toolpath, version) = _check_tool_version(chip, step, index, run_func)
1101
+ toolpath, version = _check_tool_version(chip, step, index, run_func)
1102
+
1103
+ if version:
1104
+ chip.set('record', 'toolversion', version, step=step, index=index)
1105
+
1106
+ if toolpath:
1107
+ chip.set('record', 'toolpath', toolpath, step=step, index=index)
1081
1108
 
1082
1109
  # Write manifest (tool interface) (Don't move this!)
1083
1110
  _write_task_manifest(chip, tool)
1084
1111
 
1085
1112
  send_messages.send(chip, "begin", step, index)
1086
1113
 
1087
- # Start CPU Timer
1088
- chip.logger.debug("Starting executable")
1089
- cpu_start = time.time()
1090
-
1091
1114
  _run_executable_or_builtin(chip, step, index, version, toolpath, workdir, run_func)
1092
1115
 
1093
- # Capture cpu runtime
1094
- cpu_end = time.time()
1095
- cputime = round((cpu_end - cpu_start), 2)
1096
- record_metric(chip, step, index, 'exetime', cputime, source=None, source_unit='s')
1116
+ os.environ.clear()
1117
+ os.environ.update(org_env)
1097
1118
 
1098
1119
  _post_process(chip, step, index)
1099
1120
 
@@ -1120,24 +1141,18 @@ def _pre_process(chip, step, index):
1120
1141
 
1121
1142
 
1122
1143
  def _set_env_vars(chip, step, index):
1123
- flow = chip.get('option', 'flow')
1124
- tool, task = get_tool_task(chip, step, index, flow)
1144
+ org_env = os.environ.copy()
1145
+
1146
+ tool, task = get_tool_task(chip, step, index)
1125
1147
 
1126
1148
  chip.schema._start_record_access()
1127
- # License file configuration.
1128
- for item in chip.getkeys('tool', tool, 'licenseserver'):
1129
- license_file = chip.get('tool', tool, 'licenseserver', item, step=step, index=index)
1130
- if license_file:
1131
- os.environ[item] = ':'.join(license_file)
1132
1149
 
1133
- # Tool-specific environment variables for this task.
1134
- for item in chip.getkeys('tool', tool, 'task', task, 'env'):
1135
- val = chip.get('tool', tool, 'task', task, 'env', item, step=step, index=index)
1136
- if val:
1137
- os.environ[item] = val
1150
+ os.environ.update(_get_run_env_vars(chip, tool, task, step, index, include_path=True))
1138
1151
 
1139
1152
  chip.schema._stop_record_access()
1140
1153
 
1154
+ return org_env
1155
+
1141
1156
 
1142
1157
  def _check_tool_version(chip, step, index, run_func=None):
1143
1158
  '''
@@ -1214,8 +1229,7 @@ def _hash_files(chip, step, index, setup=False):
1214
1229
  args = item.split(',')
1215
1230
  sc_type = chip.get(*args, field='type')
1216
1231
  if 'file' in sc_type or 'dir' in sc_type:
1217
- pernode = chip.get(*args, field='pernode')
1218
- if pernode == 'never':
1232
+ if chip.get(*args, field='pernode').is_never():
1219
1233
  if not setup:
1220
1234
  if chip.get(*args, field='filehash'):
1221
1235
  continue
@@ -1355,7 +1369,7 @@ def assert_required_accesses(chip, step, index):
1355
1369
  gets.add(tuple(key))
1356
1370
 
1357
1371
  def get_value(*key):
1358
- if chip.get(*key, field='pernode') == 'never':
1372
+ if chip.get(*key, field='pernode').is_never():
1359
1373
  return chip.get(*key)
1360
1374
  else:
1361
1375
  return chip.get(*key, step=step, index=index)
@@ -1728,19 +1742,6 @@ def get_record_time(chip, step, index, timetype):
1728
1742
  '%Y-%m-%d %H:%M:%S').timestamp()
1729
1743
 
1730
1744
 
1731
- #######################################
1732
- def __record_tool(chip, step, index, toolversion=None, toolpath=None, cli_args=None):
1733
- if toolversion:
1734
- chip.set('record', 'toolversion', toolversion, step=step, index=index)
1735
-
1736
- if toolpath:
1737
- chip.set('record', 'toolpath', toolpath, step=step, index=index)
1738
-
1739
- if cli_args is not None:
1740
- toolargs = ' '.join(f'"{arg}"' if ' ' in arg else arg for arg in cli_args)
1741
- chip.set('record', 'toolargs', toolargs, step=step, index=index)
1742
-
1743
-
1744
1745
  #######################################
1745
1746
  def _get_cloud_region():
1746
1747
  # TODO: add logic to figure out if we're running on a remote cluster and
@@ -1910,6 +1911,12 @@ def check_node_inputs(chip, step, index):
1910
1911
  if tool != input_tool or task != input_task:
1911
1912
  return False
1912
1913
 
1914
+ # Check if inputs changed
1915
+ new_inputs = set(_select_inputs(chip, step, index, trial=True))
1916
+ if set(input_chip.get('record', 'inputnode', step=step, index=index)) != new_inputs:
1917
+ chip.logger.warning(f'inputs to {step}{index} has been modified from previous run')
1918
+ return False
1919
+
1913
1920
  # Collect keys to check for changes
1914
1921
  required = chip.get('tool', tool, 'task', task, 'require', step=step, index=index)
1915
1922
  required.extend(input_chip.get('tool', tool, 'task', task, 'require', step=step, index=index))
@@ -1918,7 +1925,7 @@ def check_node_inputs(chip, step, index):
1918
1925
  for key in ('option', 'threads', 'prescript', 'postscript', 'refdir', 'script',):
1919
1926
  required.append(",".join([*tool_task_key, key]))
1920
1927
  for check_chip in (chip, input_chip):
1921
- for env_key in chip.getkeys(*tool_task_key, 'env'):
1928
+ for env_key in check_chip.getkeys(*tool_task_key, 'env'):
1922
1929
  required.append(",".join([*tool_task_key, 'env', env_key]))
1923
1930
 
1924
1931
  def print_warning(key, extra=None):
@@ -1937,11 +1944,9 @@ def check_node_inputs(chip, step, index):
1937
1944
  print_warning(key)
1938
1945
  return False
1939
1946
 
1940
- pernode = chip.get(*key, field='pernode')
1941
-
1942
1947
  check_step = step
1943
1948
  check_index = index
1944
- if pernode == 'never':
1949
+ if chip.get(*key, field='pernode').is_never():
1945
1950
  check_step = None
1946
1951
  check_index = None
1947
1952
 
@@ -2244,7 +2249,7 @@ def _check_manifest_dynamic(chip, step, index):
2244
2249
  error = True
2245
2250
  else:
2246
2251
  paramtype = chip.get(*keypath, field='type')
2247
- is_perstep = chip.get(*keypath, field='pernode') != 'never'
2252
+ is_perstep = not chip.get(*keypath, field='pernode').is_never()
2248
2253
  if ('file' in paramtype) or ('dir' in paramtype):
2249
2254
  for val, check_step, check_index in chip.schema._getvals(*keypath):
2250
2255
  if is_perstep:
@@ -2298,7 +2303,7 @@ def _clear_record(chip, step, index, record, preserve=None):
2298
2303
  if preserve and record in preserve:
2299
2304
  return
2300
2305
 
2301
- if chip.get('record', record, field='pernode') == 'never':
2306
+ if chip.get('record', record, field='pernode').is_never():
2302
2307
  chip.unset('record', record)
2303
2308
  else:
2304
2309
  chip.unset('record', record, step=step, index=index)
@@ -29,7 +29,7 @@ def get_volumes_directories(chip, cache_dir, workdir, step, index):
29
29
  cstep = step
30
30
  cindex = index
31
31
 
32
- if 'never' in chip.get(*key, field='pernode'):
32
+ if chip.get(*key, field='pernode').is_never():
33
33
  cstep = None
34
34
  cindex = None
35
35
 
@@ -122,7 +122,7 @@ def send(chip, msg_type, step, index):
122
122
  records = {}
123
123
  for record in chip.getkeys('record'):
124
124
  value = None
125
- if chip.get('record', record, field='pernode') == 'never':
125
+ if chip.get('record', record, field='pernode').is_never():
126
126
  value = chip.get('record', record)
127
127
  else:
128
128
  value = chip.get('record', record, step=step, index=index)