Skip to content

Commit ac91659

Browse files
authored
Merge pull request #4323 from boegel/run_cmd_hook
add `run_shell_cmd` hook
2 parents b19094d + 5b45c24 commit ac91659

File tree

9 files changed

+232
-43
lines changed

9 files changed

+232
-43
lines changed

easybuild/tools/hooks.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@
6969
CANCEL = 'cancel'
7070
FAIL = 'fail'
7171

72+
RUN_SHELL_CMD = 'run_shell_cmd'
73+
7274
PRE_PREF = 'pre_'
7375
POST_PREF = 'post_'
7476
HOOK_SUFF = '_hook'
@@ -106,6 +108,8 @@
106108
END,
107109
CANCEL,
108110
FAIL,
111+
PRE_PREF + RUN_SHELL_CMD,
112+
POST_PREF + RUN_SHELL_CMD,
109113
]
110114
KNOWN_HOOKS = [h + HOOK_SUFF for h in HOOK_NAMES]
111115

@@ -203,7 +207,7 @@ def find_hook(label, hooks, pre_step_hook=False, post_step_hook=False):
203207
return res
204208

205209

206-
def run_hook(label, hooks, pre_step_hook=False, post_step_hook=False, args=None, msg=None):
210+
def run_hook(label, hooks, pre_step_hook=False, post_step_hook=False, args=None, kwargs=None, msg=None):
207211
"""
208212
Run hook with specified label and return result of calling the hook or None.
209213
@@ -219,6 +223,8 @@ def run_hook(label, hooks, pre_step_hook=False, post_step_hook=False, args=None,
219223
if hook:
220224
if args is None:
221225
args = []
226+
if kwargs is None:
227+
kwargs = {}
222228

223229
if pre_step_hook:
224230
label = 'pre-' + label
@@ -230,6 +236,6 @@ def run_hook(label, hooks, pre_step_hook=False, post_step_hook=False, args=None,
230236
if build_option('debug'):
231237
print_msg(msg)
232238

233-
_log.info("Running '%s' hook function (arguments: %s)...", hook.__name__, args)
234-
res = hook(*args)
239+
_log.info("Running '%s' hook function (args: %s, keyword args: %s)...", hook.__name__, args, kwargs)
240+
res = hook(*args, **kwargs)
235241
return res

easybuild/tools/options.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1894,7 +1894,7 @@ def set_tmpdir(tmpdir=None, raise_error=False):
18941894
os.close(fd)
18951895
os.chmod(tmptest_file, 0o700)
18961896
if not run_cmd(tmptest_file, simple=True, log_ok=False, regexp=False, force_in_dry_run=True, trace=False,
1897-
stream_output=False):
1897+
stream_output=False, with_hooks=False):
18981898
msg = "The temporary directory (%s) does not allow to execute files. " % tempfile.gettempdir()
18991899
msg += "This can cause problems in the build process, consider using --tmpdir."
19001900
if raise_error:

easybuild/tools/run.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
from easybuild.base import fancylogger
5151
from easybuild.tools.build_log import EasyBuildError, dry_run_msg, print_msg, time_str_since
5252
from easybuild.tools.config import ERROR, IGNORE, WARN, build_option
53+
from easybuild.tools.hooks import RUN_SHELL_CMD, load_hooks, run_hook
5354
from easybuild.tools.py2vs3 import string_type
5455
from easybuild.tools.utilities import trace_msg
5556

@@ -131,7 +132,8 @@ def get_output_from_process(proc, read_size=None, asynchronous=False):
131132

132133
@run_cmd_cache
133134
def run_cmd(cmd, log_ok=True, log_all=False, simple=False, inp=None, regexp=True, log_output=False, path=None,
134-
force_in_dry_run=False, verbose=True, shell=None, trace=True, stream_output=None, asynchronous=False):
135+
force_in_dry_run=False, verbose=True, shell=None, trace=True, stream_output=None, asynchronous=False,
136+
with_hooks=True):
135137
"""
136138
Run specified command (in a subshell)
137139
:param cmd: command to run
@@ -148,6 +150,7 @@ def run_cmd(cmd, log_ok=True, log_all=False, simple=False, inp=None, regexp=True
148150
:param trace: print command being executed as part of trace output
149151
:param stream_output: enable streaming command output to stdout
150152
:param asynchronous: run command asynchronously (returns subprocess.Popen instance if set to True)
153+
:param with_hooks: trigger pre/post run_shell_cmd hooks (if defined)
151154
"""
152155
cwd = os.getcwd()
153156

@@ -233,6 +236,13 @@ def run_cmd(cmd, log_ok=True, log_all=False, simple=False, inp=None, regexp=True
233236
else:
234237
raise EasyBuildError("Don't know how to prefix with /usr/bin/env for commands of type %s", type(cmd))
235238

239+
if with_hooks:
240+
hooks = load_hooks(build_option('hooks'))
241+
hook_res = run_hook(RUN_SHELL_CMD, hooks, pre_step_hook=True, args=[cmd], kwargs={'work_dir': os.getcwd()})
242+
if isinstance(hook_res, string_type):
243+
cmd, old_cmd = hook_res, cmd
244+
_log.info("Command to run was changed by pre-%s hook: '%s' (was: '%s')", RUN_SHELL_CMD, cmd, old_cmd)
245+
236246
_log.info('running cmd: %s ' % cmd)
237247
try:
238248
proc = subprocess.Popen(cmd, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
@@ -248,7 +258,7 @@ def run_cmd(cmd, log_ok=True, log_all=False, simple=False, inp=None, regexp=True
248258
return (proc, cmd, cwd, start_time, cmd_log)
249259
else:
250260
return complete_cmd(proc, cmd, cwd, start_time, cmd_log, log_ok=log_ok, log_all=log_all, simple=simple,
251-
regexp=regexp, stream_output=stream_output, trace=trace)
261+
regexp=regexp, stream_output=stream_output, trace=trace, with_hook=with_hooks)
252262

253263

254264
def check_async_cmd(proc, cmd, owd, start_time, cmd_log, fail_on_error=True, output_read_size=1024, output=''):
@@ -293,7 +303,7 @@ def check_async_cmd(proc, cmd, owd, start_time, cmd_log, fail_on_error=True, out
293303

294304

295305
def complete_cmd(proc, cmd, owd, start_time, cmd_log, log_ok=True, log_all=False, simple=False,
296-
regexp=True, stream_output=None, trace=True, output=''):
306+
regexp=True, stream_output=None, trace=True, output='', with_hook=True):
297307
"""
298308
Complete running of command represented by passed subprocess.Popen instance.
299309
@@ -308,6 +318,7 @@ def complete_cmd(proc, cmd, owd, start_time, cmd_log, log_ok=True, log_all=False
308318
:param regexp: regex used to check the output for errors; if True it will use the default (see parse_log_for_error)
309319
:param stream_output: enable streaming command output to stdout
310320
:param trace: print command being executed as part of trace output
321+
:param with_hook: trigger post run_shell_cmd hooks (if defined)
311322
"""
312323
# use small read size when streaming output, to make it stream more fluently
313324
# read size should not be too small though, to avoid too much overhead
@@ -343,6 +354,15 @@ def complete_cmd(proc, cmd, owd, start_time, cmd_log, log_ok=True, log_all=False
343354
sys.stdout.write(output)
344355
stdouterr += output
345356

357+
if with_hook:
358+
hooks = load_hooks(build_option('hooks'))
359+
run_hook_kwargs = {
360+
'exit_code': ec,
361+
'output': stdouterr,
362+
'work_dir': os.getcwd(),
363+
}
364+
run_hook(RUN_SHELL_CMD, hooks, post_step_hook=True, args=[cmd], kwargs=run_hook_kwargs)
365+
346366
if trace:
347367
trace_msg("command completed: exit %s, ran in %s" % (ec, time_str_since(start_time)))
348368

@@ -485,6 +505,17 @@ def check_answers_list(answers):
485505
# Part 2: Run the command and answer questions
486506
# - this needs asynchronous stdout
487507

508+
hooks = load_hooks(build_option('hooks'))
509+
run_hook_kwargs = {
510+
'interactive': True,
511+
'work_dir': os.getcwd(),
512+
}
513+
hook_res = run_hook(RUN_SHELL_CMD, hooks, pre_step_hook=True, args=[cmd], kwargs=run_hook_kwargs)
514+
if isinstance(hook_res, string_type):
515+
cmd, old_cmd = hook_res, cmd
516+
_log.info("Interactive command to run was changed by pre-%s hook: '%s' (was: '%s')",
517+
RUN_SHELL_CMD, cmd, old_cmd)
518+
488519
# # Log command output
489520
if cmd_log:
490521
cmd_log.write("# output for interactive command: %s\n\n" % cmd)
@@ -599,6 +630,13 @@ def get_proc():
599630
except IOError as err:
600631
_log.debug("runqanda cmd %s: remaining data read failed: %s", cmd, err)
601632

633+
run_hook_kwargs.update({
634+
'interactive': True,
635+
'exit_code': ec,
636+
'output': stdout_err,
637+
})
638+
run_hook(RUN_SHELL_CMD, hooks, post_step_hook=True, args=[cmd], kwargs=run_hook_kwargs)
639+
602640
if trace:
603641
trace_msg("interactive command completed: exit %s, ran in %s" % (ec, time_str_since(start_time)))
604642

easybuild/tools/systemtools.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ def get_avail_core_count():
274274
core_cnt = int(sum(sched_getaffinity()))
275275
else:
276276
# BSD-type systems
277-
out, _ = run_cmd('sysctl -n hw.ncpu', force_in_dry_run=True, trace=False, stream_output=False)
277+
out, _ = run_cmd('sysctl -n hw.ncpu', force_in_dry_run=True, trace=False, stream_output=False, with_hooks=False)
278278
try:
279279
if int(out) > 0:
280280
core_cnt = int(out)
@@ -311,7 +311,7 @@ def get_total_memory():
311311
elif os_type == DARWIN:
312312
cmd = "sysctl -n hw.memsize"
313313
_log.debug("Trying to determine total memory size on Darwin via cmd '%s'", cmd)
314-
out, ec = run_cmd(cmd, force_in_dry_run=True, trace=False, stream_output=False)
314+
out, ec = run_cmd(cmd, force_in_dry_run=True, trace=False, stream_output=False, with_hooks=False)
315315
if ec == 0:
316316
memtotal = int(out.strip()) // (1024**2)
317317

@@ -393,14 +393,15 @@ def get_cpu_vendor():
393393

394394
elif os_type == DARWIN:
395395
cmd = "sysctl -n machdep.cpu.vendor"
396-
out, ec = run_cmd(cmd, force_in_dry_run=True, trace=False, stream_output=False, log_ok=False)
396+
out, ec = run_cmd(cmd, force_in_dry_run=True, trace=False, stream_output=False, log_ok=False, with_hooks=False)
397397
out = out.strip()
398398
if ec == 0 and out in VENDOR_IDS:
399399
vendor = VENDOR_IDS[out]
400400
_log.debug("Determined CPU vendor on DARWIN as being '%s' via cmd '%s" % (vendor, cmd))
401401
else:
402402
cmd = "sysctl -n machdep.cpu.brand_string"
403-
out, ec = run_cmd(cmd, force_in_dry_run=True, trace=False, stream_output=False, log_ok=False)
403+
out, ec = run_cmd(cmd, force_in_dry_run=True, trace=False, stream_output=False, log_ok=False,
404+
with_hooks=False)
404405
out = out.strip().split(' ')[0]
405406
if ec == 0 and out in CPU_VENDORS:
406407
vendor = out
@@ -503,7 +504,7 @@ def get_cpu_model():
503504

504505
elif os_type == DARWIN:
505506
cmd = "sysctl -n machdep.cpu.brand_string"
506-
out, ec = run_cmd(cmd, force_in_dry_run=True, trace=False, stream_output=False)
507+
out, ec = run_cmd(cmd, force_in_dry_run=True, trace=False, stream_output=False, with_hooks=False)
507508
if ec == 0:
508509
model = out.strip()
509510
_log.debug("Determined CPU model on Darwin using cmd '%s': %s" % (cmd, model))
@@ -548,7 +549,7 @@ def get_cpu_speed():
548549
elif os_type == DARWIN:
549550
cmd = "sysctl -n hw.cpufrequency_max"
550551
_log.debug("Trying to determine CPU frequency on Darwin via cmd '%s'" % cmd)
551-
out, ec = run_cmd(cmd, force_in_dry_run=True, trace=False, stream_output=False)
552+
out, ec = run_cmd(cmd, force_in_dry_run=True, trace=False, stream_output=False, with_hooks=False)
552553
out = out.strip()
553554
cpu_freq = None
554555
if ec == 0 and out:
@@ -596,7 +597,8 @@ def get_cpu_features():
596597
for feature_set in ['extfeatures', 'features', 'leaf7_features']:
597598
cmd = "sysctl -n machdep.cpu.%s" % feature_set
598599
_log.debug("Trying to determine CPU features on Darwin via cmd '%s'", cmd)
599-
out, ec = run_cmd(cmd, force_in_dry_run=True, trace=False, stream_output=False, log_ok=False)
600+
out, ec = run_cmd(cmd, force_in_dry_run=True, trace=False, stream_output=False, log_ok=False,
601+
with_hooks=False)
600602
if ec == 0:
601603
cpu_feat.extend(out.strip().lower().split())
602604

@@ -624,7 +626,7 @@ def get_gpu_info():
624626
cmd = "nvidia-smi --query-gpu=gpu_name,driver_version --format=csv,noheader"
625627
_log.debug("Trying to determine NVIDIA GPU info on Linux via cmd '%s'", cmd)
626628
out, ec = run_cmd(cmd, simple=False, log_ok=False, log_all=False,
627-
force_in_dry_run=True, trace=False, stream_output=False)
629+
force_in_dry_run=True, trace=False, stream_output=False, with_hooks=False)
628630
if ec == 0:
629631
for line in out.strip().split('\n'):
630632
nvidia_gpu_info = gpu_info.setdefault('NVIDIA', {})
@@ -643,14 +645,14 @@ def get_gpu_info():
643645
cmd = "rocm-smi --showdriverversion --csv"
644646
_log.debug("Trying to determine AMD GPU driver on Linux via cmd '%s'", cmd)
645647
out, ec = run_cmd(cmd, simple=False, log_ok=False, log_all=False,
646-
force_in_dry_run=True, trace=False, stream_output=False)
648+
force_in_dry_run=True, trace=False, stream_output=False, with_hooks=False)
647649
if ec == 0:
648650
amd_driver = out.strip().split('\n')[1].split(',')[1]
649651

650652
cmd = "rocm-smi --showproductname --csv"
651653
_log.debug("Trying to determine AMD GPU info on Linux via cmd '%s'", cmd)
652654
out, ec = run_cmd(cmd, simple=False, log_ok=False, log_all=False,
653-
force_in_dry_run=True, trace=False, stream_output=False)
655+
force_in_dry_run=True, trace=False, stream_output=False, with_hooks=False)
654656
if ec == 0:
655657
for line in out.strip().split('\n')[1:]:
656658
amd_card_series = line.split(',')[1]
@@ -898,7 +900,7 @@ def get_tool_version(tool, version_option='--version', ignore_ec=False):
898900
Output is returned as a single-line string (newlines are replaced by '; ').
899901
"""
900902
out, ec = run_cmd(' '.join([tool, version_option]), simple=False, log_ok=False, force_in_dry_run=True,
901-
trace=False, stream_output=False)
903+
trace=False, stream_output=False, with_hooks=False)
902904
if not ignore_ec and ec:
903905
_log.warning("Failed to determine version of %s using '%s %s': %s" % (tool, tool, version_option, out))
904906
return UNKNOWN

0 commit comments

Comments
 (0)