74
74
from easybuild .tools .build_log import print_error , print_msg , print_warning
75
75
from easybuild .tools .config import CHECKSUM_PRIORITY_JSON , DEFAULT_ENVVAR_USERS_MODULES , PYTHONPATH , EBPYTHONPREFIXES
76
76
from easybuild .tools .config import FORCE_DOWNLOAD_ALL , FORCE_DOWNLOAD_PATCHES , FORCE_DOWNLOAD_SOURCES
77
- from easybuild .tools .config import EASYBUILD_SOURCES_URL # noqa
77
+ from easybuild .tools .config import EASYBUILD_SOURCES_URL # noqa
78
78
from easybuild .tools .config import build_option , build_path , get_log_filename , get_repository , get_repositorypath
79
79
from easybuild .tools .config import install_path , log_path , package_path , source_paths
80
80
from easybuild .tools .environment import restore_env , sanitize_env
@@ -1399,43 +1399,40 @@ def make_module_pythonpath(self):
1399
1399
Add lines for module file to update $PYTHONPATH or $EBPYTHONPREFIXES,
1400
1400
if they aren't already present and the standard lib/python*/site-packages subdirectory exists
1401
1401
"""
1402
- lines = []
1403
- if not os .path .isfile (os .path .join (self .installdir , 'bin' , 'python' )): # only needed when not a python install
1404
- python_subdir_pattern = os .path .join (self .installdir , 'lib' , 'python*' , 'site-packages' )
1405
- candidate_paths = (os .path .relpath (path , self .installdir ) for path in glob .glob (python_subdir_pattern ))
1406
- python_paths = [path for path in candidate_paths if re .match (r'lib/python\d+\.\d+/site-packages' , path )]
1402
+ if os .path .isfile (os .path .join (self .installdir , 'bin' , 'python' )): # only needed when not a python install
1403
+ return []
1407
1404
1408
- # determine whether Python is a runtime dependency;
1409
- # if so, we assume it was installed with EasyBuild, and hence is aware of $EBPYTHONPREFIXES
1410
- runtime_deps = [dep ['name' ] for dep in self .cfg .dependencies (runtime_only = True )]
1405
+ python_subdir_pattern = os .path .join (self .installdir , 'lib' , 'python*' , 'site-packages' )
1406
+ candidate_paths = (os .path .relpath (path , self .installdir ) for path in glob .glob (python_subdir_pattern ))
1407
+ python_paths = [path for path in candidate_paths if re .match (r'lib/python\d+\.\d+/site-packages' , path )]
1408
+ if not python_paths :
1409
+ return []
1411
1410
1412
- # don't use $EBPYTHONPREFIXES unless we can and it's preferred or necesary (due to use of multi_deps)
1413
- use_ebpythonprefixes = False
1414
- multi_deps = self .cfg [ 'multi_deps' ]
1411
+ # determine whether Python is a runtime dependency;
1412
+ # if so, we assume it was installed with EasyBuild, and hence is aware of $EBPYTHONPREFIXES
1413
+ runtime_deps = [ dep [ 'name' ] for dep in self .cfg . dependencies ( runtime_only = True ) ]
1415
1414
1416
- if 'Python' in runtime_deps :
1417
- self .log .info ("Found Python runtime dependency, so considering $EBPYTHONPREFIXES..." )
1415
+ # don't use $EBPYTHONPREFIXES unless we can and it's preferred or necesary (due to use of multi_deps)
1416
+ use_ebpythonprefixes = False
1417
+ multi_deps = self .cfg ['multi_deps' ]
1418
1418
1419
- if build_option ('prefer_python_search_path' ) == EBPYTHONPREFIXES :
1420
- self .log .info ("Preferred Python search path is $EBPYTHONPREFIXES, so using that" )
1421
- use_ebpythonprefixes = True
1419
+ if 'Python' in runtime_deps :
1420
+ self .log .info ("Found Python runtime dependency, so considering $EBPYTHONPREFIXES..." )
1422
1421
1423
- elif multi_deps and 'Python' in multi_deps :
1424
- self .log .info ("Python is listed in 'multi_deps' , so using $EBPYTHONPREFIXES instead of $PYTHONPATH " )
1422
+ if build_option ( 'prefer_python_search_path' ) == EBPYTHONPREFIXES :
1423
+ self .log .info ("Preferred Python search path is $EBPYTHONPREFIXES , so using that " )
1425
1424
use_ebpythonprefixes = True
1426
1425
1427
- if python_paths :
1428
- # add paths unless they were already added
1429
- if use_ebpythonprefixes :
1430
- path = '' # EBPYTHONPREFIXES are relative to the install dir
1431
- if path not in self .module_generator .added_paths_per_key [EBPYTHONPREFIXES ]:
1432
- lines .append (self .module_generator .prepend_paths (EBPYTHONPREFIXES , path ))
1433
- else :
1434
- for python_path in python_paths :
1435
- if python_path not in self .module_generator .added_paths_per_key [PYTHONPATH ]:
1436
- lines .append (self .module_generator .prepend_paths (PYTHONPATH , python_path ))
1426
+ elif multi_deps and 'Python' in multi_deps :
1427
+ self .log .info ("Python is listed in 'multi_deps', so using $EBPYTHONPREFIXES instead of $PYTHONPATH" )
1428
+ use_ebpythonprefixes = True
1437
1429
1438
- return lines
1430
+ if use_ebpythonprefixes :
1431
+ path = '' # EBPYTHONPREFIXES are relative to the install dir
1432
+ lines = self .module_generator .prepend_paths (EBPYTHONPREFIXES , path , warn_exists = False )
1433
+ else :
1434
+ lines = self .module_generator .prepend_paths (PYTHONPATH , python_paths , warn_exists = False )
1435
+ return [lines ] if lines else []
1439
1436
1440
1437
def make_module_extra (self , altroot = None , altversion = None ):
1441
1438
"""
@@ -1469,22 +1466,27 @@ def make_module_extra(self, altroot=None, altversion=None):
1469
1466
for (key , value ) in self .cfg ['modextravars' ].items ():
1470
1467
lines .append (self .module_generator .set_environment (key , value ))
1471
1468
1472
- for (key , value ) in self .cfg ['modextrapaths' ].items ():
1473
- if isinstance (value , str ):
1474
- value = [value ]
1475
- elif not isinstance (value , (tuple , list )):
1476
- raise EasyBuildError ("modextrapaths dict value %s (type: %s) is not a list or tuple" ,
1477
- value , type (value ))
1478
- lines .append (self .module_generator .prepend_paths (key , value , allow_abs = self .cfg ['allow_prepend_abs_path' ]))
1479
-
1480
- for (key , value ) in self .cfg ['modextrapaths_append' ].items ():
1481
- if isinstance (value , str ):
1482
- value = [value ]
1483
- elif not isinstance (value , (tuple , list )):
1484
- raise EasyBuildError ("modextrapaths_append dict value %s (type: %s) is not a list or tuple" ,
1485
- value , type (value ))
1486
- lines .append (self .module_generator .append_paths (key , value , allow_abs = self .cfg ['allow_append_abs_path' ]))
1469
+ for extrapaths_type , prepend in [('modextrapaths' , True ), ('modextrapaths_append' , False )]:
1470
+ allow_abs = self .cfg ['allow_prepend_abs_path' ] if prepend else self .cfg ['allow_append_abs_path' ]
1471
+
1472
+ for (key , value ) in self .cfg [extrapaths_type ].items ():
1473
+ if not isinstance (value , (tuple , list , dict , str )):
1474
+ raise EasyBuildError (
1475
+ f"{ extrapaths_type } dict value '{ value } ' (type { type (value )} ) is not a 'list, dict or str'"
1476
+ )
1487
1477
1478
+ try :
1479
+ paths = value ['paths' ]
1480
+ delim = value ['delimiter' ]
1481
+ except KeyError :
1482
+ raise EasyBuildError (f'{ extrapaths_type } dict "{ value } " lacks "paths" or "delimiter" items' )
1483
+ except TypeError :
1484
+ paths = value
1485
+ delim = ':'
1486
+
1487
+ lines .append (
1488
+ self .module_generator .update_paths (key , paths , prepend = prepend , delim = delim , allow_abs = allow_abs )
1489
+ )
1488
1490
# add lines to update $PYTHONPATH or $EBPYTHONPREFIXES
1489
1491
lines .extend (self .make_module_pythonpath ())
1490
1492
@@ -1881,7 +1883,6 @@ def skip_extensions_parallel(self, exts_filter):
1881
1883
Skip already installed extensions (checking in parallel),
1882
1884
by removing them from list of Extension instances to install (self.ext_instances).
1883
1885
"""
1884
- self .log .experimental ("Skipping installed extensions in parallel" )
1885
1886
print_msg ("skipping installed extensions (in parallel)" , log = self .log )
1886
1887
1887
1888
installed_exts_ids = []
@@ -1938,13 +1939,12 @@ def install_all_extensions(self, install=True):
1938
1939
self .log .debug ("List of loaded modules: %s" , self .modules_tool .list ())
1939
1940
1940
1941
if build_option ('parallel_extensions_install' ):
1941
- self .log .experimental ("installing extensions in parallel" )
1942
1942
try :
1943
1943
self .install_extensions_parallel (install = install )
1944
1944
except NotImplementedError :
1945
1945
# If parallel extension install is not supported for this type of extension then install sequentially
1946
1946
msg = "Parallel extensions install not supported for %s - using sequential install" % self .name
1947
- self .log .experimental (msg )
1947
+ self .log .info (msg )
1948
1948
self .install_extensions_sequential (install = install )
1949
1949
else :
1950
1950
self .install_extensions_sequential (install = install )
@@ -2118,20 +2118,33 @@ def update_exts_progress_bar_helper(running_exts, progress_size):
2118
2118
# if some of the required dependencies are not installed yet, requeue this extension
2119
2119
elif pending_deps :
2120
2120
2121
- # make sure all required dependencies are actually going to be installed,
2122
- # to avoid getting stuck in an infinite loop!
2121
+ # check whether all required dependency extensions are actually going to be installed;
2122
+ # if not, we assume that they are provided by dependencies;
2123
2123
missing_deps = [x for x in required_deps if x not in all_ext_names ]
2124
2124
if missing_deps :
2125
- raise EasyBuildError ("Missing required dependencies for %s are not going to be installed: %s" ,
2126
- ext .name , ', ' .join (missing_deps ))
2127
- else :
2128
- self .log .info ("Required dependencies missing for extension %s (%s), adding it back to queue..." ,
2129
- ext .name , ', ' .join (pending_deps ))
2125
+ msg = f"Missing required extensions for { ext .name } not found "
2126
+ msg += "in list of extensions being installed, let's assume they are provided by "
2127
+ msg += "dependencies and proceed: " + ', ' .join (missing_deps )
2128
+ self .log .info (msg )
2129
+
2130
+ msg = f"Pending dependencies for { ext .name } before taking into account missing dependencies: "
2131
+ self .log .debug (msg + ', ' .join (pending_deps ))
2132
+ pending_deps = [x for x in pending_deps if x not in missing_deps ]
2133
+ msg = f"Pending dependencies for { ext .name } after taking into account missing dependencies: "
2134
+ self .log .debug (msg + ', ' .join (pending_deps ))
2135
+
2136
+ if pending_deps :
2137
+ msg = f"Required dependencies not installed yet for extension { ext .name } ("
2138
+ msg += ', ' .join (pending_deps )
2139
+ msg += "), adding it back to queue..."
2140
+ self .log .info (msg )
2130
2141
# purposely adding extension back in the queue at Nth place rather than at the end,
2131
2142
# since we assume that the required dependencies will be installed soon...
2132
2143
exts_queue .insert (max_iter , ext )
2133
2144
2134
- else :
2145
+ # list of pending dependencies may be empty now after taking into account required extensions
2146
+ # that are not being installed above, so extension may be ready to install
2147
+ if not pending_deps :
2135
2148
tup = (ext .name , ext .version or '' )
2136
2149
print_msg ("starting installation of extension %s %s..." % tup , silent = self .silent , log = self .log )
2137
2150
@@ -3190,15 +3203,21 @@ def sanity_check_rpath(self, rpath_dirs=None, check_readelf_rpath=True):
3190
3203
3191
3204
fails = []
3192
3205
3193
- # hard reset $LD_LIBRARY_PATH before running RPATH sanity check
3194
- orig_env = env .unset_env_vars (['LD_LIBRARY_PATH' ])
3206
+ if build_option ('strict_rpath_sanity_check' ):
3207
+ self .log .info ("Unsetting $LD_LIBRARY_PATH since strict RPATH sanity check is enabled..." )
3208
+ # hard reset $LD_LIBRARY_PATH before running RPATH sanity check
3209
+ orig_env = env .unset_env_vars (['LD_LIBRARY_PATH' ])
3210
+ else :
3211
+ self .log .info ("Not unsetting $LD_LIBRARY_PATH since strict RPATH sanity check is disabled..." )
3212
+ orig_env = None
3195
3213
3196
3214
ld_library_path = os .getenv ('LD_LIBRARY_PATH' , '(empty)' )
3197
3215
self .log .debug (f"$LD_LIBRARY_PATH during RPATH sanity check: { ld_library_path } " )
3198
3216
modules_list = self .modules_tool .list ()
3199
3217
self .log .debug (f"List of loaded modules: { modules_list } " )
3200
3218
3201
3219
not_found_regex = re .compile (r'(\S+)\s*\=\>\s*not found' )
3220
+ lib_path_regex = re .compile (r'\S+\s*\=\>\s*(\S+)' )
3202
3221
readelf_rpath_regex = re .compile ('(RPATH)' , re .M )
3203
3222
3204
3223
# List of libraries that should be exempt from the RPATH sanity check;
@@ -3244,6 +3263,15 @@ def sanity_check_rpath(self, rpath_dirs=None, check_readelf_rpath=True):
3244
3263
fail_msg = f"Library { match } not found for { path } "
3245
3264
self .log .warning (fail_msg )
3246
3265
fails .append (fail_msg )
3266
+
3267
+ # if any libraries were not found, log whether dependency libraries have an RPATH section
3268
+ if fails :
3269
+ lib_paths = re .findall (lib_path_regex , out )
3270
+ for lib_path in lib_paths :
3271
+ self .log .info (f"Checking whether dependency library { lib_path } has RPATH section" )
3272
+ res = run_shell_cmd (f"readelf -d { lib_path } " , fail_on_error = False )
3273
+ if res .exit_code :
3274
+ self .log .info (f"No RPATH section found in { lib_path } " )
3247
3275
else :
3248
3276
self .log .debug (f"Output of 'ldd { path } ' checked, looks OK" )
3249
3277
@@ -3266,7 +3294,8 @@ def sanity_check_rpath(self, rpath_dirs=None, check_readelf_rpath=True):
3266
3294
else :
3267
3295
self .log .debug (f"Not sanity checking files in non-existing directory { dirpath } " )
3268
3296
3269
- env .restore_env_vars (orig_env )
3297
+ if orig_env :
3298
+ env .restore_env_vars (orig_env )
3270
3299
3271
3300
return fails
3272
3301
@@ -4413,7 +4442,9 @@ def build_and_install_one(ecdict, init_env):
4413
4442
def ensure_writable_log_dir (log_dir ):
4414
4443
"""Make sure we can write into the log dir"""
4415
4444
if build_option ('read_only_installdir' ):
4416
- # temporarily re-enable write permissions for copying log/easyconfig to install dir
4445
+ # temporarily re-enable write permissions for copying log/easyconfig to install dir,
4446
+ # ensuring that we resolve symlinks
4447
+ log_dir = os .path .realpath (log_dir )
4417
4448
if os .path .exists (log_dir ):
4418
4449
adjust_permissions (log_dir , stat .S_IWUSR , add = True , recursive = True )
4419
4450
else :
0 commit comments