9393from easybuild .tools .py2vs3 import extract_method_name , string_type
9494from easybuild .tools .repository .repository import init_repository
9595from easybuild .tools .systemtools import check_linked_shared_libs , det_parallelism , get_shared_lib_ext , use_group
96- from easybuild .tools .utilities import INDENT_4SPACES , get_class_for , quote_str
96+ from easybuild .tools .utilities import INDENT_4SPACES , get_class_for , nub , quote_str
9797from easybuild .tools .utilities import remove_unwanted_chars , time2str , trace_msg
9898from easybuild .tools .version import this_is_easybuild , VERBOSE_VERSION , VERSION
9999
@@ -258,8 +258,6 @@ def __init__(self, ec):
258258 if group_name is not None :
259259 self .group = use_group (group_name )
260260
261- self .ignore_test_failure = build_option ('ignore_test_failure' )
262-
263261 # generate build/install directories
264262 self .gen_builddir ()
265263 self .gen_installdir ()
@@ -595,7 +593,13 @@ def fetch_extension_sources(self, skip_checksums=False):
595593 default_source_tmpl = resolve_template ('%(name)s-%(version)s.tar.gz' , template_values )
596594
597595 # if no sources are specified via 'sources', fall back to 'source_tmpl'
598- src_fn = ext_options .get ('source_tmpl' , default_source_tmpl )
596+ src_fn = ext_options .get ('source_tmpl' )
597+ if src_fn is None :
598+ src_fn = default_source_tmpl
599+ elif not isinstance (src_fn , string_type ):
600+ error_msg = "source_tmpl value must be a string! (found value of type '%s'): %s"
601+ raise EasyBuildError (error_msg , type (src_fn ).__name__ , src_fn )
602+
599603 src_path = self .obtain_file (src_fn , extension = True , urls = source_urls ,
600604 force_download = force_download )
601605 if src_path :
@@ -1429,9 +1433,17 @@ def make_module_req(self):
14291433 note += "for paths are skipped for the statements below due to dry run"
14301434 lines .append (self .module_generator .comment (note ))
14311435
1432- # for these environment variables, the corresponding subdirectory must include at least one file
1433- keys_requiring_files = set (('PATH' , 'LD_LIBRARY_PATH' , 'LIBRARY_PATH' , 'CPATH' ,
1434- 'CMAKE_PREFIX_PATH' , 'CMAKE_LIBRARY_PATH' ))
1436+ # For these environment variables, the corresponding directory must include at least one file.
1437+ # The values determine if detection is done recursively, i.e. if it accepts directories where files
1438+ # are only in subdirectories.
1439+ keys_requiring_files = {
1440+ 'PATH' : False ,
1441+ 'LD_LIBRARY_PATH' : False ,
1442+ 'LIBRARY_PATH' : True ,
1443+ 'CPATH' : True ,
1444+ 'CMAKE_PREFIX_PATH' : True ,
1445+ 'CMAKE_LIBRARY_PATH' : True ,
1446+ }
14351447
14361448 for key , reqs in sorted (requirements .items ()):
14371449 if isinstance (reqs , string_type ):
@@ -1461,19 +1473,16 @@ def make_module_req(self):
14611473 if fixed_paths != paths :
14621474 self .log .info ("Fixed symlink lib64 in paths for %s: %s -> %s" , key , paths , fixed_paths )
14631475 paths = fixed_paths
1464- # remove duplicate paths
1465- # don't use 'set' here, since order in 'paths' is important!
1466- uniq_paths = []
1467- for path in paths :
1468- if path not in uniq_paths :
1469- uniq_paths .append (path )
1470- paths = uniq_paths
1476+ # remove duplicate paths preserving order
1477+ paths = nub (paths )
14711478 if key in keys_requiring_files :
14721479 # only retain paths that contain at least one file
1480+ recursive = keys_requiring_files [key ]
14731481 retained_paths = [
1474- path for path in paths
1475- if os .path .isdir (os .path .join (self .installdir , path ))
1476- and dir_contains_files (os .path .join (self .installdir , path ))
1482+ path
1483+ for path , fullpath in ((path , os .path .join (self .installdir , path )) for path in paths )
1484+ if os .path .isdir (fullpath )
1485+ and dir_contains_files (fullpath , recursive = recursive )
14771486 ]
14781487 if retained_paths != paths :
14791488 self .log .info ("Only retaining paths for %s that contain at least one file: %s -> %s" ,
@@ -1782,18 +1791,19 @@ def set_parallel(self):
17821791 """Set 'parallel' easyconfig parameter to determine how many cores can/should be used for parallel builds."""
17831792 # set level of parallelism for build
17841793 par = build_option ('parallel' )
1785- if self .cfg ['parallel' ] is not None :
1786- if par is None :
1787- par = self .cfg ['parallel' ]
1788- self .log .debug ("Desired parallelism specified via 'parallel' easyconfig parameter: %s" , par )
1789- else :
1790- par = min (int (par ), int (self .cfg ['parallel' ]))
1791- self .log .debug ("Desired parallelism: minimum of 'parallel' build option/easyconfig parameter: %s" , par )
1792- else :
1794+ cfg_par = self .cfg ['parallel' ]
1795+ if cfg_par is None :
17931796 self .log .debug ("Desired parallelism specified via 'parallel' build option: %s" , par )
1797+ elif par is None :
1798+ par = cfg_par
1799+ self .log .debug ("Desired parallelism specified via 'parallel' easyconfig parameter: %s" , par )
1800+ else :
1801+ par = min (int (par ), int (cfg_par ))
1802+ self .log .debug ("Desired parallelism: minimum of 'parallel' build option/easyconfig parameter: %s" , par )
17941803
1795- self .cfg ['parallel' ] = det_parallelism (par = par , maxpar = self .cfg ['maxparallel' ])
1796- self .log .info ("Setting parallelism: %s" % self .cfg ['parallel' ])
1804+ par = det_parallelism (par , maxpar = self .cfg ['maxparallel' ])
1805+ self .log .info ("Setting parallelism: %s" % par )
1806+ self .cfg ['parallel' ] = par
17971807
17981808 def remove_module_file (self ):
17991809 """Remove module file (if it exists), and check for ghost installation directory (and deal with it)."""
@@ -1826,7 +1836,7 @@ def report_test_failure(self, msg_or_error):
18261836
18271837 :param msg_or_error: failure description (string value or an EasyBuildError instance)
18281838 """
1829- if self . ignore_test_failure :
1839+ if build_option ( ' ignore_test_failure' ) :
18301840 print_warning ("Test failure ignored: " + str (msg_or_error ), log = self .log )
18311841 else :
18321842 exception = msg_or_error if isinstance (msg_or_error , EasyBuildError ) else EasyBuildError (msg_or_error )
@@ -2409,6 +2419,7 @@ def extensions_step(self, fetch=False, install=True):
24092419
24102420 tup = (ext .name , ext .version or '' , idx + 1 , exts_cnt )
24112421 print_msg ("installing extension %s %s (%d/%d)..." % tup , silent = self .silent )
2422+ start_time = datetime .now ()
24122423
24132424 if self .dry_run :
24142425 tup = (ext .name , ext .version , ext .__class__ .__name__ )
@@ -2429,11 +2440,19 @@ def extensions_step(self, fetch=False, install=True):
24292440
24302441 # real work
24312442 if install :
2432- ext .prerun ()
2433- txt = ext .run ()
2434- if txt :
2435- self .module_extra_extensions += txt
2436- ext .postrun ()
2443+ try :
2444+ ext .prerun ()
2445+ txt = ext .run ()
2446+ if txt :
2447+ self .module_extra_extensions += txt
2448+ ext .postrun ()
2449+ finally :
2450+ if not self .dry_run :
2451+ ext_duration = datetime .now () - start_time
2452+ if ext_duration .total_seconds () >= 1 :
2453+ print_msg ("\t ... (took %s)" , time2str (ext_duration ), log = self .log , silent = self .silent )
2454+ elif self .logdebug or build_option ('trace' ):
2455+ print_msg ("\t ... (took < 1 sec)" , log = self .log , silent = self .silent )
24372456
24382457 # cleanup (unload fake module, remove fake module dir)
24392458 if fake_mod_data :
@@ -2466,7 +2485,7 @@ def package_step(self):
24662485
24672486 def fix_shebang (self ):
24682487 """Fix shebang lines for specified files."""
2469- for lang in ['perl' , 'python' ]:
2488+ for lang in ['bash' , ' perl' , 'python' ]:
24702489 shebang_regex = re .compile (r'^#![ ]*.*[/ ]%s.*' % lang )
24712490 fix_shebang_for = self .cfg ['fix_%s_shebang_for' % lang ]
24722491 if fix_shebang_for :
@@ -3685,15 +3704,21 @@ def build_and_install_one(ecdict, init_env):
36853704
36863705 if os .path .exists (app .installdir ) and build_option ('read_only_installdir' ) and (
36873706 build_option ('rebuild' ) or build_option ('force' )):
3707+ enabled_write_permissions = True
36883708 # re-enable write permissions so we can install additional modules
36893709 adjust_permissions (app .installdir , stat .S_IWUSR , add = True , recursive = True )
3710+ else :
3711+ enabled_write_permissions = False
36903712
36913713 result = app .run_all_steps (run_test_cases = run_test_cases )
36923714
36933715 if not dry_run :
36943716 # also add any extension easyblocks used during the build for reproducibility
36953717 if app .ext_instances :
36963718 copy_easyblocks_for_reprod (app .ext_instances , reprod_dir )
3719+ # If not already done remove the granted write permissions if we did so
3720+ if enabled_write_permissions and os .lstat (app .installdir )[stat .ST_MODE ] & stat .S_IWUSR :
3721+ adjust_permissions (app .installdir , stat .S_IWUSR , add = False , recursive = True )
36973722
36983723 except EasyBuildError as err :
36993724 first_n = 300
@@ -3710,28 +3735,37 @@ def build_and_install_one(ecdict, init_env):
37103735
37113736 # successful (non-dry-run) build
37123737 if result and not dry_run :
3738+ def ensure_writable_log_dir (log_dir ):
3739+ """Make sure we can write into the log dir"""
3740+ if build_option ('read_only_installdir' ):
3741+ # temporarily re-enable write permissions for copying log/easyconfig to install dir
3742+ if os .path .exists (log_dir ):
3743+ adjust_permissions (log_dir , stat .S_IWUSR , add = True , recursive = True )
3744+ else :
3745+ parent_dir = os .path .dirname (log_dir )
3746+ if os .path .exists (parent_dir ):
3747+ adjust_permissions (parent_dir , stat .S_IWUSR , add = True , recursive = False )
3748+ mkdir (log_dir , parents = True )
3749+ adjust_permissions (parent_dir , stat .S_IWUSR , add = False , recursive = False )
3750+ else :
3751+ mkdir (log_dir , parents = True )
3752+ adjust_permissions (log_dir , stat .S_IWUSR , add = True , recursive = True )
37133753
37143754 if app .cfg ['stop' ]:
37153755 ended = 'STOPPED'
37163756 if app .builddir is not None :
37173757 new_log_dir = os .path .join (app .builddir , config .log_path (ec = app .cfg ))
37183758 else :
37193759 new_log_dir = os .path .dirname (app .logfile )
3760+ ensure_writable_log_dir (new_log_dir )
37203761
37213762 # if we're only running the sanity check, we should not copy anything new to the installation directory
37223763 elif build_option ('sanity_check_only' ):
37233764 _log .info ("Only running sanity check, so skipping build stats, easyconfigs archive, reprod files..." )
37243765
37253766 else :
37263767 new_log_dir = os .path .join (app .installdir , config .log_path (ec = app .cfg ))
3727- if build_option ('read_only_installdir' ):
3728- # temporarily re-enable write permissions for copying log/easyconfig to install dir
3729- if os .path .exists (new_log_dir ):
3730- adjust_permissions (new_log_dir , stat .S_IWUSR , add = True , recursive = True )
3731- else :
3732- adjust_permissions (app .installdir , stat .S_IWUSR , add = True , recursive = False )
3733- mkdir (new_log_dir , parents = True )
3734- adjust_permissions (app .installdir , stat .S_IWUSR , add = False , recursive = False )
3768+ ensure_writable_log_dir (new_log_dir )
37353769
37363770 # collect build stats
37373771 _log .info ("Collecting build stats..." )
0 commit comments