Skip to content

Commit 748a503

Browse files
committed
Merge branch '5.0.x' into deprecate_parallel-5
2 parents 6fc101a + 34466de commit 748a503

File tree

21 files changed

+579
-288
lines changed

21 files changed

+579
-288
lines changed

easybuild/framework/easyblock.py

Lines changed: 91 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
from easybuild.tools.build_log import print_error, print_msg, print_warning
7575
from easybuild.tools.config import CHECKSUM_PRIORITY_JSON, DEFAULT_ENVVAR_USERS_MODULES, PYTHONPATH, EBPYTHONPREFIXES
7676
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
7878
from easybuild.tools.config import build_option, build_path, get_log_filename, get_repository, get_repositorypath
7979
from easybuild.tools.config import install_path, log_path, package_path, source_paths
8080
from easybuild.tools.environment import restore_env, sanitize_env
@@ -1399,43 +1399,40 @@ def make_module_pythonpath(self):
13991399
Add lines for module file to update $PYTHONPATH or $EBPYTHONPREFIXES,
14001400
if they aren't already present and the standard lib/python*/site-packages subdirectory exists
14011401
"""
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 []
14071404

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 []
14111410

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)]
14151414

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']
14181418

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...")
14221421

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")
14251424
use_ebpythonprefixes = True
14261425

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
14371429

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 []
14391436

14401437
def make_module_extra(self, altroot=None, altversion=None):
14411438
"""
@@ -1469,22 +1466,27 @@ def make_module_extra(self, altroot=None, altversion=None):
14691466
for (key, value) in self.cfg['modextravars'].items():
14701467
lines.append(self.module_generator.set_environment(key, value))
14711468

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+
)
14871477

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+
)
14881490
# add lines to update $PYTHONPATH or $EBPYTHONPREFIXES
14891491
lines.extend(self.make_module_pythonpath())
14901492

@@ -1881,7 +1883,6 @@ def skip_extensions_parallel(self, exts_filter):
18811883
Skip already installed extensions (checking in parallel),
18821884
by removing them from list of Extension instances to install (self.ext_instances).
18831885
"""
1884-
self.log.experimental("Skipping installed extensions in parallel")
18851886
print_msg("skipping installed extensions (in parallel)", log=self.log)
18861887

18871888
installed_exts_ids = []
@@ -1938,13 +1939,12 @@ def install_all_extensions(self, install=True):
19381939
self.log.debug("List of loaded modules: %s", self.modules_tool.list())
19391940

19401941
if build_option('parallel_extensions_install'):
1941-
self.log.experimental("installing extensions in parallel")
19421942
try:
19431943
self.install_extensions_parallel(install=install)
19441944
except NotImplementedError:
19451945
# If parallel extension install is not supported for this type of extension then install sequentially
19461946
msg = "Parallel extensions install not supported for %s - using sequential install" % self.name
1947-
self.log.experimental(msg)
1947+
self.log.info(msg)
19481948
self.install_extensions_sequential(install=install)
19491949
else:
19501950
self.install_extensions_sequential(install=install)
@@ -2118,20 +2118,33 @@ def update_exts_progress_bar_helper(running_exts, progress_size):
21182118
# if some of the required dependencies are not installed yet, requeue this extension
21192119
elif pending_deps:
21202120

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;
21232123
missing_deps = [x for x in required_deps if x not in all_ext_names]
21242124
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)
21302141
# purposely adding extension back in the queue at Nth place rather than at the end,
21312142
# since we assume that the required dependencies will be installed soon...
21322143
exts_queue.insert(max_iter, ext)
21332144

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:
21352148
tup = (ext.name, ext.version or '')
21362149
print_msg("starting installation of extension %s %s..." % tup, silent=self.silent, log=self.log)
21372150

@@ -3190,15 +3203,21 @@ def sanity_check_rpath(self, rpath_dirs=None, check_readelf_rpath=True):
31903203

31913204
fails = []
31923205

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
31953213

31963214
ld_library_path = os.getenv('LD_LIBRARY_PATH', '(empty)')
31973215
self.log.debug(f"$LD_LIBRARY_PATH during RPATH sanity check: {ld_library_path}")
31983216
modules_list = self.modules_tool.list()
31993217
self.log.debug(f"List of loaded modules: {modules_list}")
32003218

32013219
not_found_regex = re.compile(r'(\S+)\s*\=\>\s*not found')
3220+
lib_path_regex = re.compile(r'\S+\s*\=\>\s*(\S+)')
32023221
readelf_rpath_regex = re.compile('(RPATH)', re.M)
32033222

32043223
# 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):
32443263
fail_msg = f"Library {match} not found for {path}"
32453264
self.log.warning(fail_msg)
32463265
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}")
32473275
else:
32483276
self.log.debug(f"Output of 'ldd {path}' checked, looks OK")
32493277

@@ -3266,7 +3294,8 @@ def sanity_check_rpath(self, rpath_dirs=None, check_readelf_rpath=True):
32663294
else:
32673295
self.log.debug(f"Not sanity checking files in non-existing directory {dirpath}")
32683296

3269-
env.restore_env_vars(orig_env)
3297+
if orig_env:
3298+
env.restore_env_vars(orig_env)
32703299

32713300
return fails
32723301

@@ -4413,7 +4442,9 @@ def build_and_install_one(ecdict, init_env):
44134442
def ensure_writable_log_dir(log_dir):
44144443
"""Make sure we can write into the log dir"""
44154444
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)
44174448
if os.path.exists(log_dir):
44184449
adjust_permissions(log_dir, stat.S_IWUSR, add=True, recursive=True)
44194450
else:

easybuild/framework/easyconfig/templates.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@
102102
'cuda_sm_comma_sep': 'Comma-separated list of sm_* values that correspond with CUDA compute capabilities',
103103
'cuda_sm_space_sep': 'Space-separated list of sm_* values that correspond with CUDA compute capabilities',
104104
'mpi_cmd_prefix': 'Prefix command for running MPI programs (with default number of ranks)',
105+
# can't be a boolean (True/False), must be a string value since it's a string template
106+
'rpath_enabled': "String value indicating whether or not RPATH linking is used ('true' or 'false')",
105107
'software_commit': "Git commit id to use for the software as specified by --software-commit command line option",
106108
'sysroot': "Location root directory of system, prefix for standard paths like /usr/lib and /usr/include"
107109
"as specify by the --sysroot configuration option",
@@ -297,6 +299,9 @@ def template_constant_dict(config, ignore=None, toolchain=None):
297299
# set 'arch' for system architecture based on 'machine' (4th) element of platform.uname() return value
298300
template_values['arch'] = platform.uname()[4]
299301

302+
# set 'rpath' template based on 'rpath' configuration option, using empty string as fallback
303+
template_values['rpath_enabled'] = 'true' if build_option('rpath') else 'false'
304+
300305
# set 'sysroot' template based on 'sysroot' configuration option, using empty string as fallback
301306
template_values['sysroot'] = build_option('sysroot') or ''
302307

easybuild/framework/easystack.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,6 @@ def parse_by_easyconfigs(filepath, easyconfigs, easybuild_version=None, robot=Fa
188188
@only_if_module_is_available('yaml', pkgname='PyYAML')
189189
def parse_easystack(filepath):
190190
"""Parses through easystack file, returns what EC are to be installed together with their options."""
191-
log_msg = "Support for easybuild-ing from multiple easyconfigs based on "
192-
log_msg += "information obtained from provided file (easystack) with build specifications."
193-
_log.experimental(log_msg)
194191
_log.info("Building from easystack: '%s'" % filepath)
195192

196193
# class instance which contains all info about planned build

0 commit comments

Comments
 (0)