Skip to content

Commit 1a9a9af

Browse files
authored
Merge pull request #3820 from easybuilders/4.4.x
release EasyBuild v4.4.2
2 parents 184219a + 0650213 commit 1a9a9af

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1147
-321
lines changed

.github/workflows/bootstrap_script.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ jobs:
107107
EB_BOOTSTRAP_VERSION=$(grep '^EB_BOOTSTRAP_VERSION' easybuild/scripts/bootstrap_eb.py | sed 's/[^0-9.]//g')
108108
EB_BOOTSTRAP_SHA256SUM=$(sha256sum easybuild/scripts/bootstrap_eb.py | cut -f1 -d' ')
109109
EB_BOOTSTRAP_FOUND="$EB_BOOTSTRAP_VERSION $EB_BOOTSTRAP_SHA256SUM"
110-
EB_BOOTSTRAP_EXPECTED="20210618.01 e5d477d717c6d3648ba2027ab735713ba5804fbf52f4b4adcca0bc1379b44618"
110+
EB_BOOTSTRAP_EXPECTED="20210715.01 784dd29063d941be2d8b70e4c2eec12a9afe360f3ef8f753dcb518abf43ca7de"
111111
test "$EB_BOOTSTRAP_FOUND" = "$EB_BOOTSTRAP_EXPECTED" || (echo "Version check on bootstrap script failed $EB_BOOTSTRAP_FOUND" && exit 1)
112112
113113
# test bootstrap script

RELEASE_NOTES

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,42 @@ For more detailed information, please see the git log.
44
These release notes can also be consulted at https://easybuild.readthedocs.io/en/latest/Release_notes.html.
55

66

7+
v4.4.2 (September 7th 2021)
8+
---------------------------
9+
10+
update/bugfix release
11+
12+
- various enhancements, including:
13+
- add per-extension timing in output produced by eb command (#3734)
14+
- add definition for new toolchain nvpsmpic (NVHPC + ParaStationMPI + CUDA) (#3736)
15+
- include list of missing libraries in warning about missing FFTW libraries in imkl toolchain component (#3776)
16+
- check for recursive symlinks by default before copying a folder (#3784)
17+
- add --filter-ecs configuration option to filter out easyconfigs from set of easyconfigs to install (#3796)
18+
- check type of source_tmpl value for extensions, ensure it's a string value (not a list) (#3799)
19+
- also define $BLAS_SHARED_LIBS & co in build environment (analogous to $BLAS_STATIC_LIBS) (#3800)
20+
- report use of --ignore-test-failure in success message in output (#3806)
21+
- add get_cuda_cc_template_value method to EasyConfig class (#3807)
22+
- add support for fix_bash_shebang_for (#3808)
23+
- pick up $MODULES_CMD to facilitate using Environment Modules 4.x as modules tool (#3816)
24+
- use more sensible branch name for creating easyblocks PR with --new-pr (#3817)
25+
- various bug fixes, including:
26+
- remove Python 2.6 from list of supported Python versions in setup.py (#3767)
27+
- don't add directory that doesn't include any files to $PATH or $LD_LIBRARY_PATH (#3769)
28+
- make logdir writable also when --stop/--fetch is used and --read-only-installdir is enabled (#3771)
29+
- fix forgotten renaming of 'l' to 'char' __init__.py that is created for included Python modules (#3773)
30+
- fix verify_imports by deleting all imported modules before re-importing them one by one (#3780)
31+
- fix ignore_test_failure not set for Extension instances (#3782)
32+
- update iompi toolchain to intel-compiler subtoolchain for oneAPI versions (>= iompi 2020.12) (#3785)
33+
- don't parse patch files as easyconfigs when searching for where patch file is used (#3786)
34+
- make sure git clone with a tag argument actually downloads a tag (#3795)
35+
- fix CI by excluding GC3Pie 2.6.7 (which is broken with Python 2) and improve error reporting for option parsing (#3798)
36+
- correctly resolve templates for patches in extensions when uploading to GitHub (#3805)
37+
- add --easystack to ignored options when submitting job (#3813)
38+
- other changes:
39+
- speed up tests by caching checked paths in set_tmpdir + less test cases for test_compiler_dependent_optarch (#3802)
40+
- speed up set_parallel method in EasyBlock class (#3812)
41+
42+
743
v4.4.1 (July 6th 2021)
844
----------------------
945

easybuild/framework/easyblock.py

Lines changed: 76 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@
9393
from easybuild.tools.py2vs3 import extract_method_name, string_type
9494
from easybuild.tools.repository.repository import init_repository
9595
from 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
9797
from easybuild.tools.utilities import remove_unwanted_chars, time2str, trace_msg
9898
from 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...")

easybuild/framework/easyconfig/default.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@
9595
'easybuild_version': [None, "EasyBuild-version this spec-file was written for", BUILD],
9696
'enhance_sanity_check': [False, "Indicate that additional sanity check commands & paths should enhance "
9797
"the existin sanity check, not replace it", BUILD],
98+
'fix_bash_shebang_for': [None, "List of files for which Bash shebang should be fixed "
99+
"to '#!/usr/bin/env bash' (glob patterns supported)", BUILD],
98100
'fix_perl_shebang_for': [None, "List of files for which Perl shebang should be fixed "
99101
"to '#!/usr/bin/env perl' (glob patterns supported)", BUILD],
100102
'fix_python_shebang_for': [None, "List of files for which Python shebang should be fixed "

easybuild/framework/easyconfig/easyconfig.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
from easybuild.framework.easyconfig.licenses import EASYCONFIG_LICENSES_DICT
6060
from easybuild.framework.easyconfig.parser import DEPRECATED_PARAMETERS, REPLACED_PARAMETERS
6161
from easybuild.framework.easyconfig.parser import EasyConfigParser, fetch_parameters_from_easyconfig
62-
from easybuild.framework.easyconfig.templates import TEMPLATE_CONSTANTS, template_constant_dict
62+
from easybuild.framework.easyconfig.templates import TEMPLATE_CONSTANTS, TEMPLATE_NAMES_DYNAMIC, template_constant_dict
6363
from easybuild.tools.build_log import EasyBuildError, print_warning, print_msg
6464
from easybuild.tools.config import GENERIC_EASYBLOCK_PKG, LOCAL_VAR_NAMING_CHECK_ERROR, LOCAL_VAR_NAMING_CHECK_LOG
6565
from easybuild.tools.config import LOCAL_VAR_NAMING_CHECK_WARN
@@ -559,6 +559,13 @@ def disable_templating(self):
559559
finally:
560560
self.enable_templating = old_enable_templating
561561

562+
def __str__(self):
563+
"""Return a string representation of this EasyConfig instance"""
564+
if self.path:
565+
return '%s EasyConfig @ %s' % (self.name, self.path)
566+
else:
567+
return 'Raw %s EasyConfig' % self.name
568+
562569
def filename(self):
563570
"""Determine correct filename for this easyconfig file."""
564571

@@ -881,7 +888,7 @@ def validate_os_deps(self):
881888
raise EasyBuildError("Non-tuple value type for OS dependency specification: %s (type %s)",
882889
dep, type(dep))
883890

884-
if not any([check_os_dependency(cand_dep) for cand_dep in dep]):
891+
if not any(check_os_dependency(cand_dep) for cand_dep in dep):
885892
not_found.append(dep)
886893

887894
if not_found:
@@ -1803,6 +1810,25 @@ def asdict(self):
18031810
res[key] = value
18041811
return res
18051812

1813+
def get_cuda_cc_template_value(self, key):
1814+
"""
1815+
Get template value based on --cuda-compute-capabilities EasyBuild configuration option
1816+
and cuda_compute_capabilities easyconfig parameter.
1817+
Returns user-friendly error message in case neither are defined,
1818+
or if an unknown key is used.
1819+
"""
1820+
if key.startswith('cuda_') and any(x[0] == key for x in TEMPLATE_NAMES_DYNAMIC):
1821+
try:
1822+
return self.template_values[key]
1823+
except KeyError:
1824+
error_msg = "Template value '%s' is not defined!\n"
1825+
error_msg += "Make sure that either the --cuda-compute-capabilities EasyBuild configuration "
1826+
error_msg += "option is set, or that the cuda_compute_capabilities easyconfig parameter is defined."
1827+
raise EasyBuildError(error_msg, key)
1828+
else:
1829+
error_msg = "%s is not a template value based on --cuda-compute-capabilities/cuda_compute_capabilities"
1830+
raise EasyBuildError(error_msg, key)
1831+
18061832

18071833
def det_installversion(version, toolchain_name, toolchain_version, prefix, suffix):
18081834
"""Deprecated 'det_installversion' function, to determine exact install version, based on supplied parameters."""

easybuild/framework/easyconfig/format/format.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ def get_version_toolchain(self, version=None, tcname=None, tcversion=None):
577577
self.log.debug("No toolchain version specified, using default %s" % tcversion)
578578
else:
579579
raise EasyBuildError("No toolchain version specified, no default found.")
580-
elif any([tc.test(tcname, tcversion) for tc in tcs]):
580+
elif any(tc.test(tcname, tcversion) for tc in tcs):
581581
self.log.debug("Toolchain '%s' version '%s' is supported in easyconfig" % (tcname, tcversion))
582582
else:
583583
raise EasyBuildError("Toolchain '%s' version '%s' not supported in easyconfig (only %s)",

0 commit comments

Comments
 (0)