6262from easybuild .tools .build_log import EasyBuildError , dry_run_msg , print_warning
6363from easybuild .tools .config import build_option , install_path
6464from easybuild .tools .environment import setvar
65- from easybuild .tools .filetools import adjust_permissions , find_eb_script , read_file , which , write_file
65+ from easybuild .tools .filetools import adjust_permissions , copy_file , find_eb_script , mkdir , read_file , which , write_file
6666from easybuild .tools .module_generator import dependencies_for
6767from easybuild .tools .modules import get_software_root , get_software_root_env_var_name
6868from easybuild .tools .modules import get_software_version , get_software_version_env_var_name
@@ -839,7 +839,7 @@ def reset(self):
839839 self .variables_init ()
840840
841841 def prepare (self , onlymod = None , deps = None , silent = False , loadmod = True ,
842- rpath_filter_dirs = None , rpath_include_dirs = None ):
842+ rpath_filter_dirs = None , rpath_include_dirs = None , rpath_wrappers_dir = None ):
843843 """
844844 Prepare a set of environment parameters based on name/version of toolchain
845845 - load modules for toolchain and dependencies
@@ -853,6 +853,7 @@ def prepare(self, onlymod=None, deps=None, silent=False, loadmod=True,
853853 :param loadmod: whether or not to (re)load the toolchain module, and the modules for the dependencies
854854 :param rpath_filter_dirs: extra directories to include in RPATH filter (e.g. build dir, tmpdir, ...)
855855 :param rpath_include_dirs: extra directories to include in RPATH
856+ :param rpath_wrappers_dir: directory in which to create RPATH wrappers
856857 """
857858
858859 # take into account --sysroot configuration setting
@@ -906,7 +907,11 @@ def prepare(self, onlymod=None, deps=None, silent=False, loadmod=True,
906907
907908 if build_option ('rpath' ):
908909 if self .options .get ('rpath' , True ):
909- self .prepare_rpath_wrappers (rpath_filter_dirs , rpath_include_dirs )
910+ self .prepare_rpath_wrappers (
911+ rpath_filter_dirs = rpath_filter_dirs ,
912+ rpath_include_dirs = rpath_include_dirs ,
913+ rpath_wrappers_dir = rpath_wrappers_dir
914+ )
910915 self .use_rpath = True
911916 else :
912917 self .log .info ("Not putting RPATH wrappers in place, disabled via 'rpath' toolchain option" )
@@ -975,11 +980,13 @@ def is_rpath_wrapper(path):
975980 # need to use binary mode to read the file, since it may be an actual compiler command (which is a binary file)
976981 return b'rpath_args.py $CMD' in read_file (path , mode = 'rb' )
977982
978- def prepare_rpath_wrappers (self , rpath_filter_dirs = None , rpath_include_dirs = None ):
983+ def prepare_rpath_wrappers (self , rpath_filter_dirs = None , rpath_include_dirs = None , rpath_wrappers_dir = None ):
979984 """
980985 Put RPATH wrapper script in place for compiler and linker commands
981986
982987 :param rpath_filter_dirs: extra directories to include in RPATH filter (e.g. build dir, tmpdir, ...)
988+ :param rpath_include_dirs: extra directories to include in RPATH
989+ :param rpath_wrappers_dir: directory in which to create RPATH wrappers (tmpdir is created if None)
983990 """
984991 if get_os_type () == LINUX :
985992 self .log .info ("Putting RPATH wrappers in place..." )
@@ -989,6 +996,11 @@ def prepare_rpath_wrappers(self, rpath_filter_dirs=None, rpath_include_dirs=None
989996 if rpath_filter_dirs is None :
990997 rpath_filter_dirs = []
991998
999+ # only enable logging by RPATH wrapper scripts in debug mode
1000+ enable_wrapper_log = build_option ('debug' )
1001+
1002+ copy_rpath_args_py = False
1003+
9921004 # always include filter for 'stubs' library directory,
9931005 # cfr. https://github.com/easybuilders/easybuild-framework/issues/2683
9941006 # (since CUDA 11.something the stubs are in $EBROOTCUDA/stubs/lib64)
@@ -997,13 +1009,35 @@ def prepare_rpath_wrappers(self, rpath_filter_dirs=None, rpath_include_dirs=None
9971009 if lib_stubs_pattern not in rpath_filter_dirs :
9981010 rpath_filter_dirs .append (lib_stubs_pattern )
9991011
1000- # directory where all wrappers will be placed
1001- wrappers_dir = os .path .join (tempfile .mkdtemp (), RPATH_WRAPPERS_SUBDIR )
1012+ # directory where all RPATH wrapper script will be placed;
1013+ if rpath_wrappers_dir is None :
1014+ wrappers_dir = tempfile .mkdtemp ()
1015+ else :
1016+ wrappers_dir = rpath_wrappers_dir
1017+ # disable logging in RPATH wrapper scripts when they may be exported for use outside of EasyBuild
1018+ enable_wrapper_log = False
1019+ # copy rpath_args.py script to sit alongside RPATH wrapper scripts
1020+ copy_rpath_args_py = True
1021+
1022+ # it's important to honor RPATH_WRAPPERS_SUBDIR, see is_rpath_wrapper method
1023+ wrappers_dir = os .path .join (wrappers_dir , RPATH_WRAPPERS_SUBDIR )
1024+ mkdir (wrappers_dir , parents = True )
10021025
10031026 # must also wrap compilers commands, required e.g. for Clang ('gcc' on OS X)?
10041027 c_comps , fortran_comps = self .compilers ()
10051028
10061029 rpath_args_py = find_eb_script ('rpath_args.py' )
1030+
1031+ # copy rpath_args.py script along RPATH wrappers, if desired
1032+ if copy_rpath_args_py :
1033+ copy_file (rpath_args_py , wrappers_dir )
1034+ # use path for %(rpath_args)s template value relative to location of the RPATH wrapper script,
1035+ # to avoid that the RPATH wrapper scripts rely on a script that's located elsewhere;
1036+ # that's mostly important when RPATH wrapper scripts are retained to be used outside of EasyBuild;
1037+ # we assume that each RPATH wrapper script is created in a separate subdirectory (see wrapper_dir below);
1038+ # ${TOPDIR} is defined in template for RPATH wrapper scripts, refers to parent dir of RPATH wrapper script
1039+ rpath_args_py = os .path .join ('${TOPDIR}' , '..' , os .path .basename (rpath_args_py ))
1040+
10071041 rpath_wrapper_template = find_eb_script ('rpath_wrapper_template.sh.in' )
10081042
10091043 # figure out list of patterns to use in rpath filter
@@ -1042,11 +1076,11 @@ def prepare_rpath_wrappers(self, rpath_filter_dirs=None, rpath_include_dirs=None
10421076
10431077 # make *very* sure we don't wrap around ourselves and create a fork bomb...
10441078 if os .path .exists (cmd_wrapper ) and os .path .exists (orig_cmd ) and os .path .samefile (orig_cmd , cmd_wrapper ):
1045- raise EasyBuildError ("Refusing the create a fork bomb, which(%s) == %s" , cmd , orig_cmd )
1079+ raise EasyBuildError ("Refusing to create a fork bomb, which(%s) == %s" , cmd , orig_cmd )
10461080
10471081 # enable debug mode in wrapper script by specifying location for log file
1048- if build_option ( 'debug' ) :
1049- rpath_wrapper_log = os .path .join (tempfile .gettempdir (), 'rpath_wrapper_%s .log' % cmd )
1082+ if enable_wrapper_log :
1083+ rpath_wrapper_log = os .path .join (tempfile .gettempdir (), f 'rpath_wrapper_{ cmd } .log' )
10501084 else :
10511085 rpath_wrapper_log = '/dev/null'
10521086
@@ -1060,7 +1094,15 @@ def prepare_rpath_wrappers(self, rpath_filter_dirs=None, rpath_include_dirs=None
10601094 'rpath_wrapper_log' : rpath_wrapper_log ,
10611095 'wrapper_dir' : wrapper_dir ,
10621096 }
1063- write_file (cmd_wrapper , cmd_wrapper_txt )
1097+
1098+ # it may be the case that the wrapper already exists if the user provides a fixed location to store
1099+ # the RPATH wrappers, in this case the wrappers will be overwritten as they do not yet appear in the
1100+ # PATH (`which(cmd)` does not "see" them). Warn that they will be overwritten.
1101+ if os .path .exists (cmd_wrapper ):
1102+ _log .warning (f"Overwriting existing RPATH wrapper { cmd_wrapper } " )
1103+ write_file (cmd_wrapper , cmd_wrapper_txt , always_overwrite = True )
1104+ else :
1105+ write_file (cmd_wrapper , cmd_wrapper_txt )
10641106 adjust_permissions (cmd_wrapper , stat .S_IXUSR )
10651107
10661108 # prepend location to this wrapper to $PATH
0 commit comments