hpcflow 0.1.15__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.
- hpcflow/__init__.py +2 -11
- hpcflow/__pyinstaller/__init__.py +5 -0
- hpcflow/__pyinstaller/hook-hpcflow.py +40 -0
- hpcflow/_version.py +1 -1
- hpcflow/app.py +43 -0
- hpcflow/cli.py +2 -461
- hpcflow/data/demo_data_manifest/__init__.py +3 -0
- hpcflow/data/demo_data_manifest/demo_data_manifest.json +6 -0
- hpcflow/data/jinja_templates/test/test_template.txt +8 -0
- hpcflow/data/programs/hello_world/README.md +1 -0
- hpcflow/data/programs/hello_world/hello_world.c +87 -0
- hpcflow/data/programs/hello_world/linux/hello_world +0 -0
- hpcflow/data/programs/hello_world/macos/hello_world +0 -0
- hpcflow/data/programs/hello_world/win/hello_world.exe +0 -0
- hpcflow/data/scripts/__init__.py +1 -0
- hpcflow/data/scripts/bad_script.py +2 -0
- hpcflow/data/scripts/demo_task_1_generate_t1_infile_1.py +8 -0
- hpcflow/data/scripts/demo_task_1_generate_t1_infile_2.py +8 -0
- hpcflow/data/scripts/demo_task_1_parse_p3.py +7 -0
- hpcflow/data/scripts/do_nothing.py +2 -0
- hpcflow/data/scripts/env_specifier_test/input_file_generator_pass_env_spec.py +4 -0
- hpcflow/data/scripts/env_specifier_test/main_script_test_pass_env_spec.py +8 -0
- hpcflow/data/scripts/env_specifier_test/output_file_parser_pass_env_spec.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v1/input_file_generator_basic.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v1/main_script_test_direct_in_direct_out.py +7 -0
- hpcflow/data/scripts/env_specifier_test/v1/output_file_parser_basic.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v2/main_script_test_direct_in_direct_out.py +7 -0
- hpcflow/data/scripts/generate_t1_file_01.py +7 -0
- hpcflow/data/scripts/import_future_script.py +7 -0
- hpcflow/data/scripts/input_file_generator_basic.py +3 -0
- hpcflow/data/scripts/input_file_generator_basic_FAIL.py +3 -0
- hpcflow/data/scripts/input_file_generator_test_stdout_stderr.py +8 -0
- hpcflow/data/scripts/main_script_test_direct_in.py +3 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed_group.py +7 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_all_iters_test.py +15 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_env_spec.py +7 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_labels.py +8 -0
- hpcflow/data/scripts/main_script_test_direct_in_group_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_group_one_fail_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_sub_param_in_direct_out.py +6 -0
- hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +12 -0
- hpcflow/data/scripts/main_script_test_hdf5_in_obj_2.py +12 -0
- hpcflow/data/scripts/main_script_test_hdf5_in_obj_group.py +12 -0
- hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +11 -0
- hpcflow/data/scripts/main_script_test_json_and_direct_in_json_out.py +14 -0
- hpcflow/data/scripts/main_script_test_json_in_json_and_direct_out.py +17 -0
- hpcflow/data/scripts/main_script_test_json_in_json_out.py +14 -0
- hpcflow/data/scripts/main_script_test_json_in_json_out_labels.py +16 -0
- hpcflow/data/scripts/main_script_test_json_in_obj.py +12 -0
- hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -0
- hpcflow/data/scripts/main_script_test_json_out_obj.py +10 -0
- hpcflow/data/scripts/main_script_test_json_sub_param_in_json_out_labels.py +16 -0
- hpcflow/data/scripts/main_script_test_shell_env_vars.py +12 -0
- hpcflow/data/scripts/main_script_test_std_out_std_err.py +6 -0
- hpcflow/data/scripts/output_file_parser_basic.py +3 -0
- hpcflow/data/scripts/output_file_parser_basic_FAIL.py +7 -0
- hpcflow/data/scripts/output_file_parser_test_stdout_stderr.py +8 -0
- hpcflow/data/scripts/parse_t1_file_01.py +4 -0
- hpcflow/data/scripts/script_exit_test.py +5 -0
- hpcflow/data/template_components/__init__.py +1 -0
- hpcflow/data/template_components/command_files.yaml +26 -0
- hpcflow/data/template_components/environments.yaml +13 -0
- hpcflow/data/template_components/parameters.yaml +14 -0
- hpcflow/data/template_components/task_schemas.yaml +139 -0
- hpcflow/data/workflows/workflow_1.yaml +5 -0
- hpcflow/examples.ipynb +1037 -0
- hpcflow/sdk/__init__.py +149 -0
- hpcflow/sdk/app.py +4266 -0
- hpcflow/sdk/cli.py +1479 -0
- hpcflow/sdk/cli_common.py +385 -0
- hpcflow/sdk/config/__init__.py +5 -0
- hpcflow/sdk/config/callbacks.py +246 -0
- hpcflow/sdk/config/cli.py +388 -0
- hpcflow/sdk/config/config.py +1410 -0
- hpcflow/sdk/config/config_file.py +501 -0
- hpcflow/sdk/config/errors.py +272 -0
- hpcflow/sdk/config/types.py +150 -0
- hpcflow/sdk/core/__init__.py +38 -0
- hpcflow/sdk/core/actions.py +3857 -0
- hpcflow/sdk/core/app_aware.py +25 -0
- hpcflow/sdk/core/cache.py +224 -0
- hpcflow/sdk/core/command_files.py +814 -0
- hpcflow/sdk/core/commands.py +424 -0
- hpcflow/sdk/core/element.py +2071 -0
- hpcflow/sdk/core/enums.py +221 -0
- hpcflow/sdk/core/environment.py +256 -0
- hpcflow/sdk/core/errors.py +1043 -0
- hpcflow/sdk/core/execute.py +207 -0
- hpcflow/sdk/core/json_like.py +809 -0
- hpcflow/sdk/core/loop.py +1320 -0
- hpcflow/sdk/core/loop_cache.py +282 -0
- hpcflow/sdk/core/object_list.py +933 -0
- hpcflow/sdk/core/parameters.py +3371 -0
- hpcflow/sdk/core/rule.py +196 -0
- hpcflow/sdk/core/run_dir_files.py +57 -0
- hpcflow/sdk/core/skip_reason.py +7 -0
- hpcflow/sdk/core/task.py +3792 -0
- hpcflow/sdk/core/task_schema.py +993 -0
- hpcflow/sdk/core/test_utils.py +538 -0
- hpcflow/sdk/core/types.py +447 -0
- hpcflow/sdk/core/utils.py +1207 -0
- hpcflow/sdk/core/validation.py +87 -0
- hpcflow/sdk/core/values.py +477 -0
- hpcflow/sdk/core/workflow.py +4820 -0
- hpcflow/sdk/core/zarr_io.py +206 -0
- hpcflow/sdk/data/__init__.py +13 -0
- hpcflow/sdk/data/config_file_schema.yaml +34 -0
- hpcflow/sdk/data/config_schema.yaml +260 -0
- hpcflow/sdk/data/environments_spec_schema.yaml +21 -0
- hpcflow/sdk/data/files_spec_schema.yaml +5 -0
- hpcflow/sdk/data/parameters_spec_schema.yaml +7 -0
- hpcflow/sdk/data/task_schema_spec_schema.yaml +3 -0
- hpcflow/sdk/data/workflow_spec_schema.yaml +22 -0
- hpcflow/sdk/demo/__init__.py +3 -0
- hpcflow/sdk/demo/cli.py +242 -0
- hpcflow/sdk/helper/__init__.py +3 -0
- hpcflow/sdk/helper/cli.py +137 -0
- hpcflow/sdk/helper/helper.py +300 -0
- hpcflow/sdk/helper/watcher.py +192 -0
- hpcflow/sdk/log.py +288 -0
- hpcflow/sdk/persistence/__init__.py +18 -0
- hpcflow/sdk/persistence/base.py +2817 -0
- hpcflow/sdk/persistence/defaults.py +6 -0
- hpcflow/sdk/persistence/discovery.py +39 -0
- hpcflow/sdk/persistence/json.py +954 -0
- hpcflow/sdk/persistence/pending.py +948 -0
- hpcflow/sdk/persistence/store_resource.py +203 -0
- hpcflow/sdk/persistence/types.py +309 -0
- hpcflow/sdk/persistence/utils.py +73 -0
- hpcflow/sdk/persistence/zarr.py +2388 -0
- hpcflow/sdk/runtime.py +320 -0
- hpcflow/sdk/submission/__init__.py +3 -0
- hpcflow/sdk/submission/enums.py +70 -0
- hpcflow/sdk/submission/jobscript.py +2379 -0
- hpcflow/sdk/submission/schedulers/__init__.py +281 -0
- hpcflow/sdk/submission/schedulers/direct.py +233 -0
- hpcflow/sdk/submission/schedulers/sge.py +376 -0
- hpcflow/sdk/submission/schedulers/slurm.py +598 -0
- hpcflow/sdk/submission/schedulers/utils.py +25 -0
- hpcflow/sdk/submission/shells/__init__.py +52 -0
- hpcflow/sdk/submission/shells/base.py +229 -0
- hpcflow/sdk/submission/shells/bash.py +504 -0
- hpcflow/sdk/submission/shells/os_version.py +115 -0
- hpcflow/sdk/submission/shells/powershell.py +352 -0
- hpcflow/sdk/submission/submission.py +1402 -0
- hpcflow/sdk/submission/types.py +140 -0
- hpcflow/sdk/typing.py +194 -0
- hpcflow/sdk/utils/arrays.py +69 -0
- hpcflow/sdk/utils/deferred_file.py +55 -0
- hpcflow/sdk/utils/hashing.py +16 -0
- hpcflow/sdk/utils/patches.py +31 -0
- hpcflow/sdk/utils/strings.py +69 -0
- hpcflow/tests/api/test_api.py +32 -0
- hpcflow/tests/conftest.py +123 -0
- hpcflow/tests/data/__init__.py +0 -0
- hpcflow/tests/data/benchmark_N_elements.yaml +6 -0
- hpcflow/tests/data/benchmark_script_runner.yaml +26 -0
- hpcflow/tests/data/multi_path_sequences.yaml +29 -0
- hpcflow/tests/data/workflow_1.json +10 -0
- hpcflow/tests/data/workflow_1.yaml +5 -0
- hpcflow/tests/data/workflow_1_slurm.yaml +8 -0
- hpcflow/tests/data/workflow_1_wsl.yaml +8 -0
- hpcflow/tests/data/workflow_test_run_abort.yaml +42 -0
- hpcflow/tests/jinja_templates/test_jinja_templates.py +161 -0
- hpcflow/tests/programs/test_programs.py +180 -0
- hpcflow/tests/schedulers/direct_linux/test_direct_linux_submission.py +12 -0
- hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
- hpcflow/tests/schedulers/slurm/test_slurm_submission.py +14 -0
- hpcflow/tests/scripts/test_input_file_generators.py +282 -0
- hpcflow/tests/scripts/test_main_scripts.py +1361 -0
- hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
- hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
- hpcflow/tests/shells/wsl/test_wsl_submission.py +14 -0
- hpcflow/tests/unit/test_action.py +1066 -0
- hpcflow/tests/unit/test_action_rule.py +24 -0
- hpcflow/tests/unit/test_app.py +132 -0
- hpcflow/tests/unit/test_cache.py +46 -0
- hpcflow/tests/unit/test_cli.py +172 -0
- hpcflow/tests/unit/test_command.py +377 -0
- hpcflow/tests/unit/test_config.py +195 -0
- hpcflow/tests/unit/test_config_file.py +162 -0
- hpcflow/tests/unit/test_element.py +666 -0
- hpcflow/tests/unit/test_element_iteration.py +88 -0
- hpcflow/tests/unit/test_element_set.py +158 -0
- hpcflow/tests/unit/test_group.py +115 -0
- hpcflow/tests/unit/test_input_source.py +1479 -0
- hpcflow/tests/unit/test_input_value.py +398 -0
- hpcflow/tests/unit/test_jobscript_unit.py +757 -0
- hpcflow/tests/unit/test_json_like.py +1247 -0
- hpcflow/tests/unit/test_loop.py +2674 -0
- hpcflow/tests/unit/test_meta_task.py +325 -0
- hpcflow/tests/unit/test_multi_path_sequences.py +259 -0
- hpcflow/tests/unit/test_object_list.py +116 -0
- hpcflow/tests/unit/test_parameter.py +243 -0
- hpcflow/tests/unit/test_persistence.py +664 -0
- hpcflow/tests/unit/test_resources.py +243 -0
- hpcflow/tests/unit/test_run.py +286 -0
- hpcflow/tests/unit/test_run_directories.py +29 -0
- hpcflow/tests/unit/test_runtime.py +9 -0
- hpcflow/tests/unit/test_schema_input.py +372 -0
- hpcflow/tests/unit/test_shell.py +129 -0
- hpcflow/tests/unit/test_slurm.py +39 -0
- hpcflow/tests/unit/test_submission.py +502 -0
- hpcflow/tests/unit/test_task.py +2560 -0
- hpcflow/tests/unit/test_task_schema.py +182 -0
- hpcflow/tests/unit/test_utils.py +616 -0
- hpcflow/tests/unit/test_value_sequence.py +549 -0
- hpcflow/tests/unit/test_values.py +91 -0
- hpcflow/tests/unit/test_workflow.py +827 -0
- hpcflow/tests/unit/test_workflow_template.py +186 -0
- hpcflow/tests/unit/utils/test_arrays.py +40 -0
- hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
- hpcflow/tests/unit/utils/test_hashing.py +65 -0
- hpcflow/tests/unit/utils/test_patches.py +5 -0
- hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
- hpcflow/tests/unit/utils/test_strings.py +97 -0
- hpcflow/tests/workflows/__init__.py +0 -0
- hpcflow/tests/workflows/test_directory_structure.py +31 -0
- hpcflow/tests/workflows/test_jobscript.py +355 -0
- hpcflow/tests/workflows/test_run_status.py +198 -0
- hpcflow/tests/workflows/test_skip_downstream.py +696 -0
- hpcflow/tests/workflows/test_submission.py +140 -0
- hpcflow/tests/workflows/test_workflows.py +564 -0
- hpcflow/tests/workflows/test_zip.py +18 -0
- hpcflow/viz_demo.ipynb +6794 -0
- hpcflow-0.2.0a271.dist-info/LICENSE +375 -0
- hpcflow-0.2.0a271.dist-info/METADATA +65 -0
- hpcflow-0.2.0a271.dist-info/RECORD +237 -0
- {hpcflow-0.1.15.dist-info → hpcflow-0.2.0a271.dist-info}/WHEEL +4 -5
- hpcflow-0.2.0a271.dist-info/entry_points.txt +6 -0
- hpcflow/api.py +0 -490
- hpcflow/archive/archive.py +0 -307
- hpcflow/archive/cloud/cloud.py +0 -45
- hpcflow/archive/cloud/errors.py +0 -9
- hpcflow/archive/cloud/providers/dropbox.py +0 -427
- hpcflow/archive/errors.py +0 -5
- hpcflow/base_db.py +0 -4
- hpcflow/config.py +0 -233
- hpcflow/copytree.py +0 -66
- hpcflow/data/examples/_config.yml +0 -14
- hpcflow/data/examples/damask/demo/1.run.yml +0 -4
- hpcflow/data/examples/damask/demo/2.process.yml +0 -29
- hpcflow/data/examples/damask/demo/geom.geom +0 -2052
- hpcflow/data/examples/damask/demo/load.load +0 -1
- hpcflow/data/examples/damask/demo/material.config +0 -185
- hpcflow/data/examples/damask/inputs/geom.geom +0 -2052
- hpcflow/data/examples/damask/inputs/load.load +0 -1
- hpcflow/data/examples/damask/inputs/material.config +0 -185
- hpcflow/data/examples/damask/profiles/_variable_lookup.yml +0 -21
- hpcflow/data/examples/damask/profiles/damask.yml +0 -4
- hpcflow/data/examples/damask/profiles/damask_process.yml +0 -8
- hpcflow/data/examples/damask/profiles/damask_run.yml +0 -5
- hpcflow/data/examples/damask/profiles/default.yml +0 -6
- hpcflow/data/examples/thinking.yml +0 -177
- hpcflow/errors.py +0 -2
- hpcflow/init_db.py +0 -37
- hpcflow/models.py +0 -2595
- hpcflow/nesting.py +0 -9
- hpcflow/profiles.py +0 -455
- hpcflow/project.py +0 -81
- hpcflow/scheduler.py +0 -322
- hpcflow/utils.py +0 -103
- hpcflow/validation.py +0 -166
- hpcflow/variables.py +0 -543
- hpcflow-0.1.15.dist-info/METADATA +0 -168
- hpcflow-0.1.15.dist-info/RECORD +0 -45
- hpcflow-0.1.15.dist-info/entry_points.txt +0 -8
- hpcflow-0.1.15.dist-info/top_level.txt +0 -1
- /hpcflow/{archive → data/jinja_templates}/__init__.py +0 -0
- /hpcflow/{archive/cloud → data/programs}/__init__.py +0 -0
- /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)
|