7171from easybuild .tools .config import install_path , log_path , package_path , source_paths
7272from easybuild .tools .environment import restore_env , sanitize_env
7373from easybuild .tools .filetools import CHECKSUM_TYPE_MD5 , CHECKSUM_TYPE_SHA256
74- from easybuild .tools .filetools import adjust_permissions , apply_patch , back_up_file
75- from easybuild .tools .filetools import change_dir , convert_name , compute_checksum , copy_file , derive_alt_pypi_url
76- from easybuild .tools .filetools import diff_files , download_file , encode_class_name , extract_file
74+ from easybuild .tools .filetools import adjust_permissions , apply_patch , back_up_file , change_dir , convert_name
75+ from easybuild .tools .filetools import compute_checksum , copy_file , check_lock , create_lock , derive_alt_pypi_url
76+ from easybuild .tools .filetools import diff_files , dir_contains_files , download_file , encode_class_name , extract_file
7777from easybuild .tools .filetools import find_backup_name_candidate , get_source_tarball_from_git , is_alt_pypi_url
7878from easybuild .tools .filetools import is_binary , is_sha256_checksum , mkdir , move_file , move_logs , read_file , remove_dir
79- from easybuild .tools .filetools import remove_file , verify_checksum , weld_paths , write_file , dir_contains_files
79+ from easybuild .tools .filetools import remove_file , remove_lock , verify_checksum , weld_paths , write_file
8080from easybuild .tools .hooks import BUILD_STEP , CLEANUP_STEP , CONFIGURE_STEP , EXTENSIONS_STEP , FETCH_STEP , INSTALL_STEP
8181from easybuild .tools .hooks import MODULE_STEP , PACKAGE_STEP , PATCH_STEP , PERMISSIONS_STEP , POSTITER_STEP , POSTPROC_STEP
8282from easybuild .tools .hooks import PREPARE_STEP , READY_STEP , SANITYCHECK_STEP , SOURCE_STEP , TEST_STEP , TESTCASES_STEP
@@ -1014,6 +1014,25 @@ def make_devel_module(self, create_in_builddir=False):
10141014 # cleanup: unload fake module, remove fake module dir
10151015 self .clean_up_fake_module (fake_mod_data )
10161016
1017+ def make_module_deppaths (self ):
1018+ """
1019+ Add specific 'module use' actions to module file, in order to find
1020+ dependencies outside the end user's MODULEPATH.
1021+ """
1022+ deppaths = self .cfg ['moddependpaths' ]
1023+ if not deppaths :
1024+ return ''
1025+ elif not isinstance (deppaths , (str , list , tuple )):
1026+ raise EasyBuildError ("moddependpaths value %s (type: %s) is not a string, list or tuple" ,
1027+ deppaths , type (deppaths ))
1028+
1029+ if isinstance (deppaths , str ):
1030+ txt = self .module_generator .use ([deppaths ], guarded = True )
1031+ else :
1032+ txt = self .module_generator .use (deppaths , guarded = True )
1033+
1034+ return txt
1035+
10171036 def make_module_dep (self , unload_info = None ):
10181037 """
10191038 Make the dependencies for the module file.
@@ -1194,7 +1213,8 @@ def make_module_extra_extensions(self):
11941213 lines = [self .module_extra_extensions ]
11951214
11961215 # set environment variable that specifies list of extensions
1197- exts_list = ',' .join (['%s-%s' % (ext [0 ], ext [1 ]) for ext in self .cfg ['exts_list' ]])
1216+ # We need only name and version, so don't resolve templates
1217+ exts_list = ',' .join (['-' .join (ext [:2 ]) for ext in self .cfg .get_ref ('exts_list' )])
11981218 env_var_name = convert_name (self .name , upper = True )
11991219 lines .append (self .module_generator .set_environment ('EBEXTSLIST%s' % env_var_name , exts_list ))
12001220
@@ -1207,7 +1227,7 @@ def make_module_footer(self):
12071227 footer = [self .module_generator .comment ("Built with EasyBuild version %s" % VERBOSE_VERSION )]
12081228
12091229 # add extra stuff for extensions (if any)
1210- if self .cfg [ 'exts_list' ] :
1230+ if self .cfg . get_ref ( 'exts_list' ) :
12111231 footer .append (self .make_module_extra_extensions ())
12121232
12131233 # include modules footer if one is specified
@@ -1791,7 +1811,7 @@ def fetch_step(self, skip_checksums=False):
17911811 trace_msg (msg )
17921812
17931813 # fetch extensions
1794- if self .cfg [ 'exts_list' ] :
1814+ if self .cfg . get_ref ( 'exts_list' ) :
17951815 self .exts = self .fetch_extension_sources (skip_checksums = skip_checksums )
17961816
17971817 # create parent dirs in install and modules path already
@@ -1911,7 +1931,9 @@ def extract_step(self):
19111931 """
19121932 for src in self .src :
19131933 self .log .info ("Unpacking source %s" % src ['name' ])
1914- srcdir = extract_file (src ['path' ], self .builddir , cmd = src ['cmd' ], extra_options = self .cfg ['unpack_options' ])
1934+ srcdir = extract_file (src ['path' ], self .builddir , cmd = src ['cmd' ],
1935+ extra_options = self .cfg ['unpack_options' ], change_into_dir = False )
1936+ change_dir (srcdir )
19151937 if srcdir :
19161938 self .src [self .src .index (src )]['finalpath' ] = srcdir
19171939 else :
@@ -2063,7 +2085,7 @@ def extensions_step(self, fetch=False):
20632085 - find source for extensions, in 'extensions' (and 'packages' for legacy reasons)
20642086 - run extra_extensions
20652087 """
2066- if len ( self .cfg [ 'exts_list' ]) == 0 :
2088+ if not self .cfg . get_ref ( 'exts_list' ) :
20672089 self .log .debug ("No extensions in exts_list" )
20682090 return
20692091
@@ -2409,37 +2431,71 @@ def _sanity_check_step_common(self, custom_paths, custom_commands):
24092431 SANITY_CHECK_PATHS_DIRS : ("(non-empty) directory" , lambda dp : os .path .isdir (dp ) and os .listdir (dp )),
24102432 }
24112433
2412- # prepare sanity check paths
2413- paths = self .cfg ['sanity_check_paths' ]
2414- if not paths :
2434+ enhance_sanity_check = self .cfg ['enhance_sanity_check' ]
2435+ ec_commands = self .cfg ['sanity_check_commands' ]
2436+ ec_paths = self .cfg ['sanity_check_paths' ]
2437+
2438+ # if enhance_sanity_check is not enabled, only sanity_check_paths specified in the easyconfig file are used,
2439+ # the ones provided by the easyblock (via custom_paths) are ignored
2440+ if ec_paths and not enhance_sanity_check :
2441+ paths = ec_paths
2442+ self .log .info ("Using (only) sanity check paths specified by easyconfig file: %s" , paths )
2443+ else :
2444+ # if no sanity_check_paths are specified in easyconfig,
2445+ # we fall back to the ones provided by the easyblock via custom_paths
24152446 if custom_paths :
24162447 paths = custom_paths
2417- self .log .info ("Using customized sanity check paths: %s" % paths )
2448+ self .log .info ("Using customized sanity check paths: %s" , paths )
2449+ # if custom_paths is empty, we fall back to a generic set of paths:
2450+ # non-empty bin/ + /lib or /lib64 directories
24182451 else :
24192452 paths = {}
24202453 for key in path_keys_and_check :
24212454 paths .setdefault (key , [])
24222455 paths .update ({SANITY_CHECK_PATHS_DIRS : ['bin' , ('lib' , 'lib64' )]})
2423- self .log .info ("Using default sanity check paths: %s" % paths )
2456+ self .log .info ("Using default sanity check paths: %s" , paths )
2457+
2458+ # if enhance_sanity_check is enabled *and* sanity_check_paths are specified in the easyconfig,
2459+ # those paths are used to enhance the paths provided by the easyblock
2460+ if enhance_sanity_check and ec_paths :
2461+ for key in ec_paths :
2462+ val = ec_paths [key ]
2463+ if isinstance (val , list ):
2464+ paths [key ] = paths .get (key , []) + val
2465+ else :
2466+ error_pattern = "Incorrect value type in sanity_check_paths, should be a list: "
2467+ error_pattern += "%s (type: %s)" % (val , type (val ))
2468+ raise EasyBuildError (error_pattern )
2469+ self .log .info ("Enhanced sanity check paths after taking into account easyconfig file: %s" , paths )
2470+
2471+ sorted_keys = sorted (paths .keys ())
2472+ known_keys = sorted (path_keys_and_check .keys ())
2473+
2474+ # verify sanity_check_paths value: only known keys, correct value types, at least one non-empty value
2475+ only_list_values = all (isinstance (x , list ) for x in paths .values ())
2476+ only_empty_lists = all (not x for x in paths .values ())
2477+ if sorted_keys != known_keys or not only_list_values or only_empty_lists :
2478+ error_msg = "Incorrect format for sanity_check_paths: should (only) have %s keys, "
2479+ error_msg += "values should be lists (at least one non-empty)."
2480+ raise EasyBuildError (error_msg % ', ' .join ("'%s'" % k for k in known_keys ))
2481+
2482+ # if enhance_sanity_check is not enabled, only sanity_check_commands specified in the easyconfig file are used,
2483+ # the ones provided by the easyblock (via custom_commands) are ignored
2484+ if ec_commands and not enhance_sanity_check :
2485+ commands = ec_commands
2486+ self .log .info ("Using (only) sanity check commands specified by easyconfig file: %s" , commands )
24242487 else :
2425- self .log .info ("Using specified sanity check paths: %s" % paths )
2426-
2427- ks = sorted (paths .keys ())
2428- valnottypes = [not isinstance (x , list ) for x in paths .values ()]
2429- lenvals = [len (x ) for x in paths .values ()]
2430- req_keys = sorted (path_keys_and_check .keys ())
2431- if not ks == req_keys or sum (valnottypes ) > 0 or sum (lenvals ) == 0 :
2432- raise EasyBuildError ("Incorrect format for sanity_check_paths (should (only) have %s keys, "
2433- "values should be lists (at least one non-empty))." , ',' .join (req_keys ))
2434-
2435- commands = self .cfg ['sanity_check_commands' ]
2436- if not commands :
24372488 if custom_commands :
24382489 commands = custom_commands
2439- self .log .info ("Using customised sanity check commands: %s" % commands )
2490+ self .log .info ("Using customised sanity check commands: %s" , commands )
24402491 else :
24412492 commands = []
2442- self .log .info ("Using specified sanity check commands: %s" % commands )
2493+
2494+ # if enhance_sanity_check is enabled, the sanity_check_commands specified in the easyconfig file
2495+ # are combined with those provided by the easyblock via custom_commands
2496+ if enhance_sanity_check and ec_commands :
2497+ commands = commands + ec_commands
2498+ self .log .info ("Enhanced sanity check commands after taking into account easyconfig file: %s" , commands )
24432499
24442500 for i , command in enumerate (commands ):
24452501 # set command to default. This allows for config files with
@@ -2475,9 +2531,17 @@ def _sanity_check_step_dry_run(self, custom_paths=None, custom_commands=None, **
24752531 """
24762532 paths , path_keys_and_check , commands = self ._sanity_check_step_common (custom_paths , custom_commands )
24772533
2478- for key , (typ , _ ) in path_keys_and_check .items ():
2534+ for key in [SANITY_CHECK_PATHS_FILES , SANITY_CHECK_PATHS_DIRS ]:
2535+ (typ , _ ) = path_keys_and_check [key ]
24792536 self .dry_run_msg ("Sanity check paths - %s ['%s']" , typ , key )
2480- if paths [key ]:
2537+ entries = paths [key ]
2538+ if entries :
2539+ # some entries may be tuple values,
2540+ # we need to convert them to strings first so we can print them sorted
2541+ for idx , entry in enumerate (entries ):
2542+ if isinstance (entry , tuple ):
2543+ entries [idx ] = ' or ' .join (entry )
2544+
24812545 for path in sorted (paths [key ]):
24822546 self .dry_run_msg (" * %s" , str (path ))
24832547 else :
@@ -2608,6 +2672,9 @@ def xs2str(xs):
26082672
26092673 # run sanity check commands
26102674 for command in commands :
2675+
2676+ trace_msg ("running command '%s' ..." % command )
2677+
26112678 out , ec = run_cmd (command , simple = False , log_ok = False , log_all = False , trace = False )
26122679 if ec != 0 :
26132680 fail_msg = "sanity check command %s exited with code %s (output: %s)" % (command , ec , out )
@@ -2616,7 +2683,7 @@ def xs2str(xs):
26162683 else :
26172684 self .log .info ("sanity check command %s ran successfully! (output: %s)" % (command , out ))
26182685
2619- trace_msg ("running command '%s': %s" % (command , ('FAILED' , 'OK' )[ec == 0 ]))
2686+ trace_msg ("result for command '%s': %s" % (command , ('FAILED' , 'OK' )[ec == 0 ]))
26202687
26212688 # also run sanity check for extensions (unless we are an extension ourselves)
26222689 if not extension :
@@ -2723,6 +2790,7 @@ def make_module_step(self, fake=False):
27232790
27242791 txt += self .make_module_description ()
27252792 txt += self .make_module_group_check ()
2793+ txt += self .make_module_deppaths ()
27262794 txt += self .make_module_dep ()
27272795 txt += self .make_module_extend_modpath ()
27282796 txt += self .make_module_req ()
@@ -3049,30 +3117,14 @@ def run_all_steps(self, run_test_cases):
30493117 if ignore_locks :
30503118 self .log .info ("Ignoring locks..." )
30513119 else :
3052- locks_dir = build_option ('locks_dir' ) or os .path .join (install_path ('software' ), '.locks' )
3053- lock_path = os .path .join (locks_dir , '%s.lock' % self .installdir .replace ('/' , '_' ))
3054-
3055- # if lock already exists, either abort or wait until it disappears
3056- if os .path .exists (lock_path ):
3057- wait_on_lock = build_option ('wait_on_lock' )
3058- if wait_on_lock :
3059- while os .path .exists (lock_path ):
3060- print_msg ("lock %s exists, waiting %d seconds..." % (lock_path , wait_on_lock ),
3061- silent = self .silent )
3062- time .sleep (wait_on_lock )
3063- else :
3064- raise EasyBuildError ("Lock %s already exists, aborting!" , lock_path )
3120+ lock_name = self .installdir .replace ('/' , '_' )
30653121
3066- # create lock to avoid that another installation running in parallel messes things up;
3067- # we use a directory as a lock, since that's atomically created
3068- try :
3069- mkdir (lock_path , parents = True )
3070- except EasyBuildError as err :
3071- # clean up the error message a bit, get rid of the "Failed to create directory" part + quotes
3072- stripped_err = str (err ).split (':' , 1 )[1 ].strip ().replace ("'" , '' ).replace ('"' , '' )
3073- raise EasyBuildError ("Failed to create lock %s: %s" , lock_path , stripped_err )
3122+ # check if lock already exists;
3123+ # either aborts with an error or waits until it disappears (depends on --wait-on-lock)
3124+ check_lock (lock_name )
30743125
3075- self .log .info ("Lock created: %s" , lock_path )
3126+ # create lock to avoid that another installation running in parallel messes things up
3127+ create_lock (lock_name )
30763128
30773129 try :
30783130 for (step_name , descr , step_methods , skippable ) in steps :
@@ -3090,8 +3142,7 @@ def run_all_steps(self, run_test_cases):
30903142 pass
30913143 finally :
30923144 if not ignore_locks :
3093- remove_dir (lock_path )
3094- self .log .info ("Lock removed: %s" , lock_path )
3145+ remove_lock (lock_name )
30953146
30963147 # return True for successfull build (or stopped build)
30973148 return True
0 commit comments