hpcflow-new2 0.2.0a190__py3-none-any.whl → 0.2.0a199__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 (130) hide show
  1. hpcflow/__pyinstaller/hook-hpcflow.py +1 -0
  2. hpcflow/_version.py +1 -1
  3. hpcflow/data/scripts/bad_script.py +2 -0
  4. hpcflow/data/scripts/do_nothing.py +2 -0
  5. hpcflow/data/scripts/env_specifier_test/input_file_generator_pass_env_spec.py +4 -0
  6. hpcflow/data/scripts/env_specifier_test/main_script_test_pass_env_spec.py +8 -0
  7. hpcflow/data/scripts/env_specifier_test/output_file_parser_pass_env_spec.py +4 -0
  8. hpcflow/data/scripts/env_specifier_test/v1/input_file_generator_basic.py +4 -0
  9. hpcflow/data/scripts/env_specifier_test/v1/main_script_test_direct_in_direct_out.py +7 -0
  10. hpcflow/data/scripts/env_specifier_test/v1/output_file_parser_basic.py +4 -0
  11. hpcflow/data/scripts/env_specifier_test/v2/main_script_test_direct_in_direct_out.py +7 -0
  12. hpcflow/data/scripts/input_file_generator_basic.py +3 -0
  13. hpcflow/data/scripts/input_file_generator_basic_FAIL.py +3 -0
  14. hpcflow/data/scripts/input_file_generator_test_stdout_stderr.py +8 -0
  15. hpcflow/data/scripts/main_script_test_direct_in.py +3 -0
  16. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2.py +6 -0
  17. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed.py +6 -0
  18. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed_group.py +7 -0
  19. hpcflow/data/scripts/main_script_test_direct_in_direct_out_3.py +6 -0
  20. hpcflow/data/scripts/main_script_test_direct_in_group_direct_out_3.py +6 -0
  21. hpcflow/data/scripts/main_script_test_direct_in_group_one_fail_direct_out_3.py +6 -0
  22. hpcflow/data/scripts/main_script_test_hdf5_in_obj_2.py +12 -0
  23. hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -0
  24. hpcflow/data/scripts/main_script_test_shell_env_vars.py +12 -0
  25. hpcflow/data/scripts/main_script_test_std_out_std_err.py +6 -0
  26. hpcflow/data/scripts/output_file_parser_basic.py +3 -0
  27. hpcflow/data/scripts/output_file_parser_basic_FAIL.py +7 -0
  28. hpcflow/data/scripts/output_file_parser_test_stdout_stderr.py +8 -0
  29. hpcflow/data/scripts/script_exit_test.py +5 -0
  30. hpcflow/data/template_components/environments.yaml +1 -1
  31. hpcflow/sdk/__init__.py +5 -0
  32. hpcflow/sdk/app.py +150 -89
  33. hpcflow/sdk/cli.py +263 -84
  34. hpcflow/sdk/cli_common.py +99 -5
  35. hpcflow/sdk/config/callbacks.py +38 -1
  36. hpcflow/sdk/config/config.py +102 -13
  37. hpcflow/sdk/config/errors.py +19 -5
  38. hpcflow/sdk/config/types.py +3 -0
  39. hpcflow/sdk/core/__init__.py +25 -1
  40. hpcflow/sdk/core/actions.py +914 -262
  41. hpcflow/sdk/core/cache.py +76 -34
  42. hpcflow/sdk/core/command_files.py +14 -128
  43. hpcflow/sdk/core/commands.py +35 -6
  44. hpcflow/sdk/core/element.py +122 -50
  45. hpcflow/sdk/core/errors.py +58 -2
  46. hpcflow/sdk/core/execute.py +207 -0
  47. hpcflow/sdk/core/loop.py +408 -50
  48. hpcflow/sdk/core/loop_cache.py +4 -4
  49. hpcflow/sdk/core/parameters.py +382 -37
  50. hpcflow/sdk/core/run_dir_files.py +13 -40
  51. hpcflow/sdk/core/skip_reason.py +7 -0
  52. hpcflow/sdk/core/task.py +119 -30
  53. hpcflow/sdk/core/task_schema.py +68 -0
  54. hpcflow/sdk/core/test_utils.py +66 -27
  55. hpcflow/sdk/core/types.py +54 -1
  56. hpcflow/sdk/core/utils.py +78 -7
  57. hpcflow/sdk/core/workflow.py +1538 -336
  58. hpcflow/sdk/data/workflow_spec_schema.yaml +2 -0
  59. hpcflow/sdk/demo/cli.py +7 -0
  60. hpcflow/sdk/helper/cli.py +1 -0
  61. hpcflow/sdk/log.py +42 -15
  62. hpcflow/sdk/persistence/base.py +405 -53
  63. hpcflow/sdk/persistence/json.py +177 -52
  64. hpcflow/sdk/persistence/pending.py +237 -69
  65. hpcflow/sdk/persistence/store_resource.py +3 -2
  66. hpcflow/sdk/persistence/types.py +15 -4
  67. hpcflow/sdk/persistence/zarr.py +928 -81
  68. hpcflow/sdk/submission/jobscript.py +1408 -489
  69. hpcflow/sdk/submission/schedulers/__init__.py +40 -5
  70. hpcflow/sdk/submission/schedulers/direct.py +33 -19
  71. hpcflow/sdk/submission/schedulers/sge.py +51 -16
  72. hpcflow/sdk/submission/schedulers/slurm.py +44 -16
  73. hpcflow/sdk/submission/schedulers/utils.py +7 -2
  74. hpcflow/sdk/submission/shells/base.py +68 -20
  75. hpcflow/sdk/submission/shells/bash.py +222 -129
  76. hpcflow/sdk/submission/shells/powershell.py +200 -150
  77. hpcflow/sdk/submission/submission.py +852 -119
  78. hpcflow/sdk/submission/types.py +18 -21
  79. hpcflow/sdk/typing.py +24 -5
  80. hpcflow/sdk/utils/arrays.py +71 -0
  81. hpcflow/sdk/utils/deferred_file.py +55 -0
  82. hpcflow/sdk/utils/hashing.py +16 -0
  83. hpcflow/sdk/utils/patches.py +12 -0
  84. hpcflow/sdk/utils/strings.py +33 -0
  85. hpcflow/tests/api/test_api.py +32 -0
  86. hpcflow/tests/conftest.py +19 -0
  87. hpcflow/tests/data/multi_path_sequences.yaml +29 -0
  88. hpcflow/tests/data/workflow_test_run_abort.yaml +34 -35
  89. hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
  90. hpcflow/tests/scripts/test_input_file_generators.py +282 -0
  91. hpcflow/tests/scripts/test_main_scripts.py +821 -70
  92. hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
  93. hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
  94. hpcflow/tests/shells/wsl/test_wsl_submission.py +6 -0
  95. hpcflow/tests/unit/test_action.py +176 -0
  96. hpcflow/tests/unit/test_app.py +20 -0
  97. hpcflow/tests/unit/test_cache.py +46 -0
  98. hpcflow/tests/unit/test_cli.py +133 -0
  99. hpcflow/tests/unit/test_config.py +122 -1
  100. hpcflow/tests/unit/test_element_iteration.py +47 -0
  101. hpcflow/tests/unit/test_jobscript_unit.py +757 -0
  102. hpcflow/tests/unit/test_loop.py +1332 -27
  103. hpcflow/tests/unit/test_meta_task.py +325 -0
  104. hpcflow/tests/unit/test_multi_path_sequences.py +229 -0
  105. hpcflow/tests/unit/test_parameter.py +13 -0
  106. hpcflow/tests/unit/test_persistence.py +190 -8
  107. hpcflow/tests/unit/test_run.py +109 -3
  108. hpcflow/tests/unit/test_run_directories.py +29 -0
  109. hpcflow/tests/unit/test_shell.py +20 -0
  110. hpcflow/tests/unit/test_submission.py +5 -76
  111. hpcflow/tests/unit/utils/test_arrays.py +40 -0
  112. hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
  113. hpcflow/tests/unit/utils/test_hashing.py +65 -0
  114. hpcflow/tests/unit/utils/test_patches.py +5 -0
  115. hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
  116. hpcflow/tests/workflows/__init__.py +0 -0
  117. hpcflow/tests/workflows/test_directory_structure.py +31 -0
  118. hpcflow/tests/workflows/test_jobscript.py +332 -0
  119. hpcflow/tests/workflows/test_run_status.py +198 -0
  120. hpcflow/tests/workflows/test_skip_downstream.py +696 -0
  121. hpcflow/tests/workflows/test_submission.py +140 -0
  122. hpcflow/tests/workflows/test_workflows.py +142 -2
  123. hpcflow/tests/workflows/test_zip.py +18 -0
  124. hpcflow/viz_demo.ipynb +6587 -3
  125. {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a199.dist-info}/METADATA +7 -4
  126. hpcflow_new2-0.2.0a199.dist-info/RECORD +221 -0
  127. hpcflow_new2-0.2.0a190.dist-info/RECORD +0 -165
  128. {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a199.dist-info}/LICENSE +0 -0
  129. {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a199.dist-info}/WHEEL +0 -0
  130. {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a199.dist-info}/entry_points.txt +0 -0
@@ -39,25 +39,50 @@ class Bash(Shell):
39
39
  JS_ENV_SETUP_INDENT: ClassVar[str] = 2 * JS_INDENT
40
40
  #: Template for the jobscript shebang line.
41
41
  JS_SHEBANG: ClassVar[str] = """#!{shebang_executable} {shebang_args}"""
42
- #: Template for the common part of the jobscript header.
43
- JS_HEADER: ClassVar[str] = dedent(
42
+ #: Template for the jobscript functions file.
43
+ JS_FUNCS: ClassVar[str] = dedent(
44
44
  """\
45
45
  {workflow_app_alias} () {{
46
46
  (
47
47
  {env_setup}{app_invoc}\\
48
- --with-config log_file_path "`pwd`/{run_log_file}"\\
48
+ --with-config log_file_path "${app_caps}_LOG_PATH"\\
49
49
  --config-dir "{config_dir}"\\
50
50
  --config-key "{config_invoc_key}"\\
51
51
  "$@"
52
52
  )
53
53
  }}
54
-
54
+ """
55
+ )
56
+ #: Template for the common part of the jobscript header.
57
+ JS_HEADER: ClassVar[str] = dedent(
58
+ """\
55
59
  WK_PATH=`pwd`
56
60
  WK_PATH_ARG="$WK_PATH"
57
61
  SUB_IDX={sub_idx}
58
62
  JS_IDX={js_idx}
59
- EAR_ID_FILE="$WK_PATH/artifacts/submissions/${{SUB_IDX}}/{EAR_file_name}"
60
- ELEM_RUN_DIR_FILE="$WK_PATH/artifacts/submissions/${{SUB_IDX}}/{element_run_dirs_file_path}"
63
+ APP_CAPS={app_caps}
64
+
65
+ SUB_DIR="$WK_PATH/artifacts/submissions/${{SUB_IDX}}"
66
+ JS_FUNCS_PATH="$SUB_DIR/{jobscript_functions_dir}/{jobscript_functions_name}"
67
+ . "$JS_FUNCS_PATH"
68
+
69
+ EAR_ID_FILE="$WK_PATH/artifacts/submissions/${{SUB_IDX}}/{run_IDs_file_dir}/{run_IDs_file_name}"
70
+ SUB_TMP_DIR="$SUB_DIR/{tmp_dir_name}"
71
+ SUB_LOG_DIR="$SUB_DIR/{log_dir_name}"
72
+ SUB_STD_DIR="$SUB_DIR/{app_std_dir_name}"
73
+ SUB_SCRIPTS_DIR="$SUB_DIR/{scripts_dir_name}"
74
+
75
+ export {app_caps}_WK_PATH=$WK_PATH
76
+ export {app_caps}_WK_PATH_ARG=$WK_PATH_ARG
77
+ export {app_caps}_SUB_IDX={sub_idx}
78
+ export {app_caps}_SUB_SCRIPTS_DIR=$SUB_SCRIPTS_DIR
79
+ export {app_caps}_SUB_TMP_DIR=$SUB_TMP_DIR
80
+ export {app_caps}_SUB_LOG_DIR=$SUB_LOG_DIR
81
+ export {app_caps}_SUB_STD_DIR=$SUB_STD_DIR
82
+ export {app_caps}_LOG_PATH="$SUB_LOG_DIR/js_${{JS_IDX}}.log"
83
+ export {app_caps}_JS_FUNCS_PATH=$JS_FUNCS_PATH
84
+ export {app_caps}_JS_IDX={js_idx}
85
+ export {app_caps}_RUN_ID_FILE=$EAR_ID_FILE
61
86
  """
62
87
  )
63
88
  #: Template for the jobscript header when scheduled.
@@ -73,76 +98,118 @@ class Bash(Shell):
73
98
  JS_DIRECT_HEADER: ClassVar[str] = dedent(
74
99
  """\
75
100
  {shebang}
76
-
77
101
  {header}
78
102
  {wait_command}
79
103
  """
80
104
  )
81
- #: Template for the jobscript body.
105
+ #: Template for enabling writing of the app log.
106
+ JS_RUN_LOG_PATH_ENABLE: ClassVar[str] = '"$SUB_LOG_DIR/{run_log_file_name}"'
107
+ #: Template for disabling writing of the app log.
108
+ JS_RUN_LOG_PATH_DISABLE: ClassVar[str] = '" "'
109
+ #: Template for the run execution command.
110
+ JS_RUN_CMD: ClassVar[str] = (
111
+ '{workflow_app_alias} internal workflow "$WK_PATH_ARG" execute-run '
112
+ "$SUB_IDX $JS_IDX $block_idx $block_act_idx $EAR_ID\n"
113
+ )
114
+ #: Template for the execution command for multiple combined runs.
115
+ JS_RUN_CMD_COMBINED: ClassVar[str] = (
116
+ '{workflow_app_alias} internal workflow "$WK_PATH_ARG" execute-combined-runs '
117
+ "$SUB_IDX $JS_IDX\n"
118
+ )
119
+ #: Template for setting up run environment variables and executing the run.
120
+ JS_RUN: ClassVar[str] = dedent(
121
+ """\
122
+ EAR_ID="$(cut -d'{EAR_files_delimiter}' -f $(($block_act_idx + 1)) <<< $elem_EAR_IDs)"
123
+ if [ "$EAR_ID" = "-1" ]; then
124
+ continue
125
+ fi
126
+
127
+ export {app_caps}_RUN_ID=$EAR_ID
128
+ export {app_caps}_RUN_LOG_PATH={run_log_enable_disable}
129
+ export {app_caps}_LOG_PATH="${app_caps}_RUN_LOG_PATH"
130
+ export {app_caps}_RUN_STD_PATH="$SUB_STD_DIR/${app_caps}_RUN_ID.txt"
131
+ export {app_caps}_BLOCK_ACT_IDX=$block_act_idx
132
+
133
+ cd "$SUB_TMP_DIR"
134
+
135
+ {run_cmd}
136
+ """
137
+ )
138
+ #: Template for the action-run processing loop in a jobscript.
139
+ JS_ACT_MULTI: ClassVar[str] = dedent(
140
+ """\
141
+ for ((block_act_idx=0;block_act_idx<{num_actions};block_act_idx++))
142
+ do
143
+ {run_block}
144
+ done
145
+ """
146
+ )
147
+ #: Template for the single-action-run execution in a jobscript.
148
+ JS_ACT_SINGLE: ClassVar[str] = dedent(
149
+ """\
150
+ block_act_idx=0
151
+ {run_block}
152
+ """
153
+ )
154
+ #: Template for setting up environment variables and running one or more action-runs.
82
155
  JS_MAIN: ClassVar[str] = dedent(
83
156
  """\
157
+ block_elem_idx=$(( $JS_elem_idx - {block_start_elem_idx} ))
84
158
  elem_EAR_IDs=`sed "$((${{JS_elem_idx}} + 1))q;d" "$EAR_ID_FILE"`
85
- elem_run_dirs=`sed "$((${{JS_elem_idx}} + 1))q;d" "$ELEM_RUN_DIR_FILE"`
86
-
87
- for ((JS_act_idx=0;JS_act_idx<{num_actions};JS_act_idx++))
88
- do
89
-
90
- EAR_ID="$(cut -d'{EAR_files_delimiter}' -f $(($JS_act_idx + 1)) <<< $elem_EAR_IDs)"
91
- if [ "$EAR_ID" = "-1" ]; then
92
- continue
93
- fi
94
-
95
- run_dir="$(cut -d'{EAR_files_delimiter}' -f $(($JS_act_idx + 1)) <<< $elem_run_dirs)"
96
- cd "$WK_PATH/$run_dir"
97
- app_stream_file="`pwd`/{run_stream_file}"
98
-
99
- skip=`{workflow_app_alias} internal workflow "$WK_PATH_ARG" get-ear-skipped $EAR_ID 2>> "$app_stream_file"`
100
- exc_sk=$?
101
-
102
- if [ $exc_sk -eq 0 ]; then
103
-
104
- if [ "$skip" = "1" ]; then
105
- continue
106
- fi
107
-
108
- {workflow_app_alias} internal workflow "$WK_PATH_ARG" write-commands $SUB_IDX $JS_IDX $JS_act_idx $EAR_ID >> "$app_stream_file" 2>&1
109
- exc_wc=$?
110
-
111
- {workflow_app_alias} internal workflow "$WK_PATH_ARG" set-ear-start $EAR_ID >> "$app_stream_file" 2>&1
112
- exc_se=$?
113
-
114
- if [ $exc_wc -eq 0 ] && [ $exc_se -eq 0 ]; then
115
- . {commands_file_name}
116
- exit_code=$?
117
- else
118
- exit_code=$([ $exc_wc -ne 0 ] && echo "$exc_wc" || echo "$exc_se")
119
- fi
120
-
121
- else
122
- exit_code=$exc_sk
123
- fi
124
-
125
- {workflow_app_alias} internal workflow "$WK_PATH_ARG" set-ear-end $JS_IDX $JS_act_idx $EAR_ID "--" "$exit_code" >> "$app_stream_file" 2>&1
126
-
127
- done
159
+ export {app_caps}_JS_ELEM_IDX=$JS_elem_idx
160
+ export {app_caps}_BLOCK_ELEM_IDX=$block_elem_idx
161
+
162
+ {action}
163
+ """
164
+ )
165
+ #: Template for a jobscript-block header.
166
+ JS_BLOCK_HEADER: ClassVar[str] = dedent( # for single-block jobscripts only
167
+ """\
168
+ block_idx=0
169
+ export {app_caps}_BLOCK_IDX=0
170
+ """
171
+ )
172
+ #: Template for single-element execution.
173
+ JS_ELEMENT_SINGLE: ClassVar[str] = dedent(
174
+ """\
175
+ JS_elem_idx={block_start_elem_idx}
176
+ {main}
128
177
  """
129
178
  )
130
179
  #: Template for the element processing loop in a jobscript.
131
- JS_ELEMENT_LOOP: ClassVar[str] = dedent(
180
+ JS_ELEMENT_MULTI_LOOP: ClassVar[str] = dedent(
132
181
  """\
133
- for ((JS_elem_idx=0;JS_elem_idx<{num_elements};JS_elem_idx++))
182
+ for ((JS_elem_idx={block_start_elem_idx};JS_elem_idx<$(({block_start_elem_idx} + {num_elements}));JS_elem_idx++))
134
183
  do
135
184
  {main}
136
185
  done
137
- cd "$WK_PATH"
138
186
  """
139
187
  )
140
188
  #: Template for the array handling code in a jobscript.
141
- JS_ELEMENT_ARRAY: ClassVar[str] = dedent(
189
+ JS_ELEMENT_MULTI_ARRAY: ClassVar[str] = dedent(
142
190
  """\
143
191
  JS_elem_idx=$(({scheduler_array_item_var} - 1))
144
192
  {main}
145
- cd "$WK_PATH"
193
+ """
194
+ )
195
+ #: Template for the jobscript block loop in a jobscript.
196
+ JS_BLOCK_LOOP: ClassVar[str] = dedent(
197
+ """\
198
+ num_elements={num_elements}
199
+ num_actions={num_actions}
200
+ block_start_elem_idx=0
201
+ for ((block_idx=0;block_idx<{num_blocks};block_idx++))
202
+ do
203
+ export {app_caps}_BLOCK_IDX=$block_idx
204
+ {element_loop}
205
+ block_start_elem_idx=$(($block_start_elem_idx + ${{num_elements[$block_idx]}}))
206
+ done
207
+ """
208
+ )
209
+ #: Template for the jobscript footer.
210
+ JS_FOOTER: ClassVar[str] = dedent(
211
+ """\
212
+ cd $WK_PATH
146
213
  """
147
214
  )
148
215
 
@@ -191,22 +258,61 @@ class Bash(Shell):
191
258
  return app_invoc_exe.replace(" ", r"\ ")
192
259
 
193
260
  @override
194
- @staticmethod
195
- def format_stream_assignment(shell_var_name: str, command: str) -> str:
261
+ def format_env_var_get(self, var: str) -> str:
262
+ """
263
+ Format retrieval of a shell environment variable.
264
+ """
265
+ return f"${var}"
266
+
267
+ @override
268
+ def format_array(self, lst: list) -> str:
269
+ """
270
+ Format construction of a shell array.
271
+ """
272
+ return "(" + " ".join(str(i) for i in lst) + ")"
273
+
274
+ @override
275
+ def format_array_get_item(self, arr_name: str, index: int | str) -> str:
276
+ """
277
+ Format retrieval of a shell array item at a specified index.
278
+ """
279
+ return f"${{{arr_name}[{index}]}}"
280
+
281
+ @override
282
+ def format_stream_assignment(self, shell_var_name: str, command: str) -> str:
196
283
  """
197
284
  Produce code to assign the output of the command to a shell variable.
198
285
  """
199
286
  return f"{shell_var_name}=`{command}`"
200
287
 
288
+ @override
289
+ def format_source_functions_file(self, app_name: str, commands: str) -> str:
290
+ """
291
+ Format sourcing (i.e. invocation) of the jobscript functions file.
292
+ """
293
+ return dedent(
294
+ """\
295
+ . "${app_caps}_JS_FUNCS_PATH"
296
+
297
+ """
298
+ ).format(app_caps=app_name.upper())
299
+
300
+ @override
301
+ def format_commands_file(self, app_name: str, commands: str) -> str:
302
+ """
303
+ Format the commands file.
304
+ """
305
+ return self.format_source_functions_file(app_name, commands) + commands
306
+
201
307
  @override
202
308
  def format_save_parameter(
203
309
  self,
204
310
  workflow_app_alias: str,
205
311
  param_name: str,
206
312
  shell_var_name: str,
207
- EAR_ID: int,
208
313
  cmd_idx: int,
209
314
  stderr: bool,
315
+ app_name: str,
210
316
  ):
211
317
  """
212
318
  Produce code to save a parameter's value into the workflow persistent store.
@@ -214,78 +320,15 @@ class Bash(Shell):
214
320
  # TODO: quote shell_var_name as well? e.g. if it's a white-space delimited list?
215
321
  # and test.
216
322
  stderr_str = " --stderr" if stderr else ""
323
+ app_caps = app_name.upper()
217
324
  return (
218
- f"{workflow_app_alias} "
219
- f'internal workflow "$WK_PATH_ARG" save-parameter '
220
- f"{param_name} ${shell_var_name} {EAR_ID} {cmd_idx}{stderr_str} "
221
- f'>> "$app_stream_file" 2>&1'
325
+ f'{workflow_app_alias} --std-stream "${app_caps}_RUN_STD_PATH" '
326
+ f'internal workflow "${app_caps}_WK_PATH_ARG" save-parameter {stderr_str}'
327
+ f'"--" {param_name} ${shell_var_name} ${app_caps}_RUN_ID {cmd_idx}'
222
328
  f"\n"
223
329
  )
224
330
 
225
- @override
226
- def format_loop_check(
227
- self, workflow_app_alias: str, loop_name: str, run_ID: int
228
- ) -> str:
229
- """
230
- Produce code to check the looping status of part of a workflow.
231
- """
232
- return (
233
- f"{workflow_app_alias} "
234
- f'internal workflow "$WK_PATH_ARG" check-loop '
235
- f"{loop_name} {run_ID} "
236
- f'>> "$app_stream_file" 2>&1'
237
- f"\n"
238
- )
239
-
240
- @override
241
- def wrap_in_subshell(self, commands: str, abortable: bool) -> str:
242
- """Format commands to run within a subshell.
243
331
 
244
- This assumes commands ends in a newline.
245
-
246
- """
247
- commands = indent(commands, self.JS_INDENT)
248
- if abortable:
249
- # run commands in the background, and poll a file to check for abort requests:
250
- return dedent(
251
- """\
252
- (
253
- {commands}) &
254
-
255
- pid=$!
256
- abort_file=$WK_PATH/artifacts/submissions/$SUB_IDX/abort_EARs.txt
257
- while true
258
- do
259
- is_abort=`sed "$(($EAR_ID + 1))q;d" $abort_file`
260
- ps -p $pid > /dev/null
261
- if [ $? == 1 ]; then
262
- wait $pid
263
- exitcode=$?
264
- break
265
- elif [ "$is_abort" = "1" ]; then
266
- echo "Abort instruction received; stopping commands..." >> "$app_stream_file"
267
- kill $pid
268
- wait $pid 2>/dev/null
269
- exitcode={abort_exit_code}
270
- break
271
- else
272
- sleep 1 # TODO: TEMP: increase for production
273
- fi
274
- done
275
- return $exitcode
276
- """
277
- ).format(commands=commands, abort_exit_code=ABORT_EXIT_CODE)
278
- else:
279
- # run commands in "foreground":
280
- return dedent(
281
- """\
282
- (
283
- {commands})
284
- """
285
- ).format(commands=commands)
286
-
287
-
288
- @hydrate
289
332
  class WSLBash(Bash):
290
333
  """
291
334
  A variant of bash that handles running under WSL on Windows.
@@ -294,13 +337,59 @@ class WSLBash(Bash):
294
337
  #: Default name of the WSL interface executable.
295
338
  DEFAULT_WSL_EXE: ClassVar[str] = "wsl"
296
339
 
340
+ #: Template for the jobscript functions file.
341
+ JS_FUNCS: ClassVar[str] = dedent(
342
+ """\
343
+ {{workflow_app_alias}} () {{{{
344
+ (
345
+ {log_path_block}
346
+ {{env_setup}}{{app_invoc}}\\
347
+ --with-config log_file_path "$LOG_FILE_PATH"\\
348
+ --config-dir "{{config_dir}}"\\
349
+ --config-key "{{config_invoc_key}}"\\
350
+ "$@"
351
+ )
352
+ }}}}
353
+ """
354
+ ).format(
355
+ log_path_block=indent(
356
+ dedent(
357
+ """\
358
+ if [ -z "${app_caps}_LOG_PATH" ] || [ "${app_caps}_LOG_PATH" = " " ]; then
359
+ LOG_FILE_PATH=" "
360
+ else
361
+ LOG_FILE_PATH="$(wslpath -m ${app_caps}_LOG_PATH)"
362
+ fi
363
+ """
364
+ ),
365
+ prefix=Bash.JS_ENV_SETUP_INDENT,
366
+ )
367
+ )
297
368
  #: Template for the common part of the jobscript header.
298
369
  JS_HEADER: ClassVar[str] = Bash.JS_HEADER.replace(
299
370
  'WK_PATH_ARG="$WK_PATH"',
300
371
  'WK_PATH_ARG=`wslpath -m "$WK_PATH"`',
301
- ).replace(
302
- '--with-config log_file_path "`pwd`',
303
- '--with-config log_file_path "$(wslpath -m `pwd`)',
372
+ )
373
+ #: Template for the run execution command.
374
+ JS_RUN_CMD: ClassVar[str] = (
375
+ dedent(
376
+ """\
377
+ WSLENV=$WSLENV:${{APP_CAPS}}_WK_PATH
378
+ WSLENV=$WSLENV:${{APP_CAPS}}_WK_PATH_ARG
379
+ WSLENV=$WSLENV:${{APP_CAPS}}_JS_FUNCS_PATH
380
+ WSLENV=$WSLENV:${{APP_CAPS}}_STD_STREAM_FILE
381
+ WSLENV=$WSLENV:${{APP_CAPS}}_SUB_IDX
382
+ WSLENV=$WSLENV:${{APP_CAPS}}_JS_IDX
383
+ WSLENV=$WSLENV:${{APP_CAPS}}_RUN_ID
384
+ WSLENV=$WSLENV:${{APP_CAPS}}_BLOCK_ACT_IDX
385
+ WSLENV=$WSLENV:${{APP_CAPS}}_JS_ELEM_IDX
386
+ WSLENV=$WSLENV:${{APP_CAPS}}_BLOCK_ELEM_IDX
387
+ WSLENV=$WSLENV:${{APP_CAPS}}_BLOCK_IDX
388
+ WSLENV=$WSLENV:${{APP_CAPS}}_LOG_PATH/p
389
+
390
+ """
391
+ )
392
+ + Bash.JS_RUN_CMD
304
393
  )
305
394
 
306
395
  def __init__(
@@ -398,3 +487,7 @@ class WSLBash(Bash):
398
487
  vers_info.update(**get_OS_info_windows())
399
488
 
400
489
  return vers_info
490
+
491
+ def get_command_file_launch_command(self, cmd_file_path: str) -> list[str]:
492
+ """Get the command for launching the commands file for a given run."""
493
+ return self.executable + [self._convert_to_wsl_path(cmd_file_path)]