Skip to content

Commit 026b939

Browse files
committed
Merge branch 'develop' into finalpath-fix
2 parents 53f441f + c5019c1 commit 026b939

Some content is hidden

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

56 files changed

+556
-150
lines changed

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/base/frozendict.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def __repr__(self):
5252

5353
def __hash__(self):
5454
if self.__hash is None:
55-
self.__hash = reduce(operator.xor, map(hash, self.iteritems()), 0)
55+
self.__hash = reduce(operator.xor, map(hash, self.items()), 0)
5656

5757
return self.__hash
5858

easybuild/framework/easyblock.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ def __init__(self, ec, logfile=None):
188188

189189
# list of patch/source files, along with checksums
190190
self.patches = []
191+
self.all_patches_paths = set() # set of paths to all patches (including patches of extensions)
191192
self.src = []
192193
self.data_src = []
193194
self.checksums = []
@@ -311,6 +312,9 @@ def __init__(self, ec, logfile=None):
311312
# initialize logger
312313
self._init_log()
313314

315+
# number of iterations
316+
self.iter_cnt = -1
317+
314318
# try and use the specified group (if any)
315319
group_name = build_option('group')
316320
group_spec = self.cfg['group']
@@ -598,6 +602,7 @@ def fetch_patches(self, patch_specs=None, extension=False, checksums=None):
598602
patch_info['path'] = path
599603
patch_info['checksum'] = self.get_checksum_for(checksums, filename=patch_info['name'], index=index)
600604

605+
self.all_patches_paths.add(path)
601606
if extension:
602607
patches.append(patch_info)
603608
else:
@@ -2387,7 +2392,8 @@ def handle_iterate_opts(self):
23872392
self.log.debug("Iterating opt %s: %s", opt, self.iter_opts[opt])
23882393

23892394
if self.iter_opts:
2390-
print_msg("starting iteration #%s ..." % self.iter_idx, log=self.log, silent=self.silent)
2395+
print_msg(f"starting iteration {self.iter_idx + 1}/{self.iter_cnt} ...", log=self.log,
2396+
silent=self.silent)
23912397
self.log.info("Current iteration index: %s", self.iter_idx)
23922398

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

24302436
# we need to take into account that builddependencies is always a list
24312437
# we're only iterating over it if it's a list of lists
2432-
builddeps = self.cfg['builddependencies']
2438+
builddeps = self.cfg.get_ref('builddependencies')
24332439
if all(isinstance(x, list) for x in builddeps):
24342440
iter_opt_counts.append(len(builddeps))
24352441

@@ -4789,7 +4795,8 @@ def run_all_steps(self, run_test_cases):
47894795
if self.cfg['stop'] == 'cfg':
47904796
return True
47914797

4792-
steps = self.get_steps(run_test_cases=run_test_cases, iteration_count=self.det_iter_cnt())
4798+
self.iter_cnt = self.det_iter_cnt()
4799+
steps = self.get_steps(run_test_cases=run_test_cases, iteration_count=self.iter_cnt)
47934800

47944801
# figure out how many steps will actually be run (not be skipped)
47954802
step_cnt = 0
@@ -4922,7 +4929,7 @@ def copy_build_dirs_logs_failed_install(application_log, silent, app, easyconfig
49224929
msg = f"Build directory of failed installation copied to {build_dirs_path}"
49234930

49244931
def operation(src, dest):
4925-
copy_dir(src, dest, dirs_exist_ok=True)
4932+
copy_dir(src, dest, dirs_exist_ok=True, symlinks=True)
49264933

49274934
operation_args.append((operation, [app.builddir], build_dirs_path, msg))
49284935

@@ -5032,7 +5039,9 @@ def build_and_install_one(ecdict, init_env):
50325039
except EasyBuildError as err:
50335040
_log.warning("Failed to create build environment dump for easyconfig %s: %s", reprod_spec, err)
50345041

5035-
# also add any extension easyblocks used during the build for reproducibility
5042+
# also add any component/extension easyblocks used during the build for reproducibility
5043+
if hasattr(app, 'comp_instances'):
5044+
copy_easyblocks_for_reprod([comp for cfg, comp in app.comp_instances], reprod_dir)
50365045
if app.ext_instances:
50375046
copy_easyblocks_for_reprod(app.ext_instances, reprod_dir)
50385047
# If not already done remove the granted write permissions if we did so
@@ -5119,15 +5128,13 @@ def ensure_writable_log_dir(log_dir):
51195128
block = det_full_ec_version(app.cfg) + ".block"
51205129
repo.add_easyconfig(ecdict['original_spec'], app.name, block, buildstats, currentbuildstats)
51215130
repo.add_easyconfig(spec, app.name, det_full_ec_version(app.cfg), buildstats, currentbuildstats)
5122-
for patch in app.patches:
5123-
repo.add_patch(patch['path'], app.name)
5131+
for patch_path in app.all_patches_paths:
5132+
repo.add_patch(patch_path, app.name)
51245133
repo.commit("Built %s" % app.full_mod_name)
51255134
del repo
51265135
except EasyBuildError as err:
51275136
_log.warning("Unable to commit easyconfig to repository: %s", err)
51285137

5129-
run_hook(EASYBLOCK, hooks, post_step_hook=True, args=[app])
5130-
51315138
# cleanup logs
51325139
app.close_log()
51335140

@@ -5144,10 +5151,10 @@ def ensure_writable_log_dir(log_dir):
51445151
_log.debug("Copied easyconfig file %s to %s", spec, newspec)
51455152

51465153
# copy patches
5147-
for patch in app.patches:
5148-
target = os.path.join(new_log_dir, os.path.basename(patch['path']))
5149-
copy_file(patch['path'], target)
5150-
_log.debug("Copied patch %s to %s", patch['path'], target)
5154+
for patch_path in app.all_patches_paths:
5155+
target = os.path.join(new_log_dir, os.path.basename(patch_path))
5156+
copy_file(patch_path, target)
5157+
_log.debug("Copied patch %s to %s", patch_path, target)
51515158

51525159
if build_option('read_only_installdir') and not app.cfg['stop']:
51535160
# take away user write permissions (again)
@@ -5201,6 +5208,8 @@ def ensure_writable_log_dir(log_dir):
52015208
if not success:
52025209
copy_build_dirs_logs_failed_install(application_log, silent, app, ecdict['ec'])
52035210

5211+
run_hook(EASYBLOCK, hooks, post_step_hook=True, args=[app])
5212+
52045213
del app
52055214

52065215
return (success, application_log, error_msg, exit_code)

easybuild/framework/easyconfig/default.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
'toolchainopts': [None, 'Extra options for compilers', TOOLCHAIN],
8585

8686
# BUILD easyconfig parameters
87+
'amdgcn_capabilities': [[], "List of AMDGCN capabilities to build with (if supported)", BUILD],
8788
'banned_linked_shared_libs': [[], "List of shared libraries (names, file names, or paths) which are not allowed "
8889
"to be linked in any installed binary/library", BUILD],
8990
'bitbucket_account': ['%(namelower)s', "Bitbucket account name to be used to resolve template values in source"

easybuild/framework/easyconfig/easyconfig.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1992,6 +1992,28 @@ def get_cuda_cc_template_value(self, key, required=True):
19921992
error_msg = "%s is not a template value based on --cuda-compute-capabilities/cuda_compute_capabilities"
19931993
raise EasyBuildError(error_msg, key)
19941994

1995+
def get_amdgcn_cc_template_value(self, key, required=True):
1996+
"""
1997+
Get template value based on --amdgcn-capabilities EasyBuild configuration option
1998+
and amdgcn_capabilities easyconfig parameter.
1999+
Returns user-friendly error message in case neither are defined,
2000+
or if an unknown key is used.
2001+
"""
2002+
if key.startswith('amdgcn_') and any(x == key for x in TEMPLATE_NAMES_DYNAMIC):
2003+
try:
2004+
return self.template_values[key]
2005+
except KeyError:
2006+
if not required:
2007+
self.log.debug(f'Key {key} not found in template values, returning empty value')
2008+
return ''
2009+
error_msg = "Template value '%s' is not defined!\n"
2010+
error_msg += "Make sure that either the --amdgcn-capabilities EasyBuild configuration "
2011+
error_msg += "option is set, or that the amdgcn_capabilities easyconfig parameter is defined."
2012+
raise EasyBuildError(error_msg, key)
2013+
else:
2014+
error_msg = "%s is not a template value based on --amdgcn-capabilities/amdgcn_capabilities"
2015+
raise EasyBuildError(error_msg, key)
2016+
19952017

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

easybuild/framework/easyconfig/templates.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@
8888
# template values which are only generated dynamically
8989
TEMPLATE_NAMES_DYNAMIC = {
9090
'arch': 'System architecture (e.g. x86_64, aarch64, ppc64le, ...)',
91+
'amdgcn_capabilities': "Comma-separated list of AMDGCN capabilities, as specified via "
92+
"--amdgcn-capabilities configuration option or "
93+
"via amdgcn_capabilities easyconfig parameter",
94+
'amdgcn_cc_space_sep': "Space-separated list of AMDGCN capabilities",
95+
'amdgcn_cc_semicolon_sep': "Semicolon-separated list of AMDGCN capabilities",
9196
'cuda_compute_capabilities': "Comma-separated list of CUDA compute capabilities, as specified via "
9297
"--cuda-compute-capabilities configuration option or "
9398
"via cuda_compute_capabilities easyconfig parameter",
@@ -478,6 +483,14 @@ def template_constant_dict(config, ignore=None, toolchain=None):
478483
template_values['cuda_sm_comma_sep'] = ','.join(sm_values)
479484
template_values['cuda_sm_space_sep'] = ' '.join(sm_values)
480485

486+
# step 7. AMDGCN capabilities
487+
# Use the commandline / easybuild config option if given, else use the value from the EC (as a default)
488+
amdgcn_cc = build_option('amdgcn_capabilities') or config.get('amdgcn_capabilities')
489+
if amdgcn_cc:
490+
template_values['amdgcn_capabilities'] = ','.join(amdgcn_cc)
491+
template_values['amdgcn_cc_space_sep'] = ' '.join(amdgcn_cc)
492+
template_values['amdgcn_cc_semicolon_sep'] = ';'.join(amdgcn_cc)
493+
481494
unknown_names = []
482495
for key in template_values:
483496
if not (key in common_template_names or key in TEMPLATE_NAMES_DYNAMIC):

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

easybuild/scripts/mk_tmpl_easyblock_for.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ class %(class_name)s(%(parent)s):
131131
132132
def __init__(self, *args, **kwargs):
133133
\"\"\"Initialisation of custom class variables for %(name)s.\"\"\"
134-
super(%(class_name)s, self).__init__(*args, **kwargs)
134+
super().__init__(*args, **kwargs)
135135
136136
self.example = None
137137
@@ -154,7 +154,7 @@ def configure_step(self):
154154
run_shell_cmd(cmd)
155155
156156
# complete configuration with configure_method of parent
157-
super(%(class_name)s, self).configure_step()
157+
super().configure_step()
158158
159159
def build_step(self):
160160
\"\"\"Custom build procedure for %(name)s.\"\"\"
@@ -191,12 +191,12 @@ def sanity_check_step(self):
191191
'dirs': ['dir1', 'dir2'],
192192
}
193193
194-
super(%(class_name)s, self).sanity_check_step(custom_paths=custom_paths)
194+
super().sanity_check_step(custom_paths=custom_paths)
195195
196196
def make_module_req_guess(self):
197197
\"\"\"Custom guesses for environment variables (PATH, ...) for %(name)s.\"\"\"
198198
199-
guesses = super(%(class_name)s, self).make_module_req_guess()
199+
guesses = super().make_module_req_guess()
200200
201201
guesses.update({
202202
'VARIABLE': ['value1', 'value2'],
@@ -207,7 +207,7 @@ def make_module_req_guess(self):
207207
def make_module_extra(self):
208208
\"\"\"Custom extra module file entries for %(name)s.\"\"\"
209209
210-
txt = super(%(class_name)s, self).make_module_extra()
210+
txt = super().make_module_extra()
211211
212212
txt += self.module_generator.set_environment("VARIABLE", 'value')
213213
txt += self.module_generator.prepend_paths("PATH_VAR", ['path1', 'path2'])

easybuild/tools/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX):
228228
BUILD_OPTIONS_CMDLINE = {
229229
None: [
230230
'aggregate_regtest',
231+
'amdgcn_capabilities',
231232
'backup_modules',
232233
'banned_linked_shared_libs',
233234
'checksum_priority',

easybuild/tools/github.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,14 @@ def _easyconfigs_pr_common(paths, ecs, start_branch=None, pr_branch=None, start_
11271127

11281128
# figure out commit message to use
11291129
if commit_msg:
1130+
if pr_target_repo == GITHUB_EASYCONFIGS_REPO and all(file_info['new']) and not paths['files_to_delete']:
1131+
msg = "When only adding new easyconfigs usually a PR commit msg (--pr-commit-msg) should not be used, as "
1132+
msg += "the PR title will be automatically generated."
1133+
if build_option('force'):
1134+
print_msg(msg)
1135+
print_msg("Using the specified --pr-commit-msg as the force build option was specified.")
1136+
else:
1137+
raise EasyBuildError(msg)
11301138
cnt = len(file_info['paths_in_repo'])
11311139
_log.debug("Using specified commit message for all %d new/modified files at once: %s", cnt, commit_msg)
11321140
elif pr_target_repo == GITHUB_EASYCONFIGS_REPO and all(file_info['new']) and not paths['files_to_delete']:
@@ -2167,7 +2175,7 @@ def new_pr(paths, ecs, title=None, descr=None, commit_msg=None):
21672175
pr_metadata=(file_info, deleted_paths, diff_stat), commit_msg=commit_msg)
21682176

21692177

2170-
def det_account_branch_for_pr(pr_id, github_user=None, pr_target_repo=None):
2178+
def det_account_repo_branch_for_pr(pr_id, github_user=None, pr_target_repo=None):
21712179
"""Determine account & branch corresponding to pull request with specified id."""
21722180

21732181
if github_user is None:
@@ -2184,10 +2192,19 @@ def det_account_branch_for_pr(pr_id, github_user=None, pr_target_repo=None):
21842192

21852193
# branch that corresponds with PR is supplied in form <account>:<branch_label>
21862194
account = pr_data['head']['label'].split(':')[0]
2195+
repo = pr_data['head']['repo']['name']
21872196
branch = ':'.join(pr_data['head']['label'].split(':')[1:])
21882197
github_target = '%s/%s' % (pr_target_account, pr_target_repo)
21892198
print_msg("Determined branch name corresponding to %s PR #%s: %s" % (github_target, pr_id, branch), log=_log)
21902199

2200+
return account, repo, branch
2201+
2202+
2203+
def det_account_branch_for_pr(pr_id, github_user=None, pr_target_repo=None):
2204+
"""Deprecated version of `det_account_repo_branch_for_pr`"""
2205+
_log.deprecated("`det_account_branch_for_pr` is deprecated, use `det_account_repo_branch_for_pr` instead", "6.0")
2206+
account, _, branch = det_account_repo_branch_for_pr(pr_id, github_user=github_user, pr_target_repo=pr_target_repo)
2207+
21912208
return account, branch
21922209

21932210

@@ -2285,7 +2302,7 @@ def update_pr(pr_id, paths, ecs, commit_msg=None):
22852302
exit_code=EasyBuildExit.OPTION_ERROR
22862303
)
22872304

2288-
github_account, branch_name = det_account_branch_for_pr(pr_id, pr_target_repo=pr_target_repo)
2305+
github_account, _, branch_name = det_account_repo_branch_for_pr(pr_id, pr_target_repo=pr_target_repo)
22892306

22902307
update_branch(branch_name, paths, ecs, github_account=github_account, commit_msg=commit_msg)
22912308

@@ -2877,18 +2894,18 @@ def sync_pr_with_develop(pr_id):
28772894
target_account = build_option('pr_target_account')
28782895
target_repo = build_option('pr_target_repo') or GITHUB_EASYCONFIGS_REPO
28792896

2880-
pr_account, pr_branch = det_account_branch_for_pr(pr_id)
2897+
pr_account, pr_repo, pr_branch = det_account_repo_branch_for_pr(pr_id)
28812898

28822899
# initialize repository
28832900
git_working_dir = tempfile.mkdtemp(prefix='git-working-dir')
28842901
git_repo = init_repo(git_working_dir, target_repo)
28852902

2886-
setup_repo(git_repo, pr_account, target_repo, pr_branch)
2903+
setup_repo(git_repo, pr_account, pr_repo, pr_branch)
28872904

28882905
sync_with_develop(git_repo, pr_branch, target_account, target_repo)
28892906

28902907
# push updated branch back to GitHub (unless we're doing a dry run)
2891-
return push_branch_to_github(git_repo, pr_account, target_repo, pr_branch)
2908+
return push_branch_to_github(git_repo, pr_account, pr_repo, pr_branch)
28922909

28932910

28942911
def sync_branch_with_develop(branch_name):

0 commit comments

Comments
 (0)