@@ -70,6 +70,7 @@ def __init__(self, file_path):
7070 self .expander = None
7171 self ._usage_mode = None
7272 self .app_inst = None
73+ self ._executable_modification_applied = set ()
7374
7475 self ._mod_regex = re .compile (
7576 self ._mod_prefix_builtin + f"{ self .name } { NS_SEPARATOR } "
@@ -375,22 +376,130 @@ def applies_to_executable(self, executable):
375376
376377 return bool (self ._mod_regex .match (executable ))
377378
379+ def executable_modifier_applied (self , exec_mod ):
380+ """Check if an executable modifier has been applies alerady
381+
382+
383+ Returns:
384+ (bool): True if the executable modifier has been applied. False otherwise
385+ """
386+ if exec_mod in self ._executable_modification_applied :
387+ return True
388+ return False
389+
390+ def executable_modifier_usage_filter (filter_name : str ):
391+ """Decorator for registering a usage filter for executable modifiers"""
392+
393+ def _decorator (decorated_function ):
394+ if not hasattr (decorated_function , "_ramble_attributes" ):
395+ decorated_function ._ramble_attributes = {}
396+ decorated_function ._ramble_attributes ["filter_name" ] = filter_name
397+ return decorated_function
398+
399+ return _decorator
400+
401+ @executable_modifier_usage_filter ("once" )
402+ def filter_once (self , exec_mod , executable ) -> bool :
403+ """Usage filter for only allowing an executable modifier to be applied
404+ once in an experiment"""
405+ return not self .executable_modifier_applied (exec_mod )
406+
407+ @executable_modifier_usage_filter ("first_mpi" )
408+ def filter_first_mpi (self , exec_mod , executable ) -> bool :
409+ """Usage filter for only applying executable modifier to the first MPI
410+ executable in an experiment"""
411+ return executable .mpi and not self .executable_modifier_applied (
412+ exec_mod
413+ )
414+
415+ @executable_modifier_usage_filter ("all_mpi" )
416+ def filter_all_mpi (self , exec_mod , executable ) -> bool :
417+ """Usage filter for applying an executalbe modifier to only MPI
418+ executables in an experiment"""
419+ return executable .mpi
420+
421+ def get_executable_modifier_filter (self , filter_name ):
422+ """Get the filter function for a usage filter (by name)
423+
424+ Args:
425+ filter_name (str): Name of usage filter to extract for filtering executable modifier
426+
427+ Returns:
428+ Reference to function, if found. None otherwise"""
429+
430+ filter_names = set ()
431+ for attr in dir (self ):
432+ method = getattr (self , attr )
433+
434+ if callable (method ):
435+ method_attributes = getattr (method , "_ramble_attributes" , {})
436+ test_filter_name = None
437+ if "filter_name" in method_attributes :
438+ test_filter_name = method_attributes ["filter_name" ]
439+ filter_names .add (test_filter_name )
440+ if filter_name == test_filter_name :
441+ return method
442+
443+ if filter_name is not None and filter_name != "None" :
444+ logger .die (
445+ f"When extracting a usage_filter for an executable_modifier "
446+ f"on modifier { self .name } "
447+ f"the filter { filter_name } does not exist. Registered filters are: \n "
448+ f"{ filter_names } "
449+ )
450+ return None
451+
452+ def executable_modification_applies (
453+ self , exec_mod , filter_name , executable
454+ ):
455+ """Determine if an executable modifier applies to an executable or not
456+
457+ Args:
458+ exec_mod (str): Name of executable modifier
459+ filter_name (str): Name of usage filter to apply
460+ executable: CommandExecutable object to check if exec_mod applies to
461+
462+ """
463+ apply = True
464+
465+ filter_func = self .get_executable_modifier_filter (filter_name )
466+
467+ if filter_func is not None :
468+ apply = filter_func (exec_mod , executable )
469+
470+ return apply
471+
378472 def apply_executable_modifiers (
379473 self , executable_name , executable , app_inst = None
380474 ):
475+ """Apply all executable modifiers to an executable
476+
477+ Args:
478+ executable_name (str): Name of executable
479+ executable: CommandExecutable object
480+ app_inst: Instance of application object
481+
482+ Returns
483+ (list, list): List of CommandExecutable objects that occur before
484+ and after (respectively) to the input executable.
485+ """
381486 pre_execs = []
382487 post_execs = []
383488 for when_set , exec_mods in self .executable_modifiers .items ():
384489 if self .expander .satisfies (when_set , self .experiment_variants ()):
385- for exec_mod in exec_mods :
386- mod_func = getattr (self , exec_mod )
387-
388- pre_exec , post_exec = mod_func (
389- executable_name , executable , app_inst = app_inst
390- )
490+ for exec_mod , mod_conf in exec_mods .items ():
491+ if self .executable_modification_applies (
492+ exec_mod , mod_conf ["usage_filter" ], executable
493+ ):
494+ self ._executable_modification_applied .add (exec_mod )
495+ mod_func = getattr (self , exec_mod )
496+
497+ pre_exec , post_exec = mod_func (
498+ executable_name , executable , app_inst = app_inst
499+ )
391500
392- pre_execs .extend (pre_exec )
393- post_execs .extend (post_exec )
501+ pre_execs .extend (pre_exec )
502+ post_execs .extend (post_exec )
394503
395504 return pre_execs , post_execs
396505
0 commit comments