Skip to content

Commit 766128e

Browse files
Merge pull request #3340 from easybuilders/4.2.x
release EasyBuild 4.2.1
2 parents e9e8532 + f571279 commit 766128e

Some content is hidden

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

64 files changed

+2303
-831
lines changed

.github/workflows/linting.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Static Analysis
2+
on: [push, pull_request]
3+
jobs:
4+
python-linting:
5+
runs-on: ubuntu-18.04
6+
steps:
7+
- uses: actions/checkout@v2
8+
9+
- name: set up Python
10+
uses: actions/setup-python@v1
11+
with:
12+
python-version: 3.8
13+
14+
- name: install Python packages
15+
run: |
16+
pip install --upgrade pip
17+
pip install --upgrade flake8
18+
19+
- name: Run flake8
20+
run: |
21+
flake8 easybuild/tools
22+

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ build/
99
dist/
1010
*egg-info/
1111
*.swp
12+
.mypy_cache/
1213

1314
Dockerfile.*
1415
Singularity.*

RELEASE_NOTES

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,41 @@ For more detailed information, please see the git log.
33

44
These release notes can also be consulted at https://easybuild.readthedocs.io/en/latest/Release_notes.html.
55

6+
v4.2.1 (May 20th 2020)
7+
----------------------
8+
9+
update/bugfix release
10+
11+
- various enhancements, including:
12+
- also mention CPU architecture (x86_64, POWER) in comment for test reports (#3281)
13+
- add support for enhancing existing sanity check in easyconfigs, as opposed to overwriting paths/commands (#3288)
14+
- clean up locks when EasyBuild session is cancelled with a signal like SIGTERM (#3291, #3321)
15+
- add 'find_glob_pattern' function to filetools module (#3297)
16+
- add constants for common OS dependencies (OS_PKG_IBVERBS_DEV, OS_PKG_OPENSSL_DEV, ...) (#3309, #3334)
17+
- flesh out get_mpi_cmd_template function from Mpi.mpi_cmd_for method (#3312)
18+
- add variable 'moddependpaths' to specify extra $MODULEPATH entry to consider for loading dependency modules (#3324)
19+
- allow copying of tweaked easyconfigs when using --try-* with --copy-ec (#3332)
20+
- various bug fixes, including:
21+
- make ModulesTool.exist more robust w.r.t. module wrappers, aliases, defaults, etc. (#3216, #3337)
22+
- clean up rst output of --list-toolchains (#3246)
23+
- cast CPU arch name provided by archspec to a regular string (#3286)
24+
- get pr_title and pr_descr built_options in new_pr_from_branch instead of new_pr (and commit_msg in both) (#3298)
25+
- make pypi_source_urls more robust by using HTMLParser rather than xml.etree.ElementTree (#3303, #3329)
26+
- fix broken test for --include-easyblocks-from-pr (#3304)
27+
- don't use distutils.dir_util in copy_dir (#3310)
28+
- print trace message for sanity check command before running it (#3316)
29+
- fix problems with processing of easyconfigs using a Cray* toolchain when there are no actual external modules (#3319)
30+
- make test_find_eb_script more robust in case $EB_SCRIPT_PATH is already set (#3320)
31+
- fix several small problems wit --try-update-deps (experimental feature) (#3325, #3326, #3330)
32+
- add --disable-job in 'eb' command used in jobs, to prevent infinite job cycle (#3328)
33+
- avoid empty entries in $LD_LIBRARY_PATH and other path-like environment variables (#3333)
34+
- other changes:
35+
- fix code style issues in easybuild.tools + add flake8 linting test (#3282)
36+
- introduce contextmanager for disabling templating and reduce resolving errors (#3287)
37+
- add 'change_into_dir' named argument to 'extract_file' + print deprecation warning if it's not specified (#3292)
38+
- improve install_eb_dep.sh script to install EasyBuild dependencies in CI environment (#3314)
39+
40+
641
v4.2.0 (April 14th 2020)
742
------------------------
843

easybuild/framework/easyblock.py

Lines changed: 106 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,12 @@
7171
from easybuild.tools.config import install_path, log_path, package_path, source_paths
7272
from easybuild.tools.environment import restore_env, sanitize_env
7373
from easybuild.tools.filetools import CHECKSUM_TYPE_MD5, CHECKSUM_TYPE_SHA256
74-
from easybuild.tools.filetools import adjust_permissions, apply_patch, back_up_file
75-
from easybuild.tools.filetools import change_dir, convert_name, compute_checksum, copy_file, derive_alt_pypi_url
76-
from easybuild.tools.filetools import diff_files, download_file, encode_class_name, extract_file
74+
from easybuild.tools.filetools import adjust_permissions, apply_patch, back_up_file, change_dir, convert_name
75+
from easybuild.tools.filetools import compute_checksum, copy_file, check_lock, create_lock, derive_alt_pypi_url
76+
from easybuild.tools.filetools import diff_files, dir_contains_files, download_file, encode_class_name, extract_file
7777
from easybuild.tools.filetools import find_backup_name_candidate, get_source_tarball_from_git, is_alt_pypi_url
7878
from easybuild.tools.filetools import is_binary, is_sha256_checksum, mkdir, move_file, move_logs, read_file, remove_dir
79-
from easybuild.tools.filetools import remove_file, verify_checksum, weld_paths, write_file, dir_contains_files
79+
from easybuild.tools.filetools import remove_file, remove_lock, verify_checksum, weld_paths, write_file
8080
from easybuild.tools.hooks import BUILD_STEP, CLEANUP_STEP, CONFIGURE_STEP, EXTENSIONS_STEP, FETCH_STEP, INSTALL_STEP
8181
from easybuild.tools.hooks import MODULE_STEP, PACKAGE_STEP, PATCH_STEP, PERMISSIONS_STEP, POSTITER_STEP, POSTPROC_STEP
8282
from easybuild.tools.hooks import PREPARE_STEP, READY_STEP, SANITYCHECK_STEP, SOURCE_STEP, TEST_STEP, TESTCASES_STEP
@@ -1014,6 +1014,25 @@ def make_devel_module(self, create_in_builddir=False):
10141014
# cleanup: unload fake module, remove fake module dir
10151015
self.clean_up_fake_module(fake_mod_data)
10161016

1017+
def make_module_deppaths(self):
1018+
"""
1019+
Add specific 'module use' actions to module file, in order to find
1020+
dependencies outside the end user's MODULEPATH.
1021+
"""
1022+
deppaths = self.cfg['moddependpaths']
1023+
if not deppaths:
1024+
return ''
1025+
elif not isinstance(deppaths, (str, list, tuple)):
1026+
raise EasyBuildError("moddependpaths value %s (type: %s) is not a string, list or tuple",
1027+
deppaths, type(deppaths))
1028+
1029+
if isinstance(deppaths, str):
1030+
txt = self.module_generator.use([deppaths], guarded=True)
1031+
else:
1032+
txt = self.module_generator.use(deppaths, guarded=True)
1033+
1034+
return txt
1035+
10171036
def make_module_dep(self, unload_info=None):
10181037
"""
10191038
Make the dependencies for the module file.
@@ -1194,7 +1213,8 @@ def make_module_extra_extensions(self):
11941213
lines = [self.module_extra_extensions]
11951214

11961215
# set environment variable that specifies list of extensions
1197-
exts_list = ','.join(['%s-%s' % (ext[0], ext[1]) for ext in self.cfg['exts_list']])
1216+
# We need only name and version, so don't resolve templates
1217+
exts_list = ','.join(['-'.join(ext[:2]) for ext in self.cfg.get_ref('exts_list')])
11981218
env_var_name = convert_name(self.name, upper=True)
11991219
lines.append(self.module_generator.set_environment('EBEXTSLIST%s' % env_var_name, exts_list))
12001220

@@ -1207,7 +1227,7 @@ def make_module_footer(self):
12071227
footer = [self.module_generator.comment("Built with EasyBuild version %s" % VERBOSE_VERSION)]
12081228

12091229
# add extra stuff for extensions (if any)
1210-
if self.cfg['exts_list']:
1230+
if self.cfg.get_ref('exts_list'):
12111231
footer.append(self.make_module_extra_extensions())
12121232

12131233
# include modules footer if one is specified
@@ -1791,7 +1811,7 @@ def fetch_step(self, skip_checksums=False):
17911811
trace_msg(msg)
17921812

17931813
# fetch extensions
1794-
if self.cfg['exts_list']:
1814+
if self.cfg.get_ref('exts_list'):
17951815
self.exts = self.fetch_extension_sources(skip_checksums=skip_checksums)
17961816

17971817
# create parent dirs in install and modules path already
@@ -1911,7 +1931,9 @@ def extract_step(self):
19111931
"""
19121932
for src in self.src:
19131933
self.log.info("Unpacking source %s" % src['name'])
1914-
srcdir = extract_file(src['path'], self.builddir, cmd=src['cmd'], extra_options=self.cfg['unpack_options'])
1934+
srcdir = extract_file(src['path'], self.builddir, cmd=src['cmd'],
1935+
extra_options=self.cfg['unpack_options'], change_into_dir=False)
1936+
change_dir(srcdir)
19151937
if srcdir:
19161938
self.src[self.src.index(src)]['finalpath'] = srcdir
19171939
else:
@@ -2063,7 +2085,7 @@ def extensions_step(self, fetch=False):
20632085
- find source for extensions, in 'extensions' (and 'packages' for legacy reasons)
20642086
- run extra_extensions
20652087
"""
2066-
if len(self.cfg['exts_list']) == 0:
2088+
if not self.cfg.get_ref('exts_list'):
20672089
self.log.debug("No extensions in exts_list")
20682090
return
20692091

@@ -2409,37 +2431,71 @@ def _sanity_check_step_common(self, custom_paths, custom_commands):
24092431
SANITY_CHECK_PATHS_DIRS: ("(non-empty) directory", lambda dp: os.path.isdir(dp) and os.listdir(dp)),
24102432
}
24112433

2412-
# prepare sanity check paths
2413-
paths = self.cfg['sanity_check_paths']
2414-
if not paths:
2434+
enhance_sanity_check = self.cfg['enhance_sanity_check']
2435+
ec_commands = self.cfg['sanity_check_commands']
2436+
ec_paths = self.cfg['sanity_check_paths']
2437+
2438+
# if enhance_sanity_check is not enabled, only sanity_check_paths specified in the easyconfig file are used,
2439+
# the ones provided by the easyblock (via custom_paths) are ignored
2440+
if ec_paths and not enhance_sanity_check:
2441+
paths = ec_paths
2442+
self.log.info("Using (only) sanity check paths specified by easyconfig file: %s", paths)
2443+
else:
2444+
# if no sanity_check_paths are specified in easyconfig,
2445+
# we fall back to the ones provided by the easyblock via custom_paths
24152446
if custom_paths:
24162447
paths = custom_paths
2417-
self.log.info("Using customized sanity check paths: %s" % paths)
2448+
self.log.info("Using customized sanity check paths: %s", paths)
2449+
# if custom_paths is empty, we fall back to a generic set of paths:
2450+
# non-empty bin/ + /lib or /lib64 directories
24182451
else:
24192452
paths = {}
24202453
for key in path_keys_and_check:
24212454
paths.setdefault(key, [])
24222455
paths.update({SANITY_CHECK_PATHS_DIRS: ['bin', ('lib', 'lib64')]})
2423-
self.log.info("Using default sanity check paths: %s" % paths)
2456+
self.log.info("Using default sanity check paths: %s", paths)
2457+
2458+
# if enhance_sanity_check is enabled *and* sanity_check_paths are specified in the easyconfig,
2459+
# those paths are used to enhance the paths provided by the easyblock
2460+
if enhance_sanity_check and ec_paths:
2461+
for key in ec_paths:
2462+
val = ec_paths[key]
2463+
if isinstance(val, list):
2464+
paths[key] = paths.get(key, []) + val
2465+
else:
2466+
error_pattern = "Incorrect value type in sanity_check_paths, should be a list: "
2467+
error_pattern += "%s (type: %s)" % (val, type(val))
2468+
raise EasyBuildError(error_pattern)
2469+
self.log.info("Enhanced sanity check paths after taking into account easyconfig file: %s", paths)
2470+
2471+
sorted_keys = sorted(paths.keys())
2472+
known_keys = sorted(path_keys_and_check.keys())
2473+
2474+
# verify sanity_check_paths value: only known keys, correct value types, at least one non-empty value
2475+
only_list_values = all(isinstance(x, list) for x in paths.values())
2476+
only_empty_lists = all(not x for x in paths.values())
2477+
if sorted_keys != known_keys or not only_list_values or only_empty_lists:
2478+
error_msg = "Incorrect format for sanity_check_paths: should (only) have %s keys, "
2479+
error_msg += "values should be lists (at least one non-empty)."
2480+
raise EasyBuildError(error_msg % ', '.join("'%s'" % k for k in known_keys))
2481+
2482+
# if enhance_sanity_check is not enabled, only sanity_check_commands specified in the easyconfig file are used,
2483+
# the ones provided by the easyblock (via custom_commands) are ignored
2484+
if ec_commands and not enhance_sanity_check:
2485+
commands = ec_commands
2486+
self.log.info("Using (only) sanity check commands specified by easyconfig file: %s", commands)
24242487
else:
2425-
self.log.info("Using specified sanity check paths: %s" % paths)
2426-
2427-
ks = sorted(paths.keys())
2428-
valnottypes = [not isinstance(x, list) for x in paths.values()]
2429-
lenvals = [len(x) for x in paths.values()]
2430-
req_keys = sorted(path_keys_and_check.keys())
2431-
if not ks == req_keys or sum(valnottypes) > 0 or sum(lenvals) == 0:
2432-
raise EasyBuildError("Incorrect format for sanity_check_paths (should (only) have %s keys, "
2433-
"values should be lists (at least one non-empty)).", ','.join(req_keys))
2434-
2435-
commands = self.cfg['sanity_check_commands']
2436-
if not commands:
24372488
if custom_commands:
24382489
commands = custom_commands
2439-
self.log.info("Using customised sanity check commands: %s" % commands)
2490+
self.log.info("Using customised sanity check commands: %s", commands)
24402491
else:
24412492
commands = []
2442-
self.log.info("Using specified sanity check commands: %s" % commands)
2493+
2494+
# if enhance_sanity_check is enabled, the sanity_check_commands specified in the easyconfig file
2495+
# are combined with those provided by the easyblock via custom_commands
2496+
if enhance_sanity_check and ec_commands:
2497+
commands = commands + ec_commands
2498+
self.log.info("Enhanced sanity check commands after taking into account easyconfig file: %s", commands)
24432499

24442500
for i, command in enumerate(commands):
24452501
# set command to default. This allows for config files with
@@ -2475,9 +2531,17 @@ def _sanity_check_step_dry_run(self, custom_paths=None, custom_commands=None, **
24752531
"""
24762532
paths, path_keys_and_check, commands = self._sanity_check_step_common(custom_paths, custom_commands)
24772533

2478-
for key, (typ, _) in path_keys_and_check.items():
2534+
for key in [SANITY_CHECK_PATHS_FILES, SANITY_CHECK_PATHS_DIRS]:
2535+
(typ, _) = path_keys_and_check[key]
24792536
self.dry_run_msg("Sanity check paths - %s ['%s']", typ, key)
2480-
if paths[key]:
2537+
entries = paths[key]
2538+
if entries:
2539+
# some entries may be tuple values,
2540+
# we need to convert them to strings first so we can print them sorted
2541+
for idx, entry in enumerate(entries):
2542+
if isinstance(entry, tuple):
2543+
entries[idx] = ' or '.join(entry)
2544+
24812545
for path in sorted(paths[key]):
24822546
self.dry_run_msg(" * %s", str(path))
24832547
else:
@@ -2608,6 +2672,9 @@ def xs2str(xs):
26082672

26092673
# run sanity check commands
26102674
for command in commands:
2675+
2676+
trace_msg("running command '%s' ..." % command)
2677+
26112678
out, ec = run_cmd(command, simple=False, log_ok=False, log_all=False, trace=False)
26122679
if ec != 0:
26132680
fail_msg = "sanity check command %s exited with code %s (output: %s)" % (command, ec, out)
@@ -2616,7 +2683,7 @@ def xs2str(xs):
26162683
else:
26172684
self.log.info("sanity check command %s ran successfully! (output: %s)" % (command, out))
26182685

2619-
trace_msg("running command '%s': %s" % (command, ('FAILED', 'OK')[ec == 0]))
2686+
trace_msg("result for command '%s': %s" % (command, ('FAILED', 'OK')[ec == 0]))
26202687

26212688
# also run sanity check for extensions (unless we are an extension ourselves)
26222689
if not extension:
@@ -2723,6 +2790,7 @@ def make_module_step(self, fake=False):
27232790

27242791
txt += self.make_module_description()
27252792
txt += self.make_module_group_check()
2793+
txt += self.make_module_deppaths()
27262794
txt += self.make_module_dep()
27272795
txt += self.make_module_extend_modpath()
27282796
txt += self.make_module_req()
@@ -3049,30 +3117,14 @@ def run_all_steps(self, run_test_cases):
30493117
if ignore_locks:
30503118
self.log.info("Ignoring locks...")
30513119
else:
3052-
locks_dir = build_option('locks_dir') or os.path.join(install_path('software'), '.locks')
3053-
lock_path = os.path.join(locks_dir, '%s.lock' % self.installdir.replace('/', '_'))
3054-
3055-
# if lock already exists, either abort or wait until it disappears
3056-
if os.path.exists(lock_path):
3057-
wait_on_lock = build_option('wait_on_lock')
3058-
if wait_on_lock:
3059-
while os.path.exists(lock_path):
3060-
print_msg("lock %s exists, waiting %d seconds..." % (lock_path, wait_on_lock),
3061-
silent=self.silent)
3062-
time.sleep(wait_on_lock)
3063-
else:
3064-
raise EasyBuildError("Lock %s already exists, aborting!", lock_path)
3120+
lock_name = self.installdir.replace('/', '_')
30653121

3066-
# create lock to avoid that another installation running in parallel messes things up;
3067-
# we use a directory as a lock, since that's atomically created
3068-
try:
3069-
mkdir(lock_path, parents=True)
3070-
except EasyBuildError as err:
3071-
# clean up the error message a bit, get rid of the "Failed to create directory" part + quotes
3072-
stripped_err = str(err).split(':', 1)[1].strip().replace("'", '').replace('"', '')
3073-
raise EasyBuildError("Failed to create lock %s: %s", lock_path, stripped_err)
3122+
# check if lock already exists;
3123+
# either aborts with an error or waits until it disappears (depends on --wait-on-lock)
3124+
check_lock(lock_name)
30743125

3075-
self.log.info("Lock created: %s", lock_path)
3126+
# create lock to avoid that another installation running in parallel messes things up
3127+
create_lock(lock_name)
30763128

30773129
try:
30783130
for (step_name, descr, step_methods, skippable) in steps:
@@ -3090,8 +3142,7 @@ def run_all_steps(self, run_test_cases):
30903142
pass
30913143
finally:
30923144
if not ignore_locks:
3093-
remove_dir(lock_path)
3094-
self.log.info("Lock removed: %s", lock_path)
3145+
remove_lock(lock_name)
30953146

30963147
# return True for successfull build (or stopped build)
30973148
return True

easybuild/framework/easyconfig/constants.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,15 @@
5151
'OS_VERSION': (get_os_version(), "System version"),
5252
'SYS_PYTHON_VERSION': (platform.python_version(), "System Python version (platform.python_version())"),
5353
'SYSTEM': ({'name': 'system', 'version': 'system'}, "System toolchain"),
54+
55+
'OS_PKG_IBVERBS_DEV': (('libibverbs-dev', 'libibverbs-devel', 'rdma-core-devel'),
56+
"OS packages providing ibverbs/infiniband development support"),
57+
'OS_PKG_OPENSSL_BIN': (('openssl'),
58+
"OS packages providing the openSSL binary"),
59+
'OS_PKG_OPENSSL_LIB': (('libssl', 'libopenssl'),
60+
"OS packages providing openSSL libraries"),
61+
'OS_PKG_OPENSSL_DEV': (('openssl-devel', 'libssl-dev', 'libopenssl-devel'),
62+
"OS packages providing openSSL developement support"),
63+
'OS_PKG_PAM_DEV': (('pam-devel', 'libpam0g-dev'),
64+
"OS packages providing Pluggable Authentication Module (PAM) developement support"),
5465
}

0 commit comments

Comments
 (0)