Skip to content

Commit f6b28bc

Browse files
authored
Merge pull request #4635 from easybuilders/4.9.x
release EasyBuild v4.9.3
2 parents 7a2f78f + 795c6ab commit f6b28bc

33 files changed

+482
-122
lines changed

.github/workflows/end2end.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ jobs:
1818
fail-fast: false
1919
container:
2020
image: ghcr.io/easybuilders/${{ matrix.container }}-amd64
21+
env: {ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true} # Allow using Node16 actions
2122
steps:
2223
- name: Check out the repo
2324
uses: actions/checkout@v3

.github/workflows/unit_tests.yml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,12 @@ jobs:
101101
# and are only run after the PR gets merged
102102
GITHUB_TOKEN: ${{secrets.CI_UNIT_TESTS_GITHUB_TOKEN}}
103103
run: |
104-
# only install GitHub token when testing with Lmod 8.x + Python 3.6 or 3.9, to avoid hitting GitHub rate limit;
104+
# only install GitHub token when testing with Lmod 8.x + Python 3.6 or 3.9, to avoid hitting GitHub rate limit
105105
# tests that require a GitHub token are skipped automatically when no GitHub token is available
106106
if [[ "${{matrix.modules_tool}}" =~ 'Lmod-8' ]] && [[ "${{matrix.python}}" =~ 3.[69] ]]; then
107107
if [ ! -z $GITHUB_TOKEN ]; then
108-
SET_KEYRING="import keyrings.alt.file; keyring.set_keyring(keyrings.alt.file.PlaintextKeyring())";
109-
python -c "import keyring; $SET_KEYRING; keyring.set_password('github_token', 'easybuild_test', '$GITHUB_TOKEN')";
108+
SET_KEYRING="import keyrings.alt.file; keyring.set_keyring(keyrings.alt.file.PlaintextKeyring())"
109+
python -c "import keyring; $SET_KEYRING; keyring.set_password('github_token', 'easybuild_test', '$GITHUB_TOKEN')"
110110
fi
111111
echo "GitHub token installed!"
112112
else
@@ -191,7 +191,17 @@ jobs:
191191
# run test suite
192192
python -O -m test.framework.suite 2>&1 | tee test_framework_suite.log
193193
# try and make sure output of running tests is clean (no printed messages/warnings)
194-
IGNORE_PATTERNS="no GitHub token available|skipping SvnRepository test|requires Lmod as modules tool|stty: 'standard input': Inappropriate ioctl for device|CryptographyDeprecationWarning: Python 3.[56]|from cryptography.* import |CryptographyDeprecationWarning: Python 2|Blowfish|GC3Pie not available, skipping test"
194+
IGNORE_PATTERNS="no GitHub token available"
195+
IGNORE_PATTERNS+="|skipping SvnRepository test"
196+
IGNORE_PATTERNS+="|requires Lmod as modules tool"
197+
IGNORE_PATTERNS+="|stty: 'standard input': Inappropriate ioctl for device"
198+
IGNORE_PATTERNS+="|CryptographyDeprecationWarning: Python 3.[56]"
199+
IGNORE_PATTERNS+="|from cryptography.* import "
200+
IGNORE_PATTERNS+="|CryptographyDeprecationWarning: Python 2"
201+
IGNORE_PATTERNS+="|Blowfish"
202+
IGNORE_PATTERNS+="|GC3Pie not available, skipping test"
203+
IGNORE_PATTERNS+="|CryptographyDeprecationWarning: TripleDES has been moved"
204+
IGNORE_PATTERNS+="|algorithms.TripleDES"
195205
# '|| true' is needed to avoid that GitHub Actions stops the job on non-zero exit of grep (i.e. when there are no matches)
196206
PRINTED_MSG=$(egrep -v "${IGNORE_PATTERNS}" test_framework_suite.log | grep '\.\n*[A-Za-z]' || true)
197207
test "x$PRINTED_MSG" = "x" || (echo "ERROR: Found printed messages in output of test suite" && echo "${PRINTED_MSG}" && exit 1)

.github/workflows/unit_tests_python2.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ jobs:
1616
# CentOS 7.9 container that already includes Lmod & co,
1717
# see https://github.com/easybuilders/easybuild-containers
1818
image: ghcr.io/easybuilders/centos-7.9-amd64
19+
env: {ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true} # Allow using Node16 actions
1920
steps:
2021
- uses: actions/checkout@v3
2122

RELEASE_NOTES

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,43 @@ 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.9.3 (14 September 2024)
8+
--------------------------
9+
10+
update/bugfix release
11+
12+
- various enhancements, including:
13+
- add support for `--extra-source-urls` to fetch sources from additional URLs (#4079)
14+
- add definition for gmpflf toolchain (#4566, #4571)
15+
- reuse pre-computed checksums (#4569)
16+
- add `cuda_cc_space_sep` variant that does not have periods (#4583)
17+
- add `--skip-sanity-check` option (#4590)
18+
- add `GNU_FTP_SOURCE` template constant (#4597)
19+
- improve error messages for empty easyconfigs (#4603)
20+
- improve help string for `--dep-graph` (#4610)
21+
- only call `_sanity_check_step_extensions` if `--skip-extensions` is not set (#4620)
22+
- add support for `--software-commit` and an associated template `%(software_commit)s` (#4628)
23+
- various bug fixes, including:
24+
- correctly evaluate result for `--dep-graph` (#4554)
25+
- fix fetch progress bar showing to many files (#4568)
26+
- resolve internal for imkl>=2021 version subdir via "latest" symlink (#4570)
27+
- fix typo in message about including an easyblock from a commit (#4575)
28+
- don't use special flags for `strict`, `precise`, `loose`, `veryloose` toolchain options on RISC-V (#4576)
29+
- fix help text for `cuda_compute_capabilities` template (#4589)
30+
- fix help message for `--http-headers-fields-urlpat` configuration option (#4594)
31+
- fix `test_compiler_cache` in case `gcc` is available multiple times (#4599)
32+
- handle post-install patches in check_checksums_for (#4605)
33+
- fix `copy_file` with a folder as the target (#4609)
34+
- allow for case where `homepage = None` when generating the docs (#4626)
35+
- fix test_github_det_commit_status by using more recent commits (#4636)
36+
- other changes:
37+
- clean up code that was only there to support Python 2.6 + avoid syntax warnings when parsing py2vs3/py.p2 with Python 3.x (#3788)
38+
- use Intel's oneAPI Fortran compiler by default for version 2024.0.0 and newer (`oneapi_fortran` toolchain option set to `True`) (#4567)
39+
- allow using Node 16 actions in CI (#4574)
40+
- remove a superflous check in `EasyBlock.run_all_steps` (#4623)
41+
- remove trailing dots from backup message produced by --inject-checksums (#4632)
42+
43+
744
v4.9.2 (12 June 2024)
845
---------------------
946

easybuild/base/generaloption.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,16 @@ def what_str_list_tuple(name):
9797
"""Given name, return separator, class and helptext wrt separator.
9898
(Currently supports strlist, strtuple, pathlist, pathtuple)
9999
"""
100-
sep = ','
101-
helpsep = 'comma'
102100
if name.startswith('path'):
103101
sep = os.pathsep
104102
helpsep = 'pathsep'
103+
elif name.startswith('url'):
104+
# | is one of the only characters not in the grammar for URIs (RFC3986)
105+
sep = '|'
106+
helpsep = '|'
107+
else:
108+
sep = ','
109+
helpsep = 'comma'
105110

106111
klass = None
107112
if name.endswith('list'):
@@ -182,6 +187,7 @@ class ExtOption(CompleterOption):
182187
- strlist, strtuple : convert comma-separated string in a list resp. tuple of strings
183188
- pathlist, pathtuple : using os.pathsep, convert pathsep-separated string in a list resp. tuple of strings
184189
- the path separator is OS-dependent
190+
- urllist, urltuple: convert string seperated by '|' to a list resp. tuple of strings
185191
"""
186192
EXTEND_SEPARATOR = ','
187193

@@ -198,7 +204,7 @@ class ExtOption(CompleterOption):
198204
TYPED_ACTIONS = Option.TYPED_ACTIONS + EXTOPTION_EXTRA_OPTIONS + EXTOPTION_STORE_OR
199205
ALWAYS_TYPED_ACTIONS = Option.ALWAYS_TYPED_ACTIONS + EXTOPTION_EXTRA_OPTIONS
200206

201-
TYPE_STRLIST = ['%s%s' % (name, klass) for klass in ['list', 'tuple'] for name in ['str', 'path']]
207+
TYPE_STRLIST = ['%s%s' % (name, klass) for klass in ['list', 'tuple'] for name in ['str', 'path', 'url']]
202208
TYPE_CHECKER = {x: check_str_list_tuple for x in TYPE_STRLIST}
203209
TYPE_CHECKER.update(Option.TYPE_CHECKER)
204210
TYPES = tuple(TYPE_STRLIST + list(Option.TYPES))

easybuild/framework/easyblock.py

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
from easybuild.tools.build_log import print_error, print_msg, print_warning
7373
from easybuild.tools.config import CHECKSUM_PRIORITY_JSON, DEFAULT_ENVVAR_USERS_MODULES
7474
from easybuild.tools.config import FORCE_DOWNLOAD_ALL, FORCE_DOWNLOAD_PATCHES, FORCE_DOWNLOAD_SOURCES
75+
from easybuild.tools.config import EASYBUILD_SOURCES_URL # noqa
7576
from easybuild.tools.config import build_option, build_path, get_log_filename, get_repository, get_repositorypath
7677
from easybuild.tools.config import install_path, log_path, package_path, source_paths
7778
from easybuild.tools.environment import restore_env, sanitize_env
@@ -105,9 +106,6 @@
105106
from easybuild.tools.utilities import remove_unwanted_chars, time2str, trace_msg
106107
from easybuild.tools.version import this_is_easybuild, VERBOSE_VERSION, VERSION
107108

108-
109-
EASYBUILD_SOURCES_URL = 'https://sources.easybuild.io'
110-
111109
DEFAULT_BIN_LIB_SUBDIRS = ('bin', 'lib', 'lib64')
112110

113111
MODULE_ONLY_STEPS = [MODULE_STEP, PREPARE_STEP, READY_STEP, POSTITER_STEP, SANITYCHECK_STEP]
@@ -674,14 +672,16 @@ def collect_exts_file_info(self, fetch_files=True, verify_checksums=True):
674672
src_fn = os.path.basename(src_path)
675673

676674
# report both MD5 and SHA256 checksums, since both are valid default checksum types
675+
src_checksums = {}
677676
for checksum_type in (CHECKSUM_TYPE_MD5, CHECKSUM_TYPE_SHA256):
678677
src_checksum = compute_checksum(src_path, checksum_type=checksum_type)
678+
src_checksums[checksum_type] = src_checksum
679679
self.log.info("%s checksum for %s: %s", checksum_type, src_path, src_checksum)
680680

681681
# verify checksum (if provided)
682682
self.log.debug('Verifying checksums for extension source...')
683683
fn_checksum = self.get_checksum_for(checksums, filename=src_fn, index=0)
684-
if verify_checksum(src_path, fn_checksum):
684+
if verify_checksum(src_path, fn_checksum, src_checksums):
685685
self.log.info('Checksum for extension source %s verified', src_fn)
686686
elif build_option('ignore_checksums'):
687687
print_warning("Ignoring failing checksum verification for %s" % src_fn)
@@ -700,12 +700,15 @@ def collect_exts_file_info(self, fetch_files=True, verify_checksums=True):
700700
ext_src.update({'patches': ext_patches})
701701

702702
if verify_checksums:
703+
computed_checksums = {}
703704
for patch in ext_patches:
704705
patch = patch['path']
706+
computed_checksums[patch] = {}
705707
# report both MD5 and SHA256 checksums,
706708
# since both are valid default checksum types
707709
for checksum_type in (CHECKSUM_TYPE_MD5, CHECKSUM_TYPE_SHA256):
708710
checksum = compute_checksum(patch, checksum_type=checksum_type)
711+
computed_checksums[patch][checksum_type] = checksum
709712
self.log.info("%s checksum for %s: %s", checksum_type, patch, checksum)
710713

711714
# verify checksum (if provided)
@@ -715,7 +718,7 @@ def collect_exts_file_info(self, fetch_files=True, verify_checksums=True):
715718
patch_fn = os.path.basename(patch)
716719

717720
checksum = self.get_checksum_for(checksums, filename=patch_fn, index=idx+1)
718-
if verify_checksum(patch, checksum):
721+
if verify_checksum(patch, checksum, computed_checksums[patch]):
719722
self.log.info('Checksum for extension patch %s verified', patch_fn)
720723
elif build_option('ignore_checksums'):
721724
print_warning("Ignoring failing checksum verification for %s" % patch_fn)
@@ -754,7 +757,9 @@ def obtain_file(self, filename, extension=False, urls=None, download_filename=No
754757
"""
755758
srcpaths = source_paths()
756759

757-
update_progress_bar(PROGRESS_BAR_DOWNLOAD_ALL, label=filename)
760+
# We don't account for the checksums file in the progress bar
761+
if filename != 'checksum.json':
762+
update_progress_bar(PROGRESS_BAR_DOWNLOAD_ALL, label=filename)
758763

759764
if alt_location is None:
760765
location = self.name
@@ -890,8 +895,10 @@ def obtain_file(self, filename, extension=False, urls=None, download_filename=No
890895
source_urls = []
891896
source_urls.extend(self.cfg['source_urls'])
892897

893-
# add https://sources.easybuild.io as fallback source URL
894-
source_urls.append(EASYBUILD_SOURCES_URL + '/' + os.path.join(name_letter, location))
898+
# Add additional URLs as configured.
899+
for url in build_option("extra_source_urls"):
900+
url += "/" + name_letter + "/" + location
901+
source_urls.append(url)
895902

896903
mkdir(targetdir, parents=True)
897904

@@ -2475,7 +2482,7 @@ def check_checksums_for(self, ent, sub='', source_cnt=None):
24752482
checksum_issues = []
24762483

24772484
sources = ent.get('sources', [])
2478-
patches = ent.get('patches', [])
2485+
patches = ent.get('patches', []) + ent.get('postinstallpatches', [])
24792486
checksums = ent.get('checksums', [])
24802487
# Single source should be re-wrapped as a list, and checksums with it
24812488
if isinstance(sources, dict):
@@ -3482,10 +3489,7 @@ def _sanity_check_step_extensions(self):
34823489
"""Sanity check on extensions (if any)."""
34833490
failed_exts = []
34843491

3485-
if build_option('skip_extensions'):
3486-
self.log.info("Skipping sanity check for extensions since skip-extensions is enabled...")
3487-
return
3488-
elif not self.ext_instances:
3492+
if not self.ext_instances:
34893493
# class instances for extensions may not be initialized yet here,
34903494
# for example when using --module-only or --sanity-check-only
34913495
self.prepare_for_extensions()
@@ -3637,7 +3641,10 @@ def xs2str(xs):
36373641

36383642
# also run sanity check for extensions (unless we are an extension ourselves)
36393643
if not extension:
3640-
self._sanity_check_step_extensions()
3644+
if build_option('skip_extensions'):
3645+
self.log.info("Skipping sanity check for extensions since skip-extensions is enabled...")
3646+
else:
3647+
self._sanity_check_step_extensions()
36413648

36423649
linked_shared_lib_fails = self.sanity_check_linked_shared_libs()
36433650
if linked_shared_lib_fails:
@@ -3894,12 +3901,13 @@ def update_config_template_run_step(self):
38943901
self.cfg.generate_template_values()
38953902

38963903
def skip_step(self, step, skippable):
3897-
"""Dedice whether or not to skip the specified step."""
3904+
"""Decide whether or not to skip the specified step."""
38983905
skip = False
38993906
force = build_option('force')
39003907
module_only = build_option('module_only') or self.cfg['module_only']
39013908
sanity_check_only = build_option('sanity_check_only')
39023909
skip_extensions = build_option('skip_extensions')
3910+
skip_sanity_check = build_option('skip_sanity_check')
39033911
skip_test_step = build_option('skip_test_step')
39043912
skipsteps = self.cfg['skipsteps']
39053913

@@ -3927,6 +3935,10 @@ def skip_step(self, step, skippable):
39273935
self.log.info("Skipping %s step because of sanity-check-only mode", step)
39283936
skip = True
39293937

3938+
elif skip_sanity_check and step == SANITYCHECK_STEP:
3939+
self.log.info("Skipping %s step as request via skip-sanity-check", step)
3940+
skip = True
3941+
39303942
elif skip_extensions and step == EXTENSIONS_STEP:
39313943
self.log.info("Skipping %s step as requested via skip-extensions", step)
39323944
skip = True
@@ -3937,9 +3949,9 @@ def skip_step(self, step, skippable):
39373949

39383950
else:
39393951
msg = "Not skipping %s step (skippable: %s, skip: %s, skipsteps: %s, module_only: %s, force: %s, "
3940-
msg += "sanity_check_only: %s, skip_extensions: %s, skip_test_step: %s)"
3952+
msg += "sanity_check_only: %s, skip_extensions: %s, skip_test_step: %s, skip_sanity_check: %s)"
39413953
self.log.debug(msg, step, skippable, self.skip, skipsteps, module_only, force,
3942-
sanity_check_only, skip_extensions, skip_test_step)
3954+
sanity_check_only, skip_extensions, skip_test_step, skip_sanity_check)
39433955

39443956
return skip
39453957

@@ -4089,7 +4101,7 @@ def run_all_steps(self, run_test_cases):
40894101
Build and install this software.
40904102
run_test_cases (bool): run tests after building (e.g.: make test)
40914103
"""
4092-
if self.cfg['stop'] and self.cfg['stop'] == 'cfg':
4104+
if self.cfg['stop'] == 'cfg':
40934105
return True
40944106

40954107
steps = self.get_steps(run_test_cases=run_test_cases, iteration_count=self.det_iter_cnt())
@@ -4708,7 +4720,7 @@ def make_checksum_lines(checksums, indent_level):
47084720

47094721
# back up easyconfig file before injecting checksums
47104722
ec_backup = back_up_file(ec['spec'])
4711-
print_msg("backup of easyconfig file saved to %s..." % ec_backup, log=_log)
4723+
print_msg("backup of easyconfig file saved to %s" % ec_backup, log=_log)
47124724

47134725
# compute & inject checksums for sources/patches
47144726
print_msg("injecting %s checksums for sources & patches in %s..." % (checksum_type, ec_fn), log=_log)

easybuild/framework/easyconfig/easyconfig.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import functools
4747
import os
4848
import re
49+
from collections import OrderedDict
4950
from contextlib import contextmanager
5051

5152
import easybuild.tools.filetools as filetools
@@ -74,7 +75,7 @@
7475
from easybuild.tools.module_naming_scheme.utilities import avail_module_naming_schemes, det_full_ec_version
7576
from easybuild.tools.module_naming_scheme.utilities import det_hidden_modname, is_valid_module_name
7677
from easybuild.tools.modules import modules_tool, NoModulesTool
77-
from easybuild.tools.py2vs3 import OrderedDict, create_base_metaclass, string_type
78+
from easybuild.tools.py2vs3 import create_base_metaclass, string_type
7879
from easybuild.tools.systemtools import check_os_dependency, pick_dep_version
7980
from easybuild.tools.toolchain.toolchain import SYSTEM_TOOLCHAIN_NAME, is_system_toolchain
8081
from easybuild.tools.toolchain.toolchain import TOOLCHAIN_CAPABILITIES, TOOLCHAIN_CAPABILITY_CUDA
@@ -455,6 +456,8 @@ def __init__(self, path, extra_options=None, build_specs=None, validate=True, hi
455456
self.path = path
456457
self.rawtxt = read_file(path)
457458
self.log.debug("Raw contents from supplied easyconfig file %s: %s", path, self.rawtxt)
459+
if not self.rawtxt.strip():
460+
raise EasyBuildError('Easyconfig file is empty')
458461
else:
459462
self.rawtxt = rawtxt
460463
self.log.debug("Supplied raw easyconfig contents: %s" % self.rawtxt)
@@ -1909,7 +1912,9 @@ def get_easyblock_class(easyblock, name=None, error_on_failed_import=True, error
19091912
else:
19101913
# if no easyblock specified, try to find if one exists
19111914
if name is None:
1912-
name = "UNKNOWN"
1915+
if error_on_missing_easyblock:
1916+
raise EasyBuildError("No easyblock found as neither name nor easyblock were specified")
1917+
return None
19131918
# The following is a generic way to calculate unique class names for any funny software title
19141919
class_name = encode_class_name(name)
19151920
# modulepath will be the namespace + encoded modulename (from the classname)

0 commit comments

Comments
 (0)