Skip to content

Commit aca4901

Browse files
committed
Merge branch 'develop' into looseversion-test-alphabetical-prefix
2 parents 039d19f + 1210ac8 commit aca4901

Some content is hidden

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

63 files changed

+628
-222
lines changed

.github/workflows/container_tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ jobs:
9797
# see https://docs.easybuild.io/en/latest/Containers.html
9898
curl -OL https://raw.githubusercontent.com/easybuilders/easybuild-easyconfigs/develop/easybuild/easyconfigs/b/bzip2/bzip2-1.0.8.eb
9999
export EASYBUILD_CONTAINERPATH=$PWD
100-
export EASYBUILD_CONTAINER_CONFIG='bootstrap=docker,from=ghcr.io/easybuilders/centos-7.9-python3-amd64'
100+
export EASYBUILD_CONTAINER_CONFIG='bootstrap=docker,from=ghcr.io/easybuilders/rockylinux-8.10-amd64'
101101
eb bzip2-1.0.8.eb --containerize --experimental --container-build-image
102102
singularity exec bzip2-1.0.8.sif command -v bzip2 | grep '/app/software/bzip2/1.0.8/bin/bzip2' || (echo "Path to bzip2 '$which_bzip2' is not correct" && exit 1)
103103
singularity exec bzip2-1.0.8.sif bzip2 --help

.github/workflows/container_tests_apptainer.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ jobs:
9191
# see https://docs.easybuild.io/en/latest/Containers.html
9292
curl -OL https://raw.githubusercontent.com/easybuilders/easybuild-easyconfigs/develop/easybuild/easyconfigs/b/bzip2/bzip2-1.0.8.eb
9393
export EASYBUILD_CONTAINERPATH=$PWD
94-
export EASYBUILD_CONTAINER_CONFIG='bootstrap=docker,from=ghcr.io/easybuilders/centos-7.9-python3-amd64'
94+
export EASYBUILD_CONTAINER_CONFIG='bootstrap=docker,from=ghcr.io/easybuilders/rockylinux-8.10-amd64'
9595
export EASYBUILD_CONTAINER_TYPE='apptainer'
9696
eb bzip2-1.0.8.eb --containerize --experimental --container-build-image
9797
apptainer exec bzip2-1.0.8.sif command -v bzip2 | grep '/app/software/bzip2/1.0.8/bin/bzip2' || (echo "Path to bzip2 '$which_bzip2' is not correct" && exit 1)

RELEASE_NOTES

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,62 @@ For more detailed information, please see the git log.
44
These release notes can also be consulted at https://docs.easybuild.io/release-notes .
55

66

7+
v5.1.0 (26 May 2025)
8+
--------------------
9+
10+
feature release
11+
12+
- various enhancements, including:
13+
- add support for data installations (#4474, #4873, #4874)
14+
- allow specifying location for RPATH wrapper scripts via `rpath_wrappers_dir` (#4596)
15+
- add a CUDA device code sanity check (#4692)
16+
- add support for check_readelf_rpath easyconfig parameter to optionally skip RPATH checks (#4768)
17+
- add support for using environment variables in value used in modextravars (#4855)
18+
- print summary after the build in trace output (#4861, #4875)
19+
- avoid leaking keys by mistake with `--upload-test-report` (#4877)
20+
- obtain PR/commit diff via GitHub API rather than downloading `*.diff` file via github.com (#4878)
21+
- support options for patch command (#4886)
22+
- replace full trace message for extension check command with simple pass/fail message (#4892)
23+
- various bug fixes, including:
24+
- also pass `rpath_include_dirs` when preparing build environment for extensions (#4596)
25+
- fix `check_checksums` when `nosource: True` is used (#4806)
26+
- fix help string of findPythonDeps.py script (#4821)
27+
- take into account `job-output-dir` option in Slurm job backend (#4842)
28+
- fix unbound variable in error case in `build_and_install_software` (#4843)
29+
- avoid failure when only some passed easyconfigs exist (#4847)
30+
- restore original value for non-list easyconfig parameter values that are considered for iterating over (#4848)
31+
- add '-' before 'DMKL_ILP64' in $CFLAGS (#4850)
32+
- enhance RPATH sanity check to skip anything whose absolute path resolves to outside the install dir (#4854)
33+
- use new `ModEnvVarType.STRICT_PATH_WITH_FILES` with `CMAKE_LIBRARY_PATH` environment variable (#4858)
34+
- fix `is_patch_for` for patch dicts (#4865)
35+
- update fake module for each extension installed (#4868, #4888, #4895)
36+
- implement exponential backoff in `download_file` (#4870, #4880)
37+
- use `develop` branch for PRs that target removed `5.0.x` branch in `fetch_files_from_pr` (#4879)
38+
- also ignore errors raised during test step when `--ignore-test-failure` is used (#4881)
39+
- fix download progress bar (#4885)
40+
- fix `--dep-graph` by using `graphviz` Python package (#4891)
41+
- fixes for test suite:
42+
- fix `test_github_preview_pr` which got broken because there's no more easyconfigs for bzip2 1.0.6 in easyconfigs repo (#4827)
43+
- fix tests after removal of 5.0.x branch (#4830)
44+
- fix missed message in trivial equal-asserts (#4831)
45+
- fix wrong variable name used for Python version in linting CI (#4839)
46+
- fix failing GitHub integration CI tests (#4841)
47+
- fix testsuite badge in README (#4845)
48+
- other changes:
49+
- go back to using `develop` branch rather than `5.0.x` branch in GitHub Actions workflows (#4820)
50+
- remove source tarball for Python 3.7.2 from test sources (#4828)
51+
- delete .coveragerc (#4833)
52+
- remove Python 2 constructs (#4834)
53+
- remove superflous assignment in `_sanity_check_step` (#4851)
54+
- replace deprecation warning about reproducible tarballs on Python older than 3.9 with a regular warning (#4852)
55+
- move `EasyBlock.expand_module_search_path` into `ModuleEnvironmentVariable.expand_paths` (#4859)
56+
57+
758
v5.0.0 (18 March 2025)
859
----------------------
960

61+
major release (includes breaking changes)
62+
1063
- remove support for Python 2.7 and 3.5 (#4229, #4270, #4306, #4473, #4477, #4476, #4478, #4524, #4607, #4756, #4810, #4811)
1164
- also run unit test suite with Python 3.12 + 3.13 (#4484, #4674)
1265
- changed defaults in EasyBuild configuration:

easybuild/_deprecated.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"""
3232
import contextlib
3333
import functools
34+
import json
3435
import os
3536
import re
3637
import signal
@@ -96,6 +97,12 @@ def cache_aware_func(cmd, *args, **kwargs):
9697
return cache_aware_func
9798

9899

100+
def json_loads(body):
101+
"""Deprecated wrapper for json.loads"""
102+
_log.deprecated("json_loads is deprecated, use json.loads", '6.0')
103+
return json.loads(body)
104+
105+
99106
def get_output_from_process(proc, read_size=None, asynchronous=False, print_deprecation_warning=True):
100107
"""
101108
Get output from running process (that was opened with subprocess.Popen).

easybuild/base/fancylogger.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -578,8 +578,7 @@ def logToFile(filename, enable=True, filehandler=None, name=None, max_bytes=MAX_
578578
'maxBytes': max_bytes,
579579
'backupCount': backup_count,
580580
}
581-
if sys.version_info[0] >= 3:
582-
handleropts['encoding'] = 'utf-8'
581+
handleropts['encoding'] = 'utf-8'
583582
# logging to a file is going to create the file later on, so let's try to be helpful and create the path if needed
584583
directory = os.path.dirname(filename)
585584
if not os.path.exists(directory):

easybuild/framework/easyblock.py

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,9 @@
9898
from easybuild.tools.filetools import is_sha256_checksum, mkdir, move_file, move_logs, read_file, remove_dir
9999
from easybuild.tools.filetools import remove_file, remove_lock, symlink, verify_checksum, weld_paths, write_file
100100
from easybuild.tools.hooks import (
101-
BUILD_STEP, CLEANUP_STEP, CONFIGURE_STEP, EXTENSIONS_STEP, EXTRACT_STEP, FETCH_STEP, INSTALL_STEP, MODULE_STEP,
102-
MODULE_WRITE, PACKAGE_STEP, PATCH_STEP, PERMISSIONS_STEP, POSTITER_STEP, POSTPROC_STEP, PREPARE_STEP, READY_STEP,
103-
SANITYCHECK_STEP, SINGLE_EXTENSION, TEST_STEP, TESTCASES_STEP, load_hooks, run_hook,
101+
BUILD_STEP, CLEANUP_STEP, CONFIGURE_STEP, EASYBLOCK, EXTENSIONS_STEP, EXTRACT_STEP, FETCH_STEP, INSTALL_STEP,
102+
MODULE_STEP, MODULE_WRITE, PACKAGE_STEP, PATCH_STEP, PERMISSIONS_STEP, POSTITER_STEP, POSTPROC_STEP, PREPARE_STEP,
103+
READY_STEP, SANITYCHECK_STEP, SINGLE_EXTENSION, TEST_STEP, TESTCASES_STEP, load_hooks, run_hook,
104104
)
105105
from easybuild.tools.run import RunShellCmdError, raise_run_shell_cmd_error, run_shell_cmd
106106
from easybuild.tools.jenkins import write_to_xml
@@ -311,6 +311,9 @@ def __init__(self, ec, logfile=None):
311311
# initialize logger
312312
self._init_log()
313313

314+
# number of iterations
315+
self.iter_cnt = self.det_iter_cnt()
316+
314317
# try and use the specified group (if any)
315318
group_name = build_option('group')
316319
group_spec = self.cfg['group']
@@ -448,7 +451,7 @@ def get_checksum_for(self, checksums, filename=None, index=None):
448451
if checksum and chksum_input_git is not None:
449452
# ignore any checksum for given filename due to changes in https://github.com/python/cpython/issues/90021
450453
# tarballs made for git repos are not reproducible when created with Python < 3.9
451-
if sys.version_info[0] >= 3 and sys.version_info[1] < 9:
454+
if sys.version_info < (3, 9):
452455
print_warning(
453456
"Reproducible tarballs of Git repos are only possible when using Python 3.9+ to run EasyBuild. "
454457
f"Skipping checksum verification of {chksum_input} since Python < 3.9 is used."
@@ -2387,7 +2390,8 @@ def handle_iterate_opts(self):
23872390
self.log.debug("Iterating opt %s: %s", opt, self.iter_opts[opt])
23882391

23892392
if self.iter_opts:
2390-
print_msg("starting iteration #%s ..." % self.iter_idx, log=self.log, silent=self.silent)
2393+
print_msg(f"starting iteration {self.iter_idx + 1}/{self.iter_cnt} ...", log=self.log,
2394+
silent=self.silent)
23912395
self.log.info("Current iteration index: %s", self.iter_idx)
23922396

23932397
# pop first element from all iterative easyconfig parameters as next value to use
@@ -2429,7 +2433,7 @@ def det_iter_cnt(self):
24292433

24302434
# we need to take into account that builddependencies is always a list
24312435
# we're only iterating over it if it's a list of lists
2432-
builddeps = self.cfg['builddependencies']
2436+
builddeps = self.cfg.get_ref('builddependencies')
24332437
if all(isinstance(x, list) for x in builddeps):
24342438
iter_opt_counts.append(len(builddeps))
24352439

@@ -2830,8 +2834,6 @@ def patch_step(self, beginpath=None, patches=None):
28302834
self.log.info("Applying patch %s" % patch['name'])
28312835
trace_msg("applying patch %s" % patch['name'])
28322836

2833-
# patch source at specified index (first source if not specified)
2834-
srcind = patch.get('source', 0)
28352837
# if patch level is specified, use that (otherwise let apply_patch derive patch level)
28362838
level = patch.get('level', None)
28372839
# determine suffix of source path to apply patch in (if any)
@@ -2840,16 +2842,14 @@ def patch_step(self, beginpath=None, patches=None):
28402842
copy_patch = 'copy' in patch and 'sourcepath' not in patch
28412843
options = patch.get('opts', None) # Extra options for patch command
28422844

2843-
self.log.debug("Source index: %s; patch level: %s; source path suffix: %s; copy patch: %s; options: %s",
2844-
srcind, level, srcpathsuffix, copy_patch, options)
2845+
self.log.debug("Patch level: %s; source path suffix: %s; copy patch: %s; options: %s",
2846+
level, srcpathsuffix, copy_patch, options)
28452847

28462848
if beginpath is None:
2847-
try:
2848-
beginpath = self.src[srcind]['finalpath']
2849-
self.log.debug("Determine begin path for patch %s: %s" % (patch['name'], beginpath))
2850-
except IndexError as err:
2851-
raise EasyBuildError("Can't apply patch %s to source at index %s of list %s: %s",
2852-
patch['name'], srcind, self.src, err)
2849+
if not self.src:
2850+
raise EasyBuildError("Can't apply patch %s to source if no sources are given", patch['name'])
2851+
beginpath = self.src[0]['finalpath']
2852+
self.log.debug("Determined begin path for patch %s: %s" % (patch['name'], beginpath))
28532853
else:
28542854
self.log.debug("Using specified begin path for patch %s: %s" % (patch['name'], beginpath))
28552855

@@ -4662,18 +4662,19 @@ def run_step(self, step, step_methods):
46624662
run_hook(step, self.hooks, pre_step_hook=True, args=[self])
46634663

46644664
for step_method in step_methods:
4665+
# step_method is a lambda function that takes an EasyBlock instance as an argument,
4666+
# and returns the actual method
4667+
current_method = step_method(self)
46654668
# Remove leading underscore from e.g. "_test_step"
4666-
method_name = '_'.join(step_method.__code__.co_names).lstrip('_')
4669+
method_name = current_method.__name__.lstrip('_')
46674670
self.log.info("Running method %s part of step %s", method_name, step)
46684671

46694672
if self.dry_run:
46704673
self.dry_run_msg("[%s method]", method_name)
46714674

46724675
# if an known possible error occurs, just report it and continue
46734676
try:
4674-
# step_method is a lambda function that takes an EasyBlock instance as an argument,
4675-
# and returns the actual method, so use () to execute it
4676-
step_method(self)()
4677+
current_method()
46774678
except Exception as err:
46784679
if build_option('extended_dry_run_ignore_errors'):
46794680
dry_run_warning("ignoring error %s" % err, silent=self.silent)
@@ -4682,9 +4683,7 @@ def run_step(self, step, step_methods):
46824683
raise
46834684
self.dry_run_msg('')
46844685
else:
4685-
# step_method is a lambda function that takes an EasyBlock instance as an argument,
4686-
# and returns the actual method, so use () to execute it
4687-
step_method(self)()
4686+
current_method()
46884687

46894688
run_hook(step, self.hooks, post_step_hook=True, args=[self])
46904689

@@ -4794,7 +4793,7 @@ def run_all_steps(self, run_test_cases):
47944793
if self.cfg['stop'] == 'cfg':
47954794
return True
47964795

4797-
steps = self.get_steps(run_test_cases=run_test_cases, iteration_count=self.det_iter_cnt())
4796+
steps = self.get_steps(run_test_cases=run_test_cases, iteration_count=self.iter_cnt)
47984797

47994798
# figure out how many steps will actually be run (not be skipped)
48004799
step_cnt = 0
@@ -4927,7 +4926,7 @@ def copy_build_dirs_logs_failed_install(application_log, silent, app, easyconfig
49274926
msg = f"Build directory of failed installation copied to {build_dirs_path}"
49284927

49294928
def operation(src, dest):
4930-
copy_dir(src, dest, dirs_exist_ok=True)
4929+
copy_dir(src, dest, dirs_exist_ok=True, symlinks=True)
49314930

49324931
operation_args.append((operation, [app.builddir], build_dirs_path, msg))
49334932

@@ -5001,6 +5000,8 @@ def build_and_install_one(ecdict, init_env):
50015000
_log.debug("Skip set to %s" % skip)
50025001
app.cfg['skip'] = skip
50035002

5003+
hooks = load_hooks(build_option('hooks'))
5004+
50045005
# build easyconfig
50055006
error_msg = '(no error)'
50065007
exit_code = None
@@ -5022,6 +5023,8 @@ def build_and_install_one(ecdict, init_env):
50225023
else:
50235024
enabled_write_permissions = False
50245025

5026+
run_hook(EASYBLOCK, hooks, pre_step_hook=True, args=[app])
5027+
50255028
result = app.run_all_steps(run_test_cases=run_test_cases)
50265029

50275030
if not dry_run:
@@ -5033,7 +5036,9 @@ def build_and_install_one(ecdict, init_env):
50335036
except EasyBuildError as err:
50345037
_log.warning("Failed to create build environment dump for easyconfig %s: %s", reprod_spec, err)
50355038

5036-
# also add any extension easyblocks used during the build for reproducibility
5039+
# also add any component/extension easyblocks used during the build for reproducibility
5040+
if hasattr(app, 'comp_instances'):
5041+
copy_easyblocks_for_reprod([comp for cfg, comp in app.comp_instances], reprod_dir)
50375042
if app.ext_instances:
50385043
copy_easyblocks_for_reprod(app.ext_instances, reprod_dir)
50395044
# If not already done remove the granted write permissions if we did so
@@ -5120,8 +5125,12 @@ def ensure_writable_log_dir(log_dir):
51205125
block = det_full_ec_version(app.cfg) + ".block"
51215126
repo.add_easyconfig(ecdict['original_spec'], app.name, block, buildstats, currentbuildstats)
51225127
repo.add_easyconfig(spec, app.name, det_full_ec_version(app.cfg), buildstats, currentbuildstats)
5123-
for patch in app.patches:
5124-
repo.add_patch(patch['path'], app.name)
5128+
patches = app.patches
5129+
for ext in app.exts:
5130+
patches += ext.get('patches', [])
5131+
for patch in patches:
5132+
if 'path' in patch:
5133+
repo.add_patch(patch['path'], app.name)
51255134
repo.commit("Built %s" % app.full_mod_name)
51265135
del repo
51275136
except EasyBuildError as err:
@@ -5143,10 +5152,14 @@ def ensure_writable_log_dir(log_dir):
51435152
_log.debug("Copied easyconfig file %s to %s", spec, newspec)
51445153

51455154
# copy patches
5146-
for patch in app.patches:
5147-
target = os.path.join(new_log_dir, os.path.basename(patch['path']))
5148-
copy_file(patch['path'], target)
5149-
_log.debug("Copied patch %s to %s", patch['path'], target)
5155+
patches = app.patches
5156+
for ext in app.exts:
5157+
patches += ext.get('patches', [])
5158+
for patch in patches:
5159+
if 'path' in patch:
5160+
target = os.path.join(new_log_dir, os.path.basename(patch['path']))
5161+
copy_file(patch['path'], target)
5162+
_log.debug("Copied patch %s to %s", patch['path'], target)
51505163

51515164
if build_option('read_only_installdir') and not app.cfg['stop']:
51525165
# take away user write permissions (again)
@@ -5200,6 +5213,8 @@ def ensure_writable_log_dir(log_dir):
52005213
if not success:
52015214
copy_build_dirs_logs_failed_install(application_log, silent, app, ecdict['ec'])
52025215

5216+
run_hook(EASYBLOCK, hooks, post_step_hook=True, args=[app])
5217+
52035218
del app
52045219

52055220
return (success, application_log, error_msg, exit_code)

easybuild/framework/easyconfig/easyconfig.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1968,17 +1968,22 @@ def asdict(self):
19681968
res[key] = value
19691969
return res
19701970

1971-
def get_cuda_cc_template_value(self, key):
1971+
def get_cuda_cc_template_value(self, key, required=True):
19721972
"""
19731973
Get template value based on --cuda-compute-capabilities EasyBuild configuration option
19741974
and cuda_compute_capabilities easyconfig parameter.
19751975
Returns user-friendly error message in case neither are defined,
19761976
or if an unknown key is used.
1977+
1978+
:param required: If False and the key is not found, return an empty string instead of raising an error.
19771979
"""
19781980
if key.startswith('cuda_') and any(x == key for x in TEMPLATE_NAMES_DYNAMIC):
19791981
try:
19801982
return self.template_values[key]
19811983
except KeyError:
1984+
if not required:
1985+
self.log.debug(f'Key {key} not found in template values, returning empty value')
1986+
return ''
19821987
error_msg = "Template value '%s' is not defined!\n"
19831988
error_msg += "Make sure that either the --cuda-compute-capabilities EasyBuild configuration "
19841989
error_msg += "option is set, or that the cuda_compute_capabilities easyconfig parameter is defined."

easybuild/scripts/findPythonDeps.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,7 @@ def can_run(cmd, *arguments):
8585
def run_shell_cmd(arguments, action_desc, capture_stderr=True, **kwargs):
8686
"""Run the command and return the return code and output"""
8787
extra_args = kwargs or {}
88-
if sys.version_info[0] >= 3:
89-
extra_args['universal_newlines'] = True
88+
extra_args['universal_newlines'] = True
9089
stderr = subprocess.STDOUT if capture_stderr else subprocess.PIPE
9190
p = subprocess.Popen(arguments, stdout=subprocess.PIPE, stderr=stderr, **extra_args)
9291
out, err = p.communicate()

0 commit comments

Comments
 (0)