Skip to content

Commit e5ba343

Browse files
authored
Merge pull request #4953 from easybuilders/5.1.x
release EasyBuild v5.1.1
2 parents 8b79eb8 + e3efa8b commit e5ba343

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

+676
-233
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: 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://docs.easybuild.io/release-notes .
55

66

7+
v5.1.1 (6 July 2025)
8+
--------------------
9+
10+
update/bugfix release
11+
12+
- enhancements:
13+
- don't allow using `--pr-commit-msg` when only adding new files with `--new-pr` (unless `--force` is used) (#4498)
14+
- update `install-EasyBuild-develop.sh` script to allow installation without forking (#4899)
15+
- add blue, cyan, purple as known colors that can be used in colorize function (#4907)
16+
- trigger `post_run_shell_cmd_hook` before raising error if shell command failed + pass down full `RunShellCmdResult` instance (#4908)
17+
- also pass value of `fail_on_error` and `hidden` options of `run_shell_cmd` call down to pre/post `run_shell_cmd` hook (#4911)
18+
- add configuration option to ignore unversioned (0.0.0) Python packages (#4912)
19+
- add `required` option to `EasyConfig.get_cuda_cc_template_value` method (#4913)
20+
- add total iteration count to trace output (#4919, #4952)
21+
- add support for pre/post `easyblock` hook (#4923, #4938)
22+
- catch `HTTPException` when doing GitHub API request (#4926)
23+
- bug fixes:
24+
- fix reporting of method name in `EasyBlock.run_step` (#4920)
25+
- fix handling of broken symlinks in `filetools.remove` (#4921)
26+
- keep symlinks when copying build dirs of failed installations (#4933)
27+
- also copy patches of extensions to easybuild subdirectory of installation directory (#4939, #4946)
28+
- also copy easyblocks of bundle components to reprod dir (#4944)
29+
- take into account that forked repository may not have same name as origin `--sync-pr-with-develop` (#4947)
30+
- enhancements and fixes for test suite:
31+
- add support for passing valid unittest options to test suite (#3790)
32+
- enhance `LooseVersion` test to add cases with leading alphabetical characters (#4901)
33+
- prefer using `importlib.metadata` over the deprecated `pkg_resources` in `det_pypkg_version` + switch container tests to Rocky Linux 8.10 (#4904)
34+
- fix `TypeError` for Python version comparison in `test_fetch_sources_git` (#4934)
35+
- fix broken tests for GitHub integration features (#4954)
36+
- other changes:
37+
- change value of architectures constants RISCV32 and RISCV64 (#4915)
38+
- remove some superflous Python 2 checks (#4917)
39+
- remove use of unsupported `source` key in patch spec (#4924)
40+
- remove super() arguments from mk_tmpl_easyblock_for (#4936)
41+
42+
743
v5.1.0 (26 May 2025)
844
--------------------
945

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: 48 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 = -1
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,8 @@ 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+
self.iter_cnt = self.det_iter_cnt()
4797+
steps = self.get_steps(run_test_cases=run_test_cases, iteration_count=self.iter_cnt)
47984798

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

49294929
def operation(src, dest):
4930-
copy_dir(src, dest, dirs_exist_ok=True)
4930+
copy_dir(src, dest, dirs_exist_ok=True, symlinks=True)
49314931

49324932
operation_args.append((operation, [app.builddir], build_dirs_path, msg))
49334933

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

5004+
hooks = load_hooks(build_option('hooks'))
5005+
50045006
# build easyconfig
50055007
error_msg = '(no error)'
50065008
exit_code = None
@@ -5022,6 +5024,8 @@ def build_and_install_one(ecdict, init_env):
50225024
else:
50235025
enabled_write_permissions = False
50245026

5027+
run_hook(EASYBLOCK, hooks, pre_step_hook=True, args=[app])
5028+
50255029
result = app.run_all_steps(run_test_cases=run_test_cases)
50265030

50275031
if not dry_run:
@@ -5033,7 +5037,9 @@ def build_and_install_one(ecdict, init_env):
50335037
except EasyBuildError as err:
50345038
_log.warning("Failed to create build environment dump for easyconfig %s: %s", reprod_spec, err)
50355039

5036-
# also add any extension easyblocks used during the build for reproducibility
5040+
# also add any component/extension easyblocks used during the build for reproducibility
5041+
if hasattr(app, 'comp_instances'):
5042+
copy_easyblocks_for_reprod([comp for cfg, comp in app.comp_instances], reprod_dir)
50375043
if app.ext_instances:
50385044
copy_easyblocks_for_reprod(app.ext_instances, reprod_dir)
50395045
# If not already done remove the granted write permissions if we did so
@@ -5120,8 +5126,12 @@ def ensure_writable_log_dir(log_dir):
51205126
block = det_full_ec_version(app.cfg) + ".block"
51215127
repo.add_easyconfig(ecdict['original_spec'], app.name, block, buildstats, currentbuildstats)
51225128
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)
5129+
patches = app.patches
5130+
for ext in app.exts:
5131+
patches += ext.get('patches', [])
5132+
for patch in patches:
5133+
if 'path' in patch:
5134+
repo.add_patch(patch['path'], app.name)
51255135
repo.commit("Built %s" % app.full_mod_name)
51265136
del repo
51275137
except EasyBuildError as err:
@@ -5143,10 +5153,14 @@ def ensure_writable_log_dir(log_dir):
51435153
_log.debug("Copied easyconfig file %s to %s", spec, newspec)
51445154

51455155
# 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)
5156+
patches = app.patches
5157+
for ext in app.exts:
5158+
patches += ext.get('patches', [])
5159+
for patch in patches:
5160+
if 'path' in patch:
5161+
target = os.path.join(new_log_dir, os.path.basename(patch['path']))
5162+
copy_file(patch['path'], target)
5163+
_log.debug("Copied patch %s to %s", patch['path'], target)
51505164

51515165
if build_option('read_only_installdir') and not app.cfg['stop']:
51525166
# take away user write permissions (again)
@@ -5200,6 +5214,8 @@ def ensure_writable_log_dir(log_dir):
52005214
if not success:
52015215
copy_build_dirs_logs_failed_install(application_log, silent, app, ecdict['ec'])
52025216

5217+
run_hook(EASYBLOCK, hooks, post_step_hook=True, args=[app])
5218+
52035219
del app
52045220

52055221
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()

easybuild/scripts/install-EasyBuild-develop.sh

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ print_usage()
1313
echo "Usage: $0 <github_username> <install_dir>"
1414
echo
1515
echo " github_username: username on GitHub for which the EasyBuild repositories should be cloned"
16+
echo " Use 'easybuilders' if you don't want to use a fork"
1617
echo
1718
echo " install_dir: directory where all the EasyBuild files will be installed"
1819
echo
@@ -28,11 +29,13 @@ github_clone_branch()
2829
echo "=== Cloning ${GITHUB_USERNAME}/${REPO} ..."
2930
git clone --branch "${BRANCH}" "[email protected]:${GITHUB_USERNAME}/${REPO}.git"
3031

31-
echo "=== Adding and fetching EasyBuilders GitHub repository @ easybuilders/${REPO} ..."
32-
cd "${REPO}"
33-
git remote add "github_easybuilders" "[email protected]:easybuilders/${REPO}.git"
34-
git fetch github_easybuilders
35-
git branch --set-upstream-to "github_easybuilders/${BRANCH}" "${BRANCH}"
32+
if [[ $GITHUB_USERNAME != "easybuilders" ]]; then
33+
echo "=== Adding and fetching EasyBuilders GitHub repository @ easybuilders/${REPO} ..."
34+
cd "${REPO}"
35+
git remote add "github_easybuilders" "[email protected]:easybuilders/${REPO}.git"
36+
git fetch github_easybuilders
37+
git branch --set-upstream-to "github_easybuilders/${BRANCH}" "${BRANCH}"
38+
fi
3639
}
3740

3841
# Print the content of the module

0 commit comments

Comments
 (0)