6161from easybuild .framework .easyconfig .style import MAX_LINE_LENGTH
6262from easybuild .framework .easyconfig .tools import get_paths_for
6363from easybuild .framework .easyconfig .templates import TEMPLATE_NAMES_EASYBLOCK_RUN_STEP , template_constant_dict
64+ from easybuild .framework .extension import resolve_exts_filter_template
6465from easybuild .tools import config , run
6566from easybuild .tools .build_details import get_build_stats
6667from easybuild .tools .build_log import EasyBuildError , dry_run_msg , dry_run_warning , dry_run_set_dirs
@@ -1281,47 +1282,69 @@ def make_module_req(self):
12811282
12821283 lines = ['\n ' ]
12831284 if os .path .isdir (self .installdir ):
1284- change_dir (self .installdir )
1285+ old_dir = change_dir (self .installdir )
1286+ else :
1287+ old_dir = None
12851288
1289+ if self .dry_run :
1290+ self .dry_run_msg ("List of paths that would be searched and added to module file:\n " )
1291+ note = "note: glob patterns are not expanded and existence checks "
1292+ note += "for paths are skipped for the statements below due to dry run"
1293+ lines .append (self .module_generator .comment (note ))
1294+
1295+ # for these environment variables, the corresponding subdirectory must include at least one file
1296+ keys_requiring_files = set (('PATH' , 'LD_LIBRARY_PATH' , 'LIBRARY_PATH' , 'CPATH' ,
1297+ 'CMAKE_PREFIX_PATH' , 'CMAKE_LIBRARY_PATH' ))
1298+
1299+ for key , reqs in sorted (requirements .items ()):
1300+ if isinstance (reqs , string_type ):
1301+ self .log .warning ("Hoisting string value %s into a list before iterating over it" , reqs )
1302+ reqs = [reqs ]
12861303 if self .dry_run :
1287- self .dry_run_msg ("List of paths that would be searched and added to module file:\n " )
1288- note = "note: glob patterns are not expanded and existence checks "
1289- note += "for paths are skipped for the statements below due to dry run"
1290- lines .append (self .module_generator .comment (note ))
1291-
1292- # for these environment variables, the corresponding subdirectory must include at least one file
1293- keys_requiring_files = ('CPATH' , 'LD_LIBRARY_PATH' , 'LIBRARY_PATH' , 'PATH' )
1294-
1295- for key in sorted (requirements ):
1296- if self .dry_run :
1297- self .dry_run_msg (" $%s: %s" % (key , ', ' .join (requirements [key ])))
1298- reqs = requirements [key ]
1299- if isinstance (reqs , string_type ):
1300- self .log .warning ("Hoisting string value %s into a list before iterating over it" , reqs )
1301- reqs = [reqs ]
1302-
1303- for path in reqs :
1304- # only use glob if the string is non-empty
1305- if path and not self .dry_run :
1306- paths = sorted (glob .glob (path ))
1307- if paths and key in keys_requiring_files :
1308- # only retain paths that contain at least one file
1309- retained_paths = [
1310- path for path in paths
1311- if os .path .isdir (os .path .join (self .installdir , path ))
1312- and dir_contains_files (os .path .join (self .installdir , path ))
1313- ]
1314- self .log .info ("Only retaining paths for %s that contain at least one file: %s -> %s" ,
1315- key , paths , retained_paths )
1316- paths = retained_paths
1317- else :
1318- # empty string is a valid value here (i.e. to prepend the installation prefix, cfr $CUDA_HOME)
1319- paths = [path ]
1304+ self .dry_run_msg (" $%s: %s" % (key , ', ' .join (reqs )))
1305+ # Don't expand globs or do any filtering below for dry run
1306+ paths = sorted (reqs )
1307+ else :
1308+ # Expand globs but only if the string is non-empty
1309+ # empty string is a valid value here (i.e. to prepend the installation prefix, cfr $CUDA_HOME)
1310+ paths = sorted (sum ((glob .glob (path ) if path else [path ] for path in reqs ), [])) # sum flattens to list
1311+
1312+ # If lib64 is just a symlink to lib we fixup the paths to avoid duplicates
1313+ lib64_is_symlink = (all (os .path .isdir (path ) for path in ['lib' , 'lib64' ])
1314+ and os .path .samefile ('lib' , 'lib64' ))
1315+ if lib64_is_symlink :
1316+ fixed_paths = []
1317+ for path in paths :
1318+ if (path + os .path .sep ).startswith ('lib64' + os .path .sep ):
1319+ # We only need CMAKE_LIBRARY_PATH if there is a separate lib64 path, so skip symlink
1320+ if key == 'CMAKE_LIBRARY_PATH' :
1321+ continue
1322+ path = path .replace ('lib64' , 'lib' , 1 )
1323+ fixed_paths .append (path )
1324+ if fixed_paths != paths :
1325+ self .log .info ("Fixed symlink lib64 in paths for %s: %s -> %s" , key , paths , fixed_paths )
1326+ paths = fixed_paths
1327+ # Use a set to remove duplicates, e.g. by having lib64 and lib which get fixed to lib and lib above
1328+ paths = sorted (set (paths ))
1329+ if key in keys_requiring_files :
1330+ # only retain paths that contain at least one file
1331+ retained_paths = [
1332+ path for path in paths
1333+ if os .path .isdir (os .path .join (self .installdir , path ))
1334+ and dir_contains_files (os .path .join (self .installdir , path ))
1335+ ]
1336+ if retained_paths != paths :
1337+ self .log .info ("Only retaining paths for %s that contain at least one file: %s -> %s" ,
1338+ key , paths , retained_paths )
1339+ paths = retained_paths
1340+
1341+ if paths :
1342+ lines .append (self .module_generator .prepend_paths (key , paths ))
1343+ if self .dry_run :
1344+ self .dry_run_msg ('' )
13201345
1321- lines .append (self .module_generator .prepend_paths (key , paths ))
1322- if self .dry_run :
1323- self .dry_run_msg ('' )
1324- change_dir (self .orig_workdir )
1346+ if old_dir is not None :
1347+ change_dir (old_dir )
13251348
13261349 return '' .join (lines )
13271350
@@ -1341,6 +1364,8 @@ def make_module_req_guess(self):
13411364 'CLASSPATH' : ['*.jar' ],
13421365 'XDG_DATA_DIRS' : ['share' ],
13431366 'GI_TYPELIB_PATH' : [os .path .join (x , 'girepository-*' ) for x in lib_paths ],
1367+ 'CMAKE_PREFIX_PATH' : ['' ],
1368+ 'CMAKE_LIBRARY_PATH' : ['lib64' ], # lib and lib32 are searched through the above
13441369 }
13451370
13461371 def load_module (self , mod_paths = None , purge = True , extra_modules = None ):
@@ -1446,43 +1471,18 @@ def skip_extensions(self):
14461471
14471472 if not exts_filter or len (exts_filter ) == 0 :
14481473 raise EasyBuildError ("Skipping of extensions, but no exts_filter set in easyconfig" )
1449- elif isinstance (exts_filter , string_type ) or len (exts_filter ) != 2 :
1450- raise EasyBuildError ('exts_filter should be a list or tuple of ("command","input")' )
1451- cmdtmpl = exts_filter [0 ]
1452- cmdinputtmpl = exts_filter [1 ]
14531474
14541475 res = []
14551476 for ext in self .exts :
1456- name = ext ['name' ]
1457- if 'options' in ext and 'modulename' in ext ['options' ]:
1458- modname = ext ['options' ]['modulename' ]
1459- else :
1460- modname = name
1461- tmpldict = {
1462- 'ext_name' : modname ,
1463- 'ext_version' : ext .get ('version' ),
1464- 'src' : ext .get ('source' ),
1465- }
1466-
1467- try :
1468- cmd = cmdtmpl % tmpldict
1469- except KeyError as err :
1470- msg = "KeyError occurred on completing extension filter template: %s; "
1471- msg += "'name'/'version' keys are no longer supported, should use 'ext_name'/'ext_version' instead"
1472- self .log .nosupport (msg % err , '2.0' )
1473-
1474- if cmdinputtmpl :
1475- stdin = cmdinputtmpl % tmpldict
1476- (cmdstdouterr , ec ) = run_cmd (cmd , log_all = False , log_ok = False , simple = False , inp = stdin , regexp = False )
1477- else :
1478- (cmdstdouterr , ec ) = run_cmd (cmd , log_all = False , log_ok = False , simple = False , regexp = False )
1477+ cmd , stdin = resolve_exts_filter_template (exts_filter , ext )
1478+ (cmdstdouterr , ec ) = run_cmd (cmd , log_all = False , log_ok = False , simple = False , inp = stdin , regexp = False )
14791479 self .log .info ("exts_filter result %s %s" , cmdstdouterr , ec )
14801480 if ec :
1481- self .log .info ("Not skipping %s" % name )
1481+ self .log .info ("Not skipping %s" % ext [ ' name' ] )
14821482 self .log .debug ("exit code: %s, stdout/err: %s" % (ec , cmdstdouterr ))
14831483 res .append (ext )
14841484 else :
1485- self .log .info ("Skipping %s" % name )
1485+ self .log .info ("Skipping %s" % ext [ ' name' ] )
14861486 self .exts = res
14871487
14881488 #
@@ -1600,8 +1600,9 @@ def post_iter_step(self):
16001600
16011601 def det_iter_cnt (self ):
16021602 """Determine iteration count based on configure/build/install options that may be lists."""
1603- iter_opt_counts = [len (self .cfg [opt ]) for opt in ITERATE_OPTIONS
1604- if opt not in ['builddependencies' ] and isinstance (self .cfg [opt ], (list , tuple ))]
1603+ # Using get_ref to avoid resolving templates as their required attributes may not be available yet
1604+ iter_opt_counts = [len (self .cfg .get_ref (opt )) for opt in ITERATE_OPTIONS
1605+ if opt not in ['builddependencies' ] and isinstance (self .cfg .get_ref (opt ), (list , tuple ))]
16051606
16061607 # we need to take into account that builddependencies is always a list
16071608 # we're only iterating over it if it's a list of lists
@@ -2232,6 +2233,11 @@ def fix_shebang(self):
22322233 if should_patch :
22332234 contents = shebang_regex .sub (shebang , contents )
22342235 write_file (path , contents )
2236+ elif not contents .startswith ('#!' ):
2237+ self .log .info ("The file '%s' doesn't have any shebang present, inserting it as first line." ,
2238+ path )
2239+ contents = shebang + "\n " + contents
2240+ write_file (path , contents )
22352241
22362242 def post_install_step (self ):
22372243 """
0 commit comments