hpcflow 0.1.9__py3-none-any.whl → 0.2.0a271__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 (275) hide show
  1. hpcflow/__init__.py +2 -11
  2. hpcflow/__pyinstaller/__init__.py +5 -0
  3. hpcflow/__pyinstaller/hook-hpcflow.py +40 -0
  4. hpcflow/_version.py +1 -1
  5. hpcflow/app.py +43 -0
  6. hpcflow/cli.py +2 -462
  7. hpcflow/data/demo_data_manifest/__init__.py +3 -0
  8. hpcflow/data/demo_data_manifest/demo_data_manifest.json +6 -0
  9. hpcflow/data/jinja_templates/test/test_template.txt +8 -0
  10. hpcflow/data/programs/hello_world/README.md +1 -0
  11. hpcflow/data/programs/hello_world/hello_world.c +87 -0
  12. hpcflow/data/programs/hello_world/linux/hello_world +0 -0
  13. hpcflow/data/programs/hello_world/macos/hello_world +0 -0
  14. hpcflow/data/programs/hello_world/win/hello_world.exe +0 -0
  15. hpcflow/data/scripts/__init__.py +1 -0
  16. hpcflow/data/scripts/bad_script.py +2 -0
  17. hpcflow/data/scripts/demo_task_1_generate_t1_infile_1.py +8 -0
  18. hpcflow/data/scripts/demo_task_1_generate_t1_infile_2.py +8 -0
  19. hpcflow/data/scripts/demo_task_1_parse_p3.py +7 -0
  20. hpcflow/data/scripts/do_nothing.py +2 -0
  21. hpcflow/data/scripts/env_specifier_test/input_file_generator_pass_env_spec.py +4 -0
  22. hpcflow/data/scripts/env_specifier_test/main_script_test_pass_env_spec.py +8 -0
  23. hpcflow/data/scripts/env_specifier_test/output_file_parser_pass_env_spec.py +4 -0
  24. hpcflow/data/scripts/env_specifier_test/v1/input_file_generator_basic.py +4 -0
  25. hpcflow/data/scripts/env_specifier_test/v1/main_script_test_direct_in_direct_out.py +7 -0
  26. hpcflow/data/scripts/env_specifier_test/v1/output_file_parser_basic.py +4 -0
  27. hpcflow/data/scripts/env_specifier_test/v2/main_script_test_direct_in_direct_out.py +7 -0
  28. hpcflow/data/scripts/generate_t1_file_01.py +7 -0
  29. hpcflow/data/scripts/import_future_script.py +7 -0
  30. hpcflow/data/scripts/input_file_generator_basic.py +3 -0
  31. hpcflow/data/scripts/input_file_generator_basic_FAIL.py +3 -0
  32. hpcflow/data/scripts/input_file_generator_test_stdout_stderr.py +8 -0
  33. hpcflow/data/scripts/main_script_test_direct_in.py +3 -0
  34. hpcflow/data/scripts/main_script_test_direct_in_direct_out.py +6 -0
  35. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2.py +6 -0
  36. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed.py +6 -0
  37. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed_group.py +7 -0
  38. hpcflow/data/scripts/main_script_test_direct_in_direct_out_3.py +6 -0
  39. hpcflow/data/scripts/main_script_test_direct_in_direct_out_all_iters_test.py +15 -0
  40. hpcflow/data/scripts/main_script_test_direct_in_direct_out_env_spec.py +7 -0
  41. hpcflow/data/scripts/main_script_test_direct_in_direct_out_labels.py +8 -0
  42. hpcflow/data/scripts/main_script_test_direct_in_group_direct_out_3.py +6 -0
  43. hpcflow/data/scripts/main_script_test_direct_in_group_one_fail_direct_out_3.py +6 -0
  44. hpcflow/data/scripts/main_script_test_direct_sub_param_in_direct_out.py +6 -0
  45. hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +12 -0
  46. hpcflow/data/scripts/main_script_test_hdf5_in_obj_2.py +12 -0
  47. hpcflow/data/scripts/main_script_test_hdf5_in_obj_group.py +12 -0
  48. hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +11 -0
  49. hpcflow/data/scripts/main_script_test_json_and_direct_in_json_out.py +14 -0
  50. hpcflow/data/scripts/main_script_test_json_in_json_and_direct_out.py +17 -0
  51. hpcflow/data/scripts/main_script_test_json_in_json_out.py +14 -0
  52. hpcflow/data/scripts/main_script_test_json_in_json_out_labels.py +16 -0
  53. hpcflow/data/scripts/main_script_test_json_in_obj.py +12 -0
  54. hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -0
  55. hpcflow/data/scripts/main_script_test_json_out_obj.py +10 -0
  56. hpcflow/data/scripts/main_script_test_json_sub_param_in_json_out_labels.py +16 -0
  57. hpcflow/data/scripts/main_script_test_shell_env_vars.py +12 -0
  58. hpcflow/data/scripts/main_script_test_std_out_std_err.py +6 -0
  59. hpcflow/data/scripts/output_file_parser_basic.py +3 -0
  60. hpcflow/data/scripts/output_file_parser_basic_FAIL.py +7 -0
  61. hpcflow/data/scripts/output_file_parser_test_stdout_stderr.py +8 -0
  62. hpcflow/data/scripts/parse_t1_file_01.py +4 -0
  63. hpcflow/data/scripts/script_exit_test.py +5 -0
  64. hpcflow/data/template_components/__init__.py +1 -0
  65. hpcflow/data/template_components/command_files.yaml +26 -0
  66. hpcflow/data/template_components/environments.yaml +13 -0
  67. hpcflow/data/template_components/parameters.yaml +14 -0
  68. hpcflow/data/template_components/task_schemas.yaml +139 -0
  69. hpcflow/data/workflows/workflow_1.yaml +5 -0
  70. hpcflow/examples.ipynb +1037 -0
  71. hpcflow/sdk/__init__.py +149 -0
  72. hpcflow/sdk/app.py +4266 -0
  73. hpcflow/sdk/cli.py +1479 -0
  74. hpcflow/sdk/cli_common.py +385 -0
  75. hpcflow/sdk/config/__init__.py +5 -0
  76. hpcflow/sdk/config/callbacks.py +246 -0
  77. hpcflow/sdk/config/cli.py +388 -0
  78. hpcflow/sdk/config/config.py +1410 -0
  79. hpcflow/sdk/config/config_file.py +501 -0
  80. hpcflow/sdk/config/errors.py +272 -0
  81. hpcflow/sdk/config/types.py +150 -0
  82. hpcflow/sdk/core/__init__.py +38 -0
  83. hpcflow/sdk/core/actions.py +3857 -0
  84. hpcflow/sdk/core/app_aware.py +25 -0
  85. hpcflow/sdk/core/cache.py +224 -0
  86. hpcflow/sdk/core/command_files.py +814 -0
  87. hpcflow/sdk/core/commands.py +424 -0
  88. hpcflow/sdk/core/element.py +2071 -0
  89. hpcflow/sdk/core/enums.py +221 -0
  90. hpcflow/sdk/core/environment.py +256 -0
  91. hpcflow/sdk/core/errors.py +1043 -0
  92. hpcflow/sdk/core/execute.py +207 -0
  93. hpcflow/sdk/core/json_like.py +809 -0
  94. hpcflow/sdk/core/loop.py +1320 -0
  95. hpcflow/sdk/core/loop_cache.py +282 -0
  96. hpcflow/sdk/core/object_list.py +933 -0
  97. hpcflow/sdk/core/parameters.py +3371 -0
  98. hpcflow/sdk/core/rule.py +196 -0
  99. hpcflow/sdk/core/run_dir_files.py +57 -0
  100. hpcflow/sdk/core/skip_reason.py +7 -0
  101. hpcflow/sdk/core/task.py +3792 -0
  102. hpcflow/sdk/core/task_schema.py +993 -0
  103. hpcflow/sdk/core/test_utils.py +538 -0
  104. hpcflow/sdk/core/types.py +447 -0
  105. hpcflow/sdk/core/utils.py +1207 -0
  106. hpcflow/sdk/core/validation.py +87 -0
  107. hpcflow/sdk/core/values.py +477 -0
  108. hpcflow/sdk/core/workflow.py +4820 -0
  109. hpcflow/sdk/core/zarr_io.py +206 -0
  110. hpcflow/sdk/data/__init__.py +13 -0
  111. hpcflow/sdk/data/config_file_schema.yaml +34 -0
  112. hpcflow/sdk/data/config_schema.yaml +260 -0
  113. hpcflow/sdk/data/environments_spec_schema.yaml +21 -0
  114. hpcflow/sdk/data/files_spec_schema.yaml +5 -0
  115. hpcflow/sdk/data/parameters_spec_schema.yaml +7 -0
  116. hpcflow/sdk/data/task_schema_spec_schema.yaml +3 -0
  117. hpcflow/sdk/data/workflow_spec_schema.yaml +22 -0
  118. hpcflow/sdk/demo/__init__.py +3 -0
  119. hpcflow/sdk/demo/cli.py +242 -0
  120. hpcflow/sdk/helper/__init__.py +3 -0
  121. hpcflow/sdk/helper/cli.py +137 -0
  122. hpcflow/sdk/helper/helper.py +300 -0
  123. hpcflow/sdk/helper/watcher.py +192 -0
  124. hpcflow/sdk/log.py +288 -0
  125. hpcflow/sdk/persistence/__init__.py +18 -0
  126. hpcflow/sdk/persistence/base.py +2817 -0
  127. hpcflow/sdk/persistence/defaults.py +6 -0
  128. hpcflow/sdk/persistence/discovery.py +39 -0
  129. hpcflow/sdk/persistence/json.py +954 -0
  130. hpcflow/sdk/persistence/pending.py +948 -0
  131. hpcflow/sdk/persistence/store_resource.py +203 -0
  132. hpcflow/sdk/persistence/types.py +309 -0
  133. hpcflow/sdk/persistence/utils.py +73 -0
  134. hpcflow/sdk/persistence/zarr.py +2388 -0
  135. hpcflow/sdk/runtime.py +320 -0
  136. hpcflow/sdk/submission/__init__.py +3 -0
  137. hpcflow/sdk/submission/enums.py +70 -0
  138. hpcflow/sdk/submission/jobscript.py +2379 -0
  139. hpcflow/sdk/submission/schedulers/__init__.py +281 -0
  140. hpcflow/sdk/submission/schedulers/direct.py +233 -0
  141. hpcflow/sdk/submission/schedulers/sge.py +376 -0
  142. hpcflow/sdk/submission/schedulers/slurm.py +598 -0
  143. hpcflow/sdk/submission/schedulers/utils.py +25 -0
  144. hpcflow/sdk/submission/shells/__init__.py +52 -0
  145. hpcflow/sdk/submission/shells/base.py +229 -0
  146. hpcflow/sdk/submission/shells/bash.py +504 -0
  147. hpcflow/sdk/submission/shells/os_version.py +115 -0
  148. hpcflow/sdk/submission/shells/powershell.py +352 -0
  149. hpcflow/sdk/submission/submission.py +1402 -0
  150. hpcflow/sdk/submission/types.py +140 -0
  151. hpcflow/sdk/typing.py +194 -0
  152. hpcflow/sdk/utils/arrays.py +69 -0
  153. hpcflow/sdk/utils/deferred_file.py +55 -0
  154. hpcflow/sdk/utils/hashing.py +16 -0
  155. hpcflow/sdk/utils/patches.py +31 -0
  156. hpcflow/sdk/utils/strings.py +69 -0
  157. hpcflow/tests/api/test_api.py +32 -0
  158. hpcflow/tests/conftest.py +123 -0
  159. hpcflow/tests/data/__init__.py +0 -0
  160. hpcflow/tests/data/benchmark_N_elements.yaml +6 -0
  161. hpcflow/tests/data/benchmark_script_runner.yaml +26 -0
  162. hpcflow/tests/data/multi_path_sequences.yaml +29 -0
  163. hpcflow/tests/data/workflow_1.json +10 -0
  164. hpcflow/tests/data/workflow_1.yaml +5 -0
  165. hpcflow/tests/data/workflow_1_slurm.yaml +8 -0
  166. hpcflow/tests/data/workflow_1_wsl.yaml +8 -0
  167. hpcflow/tests/data/workflow_test_run_abort.yaml +42 -0
  168. hpcflow/tests/jinja_templates/test_jinja_templates.py +161 -0
  169. hpcflow/tests/programs/test_programs.py +180 -0
  170. hpcflow/tests/schedulers/direct_linux/test_direct_linux_submission.py +12 -0
  171. hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
  172. hpcflow/tests/schedulers/slurm/test_slurm_submission.py +14 -0
  173. hpcflow/tests/scripts/test_input_file_generators.py +282 -0
  174. hpcflow/tests/scripts/test_main_scripts.py +1361 -0
  175. hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
  176. hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
  177. hpcflow/tests/shells/wsl/test_wsl_submission.py +14 -0
  178. hpcflow/tests/unit/test_action.py +1066 -0
  179. hpcflow/tests/unit/test_action_rule.py +24 -0
  180. hpcflow/tests/unit/test_app.py +132 -0
  181. hpcflow/tests/unit/test_cache.py +46 -0
  182. hpcflow/tests/unit/test_cli.py +172 -0
  183. hpcflow/tests/unit/test_command.py +377 -0
  184. hpcflow/tests/unit/test_config.py +195 -0
  185. hpcflow/tests/unit/test_config_file.py +162 -0
  186. hpcflow/tests/unit/test_element.py +666 -0
  187. hpcflow/tests/unit/test_element_iteration.py +88 -0
  188. hpcflow/tests/unit/test_element_set.py +158 -0
  189. hpcflow/tests/unit/test_group.py +115 -0
  190. hpcflow/tests/unit/test_input_source.py +1479 -0
  191. hpcflow/tests/unit/test_input_value.py +398 -0
  192. hpcflow/tests/unit/test_jobscript_unit.py +757 -0
  193. hpcflow/tests/unit/test_json_like.py +1247 -0
  194. hpcflow/tests/unit/test_loop.py +2674 -0
  195. hpcflow/tests/unit/test_meta_task.py +325 -0
  196. hpcflow/tests/unit/test_multi_path_sequences.py +259 -0
  197. hpcflow/tests/unit/test_object_list.py +116 -0
  198. hpcflow/tests/unit/test_parameter.py +243 -0
  199. hpcflow/tests/unit/test_persistence.py +664 -0
  200. hpcflow/tests/unit/test_resources.py +243 -0
  201. hpcflow/tests/unit/test_run.py +286 -0
  202. hpcflow/tests/unit/test_run_directories.py +29 -0
  203. hpcflow/tests/unit/test_runtime.py +9 -0
  204. hpcflow/tests/unit/test_schema_input.py +372 -0
  205. hpcflow/tests/unit/test_shell.py +129 -0
  206. hpcflow/tests/unit/test_slurm.py +39 -0
  207. hpcflow/tests/unit/test_submission.py +502 -0
  208. hpcflow/tests/unit/test_task.py +2560 -0
  209. hpcflow/tests/unit/test_task_schema.py +182 -0
  210. hpcflow/tests/unit/test_utils.py +616 -0
  211. hpcflow/tests/unit/test_value_sequence.py +549 -0
  212. hpcflow/tests/unit/test_values.py +91 -0
  213. hpcflow/tests/unit/test_workflow.py +827 -0
  214. hpcflow/tests/unit/test_workflow_template.py +186 -0
  215. hpcflow/tests/unit/utils/test_arrays.py +40 -0
  216. hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
  217. hpcflow/tests/unit/utils/test_hashing.py +65 -0
  218. hpcflow/tests/unit/utils/test_patches.py +5 -0
  219. hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
  220. hpcflow/tests/unit/utils/test_strings.py +97 -0
  221. hpcflow/tests/workflows/__init__.py +0 -0
  222. hpcflow/tests/workflows/test_directory_structure.py +31 -0
  223. hpcflow/tests/workflows/test_jobscript.py +355 -0
  224. hpcflow/tests/workflows/test_run_status.py +198 -0
  225. hpcflow/tests/workflows/test_skip_downstream.py +696 -0
  226. hpcflow/tests/workflows/test_submission.py +140 -0
  227. hpcflow/tests/workflows/test_workflows.py +564 -0
  228. hpcflow/tests/workflows/test_zip.py +18 -0
  229. hpcflow/viz_demo.ipynb +6794 -0
  230. hpcflow-0.2.0a271.dist-info/LICENSE +375 -0
  231. hpcflow-0.2.0a271.dist-info/METADATA +65 -0
  232. hpcflow-0.2.0a271.dist-info/RECORD +237 -0
  233. {hpcflow-0.1.9.dist-info → hpcflow-0.2.0a271.dist-info}/WHEEL +4 -5
  234. hpcflow-0.2.0a271.dist-info/entry_points.txt +6 -0
  235. hpcflow/api.py +0 -458
  236. hpcflow/archive/archive.py +0 -308
  237. hpcflow/archive/cloud/cloud.py +0 -47
  238. hpcflow/archive/cloud/errors.py +0 -9
  239. hpcflow/archive/cloud/providers/dropbox.py +0 -432
  240. hpcflow/archive/errors.py +0 -5
  241. hpcflow/base_db.py +0 -4
  242. hpcflow/config.py +0 -232
  243. hpcflow/copytree.py +0 -66
  244. hpcflow/data/examples/_config.yml +0 -14
  245. hpcflow/data/examples/damask/demo/1.run.yml +0 -4
  246. hpcflow/data/examples/damask/demo/2.process.yml +0 -29
  247. hpcflow/data/examples/damask/demo/geom.geom +0 -2052
  248. hpcflow/data/examples/damask/demo/load.load +0 -1
  249. hpcflow/data/examples/damask/demo/material.config +0 -185
  250. hpcflow/data/examples/damask/inputs/geom.geom +0 -2052
  251. hpcflow/data/examples/damask/inputs/load.load +0 -1
  252. hpcflow/data/examples/damask/inputs/material.config +0 -185
  253. hpcflow/data/examples/damask/profiles/_variable_lookup.yml +0 -21
  254. hpcflow/data/examples/damask/profiles/damask.yml +0 -4
  255. hpcflow/data/examples/damask/profiles/damask_process.yml +0 -8
  256. hpcflow/data/examples/damask/profiles/damask_run.yml +0 -5
  257. hpcflow/data/examples/damask/profiles/default.yml +0 -6
  258. hpcflow/data/examples/thinking.yml +0 -177
  259. hpcflow/errors.py +0 -2
  260. hpcflow/init_db.py +0 -37
  261. hpcflow/models.py +0 -2549
  262. hpcflow/nesting.py +0 -9
  263. hpcflow/profiles.py +0 -455
  264. hpcflow/project.py +0 -81
  265. hpcflow/scheduler.py +0 -323
  266. hpcflow/utils.py +0 -103
  267. hpcflow/validation.py +0 -167
  268. hpcflow/variables.py +0 -544
  269. hpcflow-0.1.9.dist-info/METADATA +0 -168
  270. hpcflow-0.1.9.dist-info/RECORD +0 -45
  271. hpcflow-0.1.9.dist-info/entry_points.txt +0 -8
  272. hpcflow-0.1.9.dist-info/top_level.txt +0 -1
  273. /hpcflow/{archive → data/jinja_templates}/__init__.py +0 -0
  274. /hpcflow/{archive/cloud → data/programs}/__init__.py +0 -0
  275. /hpcflow/{archive/cloud/providers → data/workflows}/__init__.py +0 -0
@@ -0,0 +1,504 @@
1
+ """
2
+ Shell models based on the GNU Bourne-Again Shell.
3
+ """
4
+
5
+ from __future__ import annotations
6
+ from pathlib import Path
7
+ import subprocess
8
+ import shutil
9
+ from textwrap import dedent, indent
10
+ from typing import TYPE_CHECKING
11
+ from typing_extensions import override
12
+ from hpcflow.sdk.typing import hydrate
13
+ from hpcflow.sdk.core import ABORT_EXIT_CODE
14
+ from hpcflow.sdk.submission.shells.base import Shell
15
+ from hpcflow.sdk.submission.shells.os_version import (
16
+ get_OS_info_POSIX,
17
+ get_OS_info_windows,
18
+ )
19
+
20
+ if TYPE_CHECKING:
21
+ from collections.abc import Mapping
22
+ from typing import Any, ClassVar
23
+ from .base import VersionInfo, JobscriptHeaderArgs
24
+
25
+
26
+ @hydrate
27
+ class Bash(Shell):
28
+ """
29
+ Class to represent using bash on a POSIX OS to generate and submit a jobscript.
30
+ """
31
+
32
+ #: Default for executable name.
33
+ DEFAULT_EXE: ClassVar[str] = "/bin/bash"
34
+
35
+ #: File extension for jobscripts.
36
+ JS_EXT: ClassVar[str] = ".sh"
37
+ #: Basic indent.
38
+ JS_INDENT: ClassVar[str] = " "
39
+ #: Indent for environment setup.
40
+ JS_ENV_SETUP_INDENT: ClassVar[str] = 2 * JS_INDENT
41
+ #: Template for the jobscript shebang line.
42
+ JS_SHEBANG: ClassVar[str] = """#!{shebang}"""
43
+ #: Template for the jobscript functions file.
44
+ JS_FUNCS: ClassVar[str] = dedent(
45
+ """\
46
+ {workflow_app_alias} () {{
47
+ (
48
+ {env_setup}{app_invoc}\\
49
+ --with-config log_file_path "${app_caps}_LOG_PATH"\\
50
+ --config-dir "{config_dir}"\\
51
+ --config-key "{config_invoc_key}"\\
52
+ "$@"
53
+ )
54
+ }}
55
+ """
56
+ )
57
+ #: Template for the common part of the jobscript header.
58
+ JS_HEADER: ClassVar[str] = dedent(
59
+ """\
60
+ WK_PATH=`pwd`
61
+ WK_PATH_ARG="$WK_PATH"
62
+ SUB_IDX={sub_idx}
63
+ JS_IDX={js_idx}
64
+ APP_CAPS={app_caps}
65
+
66
+ SUB_DIR="$WK_PATH/artifacts/submissions/${{SUB_IDX}}"
67
+ JS_FUNCS_PATH="$SUB_DIR/{jobscript_functions_dir}/{jobscript_functions_name}"
68
+ . "$JS_FUNCS_PATH"
69
+
70
+ EAR_ID_FILE="$WK_PATH/artifacts/submissions/${{SUB_IDX}}/{run_IDs_file_dir}/{run_IDs_file_name}"
71
+ SUB_TMP_DIR="$SUB_DIR/{tmp_dir_name}"
72
+ SUB_LOG_DIR="$SUB_DIR/{log_dir_name}"
73
+ SUB_STD_DIR="$SUB_DIR/{app_std_dir_name}"
74
+ SUB_SCRIPTS_DIR="$SUB_DIR/{scripts_dir_name}"
75
+
76
+ export {app_caps}_WK_PATH=$WK_PATH
77
+ export {app_caps}_WK_PATH_ARG=$WK_PATH_ARG
78
+ export {app_caps}_SUB_IDX={sub_idx}
79
+ export {app_caps}_SUB_SCRIPTS_DIR=$SUB_SCRIPTS_DIR
80
+ export {app_caps}_SUB_TMP_DIR=$SUB_TMP_DIR
81
+ export {app_caps}_SUB_LOG_DIR=$SUB_LOG_DIR
82
+ export {app_caps}_SUB_STD_DIR=$SUB_STD_DIR
83
+ export {app_caps}_LOG_PATH="$SUB_LOG_DIR/js_${{JS_IDX}}.log"
84
+ export {app_caps}_JS_FUNCS_PATH=$JS_FUNCS_PATH
85
+ export {app_caps}_JS_IDX={js_idx}
86
+ export {app_caps}_RUN_ID_FILE=$EAR_ID_FILE
87
+ """
88
+ )
89
+ #: Template for the jobscript header when scheduled.
90
+ JS_SCHEDULER_HEADER: ClassVar[str] = dedent(
91
+ """\
92
+ {shebang}
93
+
94
+ {scheduler_options}
95
+ {header}
96
+ """
97
+ )
98
+ #: Template for the jobscript header when directly executed.
99
+ JS_DIRECT_HEADER: ClassVar[str] = dedent(
100
+ """\
101
+ {shebang}
102
+ {header}
103
+ {wait_command}
104
+ """
105
+ )
106
+ #: Template for enabling writing of the app log.
107
+ JS_RUN_LOG_PATH_ENABLE: ClassVar[str] = '"$SUB_LOG_DIR/{run_log_file_name}"'
108
+ #: Template for disabling writing of the app log.
109
+ JS_RUN_LOG_PATH_DISABLE: ClassVar[str] = '" "'
110
+ #: Template for the run execution command.
111
+ JS_RUN_CMD: ClassVar[str] = (
112
+ '{workflow_app_alias} internal workflow "$WK_PATH_ARG" execute-run '
113
+ "$SUB_IDX $JS_IDX $block_idx $block_act_idx $EAR_ID\n"
114
+ )
115
+ #: Template for the execution command for multiple combined runs.
116
+ JS_RUN_CMD_COMBINED: ClassVar[str] = (
117
+ '{workflow_app_alias} internal workflow "$WK_PATH_ARG" execute-combined-runs '
118
+ "$SUB_IDX $JS_IDX\n"
119
+ )
120
+ #: Template for setting up run environment variables and executing the run.
121
+ JS_RUN: ClassVar[str] = dedent(
122
+ """\
123
+ EAR_ID="$(cut -d'{EAR_files_delimiter}' -f $(($block_act_idx + 1)) <<< $elem_EAR_IDs)"
124
+ if [ "$EAR_ID" = "-1" ]; then
125
+ continue
126
+ fi
127
+
128
+ export {app_caps}_RUN_ID=$EAR_ID
129
+ export {app_caps}_RUN_LOG_PATH={run_log_enable_disable}
130
+ export {app_caps}_LOG_PATH="${app_caps}_RUN_LOG_PATH"
131
+ export {app_caps}_RUN_STD_PATH="$SUB_STD_DIR/${app_caps}_RUN_ID.txt"
132
+ export {app_caps}_BLOCK_ACT_IDX=$block_act_idx
133
+
134
+ cd "$SUB_TMP_DIR"
135
+
136
+ {run_cmd}
137
+ """
138
+ )
139
+ #: Template for the action-run processing loop in a jobscript.
140
+ JS_ACT_MULTI: ClassVar[str] = dedent(
141
+ """\
142
+ for ((block_act_idx=0;block_act_idx<{num_actions};block_act_idx++))
143
+ do
144
+ {run_block}
145
+ done
146
+ """
147
+ )
148
+ #: Template for the single-action-run execution in a jobscript.
149
+ JS_ACT_SINGLE: ClassVar[str] = dedent(
150
+ """\
151
+ block_act_idx=0
152
+ {run_block}
153
+ """
154
+ )
155
+ #: Template for setting up environment variables and running one or more action-runs.
156
+ JS_MAIN: ClassVar[str] = dedent(
157
+ """\
158
+ block_elem_idx=$(( $JS_elem_idx - {block_start_elem_idx} ))
159
+ elem_EAR_IDs=`sed "$((${{JS_elem_idx}} + 1))q;d" "$EAR_ID_FILE"`
160
+ export {app_caps}_JS_ELEM_IDX=$JS_elem_idx
161
+ export {app_caps}_BLOCK_ELEM_IDX=$block_elem_idx
162
+
163
+ {action}
164
+ """
165
+ )
166
+ #: Template for a jobscript-block header.
167
+ JS_BLOCK_HEADER: ClassVar[str] = dedent( # for single-block jobscripts only
168
+ """\
169
+ block_idx=0
170
+ export {app_caps}_BLOCK_IDX=0
171
+ """
172
+ )
173
+ #: Template for single-element execution.
174
+ JS_ELEMENT_SINGLE: ClassVar[str] = dedent(
175
+ """\
176
+ JS_elem_idx={block_start_elem_idx}
177
+ {main}
178
+ """
179
+ )
180
+ #: Template for the element processing loop in a jobscript.
181
+ JS_ELEMENT_MULTI_LOOP: ClassVar[str] = dedent(
182
+ """\
183
+ for ((JS_elem_idx={block_start_elem_idx};JS_elem_idx<$(({block_start_elem_idx} + {num_elements}));JS_elem_idx++))
184
+ do
185
+ {main}
186
+ done
187
+ """
188
+ )
189
+ #: Template for the array handling code in a jobscript.
190
+ JS_ELEMENT_MULTI_ARRAY: ClassVar[str] = dedent(
191
+ """\
192
+ JS_elem_idx=$(({scheduler_array_item_var} - 1))
193
+ {main}
194
+ """
195
+ )
196
+ #: Template for the jobscript block loop in a jobscript.
197
+ JS_BLOCK_LOOP: ClassVar[str] = dedent(
198
+ """\
199
+ num_elements={num_elements}
200
+ num_actions={num_actions}
201
+ block_start_elem_idx=0
202
+ for ((block_idx=0;block_idx<{num_blocks};block_idx++))
203
+ do
204
+ export {app_caps}_BLOCK_IDX=$block_idx
205
+ {element_loop}
206
+ block_start_elem_idx=$(($block_start_elem_idx + ${{num_elements[$block_idx]}}))
207
+ done
208
+ """
209
+ )
210
+ #: Template for the jobscript footer.
211
+ JS_FOOTER: ClassVar[str] = dedent(
212
+ """\
213
+ cd $WK_PATH
214
+ """
215
+ )
216
+
217
+ @property
218
+ def linux_release_file(self) -> str:
219
+ """
220
+ The name of the file describing the Linux version.
221
+ """
222
+ return self.os_args["linux_release_file"]
223
+
224
+ def _get_OS_info_POSIX(self) -> Mapping[str, str]:
225
+ return get_OS_info_POSIX(linux_release_file=self.linux_release_file)
226
+
227
+ @override
228
+ def get_version_info(self, exclude_os: bool = False) -> VersionInfo:
229
+ """Get bash version information.
230
+
231
+ Parameters
232
+ ----------
233
+ exclude_os
234
+ If True, exclude operating system information.
235
+
236
+ """
237
+
238
+ bash_proc = subprocess.run(
239
+ args=self.executable + ["--version"],
240
+ stdout=subprocess.PIPE,
241
+ text=True,
242
+ )
243
+ if bash_proc.returncode == 0:
244
+ first_line = bash_proc.stdout.splitlines()[0]
245
+ bash_version = first_line.split(" ")[3]
246
+ else:
247
+ raise RuntimeError("Failed to parse bash version information.")
248
+
249
+ return {
250
+ "shell_name": "bash",
251
+ "shell_executable": self.executable,
252
+ "shell_version": bash_version,
253
+ **({} if exclude_os else self._get_OS_info_POSIX()),
254
+ }
255
+
256
+ @staticmethod
257
+ def process_app_invoc_executable(app_invoc_exe: str) -> str:
258
+ # escape spaces with a back slash:
259
+ return app_invoc_exe.replace(" ", r"\ ")
260
+
261
+ @override
262
+ def format_env_var_get(self, var: str) -> str:
263
+ """
264
+ Format retrieval of a shell environment variable.
265
+ """
266
+ return f"${var}"
267
+
268
+ @override
269
+ def format_array(self, lst: list) -> str:
270
+ """
271
+ Format construction of a shell array.
272
+ """
273
+ return "(" + " ".join(str(i) for i in lst) + ")"
274
+
275
+ @override
276
+ def format_array_get_item(self, arr_name: str, index: int | str) -> str:
277
+ """
278
+ Format retrieval of a shell array item at a specified index.
279
+ """
280
+ return f"${{{arr_name}[{index}]}}"
281
+
282
+ @override
283
+ def format_stream_assignment(self, shell_var_name: str, command: str) -> str:
284
+ """
285
+ Produce code to assign the output of the command to a shell variable.
286
+ """
287
+ return f"{shell_var_name}=`{command}`"
288
+
289
+ @override
290
+ def format_source_functions_file(self, app_name: str, commands: str) -> str:
291
+ """
292
+ Format sourcing (i.e. invocation) of the jobscript functions file.
293
+ """
294
+ return dedent(
295
+ """\
296
+ . "${app_caps}_JS_FUNCS_PATH"
297
+
298
+ """
299
+ ).format(app_caps=app_name.upper())
300
+
301
+ @override
302
+ def format_commands_file(self, app_name: str, commands: str) -> str:
303
+ """
304
+ Format the commands file.
305
+ """
306
+ return self.format_source_functions_file(app_name, commands) + commands
307
+
308
+ @override
309
+ def format_save_parameter(
310
+ self,
311
+ workflow_app_alias: str,
312
+ param_name: str,
313
+ shell_var_name: str,
314
+ cmd_idx: int,
315
+ stderr: bool,
316
+ app_name: str,
317
+ ):
318
+ """
319
+ Produce code to save a parameter's value into the workflow persistent store.
320
+ """
321
+ # TODO: quote shell_var_name as well? e.g. if it's a white-space delimited list?
322
+ # and test.
323
+ stderr_str = " --stderr" if stderr else ""
324
+ app_caps = app_name.upper()
325
+ return (
326
+ f'{workflow_app_alias} --std-stream "${app_caps}_RUN_STD_PATH" '
327
+ f'internal workflow "${app_caps}_WK_PATH_ARG" save-parameter {stderr_str}'
328
+ f'"--" {param_name} ${shell_var_name} ${app_caps}_RUN_ID {cmd_idx}'
329
+ f"\n"
330
+ )
331
+
332
+
333
+ class WSLBash(Bash):
334
+ """
335
+ A variant of bash that handles running under WSL on Windows.
336
+ """
337
+
338
+ #: Default name of the WSL interface executable.
339
+ DEFAULT_WSL_EXE: ClassVar[str] = "wsl.exe"
340
+
341
+ #: Template for the jobscript functions file.
342
+ JS_FUNCS: ClassVar[str] = dedent(
343
+ """\
344
+ {{workflow_app_alias}} () {{{{
345
+ (
346
+ {log_path_block}
347
+ {{env_setup}}{{app_invoc}}\\
348
+ --with-config log_file_path "$LOG_FILE_PATH"\\
349
+ --config-dir "{{config_dir}}"\\
350
+ --config-key "{{config_invoc_key}}"\\
351
+ "$@"
352
+ )
353
+ }}}}
354
+ """
355
+ ).format(
356
+ log_path_block=indent(
357
+ dedent(
358
+ """\
359
+ if [ -z "${app_caps}_LOG_PATH" ] || [ "${app_caps}_LOG_PATH" = " " ]; then
360
+ LOG_FILE_PATH=" "
361
+ else
362
+ LOG_FILE_PATH="$(wslpath -m ${app_caps}_LOG_PATH)"
363
+ fi
364
+ """
365
+ ),
366
+ prefix=Bash.JS_ENV_SETUP_INDENT,
367
+ )
368
+ )
369
+ #: Template for the common part of the jobscript header.
370
+ JS_HEADER: ClassVar[str] = Bash.JS_HEADER.replace(
371
+ 'WK_PATH_ARG="$WK_PATH"',
372
+ 'WK_PATH_ARG=`wslpath -m "$WK_PATH"`',
373
+ )
374
+ #: Template for the run execution command.
375
+ JS_RUN_CMD: ClassVar[str] = (
376
+ dedent(
377
+ """\
378
+ WSLENV=$WSLENV:${{APP_CAPS}}_WK_PATH
379
+ WSLENV=$WSLENV:${{APP_CAPS}}_WK_PATH_ARG
380
+ WSLENV=$WSLENV:${{APP_CAPS}}_JS_FUNCS_PATH
381
+ WSLENV=$WSLENV:${{APP_CAPS}}_STD_STREAM_FILE
382
+ WSLENV=$WSLENV:${{APP_CAPS}}_SUB_IDX
383
+ WSLENV=$WSLENV:${{APP_CAPS}}_JS_IDX
384
+ WSLENV=$WSLENV:${{APP_CAPS}}_RUN_ID
385
+ WSLENV=$WSLENV:${{APP_CAPS}}_BLOCK_ACT_IDX
386
+ WSLENV=$WSLENV:${{APP_CAPS}}_JS_ELEM_IDX
387
+ WSLENV=$WSLENV:${{APP_CAPS}}_BLOCK_ELEM_IDX
388
+ WSLENV=$WSLENV:${{APP_CAPS}}_BLOCK_IDX
389
+ WSLENV=$WSLENV:${{APP_CAPS}}_LOG_PATH/p
390
+
391
+ """
392
+ )
393
+ + Bash.JS_RUN_CMD
394
+ )
395
+
396
+ def __init__(
397
+ self,
398
+ WSL_executable: str | None = None,
399
+ WSL_distribution: str | None = None,
400
+ WSL_user: str | None = None,
401
+ *args,
402
+ **kwargs,
403
+ ):
404
+
405
+ # `Start-Process` (see `Jobscript._launch_direct_js_win`) seems to resolve the
406
+ # executable, which means the process's `cmdline` might look different to what we
407
+ # record; so let's resolve the WSL executable ourselves:
408
+ resolved_exec = shutil.which(WSL_executable or self.DEFAULT_WSL_EXE)
409
+ assert resolved_exec
410
+ #: The WSL executable wrapper.
411
+ self.WSL_executable = resolved_exec
412
+ #: The WSL distribution to use, if any.
413
+ self.WSL_distribution = WSL_distribution
414
+ #: The WSL user to use, if any.
415
+ self.WSL_user = WSL_user
416
+ super().__init__(*args, **kwargs)
417
+
418
+ def __eq__(self, other: Any) -> bool:
419
+ return super().__eq__(other) and (
420
+ self.WSL_executable == other.WSL_executable
421
+ and self.WSL_distribution == other.WSL_distribution
422
+ and self.WSL_user == other.WSL_user
423
+ )
424
+
425
+ def _get_WSL_command(self) -> list[str]:
426
+ out = [self.WSL_executable]
427
+ if self.WSL_distribution:
428
+ out += ["--distribution", self.WSL_distribution]
429
+ if self.WSL_user:
430
+ out += ["--user", self.WSL_user]
431
+ return out
432
+
433
+ @property
434
+ def executable(self) -> list[str]:
435
+ return self._get_WSL_command() + super().executable
436
+
437
+ @property
438
+ def shebang_executable(self) -> list[str]:
439
+ """
440
+ The executable to use in a shebang line, overridden here to exclude the WSL
441
+ command.
442
+ """
443
+ return super().executable
444
+
445
+ def _get_OS_info_POSIX(self) -> Mapping[str, str]:
446
+ return get_OS_info_POSIX(
447
+ WSL_executable=self._get_WSL_command(),
448
+ use_py=False,
449
+ linux_release_file=self.linux_release_file,
450
+ )
451
+
452
+ @staticmethod
453
+ def _convert_to_wsl_path(win_path: str | Path) -> str:
454
+ win_path = Path(win_path)
455
+ parts = list(win_path.parts)
456
+ parts[0] = f"/mnt/{win_path.drive.lower().rstrip(':')}"
457
+ return "/".join(parts)
458
+
459
+ def process_JS_header_args(
460
+ self, header_args: JobscriptHeaderArgs
461
+ ) -> JobscriptHeaderArgs:
462
+ # convert executable windows paths to posix style as expected by WSL:
463
+ ai = header_args["app_invoc"]
464
+ if isinstance(ai, list):
465
+ ai[0] = self._convert_to_wsl_path(ai[0])
466
+ return super().process_JS_header_args(header_args)
467
+
468
+ def prepare_JS_path(self, js_path: Path) -> str:
469
+ return self._convert_to_wsl_path(js_path)
470
+
471
+ def prepare_element_run_dirs(self, run_dirs: list[list[Path]]) -> list[list[str]]:
472
+ return [[str(path).replace("\\", "/") for path in i] for i in run_dirs]
473
+
474
+ @override
475
+ def get_version_info(self, exclude_os: bool = False) -> VersionInfo:
476
+ """Get WSL and bash version information.
477
+
478
+ Parameters
479
+ ----------
480
+ exclude_os
481
+ If True, exclude operating system information.
482
+
483
+ """
484
+ vers_info = super().get_version_info(exclude_os=exclude_os)
485
+
486
+ vers_info["shell_name"] = f"wsl+{vers_info['shell_name']}".lower()
487
+ vers_info["WSL_executable"] = self.WSL_executable
488
+ if self.WSL_distribution:
489
+ vers_info["WSL_distribution"] = self.WSL_distribution
490
+ if self.WSL_user:
491
+ vers_info["WSL_user"] = self.WSL_user
492
+
493
+ for key in tuple(vers_info):
494
+ if key.startswith("OS_"):
495
+ vers_info[f"WSL_{key}"] = vers_info.pop(key)
496
+
497
+ if not exclude_os:
498
+ vers_info.update(**get_OS_info_windows())
499
+
500
+ return vers_info
501
+
502
+ def get_command_file_launch_command(self, cmd_file_path: str) -> list[str]:
503
+ """Get the command for launching the commands file for a given run."""
504
+ return self.executable + [self._convert_to_wsl_path(cmd_file_path)]
@@ -0,0 +1,115 @@
1
+ """
2
+ Operating system information discovery helpers.
3
+ """
4
+
5
+ from __future__ import annotations
6
+ from collections.abc import Mapping
7
+ import platform
8
+ import re
9
+ import subprocess
10
+ from typing import Final
11
+
12
+ _DEFAULT_LINUX_RELEASE_FILE: Final = "/etc/os-release"
13
+
14
+
15
+ def get_OS_info() -> Mapping[str, str]:
16
+ """
17
+ Get basic operating system version info.
18
+ """
19
+ uname = platform.uname()
20
+ return {
21
+ "OS_name": uname.system,
22
+ "OS_version": uname.version,
23
+ "OS_release": uname.release,
24
+ }
25
+
26
+
27
+ def get_OS_info_windows() -> Mapping[str, str]:
28
+ """
29
+ Get operating system version info: Windows version.
30
+ """
31
+ return get_OS_info()
32
+
33
+
34
+ def get_OS_info_POSIX(
35
+ WSL_executable: list[str] | None = None,
36
+ use_py: bool = True,
37
+ linux_release_file: str | None = None,
38
+ ) -> Mapping[str, str]:
39
+ """
40
+ Get operating system version info: POSIX version.
41
+
42
+ Parameters
43
+ ----------
44
+ WSL_executable:
45
+ Executable to run subprocess calls via WSL on Windows.
46
+ use_py:
47
+ If True, use the :py:func:`platform.uname` Python function to get the OS
48
+ information. Otherwise use subprocess to call ``uname``. We set this to False
49
+ when getting OS info in WSL on Windows, since we need to call the WSL executable.
50
+ linux_release_file:
51
+ If on Linux, record the name and version fields from this file.
52
+ """
53
+
54
+ def try_subprocess_call(*args: str) -> str:
55
+ exc = None
56
+ command = [*WSL_exe, *args]
57
+ try:
58
+ proc = subprocess.run(
59
+ args=command,
60
+ stdout=subprocess.PIPE,
61
+ stderr=subprocess.PIPE,
62
+ text=True,
63
+ )
64
+ except Exception as err:
65
+ exc = err
66
+
67
+ if proc.returncode or exc:
68
+ raise RuntimeError(
69
+ f"Failed to get POSIX OS info. Command was: {command!r}. Subprocess "
70
+ f"exception was: {exc!r}. Stderr was: {proc.stderr!r}."
71
+ )
72
+ return proc.stdout
73
+
74
+ WSL_exe = WSL_executable or []
75
+ out: dict[str, str] = {}
76
+ if use_py:
77
+ out.update(**get_OS_info())
78
+ else:
79
+ OS_name = try_subprocess_call("uname", "-s").strip()
80
+ OS_release = try_subprocess_call("uname", "-r").strip()
81
+ OS_version = try_subprocess_call("uname", "-v").strip()
82
+
83
+ out["OS_name"] = OS_name
84
+ out["OS_release"] = OS_release
85
+ out["OS_version"] = OS_version
86
+
87
+ if out["OS_name"] == "Linux":
88
+ # get linux distribution name and version:
89
+ linux_release_file = linux_release_file or _DEFAULT_LINUX_RELEASE_FILE
90
+ release_out = try_subprocess_call("cat", linux_release_file)
91
+
92
+ name_match = _NAME_RE.search(release_out)
93
+ if not name_match:
94
+ raise RuntimeError(
95
+ f"Failed to get Linux distribution name from file `{linux_release_file}`."
96
+ )
97
+ lin_name: str = name_match[1]
98
+
99
+ version_match = _VERSION_RE.search(release_out)
100
+ if not version_match:
101
+ raise RuntimeError(
102
+ f"Failed to get Linux distribution version from file "
103
+ f"`{linux_release_file}`."
104
+ )
105
+ lin_version: str = version_match[1]
106
+
107
+ out["linux_release_file"] = linux_release_file
108
+ out["linux_distribution_name"] = lin_name
109
+ out["linux_distribution_version"] = lin_version
110
+
111
+ return out
112
+
113
+
114
+ _NAME_RE: Final = re.compile(r"^NAME=\"(.*)\"", flags=re.MULTILINE)
115
+ _VERSION_RE: Final = re.compile(r"^VERSION=\"(.*)\"", flags=re.MULTILINE)