From cad784e94618ba56e77b8e056a697b9168f639ed Mon Sep 17 00:00:00 2001 From: e0958350 Date: Mon, 3 Jun 2024 16:28:45 +0800 Subject: [PATCH 01/62] add arm support to NAMD easyblock --- easybuild/easyblocks/n/namd.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/easybuild/easyblocks/n/namd.py b/easybuild/easyblocks/n/namd.py index 2fb826b4cfb..b06eda256d9 100644 --- a/easybuild/easyblocks/n/namd.py +++ b/easybuild/easyblocks/n/namd.py @@ -27,7 +27,7 @@ from easybuild.tools.filetools import apply_regex_substitutions, change_dir, extract_file from easybuild.tools.modules import get_software_root, get_software_version from easybuild.tools.run import run_cmd -from easybuild.tools.systemtools import POWER, X86_64, get_cpu_architecture +from easybuild.tools.systemtools import POWER, X86_64, AARCH64, get_cpu_architecture class EB_NAMD(MakeCp): @@ -71,6 +71,10 @@ def prepare_step(self, *args, **kwargs): basearch = 'Linux-x86_64' elif arch == POWER: basearch = 'Linux-POWER' + elif arch == AARCH64: + basearch = 'Linux-ARM64' + else: + raise EasyBuildError("The NAMD easyblock does not currently support architecture %s", arch) self.cfg['namd_basearch'] = basearch self.log.info("Derived value for 'namd_basearch': %s", self.cfg['namd_basearch']) From d1e5a64e690208c82e5b6b77f37b258391f655d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bob=20Dr=C3=B6ge?= Date: Fri, 13 Dec 2024 14:03:47 +0100 Subject: [PATCH 02/62] enable HeFFTe support --- easybuild/easyblocks/g/gromacs.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index 4450448b86e..c4c2efcd910 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -203,6 +203,12 @@ def configure_step(self): cuda_cc_semicolon_sep = self.cfg.get_cuda_cc_template_value( "cuda_cc_semicolon_sep").replace('.', '') self.cfg.update('configopts', '-DGMX_CUDA_TARGET_SM="%s"' % cuda_cc_semicolon_sep) + + # Enable HeFFTe support for multi-GPU FFT support if it's listed as a dependency + heffte_root = get_software_root('HeFFTe') + if heffte_root: + self.cfg.update('configopts', '-DGMX_USE_HEFFTE=ON') + self.cfg.update('configopts', '-DHeffte_ROOT=%s' % heffte_root) else: # explicitly disable GPU support if CUDA is not available, # to avoid that GROMACS finds and uses a system-wide CUDA compiler From 53a9a2357729d7e070dc8da548ec123c492becfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bob=20Dr=C3=B6ge?= Date: Fri, 13 Dec 2024 14:03:55 +0100 Subject: [PATCH 03/62] enable hwloc support --- easybuild/easyblocks/g/gromacs.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index c4c2efcd910..604a28c9f7b 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -244,6 +244,11 @@ def configure_step(self): # version of GROMACS. Just prepare first part of cmd here plumed_cmd = "plumed-patch -p -e %s" % engine + # Enable hwloc support if it's listed as dependency + if get_software_root('hwloc'): + self.cfg.update('configopts', '-DGMX_HWLOC=ON') + self.cfg.update('configopts', '-DHWLOC_DIR=%s' % get_software_root('hwloc')) + # Ensure that the GROMACS log files report how the code was patched # during the build, so that any problems are easier to diagnose. # The GMX_VERSION_STRING_OF_FORK feature is available since 2020. From 82c22bddf921fbe6c6f750c7d0ce8061a5b2d965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bob=20Dr=C3=B6ge?= Date: Mon, 16 Dec 2024 10:41:28 +0100 Subject: [PATCH 04/62] only enable hwloc support for gromacs >= 2016 --- easybuild/easyblocks/g/gromacs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index 604a28c9f7b..4984b7e6968 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -245,7 +245,7 @@ def configure_step(self): plumed_cmd = "plumed-patch -p -e %s" % engine # Enable hwloc support if it's listed as dependency - if get_software_root('hwloc'): + if get_software_root('hwloc') and gromacs_version >= '2016': self.cfg.update('configopts', '-DGMX_HWLOC=ON') self.cfg.update('configopts', '-DHWLOC_DIR=%s' % get_software_root('hwloc')) From 20423129928278d0d55f66ab5bceb437fe8bc5d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bob=20Dr=C3=B6ge?= Date: Mon, 16 Dec 2024 10:45:45 +0100 Subject: [PATCH 05/62] add version check for HeFFTe support --- easybuild/easyblocks/g/gromacs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index 4984b7e6968..94b537de555 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -204,9 +204,9 @@ def configure_step(self): "cuda_cc_semicolon_sep").replace('.', '') self.cfg.update('configopts', '-DGMX_CUDA_TARGET_SM="%s"' % cuda_cc_semicolon_sep) - # Enable HeFFTe support for multi-GPU FFT support if it's listed as a dependency + # Enable HeFFTe support for multi-GPU FFT support (added in v2023) if it's listed as a dependency heffte_root = get_software_root('HeFFTe') - if heffte_root: + if gromacs_version >= '2023' and heffte_root: self.cfg.update('configopts', '-DGMX_USE_HEFFTE=ON') self.cfg.update('configopts', '-DHeffte_ROOT=%s' % heffte_root) else: From 49ca92e48d96f40036351c3a2147a8ec4acf5bb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bob=20Dr=C3=B6ge?= Date: Mon, 16 Dec 2024 10:46:04 +0100 Subject: [PATCH 06/62] update comment about hwloc support --- easybuild/easyblocks/g/gromacs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index 94b537de555..6d480ab3fed 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -244,7 +244,7 @@ def configure_step(self): # version of GROMACS. Just prepare first part of cmd here plumed_cmd = "plumed-patch -p -e %s" % engine - # Enable hwloc support if it's listed as dependency + # Enable hwloc support (added in v2016) if it's listed as dependency if get_software_root('hwloc') and gromacs_version >= '2016': self.cfg.update('configopts', '-DGMX_HWLOC=ON') self.cfg.update('configopts', '-DHWLOC_DIR=%s' % get_software_root('hwloc')) From fa45a5356853f2013effb5f043067c5418075792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bob=20Dr=C3=B6ge?= Date: Mon, 16 Dec 2024 10:46:44 +0100 Subject: [PATCH 07/62] switch order of checks to keep things consistent --- easybuild/easyblocks/g/gromacs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index 6d480ab3fed..15cb2f75f9b 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -245,7 +245,7 @@ def configure_step(self): plumed_cmd = "plumed-patch -p -e %s" % engine # Enable hwloc support (added in v2016) if it's listed as dependency - if get_software_root('hwloc') and gromacs_version >= '2016': + if gromacs_version >= '2016' and get_software_root('hwloc'): self.cfg.update('configopts', '-DGMX_HWLOC=ON') self.cfg.update('configopts', '-DHWLOC_DIR=%s' % get_software_root('hwloc')) From df5875c8e6cca79bfc7e041d4417496c05df210a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Mon, 16 Feb 2026 16:24:22 +0100 Subject: [PATCH 08/62] ESPResSo easyblock --- easybuild/easyblocks/e/espresso.py | 195 +++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 easybuild/easyblocks/e/espresso.py diff --git a/easybuild/easyblocks/e/espresso.py b/easybuild/easyblocks/e/espresso.py new file mode 100644 index 00000000000..51c750a236a --- /dev/null +++ b/easybuild/easyblocks/e/espresso.py @@ -0,0 +1,195 @@ +## +# Copyright 2025-2026 Jean-Noël Grad +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see . +## +""" +EasyBuild support for ESPResSo, implemented as an easyblock. + +@author: Jean-Noël Grad (University of Stuttgart) +""" + +import os +import re + +from easybuild.easyblocks.generic.cmakeninja import CMakeNinja +from easybuild.tools.systemtools import get_cpu_architecture, get_cpu_features +from easybuild.tools.systemtools import X86_64 +from easybuild.tools.utilities import trace_msg +from easybuild.tools.build_log import print_error +from easybuild.tools.filetools import remove_file, remove_dir +from easybuild.tools import LooseVersion + + +class EB_ESPResSo(CMakeNinja): + """Support for building and installing ESPResSo.""" + + def _get_extracted_tarball_paths(self): + """ + Locate the source code of all dependencies. + """ + extracted_paths = {} + for src in self.src: + name = src['name'].split('-', 1)[0] + # process main software + if name == 'espresso': + extracted_paths['espresso'] = src['finalpath'] + continue + # process dependencies + tarball = src['name'] + if not tarball.endswith('.tar.gz'): + raise ValueError(tarball + ' is not a tar.gz file') + prefix = tarball.rsplit('.', 2)[0] + matches = [x for x in os.listdir(src['finalpath']) if x.startswith(prefix)] + if len(matches) == 0: + raise RuntimeError(tarball + ' was not extracted') + if len(matches) > 1: + raise RuntimeError(tarball + ' matches multiple folders: ' + str(matches)) + extracted_paths[name] = os.path.join(src['finalpath'], matches[0]) + return extracted_paths + + def _patch_fetchcontent(self): + """ + Modify CMake ``FetchContent_Declare`` blocks to point to the folders + containing the already-downloaded dependencies rather than to URLs. + This avoids a download step during configuration. + """ + extracted_paths = self._get_extracted_tarball_paths() + cmakelists_path = os.path.join(extracted_paths['espresso'], 'CMakeLists.txt') + with open(cmakelists_path, 'r') as f: + content = f.read() + for name, local_uri in extracted_paths.items(): + if name == 'espresso': + continue + pattern = fr'FetchContent_Declare\(\s*{name}\s+GIT_REPOSITORY\s+\S+\s+GIT_TAG\s+\S+(?=\s|\))' + m = re.search(pattern, content, flags=re.IGNORECASE) + if m is None: + raise RuntimeError(f'{name} is not part of the ESPResSo FetchContent workflow') + content = re.sub(pattern, f'FetchContent_Declare({name} URL {local_uri}', content, flags=re.IGNORECASE) + with open(cmakelists_path, 'w') as f: + f.write(content) + + def _get_version(self): + if '.' in self.version: + version = tuple(LooseVersion(self.version).version) + else: + version = 'commit' + return version + + def _configure_step_release_420(self): + configopts = self.cfg.get('configopts', '') + dependencies = self.cfg.dependencies() + dependencies_names = {x.get('name', '') for x in dependencies} + + def cmake_if_has(name): + return 'ON' if name in dependencies_names else 'OFF' + + configopts += f' -DWITH_CUDA={cmake_if_has("CUDA")}' + configopts += f' -DWITH_GSL={cmake_if_has("GSL")}' + configopts += ' -DWITH_FFTW=ON' + configopts += ' -DWITH_PYTHON=ON' + configopts += ' -DWITH_SCAFACOS=OFF' + configopts += ' -DWITH_STOKESIAN_DYNAMICS=OFF' + configopts += ' -DWITH_TESTS=ON' + + self.cfg['configopts'] = configopts + + def _configure_step_release_500(self): + configopts = self.cfg.get('configopts', '') + dependencies = self.cfg.dependencies() + dependencies_names = {x.get('name', '') for x in dependencies} + + def cmake_if_has(name): + return 'ON' if name in dependencies_names else 'OFF' + + cpu_features = get_cpu_features() + + configopts += f' -DESPRESSO_BUILD_WITH_CUDA={cmake_if_has("CUDA")}' + configopts += f' -DESPRESSO_BUILD_WITH_HDF5={cmake_if_has("HDF5")}' + configopts += f' -DESPRESSO_BUILD_WITH_GSL={cmake_if_has("GSL")}' + configopts += f' -DESPRESSO_BUILD_WITH_NLOPT={cmake_if_has("NLopt")}' + configopts += ' -DESPRESSO_BUILD_WITH_SHARED_MEMORY_PARALLELISM=ON' + configopts += ' -DESPRESSO_BUILD_WITH_WALBERLA=ON' + if get_cpu_architecture() == X86_64 and 'avx2' in cpu_features: + configopts += ' -DESPRESSO_BUILD_WITH_WALBERLA_AVX=ON' + configopts += ' -DESPRESSO_BUILD_WITH_FFTW=ON' + configopts += ' -DESPRESSO_BUILD_WITH_PYTHON=ON' + configopts += ' -DESPRESSO_BUILD_WITH_SCAFACOS=OFF' + configopts += ' -DESPRESSO_BUILD_WITH_STOKESIAN_DYNAMICS=OFF' + configopts += ' -DESPRESSO_BUILD_TESTS=ON ' + + self.cfg['configopts'] = configopts + + def configure_step(self): + # patch FetchContent to avoid re-downloading dependencies + self._patch_fetchcontent() + + version = self._get_version() + if version == 'commit': + self._configure_step_release_500() + elif version[:2] >= (5, 0): + self._configure_step_release_500() + elif version[:2] >= (4, 2): + self._configure_step_release_420() + else: + raise NotImplementedError( + f'EasyBlock {self.__class__.__name__} doesn\'t implement the ' + f'configure step for ESPResSo {self.version}') + + return super(EB_ESPResSo, self).configure_step() + + def test_step(self): + version = self._get_version() + if version == 'commit' or version[:2] >= (5, 0): + testopts = self.cfg.get('testopts', '') + testopts += f' -j{self.cfg.parallel}' + testopts += f' --resource-spec-file {self.builddir}/easybuild_obj/testsuite/python/resources.json' + self.cfg['testopts'] = testopts + + return super(EB_ESPResSo, self).test_step() + + def _cleanup_aux_files(self): + """ + Remove files automatically installed by CMake outside the ESPResSo + main directory: header files, config files, duplicated shared objects. + """ + def delete_dir(path): + if os.path.isdir(path): + trace_msg('removing directory \'%s\'' % path.replace(f'{self.installdir}/', '')) + remove_dir(path) + + def delete_file(path): + if os.path.isfile(path) or os.path.islink(path): + trace_msg('removing file \'%s\'' % path.replace(f'{self.installdir}/', '')) + remove_file(path) + + version = self._get_version() + if version == 'commit' or version[:2] >= (5, 0): + lib_dir = f'{self.installdir}/lib' + if os.path.isdir(f'{self.installdir}/lib64'): + lib_dir = f'{self.installdir}/lib64' + delete_dir(f'{self.installdir}/include') + delete_dir(f'{self.installdir}/share') + delete_dir(f'{self.installdir}/walberla') + delete_dir(f'{lib_dir}/cmake') + for path in os.listdir(lib_dir): + if '.so' in path: + delete_file(f'{lib_dir}/{path}') + + def post_processing_step(self): + try: + self._cleanup_aux_files() + except Exception as err: + print_error('Failed to remove some auxiliary files ' + f'(easyblock: {self.__class__.__name__}): {err}') + return super(EB_ESPResSo, self).post_processing_step() From 816c725ff4e81a360fbf1f0d3b3a08348abef5e3 Mon Sep 17 00:00:00 2001 From: Charles Coulombe Date: Thu, 5 Mar 2026 00:36:40 -0500 Subject: [PATCH 09/62] Updated Boost easyblock to avoid libboost_system on version 1.89 or higher See https://github.com/boostorg/system/commit/7a495bb46d7ccd808e4be2a6589260839b0fd3a3 --- easybuild/easyblocks/b/boost.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/easybuild/easyblocks/b/boost.py b/easybuild/easyblocks/b/boost.py index cc89891899c..d71709b50c1 100644 --- a/easybuild/easyblocks/b/boost.py +++ b/easybuild/easyblocks/b/boost.py @@ -348,10 +348,12 @@ def sanity_check_step(self): os.path.join('lib', 'libboost_python%s%s.%s' % (suffix, lib_mt_suffix, shlib_ext))) else: - custom_paths['files'].append(os.path.join('lib', 'libboost_system.%s' % shlib_ext)) + if LooseVersion(self.version) <= LooseVersion("1.88.0"): + custom_paths['files'].append(os.path.join('lib', 'libboost_system.%s' % shlib_ext)) if self.cfg['tagged_layout']: - custom_paths['files'].append(os.path.join('lib', 'libboost_system%s.%s' % (lib_mt_suffix, shlib_ext))) + if LooseVersion(self.version) <= LooseVersion("1.88.0"): + custom_paths['files'].append(os.path.join('lib', 'libboost_system%s.%s' % (lib_mt_suffix, shlib_ext))) custom_paths['files'].append(os.path.join('lib', 'libboost_thread%s.%s' % (lib_mt_suffix, shlib_ext))) if self.cfg['boost_mpi']: From 6461839cf0f5c4be388a28465e853b4345187397 Mon Sep 17 00:00:00 2001 From: Charles Coulombe Date: Thu, 5 Mar 2026 00:42:53 -0500 Subject: [PATCH 10/62] Appease hound for line lenght --- easybuild/easyblocks/b/boost.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/easybuild/easyblocks/b/boost.py b/easybuild/easyblocks/b/boost.py index d71709b50c1..3f160ea3080 100644 --- a/easybuild/easyblocks/b/boost.py +++ b/easybuild/easyblocks/b/boost.py @@ -353,7 +353,8 @@ def sanity_check_step(self): if self.cfg['tagged_layout']: if LooseVersion(self.version) <= LooseVersion("1.88.0"): - custom_paths['files'].append(os.path.join('lib', 'libboost_system%s.%s' % (lib_mt_suffix, shlib_ext))) + p = os.path.join('lib', 'libboost_system%s.%s' % (lib_mt_suffix, shlib_ext)) + custom_paths['files'].append(p) custom_paths['files'].append(os.path.join('lib', 'libboost_thread%s.%s' % (lib_mt_suffix, shlib_ext))) if self.cfg['boost_mpi']: From 87a5900c71008f0c0b67ebeae1c1262145215254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Mon, 16 Mar 2026 20:56:47 +0100 Subject: [PATCH 11/62] Simplify ESPResSo easyblock Co-authored-by: Alan O'Cais --- easybuild/easyblocks/e/espresso.py | 81 ++++++++++++------------------ 1 file changed, 33 insertions(+), 48 deletions(-) diff --git a/easybuild/easyblocks/e/espresso.py b/easybuild/easyblocks/e/espresso.py index 51c750a236a..4d1ea72144a 100644 --- a/easybuild/easyblocks/e/espresso.py +++ b/easybuild/easyblocks/e/espresso.py @@ -26,8 +26,9 @@ from easybuild.tools.systemtools import get_cpu_architecture, get_cpu_features from easybuild.tools.systemtools import X86_64 from easybuild.tools.utilities import trace_msg -from easybuild.tools.build_log import print_error +from easybuild.tools.build_log import EasyBuildError, print_error from easybuild.tools.filetools import remove_file, remove_dir +from easybuild.tools.modules import get_software_root from easybuild.tools import LooseVersion @@ -47,14 +48,17 @@ def _get_extracted_tarball_paths(self): continue # process dependencies tarball = src['name'] - if not tarball.endswith('.tar.gz'): - raise ValueError(tarball + ' is not a tar.gz file') - prefix = tarball.rsplit('.', 2)[0] - matches = [x for x in os.listdir(src['finalpath']) if x.startswith(prefix)] + if tarball.endswith(('.tar.bz2', '.tar.gz', '.tar.xz', '.tar.lz', '.tar.lz4', '.tar.Z')): + prefix = tarball.rsplit('.', 2)[0] + elif tarball.endswith(('.zip', '.7z', '.tar')): + prefix = tarball.rsplit('.', 1)[0] + else: + raise EasyBuildError(f'unexpected archive/compression format: {tarball}') + matches = [x for x in os.listdir(src['finalpath']) if x.lower().startswith(prefix.lower())] if len(matches) == 0: - raise RuntimeError(tarball + ' was not extracted') + raise EasyBuildError(f'{tarball} was not extracted') if len(matches) > 1: - raise RuntimeError(tarball + ' matches multiple folders: ' + str(matches)) + raise EasyBuildError(f'{tarball} matches multiple folders: {matches}') extracted_paths[name] = os.path.join(src['finalpath'], matches[0]) return extracted_paths @@ -65,7 +69,10 @@ def _patch_fetchcontent(self): This avoids a download step during configuration. """ extracted_paths = self._get_extracted_tarball_paths() - cmakelists_path = os.path.join(extracted_paths['espresso'], 'CMakeLists.txt') + if 'espresso' in extracted_paths.keys(): + cmakelists_path = os.path.join(extracted_paths['espresso'], 'CMakeLists.txt') + else: + raise EasyBuildError(f"espresso not found in extracted_paths dict: {extracted_paths}") with open(cmakelists_path, 'r') as f: content = f.read() for name, local_uri in extracted_paths.items(): @@ -74,7 +81,7 @@ def _patch_fetchcontent(self): pattern = fr'FetchContent_Declare\(\s*{name}\s+GIT_REPOSITORY\s+\S+\s+GIT_TAG\s+\S+(?=\s|\))' m = re.search(pattern, content, flags=re.IGNORECASE) if m is None: - raise RuntimeError(f'{name} is not part of the ESPResSo FetchContent workflow') + raise EasyBuildError(f'{name} is not part of the ESPResSo FetchContent workflow') content = re.sub(pattern, f'FetchContent_Declare({name} URL {local_uri}', content, flags=re.IGNORECASE) with open(cmakelists_path, 'w') as f: f.write(content) @@ -87,48 +94,26 @@ def _get_version(self): return version def _configure_step_release_420(self): - configopts = self.cfg.get('configopts', '') - dependencies = self.cfg.dependencies() - dependencies_names = {x.get('name', '') for x in dependencies} - - def cmake_if_has(name): - return 'ON' if name in dependencies_names else 'OFF' - - configopts += f' -DWITH_CUDA={cmake_if_has("CUDA")}' - configopts += f' -DWITH_GSL={cmake_if_has("GSL")}' - configopts += ' -DWITH_FFTW=ON' - configopts += ' -DWITH_PYTHON=ON' - configopts += ' -DWITH_SCAFACOS=OFF' - configopts += ' -DWITH_STOKESIAN_DYNAMICS=OFF' - configopts += ' -DWITH_TESTS=ON' - - self.cfg['configopts'] = configopts + for dep in ['CUDA', 'GSL', 'FFTW', 'PYTHON', 'SCAFACOS']: + dep_flag = 'OFF' + if get_software_root(dep): + dep_flag = 'ON' + self.cfg.update('configopts', f"-DWITH_{dep.upper()}={dep_flag}") + self.cfg.update('configopts', ' -DWITH_STOKESIAN_DYNAMICS=OFF') + self.cfg.update('configopts', ' -DWITH_TESTS=ON') def _configure_step_release_500(self): - configopts = self.cfg.get('configopts', '') - dependencies = self.cfg.dependencies() - dependencies_names = {x.get('name', '') for x in dependencies} - - def cmake_if_has(name): - return 'ON' if name in dependencies_names else 'OFF' - cpu_features = get_cpu_features() - - configopts += f' -DESPRESSO_BUILD_WITH_CUDA={cmake_if_has("CUDA")}' - configopts += f' -DESPRESSO_BUILD_WITH_HDF5={cmake_if_has("HDF5")}' - configopts += f' -DESPRESSO_BUILD_WITH_GSL={cmake_if_has("GSL")}' - configopts += f' -DESPRESSO_BUILD_WITH_NLOPT={cmake_if_has("NLopt")}' - configopts += ' -DESPRESSO_BUILD_WITH_SHARED_MEMORY_PARALLELISM=ON' - configopts += ' -DESPRESSO_BUILD_WITH_WALBERLA=ON' + for dep in ['CUDA', 'GSL', 'FFTW', 'PYTHON', 'SCAFACOS', 'HDF5', 'NLOPT']: + dep_flag = 'OFF' + if get_software_root(dep): + dep_flag = 'ON' + self.cfg.update('configopts', f"-DESPRESSO_BUILD_WITH_{dep.upper()}={dep_flag}") + self.cfg.update('configopts', ' -DESPRESSO_BUILD_WITH_STOKESIAN_DYNAMICS=OFF') + self.cfg.update('configopts', ' -DESPRESSO_BUILD_WITH_WALBERLA=ON') if get_cpu_architecture() == X86_64 and 'avx2' in cpu_features: - configopts += ' -DESPRESSO_BUILD_WITH_WALBERLA_AVX=ON' - configopts += ' -DESPRESSO_BUILD_WITH_FFTW=ON' - configopts += ' -DESPRESSO_BUILD_WITH_PYTHON=ON' - configopts += ' -DESPRESSO_BUILD_WITH_SCAFACOS=OFF' - configopts += ' -DESPRESSO_BUILD_WITH_STOKESIAN_DYNAMICS=OFF' - configopts += ' -DESPRESSO_BUILD_TESTS=ON ' - - self.cfg['configopts'] = configopts + self.cfg.update('configopts', ' -DESPRESSO_BUILD_WITH_WALBERLA_AVX=ON') + self.cfg.update('configopts', ' -DESPRESSO_BUILD_TESTS=ON') def configure_step(self): # patch FetchContent to avoid re-downloading dependencies @@ -142,7 +127,7 @@ def configure_step(self): elif version[:2] >= (4, 2): self._configure_step_release_420() else: - raise NotImplementedError( + raise EasyBuildError( f'EasyBlock {self.__class__.__name__} doesn\'t implement the ' f'configure step for ESPResSo {self.version}') From 87ca6c66d65032a2ce367a1445c95f859d667534 Mon Sep 17 00:00:00 2001 From: Samuel Moors Date: Fri, 10 Apr 2026 18:02:14 +0200 Subject: [PATCH 12/62] print warning for mismatched python package names or versions --- easybuild/easyblocks/generic/pythonbundle.py | 2 +- easybuild/easyblocks/generic/pythonpackage.py | 2 +- easybuild/easyblocks/p/python.py | 79 +++++++++++-------- 3 files changed, 46 insertions(+), 37 deletions(-) diff --git a/easybuild/easyblocks/generic/pythonbundle.py b/easybuild/easyblocks/generic/pythonbundle.py index f108ce8b6e5..90b9cf3326b 100644 --- a/easybuild/easyblocks/generic/pythonbundle.py +++ b/easybuild/easyblocks/generic/pythonbundle.py @@ -247,7 +247,7 @@ def _sanity_check_step_extensions(self): run_pip_check(python_cmd=self.python_cmd) pkgs = [(x.name, x.version) for x in py_exts] run_pip_list(pkgs, python_cmd=self.python_cmd, unversioned_packages=all_unversioned_packages, - check_names_versions=params['sanity_check_pip_list']) + strict_check=params['sanity_check_pip_list']) def make_module_footer(self): """ diff --git a/easybuild/easyblocks/generic/pythonpackage.py b/easybuild/easyblocks/generic/pythonpackage.py index 4f4aade8ddb..e2404910aad 100644 --- a/easybuild/easyblocks/generic/pythonpackage.py +++ b/easybuild/easyblocks/generic/pythonpackage.py @@ -1304,7 +1304,7 @@ def sanity_check_step(self, *args, **kwargs): unversioned_packages = self.cfg.get('unversioned_packages', []) pkgs = [(self.name, self.version)] run_pip_list(pkgs, python_cmd=python_cmd, unversioned_packages=unversioned_packages, - check_names_versions=params['sanity_check_pip_list']) + strict_check=params['sanity_check_pip_list']) # ExtensionEasyBlock handles loading modules correctly for multi_deps, so we clean up fake_mod_data # and let ExtensionEasyBlock do its job diff --git a/easybuild/easyblocks/p/python.py b/easybuild/easyblocks/p/python.py index 172cde0dffe..2140d09b608 100644 --- a/easybuild/easyblocks/p/python.py +++ b/easybuild/easyblocks/p/python.py @@ -233,14 +233,14 @@ def normalize_pip(name): return REGEX_PIP_NORMALIZE.sub('-', name).lower() -def run_pip_list(pkgs, python_cmd=None, unversioned_packages=None, check_names_versions=None): +def run_pip_list(pkgs, python_cmd=None, unversioned_packages=None, strict_check=None): """ Run pip list to verify normalized names and versions of installed Python packages :param pkgs: list of package tuples (name, version) as specified in the easyconfig :param python_cmd: Python command to use (if None, 'python' is used) :param unversioned_packages: set of Python packages to exclude in the version existence check - :param check_names_versions: boolean to indicate whether name and versions of Python packages should be checked + :param strict_check: boolean to indicate whether to raise an error if package names or versions don’t match """ log = fancylogger.getLogger('run_pip_list', fname=False) @@ -256,15 +256,16 @@ def run_pip_list(pkgs, python_cmd=None, unversioned_packages=None, check_names_v if build_option('ignore_pip_unversioned_pkgs'): unversioned_packages.update(build_option('ignore_pip_unversioned_pkgs')) - if check_names_versions is None: - # by default only check names and versions if --upload-test-report is used, - # so we can enforce that extension names/versions are correct for contributions + if strict_check is None: + # by default only raise an error for mismatched names or versions if --upload-test-report is used, + # to enforce correct extension names/versions for contributions if build_option('upload_test_report'): - check_names_versions = True + strict_check = True else: - check_names_versions = False + strict_check = False pip_list_errors = [] + pip_list_warnings = [] msg = "Check on installed Python package names and versions with 'pip list': " try: @@ -317,44 +318,52 @@ def run_pip_list(pkgs, python_cmd=None, unversioned_packages=None, check_names_v msg += "required (check the source for a pyproject.toml and see PEP517 for details on that)." pip_list_errors.append(msg) - if check_names_versions: - normalized_pkgs = [(normalize_pip(name), version) for name, version in pkgs] + normalized_pkgs = [(normalize_pip(name), version) for name, version in pkgs] - missing_names = [] - missing_versions = [] + missing_names = [] + missing_versions = [] - for name, version in normalized_pkgs: - # Skip packages in the unversioned list: they have already been checked - if name in normalized_unversioned: - continue + for name, version in normalized_pkgs: + # Skip packages in the unversioned list: they have already been checked + if name in normalized_unversioned: + continue - # Skip packages in the zero_pkg_names list: they have already been added to pip_list_errors - if name in zero_pkg_names: - continue + # Skip packages in the zero_pkg_names list: they have already been added to pip_list_errors + if name in zero_pkg_names: + continue - # Check for missing (likely wrong) packages names and propose close matches - if name not in normalized_pip_pkgs: - close_matches = difflib.get_close_matches(name, normalized_pip_pkgs.keys()) - missing_names.append(f"{name} (close matches in 'pip list' output: " + ', '.join(close_matches)) + # Check for missing (likely wrong) packages names and propose close matches + if name not in normalized_pip_pkgs: + close_matches = difflib.get_close_matches(name, normalized_pip_pkgs.keys()) + missing_names.append(f"{name} (close matches in 'pip list' output: " + ', '.join(close_matches)) - # Check for missing (likely wrong) package versions - elif version != normalized_pip_pkgs[name]: - missing_versions.append(f"{name} {version} (version in 'pip list' output: {normalized_pip_pkgs[name]})") + # Check for missing (likely wrong) package versions + elif version != normalized_pip_pkgs[name]: + missing_versions.append(f"{name} {version} (version in 'pip list' output: {normalized_pip_pkgs[name]})") - log.info(f"Found {len(missing_names)} missing names and {len(missing_versions)} missing versions " - f"out of {len(pkgs)} packages") + log.info(f"Found {len(missing_names)} missing names and {len(missing_versions)} missing versions " + f"out of {len(pkgs)} packages") - if missing_names: - missing_names_str = '\n'.join(missing_names) - msg = "The following Python packages were likely specified with a wrong name because they are missing " - msg += f"in the 'pip list' output:\n{missing_names_str}" + if missing_names: + missing_names_str = '\n'.join(missing_names) + msg = "The following Python packages were likely specified with a wrong name because they are missing " + msg += f"in the 'pip list' output:\n{missing_names_str}" + if strict_check: pip_list_errors.append(msg) + else: + pip_list_warnings.append(msg) - if missing_versions: - missing_versions_str = '\n'.join(missing_versions) - msg = "The following Python packages were likely specified with a wrong version because they have " - msg += f"another version in the 'pip list' output:\n{missing_versions_str}" + if missing_versions: + missing_versions_str = '\n'.join(missing_versions) + msg = "The following Python packages were likely specified with a wrong version because they have " + msg += f"another version in the 'pip list' output:\n{missing_versions_str}" + if strict_check: pip_list_errors.append(msg) + else: + pip_list_warnings.append(msg) + + if pip_list_warnings: + print_warning(msg, log=log) if pip_list_errors: raise EasyBuildError('\n' + '\n'.join(pip_list_errors)) From 0f58e00852b8a95529a35cc829b5912b3da58cb6 Mon Sep 17 00:00:00 2001 From: Samuel Moors Date: Fri, 10 Apr 2026 18:29:36 +0200 Subject: [PATCH 13/62] fix test --- test/easyblocks/easyblock_specific.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/easyblocks/easyblock_specific.py b/test/easyblocks/easyblock_specific.py index fa00a0c33a6..bbf917160a6 100644 --- a/test/easyblocks/easyblock_specific.py +++ b/test/easyblocks/easyblock_specific.py @@ -627,7 +627,7 @@ def mocked_run_shell_cmd_pip(cmd, **kwargs): with self.mocked_stdout_stderr(): self.assertErrorRegex(EasyBuildError, error_pattern, python.run_pip_list, [('wrong_name', '1.2.3'), ('wrong_version', '5.6.7')], - python_cmd=sys.executable, check_names_versions=True) + python_cmd=sys.executable, strict_check=True) def test_symlink_dist_site_packages(self): """Test symlink_dist_site_packages provided by PythonPackage easyblock.""" From 1da2e6803c7978ccddbce40d55759958292337c1 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 10 Apr 2026 21:20:03 +0200 Subject: [PATCH 14/62] bump version to 5.3.1dev --- easybuild/easyblocks/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/easyblocks/__init__.py b/easybuild/easyblocks/__init__.py index 4575f3678a1..fd52841b45d 100644 --- a/easybuild/easyblocks/__init__.py +++ b/easybuild/easyblocks/__init__.py @@ -42,7 +42,7 @@ # recent setuptools versions will *TRANSFORM* something like 'X.Y.Zdev' into 'X.Y.Z.dev0', with a warning like # UserWarning: Normalizing '2.4.0dev' to '2.4.0.dev0' # This causes problems further up the dependency chain... -VERSION = '5.3.0' +VERSION = '5.3.1.dev0' UNKNOWN = 'UNKNOWN' From da0e52c669f984cd7ce618dba247276d3c87b08e Mon Sep 17 00:00:00 2001 From: Samuel Moors Date: Mon, 13 Apr 2026 16:30:51 +0200 Subject: [PATCH 15/62] mention failure if --upload-test-report is set in 'sanity_check_pip_list' description and in warnings --- easybuild/easyblocks/generic/pythonpackage.py | 5 +++-- easybuild/easyblocks/p/python.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/easybuild/easyblocks/generic/pythonpackage.py b/easybuild/easyblocks/generic/pythonpackage.py index e2404910aad..f0893fc6a76 100644 --- a/easybuild/easyblocks/generic/pythonpackage.py +++ b/easybuild/easyblocks/generic/pythonpackage.py @@ -509,8 +509,9 @@ def extra_options(extra_vars=None): 'max_py_minver': [None, "Maximum minor Python version (only relevant when using system Python)", CUSTOM], 'sanity_pip_check': [True, "Run 'python -m pip check' to ensure all required Python packages are " "installed and check for any package with an invalid (0.0.0) version.", CUSTOM], - 'sanity_check_pip_list': [None, "Run 'python -m pip list' to ensure specified package names and versions " - "are correct.", CUSTOM], + 'sanity_check_pip_list': [None, "Fail if specified package names and versions do not match " + "'python -m pip list' output. Defaults to True if --upload-test-report is " + "set. The check only runs if 'sanity_pip_check' is True.", CUSTOM], 'runtest': [True, "Run unit tests.", CUSTOM], # overrides default 'testinstall': [False, "Install into temporary directory prior to running the tests.", CUSTOM], 'unpack_sources': [None, "Unpack sources prior to build/install. Defaults to 'True' except for whl files", diff --git a/easybuild/easyblocks/p/python.py b/easybuild/easyblocks/p/python.py index 2140d09b608..1b5cfaf6899 100644 --- a/easybuild/easyblocks/p/python.py +++ b/easybuild/easyblocks/p/python.py @@ -347,7 +347,7 @@ def run_pip_list(pkgs, python_cmd=None, unversioned_packages=None, strict_check= if missing_names: missing_names_str = '\n'.join(missing_names) msg = "The following Python packages were likely specified with a wrong name because they are missing " - msg += f"in the 'pip list' output:\n{missing_names_str}" + msg += f"in the 'pip list' output (causes failure if --upload-test-report is set):\n{missing_names_str}" if strict_check: pip_list_errors.append(msg) else: @@ -356,7 +356,8 @@ def run_pip_list(pkgs, python_cmd=None, unversioned_packages=None, strict_check= if missing_versions: missing_versions_str = '\n'.join(missing_versions) msg = "The following Python packages were likely specified with a wrong version because they have " - msg += f"another version in the 'pip list' output:\n{missing_versions_str}" + msg += "another version in the 'pip list' output (causes failure if --upload-test-report is set):\n" + msg += missing_versions_str if strict_check: pip_list_errors.append(msg) else: From 63d8490c4c6145d41205fbf530be005c831fa59e Mon Sep 17 00:00:00 2001 From: Aayush Joglekar Date: Mon, 27 Apr 2026 14:03:24 +0200 Subject: [PATCH 16/62] Adapt sanity checks for AMD ROCm --- easybuild/easyblocks/o/openmpi.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/easybuild/easyblocks/o/openmpi.py b/easybuild/easyblocks/o/openmpi.py index 11fa9428543..eec91e13881 100644 --- a/easybuild/easyblocks/o/openmpi.py +++ b/easybuild/easyblocks/o/openmpi.py @@ -218,11 +218,11 @@ def sanity_check_step(self): expected['mpif90'] = 'pgfortran' # for Clang the pattern is always clang for key in ['mpicc', 'mpicxx']: - if expected[key] in ['clang++']: + if expected[key] in ['clang++', 'amdclang', 'amdclang++']: expected[key] = 'clang' # for flang/flang-new the pattern is always flang for key in ['mpifort', 'mpif90']: - if expected[key] in ['flang', 'flang-new']: + if expected[key] in ['flang', 'flang-new', 'amdflang']: expected[key] = 'flang' custom_commands = ["%s --version | grep '%s'" % (key, expected[key]) for key in sorted(expected.keys())] @@ -265,4 +265,8 @@ def sanity_check_step(self): params['nr_ranks'] = 1 custom_commands.append(mpi_cmd_tmpl % params) + rocmroot = get_software_root('ROCm') + if rocmroot: + custom_commands.append("ompi_info | grep -i 'rocm'") + super().sanity_check_step(custom_paths=custom_paths, custom_commands=custom_commands) From 76340f7df4c64645042f3515ccdc255dadb921a9 Mon Sep 17 00:00:00 2001 From: Aayush Joglekar Date: Mon, 27 Apr 2026 14:16:28 +0200 Subject: [PATCH 17/62] Update software root check to ROCm-LLVM --- easybuild/easyblocks/o/openmpi.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/easybuild/easyblocks/o/openmpi.py b/easybuild/easyblocks/o/openmpi.py index eec91e13881..92908d4b291 100644 --- a/easybuild/easyblocks/o/openmpi.py +++ b/easybuild/easyblocks/o/openmpi.py @@ -227,6 +227,10 @@ def sanity_check_step(self): custom_commands = ["%s --version | grep '%s'" % (key, expected[key]) for key in sorted(expected.keys())] + rocmroot = get_software_root('ROCm-LLVM') + if rocmroot: + custom_commands.append("ompi_info | grep -i 'rocm'") + # Add minimal test program to sanity checks # Run with correct MPI launcher mpi_cmd_tmpl, params = get_mpi_cmd_template(toolchain.OPENMPI, {}, mpi_version=self.version) @@ -265,8 +269,4 @@ def sanity_check_step(self): params['nr_ranks'] = 1 custom_commands.append(mpi_cmd_tmpl % params) - rocmroot = get_software_root('ROCm') - if rocmroot: - custom_commands.append("ompi_info | grep -i 'rocm'") - super().sanity_check_step(custom_paths=custom_paths, custom_commands=custom_commands) From 23eda476295077c6d303220f5a2cdf7d16a1ac74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bob=20Dr=C3=B6ge?= Date: Tue, 28 Apr 2026 16:43:52 +0200 Subject: [PATCH 18/62] also add support for cuFFTMp --- easybuild/easyblocks/g/gromacs.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index 9bd41fb7b2b..8ef088ea6be 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -212,11 +212,18 @@ def configure_step(self): "cuda_cc_semicolon_sep").replace('.', '') self.cfg.update('configopts', '-DGMX_CUDA_TARGET_SM="%s"' % cuda_cc_semicolon_sep) - # Enable HeFFTe support for multi-GPU FFT support (added in v2023) if it's listed as a dependency + # Enable cuFFTMp or HeFFTe support for multi-GPU FFT support (added in v2023) + # if one of them (not both) is listed as a dependency heffte_root = get_software_root('HeFFTe') + cufftmp_root = get_software_root('cuFFTMp') + if heffte_root and cufftmp_root: + raise EasyBuildError("HeFFTe and cuFFTMp are both listed as dependency, but cannot be combined.") if gromacs_version >= '2023' and heffte_root: self.cfg.update('configopts', '-DGMX_USE_HEFFTE=ON') self.cfg.update('configopts', '-DHeffte_ROOT=%s' % heffte_root) + if gromacs_version >= '2023' and cufftmp_root: + self.cfg.update('configopts', '-DGMX_USE_CUFFTMP=ON') + self.cfg.update('configopts', '-DcuFFTMp_ROOT=%s' % cufftmp_root) else: # explicitly disable GPU support if CUDA is not available, # to avoid that GROMACS finds and uses a system-wide CUDA compiler From cfb6f93bfae8bef5d8996ae6696defcafbf0d4ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bob=20Dr=C3=B6ge?= Date: Tue, 28 Apr 2026 23:13:56 +0200 Subject: [PATCH 19/62] add fix for finding right cufft.h --- easybuild/easyblocks/g/gromacs.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index 8ef088ea6be..0b1716b27b0 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -47,7 +47,7 @@ from easybuild.tools import LooseVersion from easybuild.tools.build_log import EasyBuildError, print_warning from easybuild.tools.config import build_option -from easybuild.tools.filetools import copy_dir, find_backup_name_candidate, remove_dir, which +from easybuild.tools.filetools import copy_dir, find_backup_name_candidate, remove_dir, symlink, which from easybuild.tools.modules import get_software_libdir, get_software_root, get_software_version from easybuild.tools.run import run_shell_cmd from easybuild.tools.systemtools import X86_64, get_cpu_architecture, get_cpu_features, get_shared_lib_ext @@ -224,6 +224,10 @@ def configure_step(self): if gromacs_version >= '2023' and cufftmp_root: self.cfg.update('configopts', '-DGMX_USE_CUFFTMP=ON') self.cfg.update('configopts', '-DcuFFTMp_ROOT=%s' % cufftmp_root) + # Prevent that we pick up the cufft.h from CUDA itself instead of the one provided by cuFFTMp + # by making a symlink to the latter in the GROMACS source dir (which is first in the searh path) + cufft_header = os.path.join(cufftmp_root, 'include', 'cufft.h') + symlink(cufft_header, os.path.join(self.cfg['start_dir'], 'src', 'include', 'cufft.h')) else: # explicitly disable GPU support if CUDA is not available, # to avoid that GROMACS finds and uses a system-wide CUDA compiler From 1569e31e9e373aa6c43a396bb919d280d07af384 Mon Sep 17 00:00:00 2001 From: ocaisa Date: Tue, 28 Apr 2026 23:19:09 +0200 Subject: [PATCH 20/62] Apply suggestion from @ocaisa --- easybuild/easyblocks/g/gromacs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index 0b1716b27b0..b7fb1754c3d 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -225,7 +225,7 @@ def configure_step(self): self.cfg.update('configopts', '-DGMX_USE_CUFFTMP=ON') self.cfg.update('configopts', '-DcuFFTMp_ROOT=%s' % cufftmp_root) # Prevent that we pick up the cufft.h from CUDA itself instead of the one provided by cuFFTMp - # by making a symlink to the latter in the GROMACS source dir (which is first in the searh path) + # by making a symlink to the latter in the GROMACS source dir (which is first in the search path) cufft_header = os.path.join(cufftmp_root, 'include', 'cufft.h') symlink(cufft_header, os.path.join(self.cfg['start_dir'], 'src', 'include', 'cufft.h')) else: From 42cf650fa57c899b0b2df3e46772b61132085f0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Mon, 4 May 2026 19:06:53 +0200 Subject: [PATCH 21/62] Move options from EasyConfig to EasyBlock --- easybuild/easyblocks/e/espresso.py | 138 +++++++++++++++++++++++++++-- 1 file changed, 132 insertions(+), 6 deletions(-) diff --git a/easybuild/easyblocks/e/espresso.py b/easybuild/easyblocks/e/espresso.py index 4d1ea72144a..f91cb281596 100644 --- a/easybuild/easyblocks/e/espresso.py +++ b/easybuild/easyblocks/e/espresso.py @@ -25,10 +25,11 @@ from easybuild.easyblocks.generic.cmakeninja import CMakeNinja from easybuild.tools.systemtools import get_cpu_architecture, get_cpu_features from easybuild.tools.systemtools import X86_64 +from easybuild.tools.systemtools import get_shared_lib_ext from easybuild.tools.utilities import trace_msg from easybuild.tools.build_log import EasyBuildError, print_error from easybuild.tools.filetools import remove_file, remove_dir -from easybuild.tools.modules import get_software_root +from easybuild.tools.modules import get_software_root, get_software_version from easybuild.tools import LooseVersion @@ -93,18 +94,100 @@ def _get_version(self): version = 'commit' return version + def _set_exe_linker_flags(self): + exe_linker_flags_relpaths = [] + if get_software_root('HeFFTe'): + exe_linker_flags_relpaths.append('heffte-build') + if get_software_root('Kokkos'): + exe_linker_flags_relpaths += [ + 'kokkos-build/containers/src', + 'kokkos-build/core/src', + 'kokkos-build/simd/src', + ] + if exe_linker_flags_relpaths: + # workaround for https://gitlab.kitware.com/cmake/cmake/-/issues/22678 + # (this only affects testsuite executable files in the build folder) + exe_linker_flags = ':'.join(f'{self.builddir}/easybuild_obj/_deps/{path}' + for path in exe_linker_flags_relpaths) + self.cfg.update('configopts', f' -DCMAKE_EXE_LINKER_FLAGS="-Wl,-rpath-link,{exe_linker_flags}" ') + def _configure_step_release_420(self): - for dep in ['CUDA', 'GSL', 'FFTW', 'PYTHON', 'SCAFACOS']: + for dep in ['CUDA', 'GSL', 'FFTW', 'Python', 'ScaFaCoS']: dep_flag = 'OFF' if get_software_root(dep): dep_flag = 'ON' self.cfg.update('configopts', f"-DWITH_{dep.upper()}={dep_flag}") self.cfg.update('configopts', ' -DWITH_STOKESIAN_DYNAMICS=OFF') self.cfg.update('configopts', ' -DWITH_TESTS=ON') + self.cfg.update('configopts', ' -DCMAKE_SKIP_RPATH=OFF') + # make sure the right Python is used (note: -DPython3_EXECUTABLE or -DPython_EXECUTABLE does not work!) + self.cfg.update('configopts', f' -DPYTHON_EXECUTABLE={get_software_root("Python")}/bin/python') + + # list packaged files + pyshortver = '.'.join(get_software_version('Python').split('.')[:2]) + _libs = [ + 'Espresso_config', 'Espresso_core', 'Espresso_script_interface', + 'Espresso_shapes', '_init', 'analyze', 'code_info', 'electrokinetics', + 'galilei', 'integrate', 'interactions', 'lb', 'particle_data', 'polymer', + 'profiler', 'script_interface', 'system', 'thermostat', 'utils', 'version', + ] + _python_modules = [ + '__init__.py', 'collision_detection.py', 'accumulators.py', + 'constraints.py', 'electrostatics.py', 'magnetostatics.py', + 'observables.py', 'reaction_methods.py', + ] + if get_software_root('CUDA'): + _libs.append('cuda_init') + _binaries = ['ipypresso', 'pypresso'] + _lib_path = f'lib/python{pyshortver}/site-packages/espressomd' + + return _binaries, _lib_path, _libs, _python_modules def _configure_step_release_500(self): cpu_features = get_cpu_features() - for dep in ['CUDA', 'GSL', 'FFTW', 'PYTHON', 'SCAFACOS', 'HDF5', 'NLOPT']: + for dep in ['CUDA', 'GSL', 'FFTW', 'Python', 'ScaFaCoS', 'HDF5', 'NLopt']: + dep_flag = 'OFF' + if get_software_root(dep): + dep_flag = 'ON' + self.cfg.update('configopts', f"-DESPRESSO_BUILD_WITH_{dep.upper()}={dep_flag}") + if get_software_root('Kokkos') and get_software_root('Cabana'): + self.cfg.update('configopts', ' -DESPRESSO_BUILD_WITH_SHARED_MEMORY_PARALLELISM=ON') + self.cfg.update('configopts', ' -DESPRESSO_BUILD_WITH_STOKESIAN_DYNAMICS=OFF') + self.cfg.update('configopts', ' -DESPRESSO_BUILD_WITH_WALBERLA=ON') + if get_cpu_architecture() == X86_64 and 'avx2' in cpu_features: + self.cfg.update('configopts', ' -DESPRESSO_BUILD_WITH_WALBERLA_AVX=ON') + self.cfg.update('configopts', ' -DESPRESSO_BUILD_TESTS=ON') + self._set_exe_linker_flags() + + # build_cmd_targets does not work with CMakeNinja, use buildopts instead + self.cfg['buildopts'] = 'espresso_packaging_dependencies' + + # list packaged files + pyshortver = '.'.join(get_software_version('Python').split('.')[:2]) + _libs = [ + 'espresso_core', 'espresso_shapes', 'espresso_walberla', + 'espresso_script_interface', 'script_interface', 'utils', '_init', + ] + _python_modules = [ + '__init__.py', 'accumulators.py', 'collision_detection.py', + 'constraints.py', 'electrokinetics.py', 'electrostatics.py', + 'magnetostatics.py', 'lb.py', 'lees_edwards.py', 'observables.py', + 'particle_data.py', 'reaction_methods.py', 'system.py', + 'thermostat.py', 'version.py', + ] + if get_software_root('HDF5'): + _libs.append('espresso_hdf5') + _python_modules.append('io/writer/h5md.py') + if get_software_root('CUDA'): + _python_modules.append('cuda_init.py') + _binaries = ['ipypresso', 'pypresso'] + _lib_path = f'lib/python{pyshortver}/site-packages/espressomd' + + return _binaries, _lib_path, _libs, _python_modules + + def _configure_step_release_510(self): + cpu_features = get_cpu_features() + for dep in ['CUDA', 'GSL', 'FFTW', 'Python', 'ScaFaCoS', 'HDF5', 'NLopt']: dep_flag = 'OFF' if get_software_root(dep): dep_flag = 'ON' @@ -114,6 +197,33 @@ def _configure_step_release_500(self): if get_cpu_architecture() == X86_64 and 'avx2' in cpu_features: self.cfg.update('configopts', ' -DESPRESSO_BUILD_WITH_WALBERLA_AVX=ON') self.cfg.update('configopts', ' -DESPRESSO_BUILD_TESTS=ON') + self._set_exe_linker_flags() + + # build_cmd_targets does not work with CMakeNinja, use buildopts instead + self.cfg['buildopts'] = 'espresso_packaging_dependencies' + + # list packaged files + pyshortver = '.'.join(get_software_version('Python').split('.')[:2]) + _libs = [ + 'espresso_core', 'espresso_shapes', 'espresso_walberla', + 'espresso_script_interface', 'script_interface', 'utils', '_init', + ] + _python_modules = [ + '__init__.py', 'accumulators.py', 'collision_detection.py', + 'constraints.py', 'electrokinetics.py', 'electrostatics.py', + 'magnetostatics.py', 'lb.py', 'lees_edwards.py', 'observables.py', + 'particle_data.py', 'reaction_methods.py', 'system.py', + 'thermostat.py', 'version.py', + ] + if get_software_root('HDF5'): + _libs.append('espresso_hdf5') + _python_modules.append('io/writer/h5md.py') + if get_software_root('CUDA'): + _python_modules.append('cuda_init.py') + _binaries = ['ipypresso', 'pypresso'] + _lib_path = f'lib/python{pyshortver}/site-packages/espressomd' + + return _binaries, _lib_path, _libs, _python_modules def configure_step(self): # patch FetchContent to avoid re-downloading dependencies @@ -121,16 +231,32 @@ def configure_step(self): version = self._get_version() if version == 'commit': - self._configure_step_release_500() + paths = self._configure_step_release_510() + elif version[:2] >= (5, 1): + paths = self._configure_step_release_510() elif version[:2] >= (5, 0): - self._configure_step_release_500() + paths = self._configure_step_release_500() elif version[:2] >= (4, 2): - self._configure_step_release_420() + paths = self._configure_step_release_420() else: raise EasyBuildError( f'EasyBlock {self.__class__.__name__} doesn\'t implement the ' f'configure step for ESPResSo {self.version}') + _binaries, _lib_path, _libs, _python_modules = paths + + self.cfg['sanity_check_paths'] = { + 'files': [f'bin/{x}' for x in _binaries] + + [f'{_lib_path}/{x}.{get_shared_lib_ext()}' for x in _libs] + + [f'{_lib_path}/{x}' for x in _python_modules], + 'dirs': ['bin', 'lib'] + } + self.cfg['sanity_check_commands'] = [ + 'pypresso -h', 'ipypresso -h', + 'pypresso -c "import espressomd.version;print(espressomd.version.friendly())"', + 'python3 -c "import espressomd.version;print(espressomd.version.friendly())"', + ] + return super(EB_ESPResSo, self).configure_step() def test_step(self): From 83fc5a58500416c6b6f069ecb1d8f6f0dcd10c82 Mon Sep 17 00:00:00 2001 From: Samuel Moors Date: Tue, 5 May 2026 09:32:09 +0200 Subject: [PATCH 22/62] fix location of object_storage for Dataset easyblock --- easybuild/easyblocks/generic/dataset.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/easybuild/easyblocks/generic/dataset.py b/easybuild/easyblocks/generic/dataset.py index 75525167f3e..0582513e6f5 100644 --- a/easybuild/easyblocks/generic/dataset.py +++ b/easybuild/easyblocks/generic/dataset.py @@ -33,8 +33,8 @@ from easybuild.easyblocks.generic.binary import Binary from easybuild.framework.easyconfig.default import CUSTOM from easybuild.tools.build_log import EasyBuildError -from easybuild.tools.filetools import compute_checksum, create_index, is_readable, mkdir, move_file, remove_file -from easybuild.tools.filetools import symlink +from easybuild.tools.filetools import compute_checksum, change_dir, create_index, is_readable, mkdir, move_file +from easybuild.tools.filetools import remove_file, symlink from easybuild.tools.utilities import trace_msg @@ -72,10 +72,12 @@ def install_step(self): def post_processing_step(self): """Add files to object_storage, remove duplicates, add symlinks""" - trace_msg('adding files to object_storage...') + trace_msg("adding files to 'object_storage'...") # creating object storage at root of software name to reuse identical files in different versions - object_storage = os.path.join(os.pardir, 'object_storage') + change_dir(self.installdir) + object_storage = os.path.normpath(os.path.join(os.getcwd(), os.pardir, 'object_storage')) + datafiles = create_index(os.curdir) for datafile in datafiles: From b3214859e761d644a4ce2df084e7de87421ace73 Mon Sep 17 00:00:00 2001 From: Samuel Moors Date: Tue, 5 May 2026 11:11:25 +0200 Subject: [PATCH 23/62] fix import order --- easybuild/easyblocks/generic/dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/easyblocks/generic/dataset.py b/easybuild/easyblocks/generic/dataset.py index 0582513e6f5..43ad51dad76 100644 --- a/easybuild/easyblocks/generic/dataset.py +++ b/easybuild/easyblocks/generic/dataset.py @@ -33,7 +33,7 @@ from easybuild.easyblocks.generic.binary import Binary from easybuild.framework.easyconfig.default import CUSTOM from easybuild.tools.build_log import EasyBuildError -from easybuild.tools.filetools import compute_checksum, change_dir, create_index, is_readable, mkdir, move_file +from easybuild.tools.filetools import change_dir, compute_checksum, create_index, is_readable, mkdir, move_file from easybuild.tools.filetools import remove_file, symlink from easybuild.tools.utilities import trace_msg From c247c66653e20bd441d73c4190314872c930a2a1 Mon Sep 17 00:00:00 2001 From: Charles Coulombe Date: Tue, 5 May 2026 16:49:01 -0400 Subject: [PATCH 24/62] Added CUDNN_HOME as an extra variable. Helps some softwares build and find libcudnn at runtime --- easybuild/easyblocks/c/cudnn.py | 1 + 1 file changed, 1 insertion(+) diff --git a/easybuild/easyblocks/c/cudnn.py b/easybuild/easyblocks/c/cudnn.py index 24d0ba6d7ec..1c88fcd4ba0 100644 --- a/easybuild/easyblocks/c/cudnn.py +++ b/easybuild/easyblocks/c/cudnn.py @@ -68,6 +68,7 @@ def __init__(self, *args, **kwargs): self.cfg['keepsymlinks'] = True self.cfg.template_values['cudnnarch'] = cudnnarch self.cfg.generate_template_values() + self.cfg.update('modextravars', {'CUDNN_HOME': self.installdir}) def fetch_step(self, *args, **kwargs): """Check for EULA acceptance prior to getting sources.""" From 55f84da78c8b08f24f5bd0fe817b9838ecaaaac0 Mon Sep 17 00:00:00 2001 From: Charles Coulombe Date: Fri, 8 May 2026 07:19:30 -0400 Subject: [PATCH 25/62] Added CUDNN_PATH and copied how done on the cuda easyblock --- easybuild/easyblocks/c/cudnn.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/easybuild/easyblocks/c/cudnn.py b/easybuild/easyblocks/c/cudnn.py index 1c88fcd4ba0..0b05b58ddf7 100644 --- a/easybuild/easyblocks/c/cudnn.py +++ b/easybuild/easyblocks/c/cudnn.py @@ -68,7 +68,6 @@ def __init__(self, *args, **kwargs): self.cfg['keepsymlinks'] = True self.cfg.template_values['cudnnarch'] = cudnnarch self.cfg.generate_template_values() - self.cfg.update('modextravars', {'CUDNN_HOME': self.installdir}) def fetch_step(self, *args, **kwargs): """Check for EULA acceptance prior to getting sources.""" @@ -79,3 +78,17 @@ def fetch_step(self, *args, **kwargs): more_info='https://docs.nvidia.com/deeplearning/cudnn/latest/reference/eula.html' ) return super().fetch_step(*args, **kwargs) + + def make_module_extra(self): + """Set the install directory as CUDNN_HOME, CUDNN_PATH.""" + + # avoid adding of installation directory to $PATH (cfr. Binary easyblock) since that may cause trouble, + # for example when there's a clash between command name and a subdirectory in the installation directory + # (like compute-sanitizer) + self.cfg['prepend_to_path'] = False + + txt = super().make_module_extra() + txt += self.module_generator.set_environment('CUDNN_HOME', self.installdir) + txt += self.module_generator.set_environment('CUDNN_PATH', self.installdir) + self.log.debug("make_module_extra added this: %s", txt) + return txt From ad40bca4a7fdf2548dfc33732eaf84a6741e4c3e Mon Sep 17 00:00:00 2001 From: Aayush Joglekar Date: Mon, 11 May 2026 10:47:50 +0300 Subject: [PATCH 26/62] Add ROCm support to openmpi.py --- easybuild/easyblocks/o/openmpi.py | 32 +++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/easybuild/easyblocks/o/openmpi.py b/easybuild/easyblocks/o/openmpi.py index 92908d4b291..80d4f8cb7fc 100644 --- a/easybuild/easyblocks/o/openmpi.py +++ b/easybuild/easyblocks/o/openmpi.py @@ -76,6 +76,14 @@ def config_opt_used(key, enable_opt=False): if LooseVersion(self.version) >= '5.0.0': known_dependencies.append('PRRTE') + # ROCm support added to OpenMPI after 5.0.x + rocmroot = get_software_root('ROCm-LLVM') + if rocmroot: + # remove plain UCC and UCX + known_dependencies = [d for d in known_dependencies if d not in ('UCX', 'UCC')] + # replace with rocm versions + known_dependencies.extend(['HIP', 'UCX-ROCm', 'UCC-ROCm']) + # Value to use for `--with-=` if the dependency is not specified in the easyconfig # No entry is interpreted as no option added at all # This is to make builds reproducible even when the system libraries are changed and avoids failures @@ -101,9 +109,17 @@ def config_opt_used(key, enable_opt=False): # libfabric option renamed in OpenMPI 3.1.0 to ofi if dep == 'libfabric' and LooseVersion(self.version) >= LooseVersion('3.1'): opt_name = 'ofi' - # Check new option name. They are synonyms since 3.1.0 for backward compatibility - if config_opt_used(opt_name): - continue + + # needed in easybuild setup as rocm-llvm and hip live in separate dirs + if dep == 'HIP': + opt_name = 'rocm' + if dep == 'UCC-ROCm': + opt_name = 'ucc' + if dep == 'UCX-ROCm': + opt_name = 'ucx' + + if config_opt_used(opt_name): + continue dep_root = get_software_root(dep) # If the dependency is loaded, specify its path, else use the "unused" value, if any @@ -229,7 +245,15 @@ def sanity_check_step(self): rocmroot = get_software_root('ROCm-LLVM') if rocmroot: - custom_commands.append("ompi_info | grep -i 'rocm'") + custom_commands.extend([ + "ompi_info | grep -i 'rocm'", + # ROCm MPI extension is built and exposed + "ompi_info --all | grep -E 'MPI extensions:.*rocm'", + # UCX PML can see the ROCm memory type + "ompi_info --param pml ucx --level 9 | grep -i rocm_ipc", + # The ROCm accelerator framework component is present + "ompi_info | grep -E 'MCA accelerator: rocm'", + ]) # Add minimal test program to sanity checks # Run with correct MPI launcher From 696134ba401d3666fbfe1a9c5cc5f18e5e17ea34 Mon Sep 17 00:00:00 2001 From: Samuel Moors Date: Wed, 13 May 2026 17:44:10 +0200 Subject: [PATCH 27/62] support postinstallcmds for dataset easyblock --- easybuild/easyblocks/generic/dataset.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/easybuild/easyblocks/generic/dataset.py b/easybuild/easyblocks/generic/dataset.py index 43ad51dad76..3fcfc6f4200 100644 --- a/easybuild/easyblocks/generic/dataset.py +++ b/easybuild/easyblocks/generic/dataset.py @@ -72,6 +72,9 @@ def install_step(self): def post_processing_step(self): """Add files to object_storage, remove duplicates, add symlinks""" + + EasyBlock.post_processing_step(self) + trace_msg("adding files to 'object_storage'...") # creating object storage at root of software name to reuse identical files in different versions From 20951cd2bb7f5b67799d1b313fc40f1423daae90 Mon Sep 17 00:00:00 2001 From: Charles Coulombe Date: Thu, 14 May 2026 11:52:08 -0400 Subject: [PATCH 28/62] Removed prepend_to_path --- easybuild/easyblocks/c/cudnn.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/easybuild/easyblocks/c/cudnn.py b/easybuild/easyblocks/c/cudnn.py index 0b05b58ddf7..49962c6fcc4 100644 --- a/easybuild/easyblocks/c/cudnn.py +++ b/easybuild/easyblocks/c/cudnn.py @@ -82,11 +82,6 @@ def fetch_step(self, *args, **kwargs): def make_module_extra(self): """Set the install directory as CUDNN_HOME, CUDNN_PATH.""" - # avoid adding of installation directory to $PATH (cfr. Binary easyblock) since that may cause trouble, - # for example when there's a clash between command name and a subdirectory in the installation directory - # (like compute-sanitizer) - self.cfg['prepend_to_path'] = False - txt = super().make_module_extra() txt += self.module_generator.set_environment('CUDNN_HOME', self.installdir) txt += self.module_generator.set_environment('CUDNN_PATH', self.installdir) From 9f3aaf02620c492b6d9fbb7905348c5cd28caf41 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Fri, 22 May 2026 10:11:44 +0200 Subject: [PATCH 29/62] Add logging if PyTorch tests were successful --- easybuild/easyblocks/p/pytorch.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/easybuild/easyblocks/p/pytorch.py b/easybuild/easyblocks/p/pytorch.py index 7bb1b7a313b..22bc6b398d8 100755 --- a/easybuild/easyblocks/p/pytorch.py +++ b/easybuild/easyblocks/p/pytorch.py @@ -651,15 +651,15 @@ def test_step(self): 'excluded_tests': ' '.join(excluded_tests) }) - parsed_test_result = super().test_step(return_output_ec=True) - if parsed_test_result is None: + test_step_result = super().test_step(return_output_ec=True) + if test_step_result is None: if self.cfg['runtest'] is False: msg = "Do not set 'runtest' to False, use --skip-test-step instead." else: msg = "Tests did not run. Make sure 'runtest' is set to a command." raise EasyBuildError(msg) - tests_out, tests_ec = parsed_test_result + tests_out, tests_ec = test_step_result failed_test_names = find_failed_test_names(tests_out) parsed_test_result = parse_test_log(tests_out) @@ -709,6 +709,11 @@ def suite_is_in_xml_results(suite_name): parsed_test_result = new_result failed_test_names = new_failed_names + # Calculate total number of unsuccesful tests + failed_test_cnt = parsed_test_result.failure_cnt + parsed_test_result.error_cnt + # Always log what we detected, allows for easy comparison of different builds + self.log.info("Detected %d failed tests (out of %d)", failed_test_cnt, parsed_test_result.test_cnt) + # Show failed subtests, if any, to aid in debugging failures if failed_test_names.error or failed_test_names.fail: msg = [] @@ -723,8 +728,6 @@ def suite_is_in_xml_results(suite_name): # Create clear summary report # Use a list of messages we can later join together failure_msgs = [] - # Calculate total number of unsuccesful and total tests - failed_test_cnt = parsed_test_result.failure_cnt + parsed_test_result.error_cnt # Only add count message if we detected any failed tests if failed_test_cnt > 0: failure_or_failures = 'failure' if parsed_test_result.failure_cnt == 1 else 'failures' @@ -836,6 +839,8 @@ def to_num(bool_or_int): raise EasyBuildError("Test ended with failures! Exit code: %s\n%s", tests_ec, failure_report) elif tests_ec: raise EasyBuildError("Test command had non-zero exit code (%s), but no failed tests found?!", tests_ec) + else: + self.log.info("All tests passed successfully!") def test_cases_step(self): self._set_cache_dirs() From f96cb2d99d40f20e06ad2edf27e7eb17b14d63f9 Mon Sep 17 00:00:00 2001 From: Pavel Tomanek <99190809+pavelToman@users.noreply.github.com> Date: Thu, 28 May 2026 14:25:16 +0200 Subject: [PATCH 30/62] Update scipy.py for v1.17 --- easybuild/easyblocks/s/scipy.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/easybuild/easyblocks/s/scipy.py b/easybuild/easyblocks/s/scipy.py index 93900cccffc..2f71e7493c2 100644 --- a/easybuild/easyblocks/s/scipy.py +++ b/easybuild/easyblocks/s/scipy.py @@ -85,7 +85,15 @@ def __init__(self, *args, **kwargs): # see https://github.com/easybuilders/easybuild-easyblocks/issues/2237 self.testcmd = "cd .. && %(python)s -c 'import numpy; import scipy; scipy.test(verbose=2)'" else: - if LooseVersion(self.version) >= LooseVersion('1.11'): + if LooseVersion(self.version) >= LooseVersion('1.17'): + # SciPy >= 1.17 no longer ships dev.py; EasyBuild already installs + # SciPy into a temporary prefix for testing, so run pytest directly + # against that installed tree. + self.testcmd = " && ".join([ + "cd %(tmp_pylibdir)s", + "%(python)s -m pytest -v%(pytest_filter)s scipy", + ]) + elif LooseVersion(self.version) >= LooseVersion('1.11'): self.testcmd = " && ".join([ "cd ..", # note: beware of adding --parallel here to speed up running the tests: @@ -100,7 +108,7 @@ def __init__(self, *args, **kwargs): "%(python)s %(srcdir)s/runtests.py -v --no-build --parallel %(parallel)s", ]) - if self.cfg['enable_slow_tests']: + if LooseVersion(self.version) < LooseVersion('1.17') and self.cfg['enable_slow_tests']: self.testcmd += " -m full " def configure_step(self): @@ -185,6 +193,10 @@ def test_step(self): tmp_pylibdir = os.path.join(tmp_installdir, det_pylibdir()) self.prepare_python() + + pytest_filter = "" + if LooseVersion(self.version) >= LooseVersion('1.17') and not self.cfg['enable_slow_tests']: + pytest_filter = " -m 'not slow'" self.cfg['pretestopts'] = " && ".join([ # LDFLAGS should not be set when testing numpy/scipy, because it overwrites whatever numpy/scipy sets @@ -196,8 +208,10 @@ def test_step(self): self.cfg['runtest'] = self.testcmd % { 'python': self.python_cmd, 'srcdir': self.cfg['start_dir'], + 'tmp_pylibdir': tmp_pylibdir, 'installdir': tmp_installdir, 'parallel': self.cfg.parallel, + 'pytest_filter': pytest_filter, } MesonNinja.test_step(self) @@ -208,6 +222,7 @@ def test_step(self): 'srcdir': self.cfg['start_dir'], 'installdir': '', 'parallel': self.cfg.parallel, + 'pytest_filter': '', } FortranPythonPackage.test_step(self) From a1d17980fe9480def56cf3f67e6b00cba1ef5e30 Mon Sep 17 00:00:00 2001 From: Pavel Tomanek <99190809+pavelToman@users.noreply.github.com> Date: Thu, 28 May 2026 14:29:00 +0200 Subject: [PATCH 31/62] Update scipy.py - whiteline --- easybuild/easyblocks/s/scipy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/easyblocks/s/scipy.py b/easybuild/easyblocks/s/scipy.py index 2f71e7493c2..8c8382e34eb 100644 --- a/easybuild/easyblocks/s/scipy.py +++ b/easybuild/easyblocks/s/scipy.py @@ -193,7 +193,7 @@ def test_step(self): tmp_pylibdir = os.path.join(tmp_installdir, det_pylibdir()) self.prepare_python() - + pytest_filter = "" if LooseVersion(self.version) >= LooseVersion('1.17') and not self.cfg['enable_slow_tests']: pytest_filter = " -m 'not slow'" From cd8f3aa3c7917a9851a8438235fe4e5de7122c0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Andr=C3=A9=20Reuter?= Date: Thu, 28 May 2026 14:42:59 +0200 Subject: [PATCH 32/62] pythonpackage: Limit ulimit stack size when unlimited --- easybuild/easyblocks/generic/pythonpackage.py | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/easybuild/easyblocks/generic/pythonpackage.py b/easybuild/easyblocks/generic/pythonpackage.py index 4f4aade8ddb..bfd6a6b484c 100644 --- a/easybuild/easyblocks/generic/pythonpackage.py +++ b/easybuild/easyblocks/generic/pythonpackage.py @@ -32,6 +32,7 @@ @author: Jens Timmerman (Ghent University) @author: Alexander Grund (TU Dresden) @author: Samuel Moors (Vrije Universiteit Brussel) +@author: Jan Andre Reuter (Forschungszentrum Jülich) """ import os import re @@ -44,11 +45,12 @@ from easybuild.base import fancylogger from easybuild.easyblocks.python import EXTS_FILTER_DUMMY_PACKAGES, EXTS_FILTER_PYTHON_PACKAGES, set_py_env_vars from easybuild.easyblocks.python import det_installed_python_packages, det_pip_version, run_pip_check, run_pip_list +from easybuild.easyblocks.python import UNLIMITED from easybuild.framework.easyconfig import CUSTOM from easybuild.framework.easyconfig.default import DEFAULT_CONFIG from easybuild.framework.easyconfig.templates import PYPI_SOURCE from easybuild.framework.extensioneasyblock import ExtensionEasyBlock -from easybuild.tools.build_log import EasyBuildError, print_msg +from easybuild.tools.build_log import EasyBuildError, print_msg, print_warning from easybuild.tools.config import build_option, PYTHONPATH, EBPYTHONPREFIXES from easybuild.tools.filetools import change_dir, mkdir, read_file, remove_dir, symlink, which, write_file, search_file from easybuild.tools.modules import ModEnvVarType, get_software_root @@ -58,6 +60,12 @@ from easybuild.tools.hooks import CONFIGURE_STEP, BUILD_STEP, TEST_STEP, INSTALL_STEP +# Default ulimit for stack size, enorced when ulimit is set to unlimited on the system. +# This is especially important with Python 3.14 and newer, where an unlimited stack size +# can yield an infinite recursion in recoursion tests, eventually filling up all available +# memory. See: https://github.com/python/cpython/issues/143460 +ULIMIT_DEFAULT = 8192 + # not 'easy_install' deliberately, to avoid that pkg installations listed in easy-install.pth get preference # '.' is required at the end when using easy_install/pip in unpacked source dir EASY_INSTALL_TARGET = "easy_install" @@ -513,6 +521,8 @@ def extra_options(extra_vars=None): "are correct.", CUSTOM], 'runtest': [True, "Run unit tests.", CUSTOM], # overrides default 'testinstall': [False, "Install into temporary directory prior to running the tests.", CUSTOM], + 'ulimit': [None, f"Set ulimit -s to specified value. Default: Limit to {ULIMIT_DEFAULT} if unlimited.", + CUSTOM], 'unpack_sources': [None, "Unpack sources prior to build/install. Defaults to 'True' except for whl files", CUSTOM], # A version of 0.0.0 is usually an error on installation unless the package does really not provide a @@ -594,6 +604,7 @@ def __init__(self, *args, **kwargs): self.multi_python = 'Python' in self.cfg['multi_deps'] self.determine_install_command() + self.set_ulimit() set_py_env_vars(self.log) @@ -609,6 +620,39 @@ def __init__(self, *args, **kwargs): else: raise + def set_ulimit(self): + """ + Sets the ulimit stack size based on the ulimit config option. + Stack size is never set to any value above the hard limit specified by the system. + If the user has not set any stack size and the system specifies unlimited, set the + value to the default of ULIMIT_DEFAULT. + If the system specified anything else than unlimited, we only update the stack size + if it was set by the user. + """ + # determine current stack size limit + res = run_shell_cmd("ulimit -s", hidden=True) + curr_ulimit_s = res.output.strip() + if not self.cfg['ulimit']: + if curr_ulimit_s != UNLIMITED: + return + self.cfg['ulimit'] = ULIMIT_DEFAULT + + # figure out hard limit for stack size limit; + # this determines whether or not we can use "ulimit -s unlimited" + res = run_shell_cmd("ulimit -s -H", hidden=True) + max_ulimit_s = res.output.strip() + + if int(self.cfg['ulimit']) < int(max_ulimit_s): + msg = "Current stack size limit is %s, and can not be set to %s due to hard limit of %s;" + msg += " setting stack size limit to %s instead, " + msg += " this may break part of the compilation (e.g. hashlib)..." + print_warning(msg % (curr_ulimit_s, UNLIMITED, max_ulimit_s, max_ulimit_s)) + self.cfg['ulimit'] = max_ulimit_s + + self.log.info(f"Current stack size limit is {curr_ulimit_s}, limiting stack size to {ULIMIT_DEFAULT}") + for opt in 'prebuildopts', 'pretestopts', 'preconfigopts': + self.cfg.update(opt, "ulimit -s %s && " % self.cfg['ulimit']) + def determine_install_command(self): """ Determine install command to use. From db113b26493d006b3a1590c94ea071187e883f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Andr=C3=A9=20Reuter?= Date: Thu, 28 May 2026 17:07:48 +0200 Subject: [PATCH 33/62] pythonpackage: Respect max ulimit of unlimited MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jan André Reuter --- easybuild/easyblocks/generic/pythonpackage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/easyblocks/generic/pythonpackage.py b/easybuild/easyblocks/generic/pythonpackage.py index bfd6a6b484c..7647e81b25e 100644 --- a/easybuild/easyblocks/generic/pythonpackage.py +++ b/easybuild/easyblocks/generic/pythonpackage.py @@ -642,7 +642,7 @@ def set_ulimit(self): res = run_shell_cmd("ulimit -s -H", hidden=True) max_ulimit_s = res.output.strip() - if int(self.cfg['ulimit']) < int(max_ulimit_s): + if max_ulimit_s != UNLIMITED and int(self.cfg['ulimit']) < int(max_ulimit_s): msg = "Current stack size limit is %s, and can not be set to %s due to hard limit of %s;" msg += " setting stack size limit to %s instead, " msg += " this may break part of the compilation (e.g. hashlib)..." From 6e3ccd06b20c26722ee1fba11680e4bc82cc4a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Andr=C3=A9=20Reuter?= Date: Thu, 28 May 2026 17:20:47 +0200 Subject: [PATCH 34/62] pythonpackage: Fix typos in comments Co-authored-by: Jasper Grimm <65227842+jfgrimm@users.noreply.github.com> --- easybuild/easyblocks/generic/pythonpackage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/easybuild/easyblocks/generic/pythonpackage.py b/easybuild/easyblocks/generic/pythonpackage.py index 7647e81b25e..9f34937eab0 100644 --- a/easybuild/easyblocks/generic/pythonpackage.py +++ b/easybuild/easyblocks/generic/pythonpackage.py @@ -60,9 +60,9 @@ from easybuild.tools.hooks import CONFIGURE_STEP, BUILD_STEP, TEST_STEP, INSTALL_STEP -# Default ulimit for stack size, enorced when ulimit is set to unlimited on the system. +# Default ulimit for stack size, enforced when ulimit is set to unlimited on the system. # This is especially important with Python 3.14 and newer, where an unlimited stack size -# can yield an infinite recursion in recoursion tests, eventually filling up all available +# can yield an infinite recursion in recursion tests, eventually filling up all available # memory. See: https://github.com/python/cpython/issues/143460 ULIMIT_DEFAULT = 8192 From ddd0c243e00227bd3c4d51dfa254aee020687ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Andr=C3=A9=20Reuter?= Date: Sat, 30 May 2026 13:03:14 +0200 Subject: [PATCH 35/62] pythonpackage: fix warning for max ulimit --- easybuild/easyblocks/generic/pythonpackage.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/easybuild/easyblocks/generic/pythonpackage.py b/easybuild/easyblocks/generic/pythonpackage.py index 9f34937eab0..746474168dd 100644 --- a/easybuild/easyblocks/generic/pythonpackage.py +++ b/easybuild/easyblocks/generic/pythonpackage.py @@ -638,15 +638,15 @@ def set_ulimit(self): self.cfg['ulimit'] = ULIMIT_DEFAULT # figure out hard limit for stack size limit; - # this determines whether or not we can use "ulimit -s unlimited" + # this determines whether or not we can use "ulimit -s self.cfg['ulimit']" res = run_shell_cmd("ulimit -s -H", hidden=True) max_ulimit_s = res.output.strip() if max_ulimit_s != UNLIMITED and int(self.cfg['ulimit']) < int(max_ulimit_s): msg = "Current stack size limit is %s, and can not be set to %s due to hard limit of %s;" msg += " setting stack size limit to %s instead, " - msg += " this may break part of the compilation (e.g. hashlib)..." - print_warning(msg % (curr_ulimit_s, UNLIMITED, max_ulimit_s, max_ulimit_s)) + msg += " this may break part of the compilation..." + print_warning(msg % (curr_ulimit_s, self.cfg['ulimit'], max_ulimit_s, max_ulimit_s)) self.cfg['ulimit'] = max_ulimit_s self.log.info(f"Current stack size limit is {curr_ulimit_s}, limiting stack size to {ULIMIT_DEFAULT}") From 109e5967b5f7feaa4ba1b0f4ecc41b665baa4b40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=96hman?= Date: Mon, 8 Jun 2026 16:08:28 +0200 Subject: [PATCH 36/62] Allow for either Mesa or OpenGL as dep for VMD --- easybuild/easyblocks/v/vmd.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/easybuild/easyblocks/v/vmd.py b/easybuild/easyblocks/v/vmd.py index 235940b7d91..3de8bc5f7f5 100644 --- a/easybuild/easyblocks/v/vmd.py +++ b/easybuild/easyblocks/v/vmd.py @@ -68,13 +68,13 @@ def configure_step(self): """ # make sure required dependencies are available deps = {} - for dep in ['FLTK', 'Mesa', 'netCDF', 'Python', 'Tcl', 'Tk']: + for dep in ['FLTK', 'netCDF', 'Python', 'Tcl', 'Tk']: deps[dep] = get_software_root(dep) if deps[dep] is None: raise EasyBuildError("Required dependency %s is missing", dep) - # optional dependencies - for dep in ['ACTC', 'CUDA', 'OptiX']: + # optional dependencies (either Mesa or OpenGL should be there) + for dep in ['ACTC', 'CUDA', 'OptiX', 'Mesa', 'OpenGL']: deps[dep] = get_software_root(dep) # specify Tcl/Tk locations & libraries @@ -172,6 +172,8 @@ def configure_step(self): if deps[key]: if key == 'Mesa': self.cfg.update('configopts', "OPENGL MESA", allow_duplicate=False) + if key == 'OpenGL': + self.cfg.update('configopts', "OPENGL MESA", allow_duplicate=False) elif key == 'OptiX': self.cfg.update('configopts', "LIBOPTIX", allow_duplicate=False) elif key == 'Python': From ebcd874f5317e18ddf0f90cb14903a633e013a6e Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 9 Jun 2026 19:02:20 +0200 Subject: [PATCH 37/62] enhance SAMtools easyblock to specify ncursesw libraries to link to via `CURSES_LIB` --- easybuild/easyblocks/s/samtools.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/easybuild/easyblocks/s/samtools.py b/easybuild/easyblocks/s/samtools.py index 31772f53726..3957293d61d 100644 --- a/easybuild/easyblocks/s/samtools.py +++ b/easybuild/easyblocks/s/samtools.py @@ -92,6 +92,13 @@ def configure_step(self): if var in os.environ: self.cfg.update('buildopts', '%s="%s"' % (var, os.getenv(var))) + # also specify ncursesw libraries to link to, + # to avoid that detection of ncurses/ncursesw is broken due to error like: + # error adding symbols: DSO missing from command line + # only really required when ncurses was configured in a particular way, + # see also https://bugs.gentoo.org/457530 + self.cfg.update('configopts', 'CURSES_LIB="$(pkgconf --libs ncursesw)"') + # configuring with --prefix only supported with v1.3 and more recent if LooseVersion(self.version) >= LooseVersion('1.3'): super().configure_step() From 329a99381714b380d8c68e9cb55d67409f08fcfb Mon Sep 17 00:00:00 2001 From: Simon Branford Date: Wed, 10 Jun 2026 09:13:35 +0100 Subject: [PATCH 38/62] use `sanity_check_load_module` in sanity check step of custom easyblock for GROMACS --- easybuild/easyblocks/g/gromacs.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index b7fb1754c3d..51dca21278c 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -647,6 +647,9 @@ def make_module_step(self, *args, **kwargs): def sanity_check_step(self): """Custom sanity check for GROMACS.""" + # Load module to prepare environment for sanity check. + mod_data = super().sanity_check_load_module() + dirs = [os.path.join('include', 'gromacs')] # in GROMACS v5.1, only 'gmx' binary is there @@ -727,6 +730,9 @@ def sanity_check_step(self): } super().sanity_check_step(custom_paths=custom_paths) + if mod_data: + self.clean_up_fake_module(mod_data) + def run_all_steps(self, *args, **kwargs): """ Put configure options in place for different variants, (no)mpi, single/double precision. From f2ea0d6ceacd9aa89b58ce394d9a82204de2f494 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 11 Jun 2026 08:55:50 +0200 Subject: [PATCH 39/62] refactor custom easyblock for ESPResSo to avoid copy-pasting across logic for different ESPResSo versions + add custom sanity_check_step method --- easybuild/easyblocks/e/espresso.py | 226 ++++++++++++++--------------- 1 file changed, 107 insertions(+), 119 deletions(-) diff --git a/easybuild/easyblocks/e/espresso.py b/easybuild/easyblocks/e/espresso.py index f91cb281596..261b7f5e0c9 100644 --- a/easybuild/easyblocks/e/espresso.py +++ b/easybuild/easyblocks/e/espresso.py @@ -23,14 +23,12 @@ import re from easybuild.easyblocks.generic.cmakeninja import CMakeNinja -from easybuild.tools.systemtools import get_cpu_architecture, get_cpu_features -from easybuild.tools.systemtools import X86_64 -from easybuild.tools.systemtools import get_shared_lib_ext -from easybuild.tools.utilities import trace_msg -from easybuild.tools.build_log import EasyBuildError, print_error -from easybuild.tools.filetools import remove_file, remove_dir -from easybuild.tools.modules import get_software_root, get_software_version from easybuild.tools import LooseVersion +from easybuild.tools.build_log import EasyBuildError +from easybuild.tools.filetools import read_file, remove_dir, remove_file, write_file +from easybuild.tools.modules import get_software_root, get_software_version +from easybuild.tools.systemtools import X86_64, get_cpu_architecture, get_cpu_features, get_shared_lib_ext +from easybuild.tools.utilities import trace_msg class EB_ESPResSo(CMakeNinja): @@ -74,8 +72,9 @@ def _patch_fetchcontent(self): cmakelists_path = os.path.join(extracted_paths['espresso'], 'CMakeLists.txt') else: raise EasyBuildError(f"espresso not found in extracted_paths dict: {extracted_paths}") - with open(cmakelists_path, 'r') as f: - content = f.read() + + content = read_file(cmakelists_path) + for name, local_uri in extracted_paths.items(): if name == 'espresso': continue @@ -84,10 +83,13 @@ def _patch_fetchcontent(self): if m is None: raise EasyBuildError(f'{name} is not part of the ESPResSo FetchContent workflow') content = re.sub(pattern, f'FetchContent_Declare({name} URL {local_uri}', content, flags=re.IGNORECASE) - with open(cmakelists_path, 'w') as f: - f.write(content) + + write_file(cmakelists_path, content) def _get_version(self): + """ + Internal helper function to get ESPResSo version + """ if '.' in self.version: version = tuple(LooseVersion(self.version).version) else: @@ -95,6 +97,9 @@ def _get_version(self): return version def _set_exe_linker_flags(self): + """ + Internal helper function to set DCMAKE_EXE_LINKER_FLAGS configure option + """ exe_linker_flags_relpaths = [] if get_software_root('HeFFTe'): exe_linker_flags_relpaths.append('heffte-build') @@ -111,7 +116,10 @@ def _set_exe_linker_flags(self): for path in exe_linker_flags_relpaths) self.cfg.update('configopts', f' -DCMAKE_EXE_LINKER_FLAGS="-Wl,-rpath-link,{exe_linker_flags}" ') - def _configure_step_release_420(self): + def _set_configure_options_release_420(self): + """ + Internal helper function to set configure options for ESPResSo v4.2+ (< 5.0) + """ for dep in ['CUDA', 'GSL', 'FFTW', 'Python', 'ScaFaCoS']: dep_flag = 'OFF' if get_software_root(dep): @@ -123,75 +131,21 @@ def _configure_step_release_420(self): # make sure the right Python is used (note: -DPython3_EXECUTABLE or -DPython_EXECUTABLE does not work!) self.cfg.update('configopts', f' -DPYTHON_EXECUTABLE={get_software_root("Python")}/bin/python') - # list packaged files - pyshortver = '.'.join(get_software_version('Python').split('.')[:2]) - _libs = [ - 'Espresso_config', 'Espresso_core', 'Espresso_script_interface', - 'Espresso_shapes', '_init', 'analyze', 'code_info', 'electrokinetics', - 'galilei', 'integrate', 'interactions', 'lb', 'particle_data', 'polymer', - 'profiler', 'script_interface', 'system', 'thermostat', 'utils', 'version', - ] - _python_modules = [ - '__init__.py', 'collision_detection.py', 'accumulators.py', - 'constraints.py', 'electrostatics.py', 'magnetostatics.py', - 'observables.py', 'reaction_methods.py', - ] - if get_software_root('CUDA'): - _libs.append('cuda_init') - _binaries = ['ipypresso', 'pypresso'] - _lib_path = f'lib/python{pyshortver}/site-packages/espressomd' - - return _binaries, _lib_path, _libs, _python_modules - - def _configure_step_release_500(self): + def _set_configure_options_release_500(self): + """ + Internal helper function to set configure options for ESPResSo v5.0 + """ cpu_features = get_cpu_features() for dep in ['CUDA', 'GSL', 'FFTW', 'Python', 'ScaFaCoS', 'HDF5', 'NLopt']: dep_flag = 'OFF' if get_software_root(dep): dep_flag = 'ON' self.cfg.update('configopts', f"-DESPRESSO_BUILD_WITH_{dep.upper()}={dep_flag}") - if get_software_root('Kokkos') and get_software_root('Cabana'): - self.cfg.update('configopts', ' -DESPRESSO_BUILD_WITH_SHARED_MEMORY_PARALLELISM=ON') - self.cfg.update('configopts', ' -DESPRESSO_BUILD_WITH_STOKESIAN_DYNAMICS=OFF') - self.cfg.update('configopts', ' -DESPRESSO_BUILD_WITH_WALBERLA=ON') - if get_cpu_architecture() == X86_64 and 'avx2' in cpu_features: - self.cfg.update('configopts', ' -DESPRESSO_BUILD_WITH_WALBERLA_AVX=ON') - self.cfg.update('configopts', ' -DESPRESSO_BUILD_TESTS=ON') - self._set_exe_linker_flags() - - # build_cmd_targets does not work with CMakeNinja, use buildopts instead - self.cfg['buildopts'] = 'espresso_packaging_dependencies' - - # list packaged files - pyshortver = '.'.join(get_software_version('Python').split('.')[:2]) - _libs = [ - 'espresso_core', 'espresso_shapes', 'espresso_walberla', - 'espresso_script_interface', 'script_interface', 'utils', '_init', - ] - _python_modules = [ - '__init__.py', 'accumulators.py', 'collision_detection.py', - 'constraints.py', 'electrokinetics.py', 'electrostatics.py', - 'magnetostatics.py', 'lb.py', 'lees_edwards.py', 'observables.py', - 'particle_data.py', 'reaction_methods.py', 'system.py', - 'thermostat.py', 'version.py', - ] - if get_software_root('HDF5'): - _libs.append('espresso_hdf5') - _python_modules.append('io/writer/h5md.py') - if get_software_root('CUDA'): - _python_modules.append('cuda_init.py') - _binaries = ['ipypresso', 'pypresso'] - _lib_path = f'lib/python{pyshortver}/site-packages/espressomd' - return _binaries, _lib_path, _libs, _python_modules + version = self._get_version() + if version[:2] < (5, 1) and get_software_root('Kokkos') and get_software_root('Cabana'): + self.cfg.update('configopts', ' -DESPRESSO_BUILD_WITH_SHARED_MEMORY_PARALLELISM=ON') - def _configure_step_release_510(self): - cpu_features = get_cpu_features() - for dep in ['CUDA', 'GSL', 'FFTW', 'Python', 'ScaFaCoS', 'HDF5', 'NLopt']: - dep_flag = 'OFF' - if get_software_root(dep): - dep_flag = 'ON' - self.cfg.update('configopts', f"-DESPRESSO_BUILD_WITH_{dep.upper()}={dep_flag}") self.cfg.update('configopts', ' -DESPRESSO_BUILD_WITH_STOKESIAN_DYNAMICS=OFF') self.cfg.update('configopts', ' -DESPRESSO_BUILD_WITH_WALBERLA=ON') if get_cpu_architecture() == X86_64 and 'avx2' in cpu_features: @@ -202,64 +156,29 @@ def _configure_step_release_510(self): # build_cmd_targets does not work with CMakeNinja, use buildopts instead self.cfg['buildopts'] = 'espresso_packaging_dependencies' - # list packaged files - pyshortver = '.'.join(get_software_version('Python').split('.')[:2]) - _libs = [ - 'espresso_core', 'espresso_shapes', 'espresso_walberla', - 'espresso_script_interface', 'script_interface', 'utils', '_init', - ] - _python_modules = [ - '__init__.py', 'accumulators.py', 'collision_detection.py', - 'constraints.py', 'electrokinetics.py', 'electrostatics.py', - 'magnetostatics.py', 'lb.py', 'lees_edwards.py', 'observables.py', - 'particle_data.py', 'reaction_methods.py', 'system.py', - 'thermostat.py', 'version.py', - ] - if get_software_root('HDF5'): - _libs.append('espresso_hdf5') - _python_modules.append('io/writer/h5md.py') - if get_software_root('CUDA'): - _python_modules.append('cuda_init.py') - _binaries = ['ipypresso', 'pypresso'] - _lib_path = f'lib/python{pyshortver}/site-packages/espressomd' - - return _binaries, _lib_path, _libs, _python_modules - def configure_step(self): + """ + Custom configure step for ESPResSo + """ # patch FetchContent to avoid re-downloading dependencies self._patch_fetchcontent() version = self._get_version() - if version == 'commit': - paths = self._configure_step_release_510() - elif version[:2] >= (5, 1): - paths = self._configure_step_release_510() - elif version[:2] >= (5, 0): - paths = self._configure_step_release_500() + if version[:2] >= (5, 0): + paths = self._set_configure_options_release_500() elif version[:2] >= (4, 2): - paths = self._configure_step_release_420() + paths = self._set_configure_options_release_420() else: raise EasyBuildError( f'EasyBlock {self.__class__.__name__} doesn\'t implement the ' f'configure step for ESPResSo {self.version}') - _binaries, _lib_path, _libs, _python_modules = paths - - self.cfg['sanity_check_paths'] = { - 'files': [f'bin/{x}' for x in _binaries] + - [f'{_lib_path}/{x}.{get_shared_lib_ext()}' for x in _libs] + - [f'{_lib_path}/{x}' for x in _python_modules], - 'dirs': ['bin', 'lib'] - } - self.cfg['sanity_check_commands'] = [ - 'pypresso -h', 'ipypresso -h', - 'pypresso -c "import espressomd.version;print(espressomd.version.friendly())"', - 'python3 -c "import espressomd.version;print(espressomd.version.friendly())"', - ] - return super(EB_ESPResSo, self).configure_step() def test_step(self): + """ + Custom test step for ESPResSo + """ version = self._get_version() if version == 'commit' or version[:2] >= (5, 0): testopts = self.cfg.get('testopts', '') @@ -298,9 +217,78 @@ def delete_file(path): delete_file(f'{lib_dir}/{path}') def post_processing_step(self): + """ + Custom post-processing step for ESPResSo: clean up some auxilary files + """ try: self._cleanup_aux_files() except Exception as err: - print_error('Failed to remove some auxiliary files ' - f'(easyblock: {self.__class__.__name__}): {err}') + error_msg = "Failed to remove some auxiliary files " + error_msg = f"(easyblock: {self.__class__.__name__}): {err}" + raise EasyBuildError(error_msg) return super(EB_ESPResSo, self).post_processing_step() + + def sanity_check_step(self): + """ + Custom sanity check step for ESPResSo + """ + version = self._get_version() + + # libraries + if version[:2] >= (5, 0): + _libs = [ + 'espresso_core', 'espresso_shapes', 'espresso_walberla', + 'espresso_script_interface', 'script_interface', 'utils', '_init', + ] + else: + _libs = [ + 'Espresso_config', 'Espresso_core', 'Espresso_script_interface', + 'Espresso_shapes', '_init', 'analyze', 'code_info', 'electrokinetics', + 'galilei', 'integrate', 'interactions', 'lb', 'particle_data', 'polymer', + 'profiler', 'script_interface', 'system', 'thermostat', 'utils', 'version', + ] + + # Python modules + _python_modules = [ + '__init__.py', 'collision_detection.py', 'accumulators.py', + 'constraints.py', 'electrostatics.py', 'magnetostatics.py', + 'observables.py', 'reaction_methods.py', + ] + if version[:2] >= (5, 0): + _extra_python_modules = [ + 'electrokinetics.py', 'lb.py', 'lees_edwards.py', + 'particle_data.py', 'system.py', 'thermostat.py', 'version.py', + ] + _python_modules = sorted(_python_modules + _extra_python_modules) + + if get_software_root('HDF5'): + _libs.append('espresso_hdf5') + _python_modules.append(os.path.join('io', 'writer', 'h5md.py')) + if get_software_root('CUDA'): + if version[:2] >= (5, 0): + _python_modules.append('cuda_init.py') + else: + _libs.append('cuda_init') + + # binaries + _binaries = ['ipypresso', 'pypresso'] + + # Python package directory + pyshortver = '.'.join(get_software_version('Python').split('.')[:2]) + _lib_path = f'lib/python{pyshortver}/site-packages/espressomd' + + custom_paths = { + 'files': [f'bin/{x}' for x in _binaries] + + [f'{_lib_path}/{x}.{get_shared_lib_ext()}' for x in _libs] + + [f'{_lib_path}/{x}' for x in _python_modules], + 'dirs': ['bin', 'lib'] + } + custom_commands = [ + "pypresso -h", + "ipypresso -h", + 'pypresso -c "import espressomd.version;print(espressomd.version.friendly())"', + 'python3 -c "import espressomd.version;print(espressomd.version.friendly())"', + ] + + # call out to parent to do the actual sanity checking, pass through custom paths + super().sanity_check_step(custom_paths=custom_paths, custom_commads=custom_commands) From 3e76ad57cc16ebb6484effc4348182bf61c03720 Mon Sep 17 00:00:00 2001 From: JarneRenders Date: Thu, 11 Jun 2026 10:52:52 +0200 Subject: [PATCH 40/62] ABAQUS: fix incorrect plugins path --- easybuild/easyblocks/a/abaqus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/easyblocks/a/abaqus.py b/easybuild/easyblocks/a/abaqus.py index 59f41637eb4..6811dcce2d8 100644 --- a/easybuild/easyblocks/a/abaqus.py +++ b/easybuild/easyblocks/a/abaqus.py @@ -180,7 +180,7 @@ def install_step(self): (r"SIMULIA[0-9]*doc.*:", os.path.join(self.installdir, 'doc')), # if docs are installed (r"SimulationServices.*:", sim_subdir), (r"Choose the CODE installation directory.*:\n.*\n\n.*:", sim_subdir), - (r"SIMULIA/CAE.*:", cae_subdir), + (r"SIMULIA/CAE(?!/plugins).*:", cae_subdir), (r"location of your Abaqus services \(solvers\).*(\n.*){8}:\s*", sim_subdir), (r"Default.*SIMULIA/Commands\]:\s*", os.path.join(self.installdir, 'Commands')), (r"Default.*SIMULIA/CAE/plugins.*:\s*", os.path.join(self.installdir, 'plugins')), From 4fc1a9e790985be73176ea361cbed272164cb8b4 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 11 Jun 2026 09:22:53 +0200 Subject: [PATCH 41/62] fix minor style issue + fix typo --- easybuild/easyblocks/e/espresso.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/easybuild/easyblocks/e/espresso.py b/easybuild/easyblocks/e/espresso.py index 261b7f5e0c9..2369ef8bfaa 100644 --- a/easybuild/easyblocks/e/espresso.py +++ b/easybuild/easyblocks/e/espresso.py @@ -277,11 +277,12 @@ def sanity_check_step(self): pyshortver = '.'.join(get_software_version('Python').split('.')[:2]) _lib_path = f'lib/python{pyshortver}/site-packages/espressomd' + files = [f'bin/{x}' for x in _binaries] + files += [f'{_lib_path}/{x}.{get_shared_lib_ext()}' for x in _libs] + files += [f'{_lib_path}/{x}' for x in _python_modules] custom_paths = { - 'files': [f'bin/{x}' for x in _binaries] + - [f'{_lib_path}/{x}.{get_shared_lib_ext()}' for x in _libs] + - [f'{_lib_path}/{x}' for x in _python_modules], - 'dirs': ['bin', 'lib'] + 'files': files, + 'dirs': [], } custom_commands = [ "pypresso -h", @@ -291,4 +292,4 @@ def sanity_check_step(self): ] # call out to parent to do the actual sanity checking, pass through custom paths - super().sanity_check_step(custom_paths=custom_paths, custom_commads=custom_commands) + super().sanity_check_step(custom_paths=custom_paths, custom_commands=custom_commands) From 386f95854522b6890bb2b6d330ceb509403494fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Thu, 11 Jun 2026 11:27:13 +0200 Subject: [PATCH 42/62] Fix regression in the ESPResSo 4.2 sanity checks --- easybuild/easyblocks/e/espresso.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/easybuild/easyblocks/e/espresso.py b/easybuild/easyblocks/e/espresso.py index 2369ef8bfaa..23d2e7b2575 100644 --- a/easybuild/easyblocks/e/espresso.py +++ b/easybuild/easyblocks/e/espresso.py @@ -88,7 +88,7 @@ def _patch_fetchcontent(self): def _get_version(self): """ - Internal helper function to get ESPResSo version + Internal helper function to get the ESPResSo version. """ if '.' in self.version: version = tuple(LooseVersion(self.version).version) @@ -98,7 +98,7 @@ def _get_version(self): def _set_exe_linker_flags(self): """ - Internal helper function to set DCMAKE_EXE_LINKER_FLAGS configure option + Internal helper function to set the -DCMAKE_EXE_LINKER_FLAGS configure option. """ exe_linker_flags_relpaths = [] if get_software_root('HeFFTe'): @@ -118,7 +118,7 @@ def _set_exe_linker_flags(self): def _set_configure_options_release_420(self): """ - Internal helper function to set configure options for ESPResSo v4.2+ (< 5.0) + Internal helper function to set configure options for ESPResSo v4.2+ (< 5.0). """ for dep in ['CUDA', 'GSL', 'FFTW', 'Python', 'ScaFaCoS']: dep_flag = 'OFF' @@ -133,7 +133,7 @@ def _set_configure_options_release_420(self): def _set_configure_options_release_500(self): """ - Internal helper function to set configure options for ESPResSo v5.0 + Internal helper function to set configure options for ESPResSo v5.0+. """ cpu_features = get_cpu_features() for dep in ['CUDA', 'GSL', 'FFTW', 'Python', 'ScaFaCoS', 'HDF5', 'NLopt']: @@ -165,9 +165,9 @@ def configure_step(self): version = self._get_version() if version[:2] >= (5, 0): - paths = self._set_configure_options_release_500() + self._set_configure_options_release_500() elif version[:2] >= (4, 2): - paths = self._set_configure_options_release_420() + self._set_configure_options_release_420() else: raise EasyBuildError( f'EasyBlock {self.__class__.__name__} doesn\'t implement the ' @@ -262,7 +262,8 @@ def sanity_check_step(self): _python_modules = sorted(_python_modules + _extra_python_modules) if get_software_root('HDF5'): - _libs.append('espresso_hdf5') + if version[:2] >= (5, 0): + _libs.append('espresso_hdf5') _python_modules.append(os.path.join('io', 'writer', 'h5md.py')) if get_software_root('CUDA'): if version[:2] >= (5, 0): From 4ec63c949166ae19ee3e329e8ab5fc8358926384 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Thu, 11 Jun 2026 11:48:40 +0200 Subject: [PATCH 43/62] enhance test step in custom easyblock for ESPResSo (#2) --- easybuild/easyblocks/e/espresso.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/easybuild/easyblocks/e/espresso.py b/easybuild/easyblocks/e/espresso.py index 23d2e7b2575..cd2a8f0179b 100644 --- a/easybuild/easyblocks/e/espresso.py +++ b/easybuild/easyblocks/e/espresso.py @@ -184,8 +184,13 @@ def test_step(self): testopts = self.cfg.get('testopts', '') testopts += f' -j{self.cfg.parallel}' testopts += f' --resource-spec-file {self.builddir}/easybuild_obj/testsuite/python/resources.json' + testopts += ' --output-on-failure --no-tests=error' self.cfg['testopts'] = testopts + if self.cfg['runtest'] is None: + self.cfg['test_cmd'] = 'ctest' + self.cfg['runtest'] = '-L "unit_test|python_test"' + return super(EB_ESPResSo, self).test_step() def _cleanup_aux_files(self): From 4dd076f738937d8fb33d702a96ec9dc1d6457d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=96hman?= Date: Fri, 12 Jun 2026 14:51:55 +0200 Subject: [PATCH 44/62] add warning --- easybuild/easyblocks/v/vmd.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/easybuild/easyblocks/v/vmd.py b/easybuild/easyblocks/v/vmd.py index 3de8bc5f7f5..8bb885d1123 100644 --- a/easybuild/easyblocks/v/vmd.py +++ b/easybuild/easyblocks/v/vmd.py @@ -77,6 +77,9 @@ def configure_step(self): for dep in ['ACTC', 'CUDA', 'OptiX', 'Mesa', 'OpenGL']: deps[dep] = get_software_root(dep) + if not ('Mesa' in deps or 'OpenGL' in deps): + raise EasyBuildError("Required dependency Mesa or OpenGL is missing") + # specify Tcl/Tk locations & libraries tclinc = os.path.join(deps['Tcl'], 'include') tcllib = os.path.join(deps['Tcl'], 'lib') From 96b1aec16add9072e7bf1b1ffc41b30742d28add Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 13 Jun 2026 15:09:31 +0200 Subject: [PATCH 45/62] improve error reporting for Python package without any close matches in output of 'pip list' --- easybuild/easyblocks/p/python.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/easybuild/easyblocks/p/python.py b/easybuild/easyblocks/p/python.py index 172cde0dffe..b02a5cbe4d2 100644 --- a/easybuild/easyblocks/p/python.py +++ b/easybuild/easyblocks/p/python.py @@ -335,7 +335,11 @@ def run_pip_list(pkgs, python_cmd=None, unversioned_packages=None, check_names_v # Check for missing (likely wrong) packages names and propose close matches if name not in normalized_pip_pkgs: close_matches = difflib.get_close_matches(name, normalized_pip_pkgs.keys()) - missing_names.append(f"{name} (close matches in 'pip list' output: " + ', '.join(close_matches)) + if close_matches: + msg = f"{name} (close matches in 'pip list' output: " + ', '.join(close_matches) + ")" + else: + msg = f"{name} (no close matches found in 'pip list' output)" + missing_names.append(msg) # Check for missing (likely wrong) package versions elif version != normalized_pip_pkgs[name]: From cb5942044a53445682465c9f5220247d10799e22 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 13 Jun 2026 15:20:21 +0200 Subject: [PATCH 46/62] use --verbose when building/installing Rust when debugging is enabled --- easybuild/easyblocks/r/rust.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/easybuild/easyblocks/r/rust.py b/easybuild/easyblocks/r/rust.py index 9a8ba93d2c5..f4c7361fcba 100644 --- a/easybuild/easyblocks/r/rust.py +++ b/easybuild/easyblocks/r/rust.py @@ -62,8 +62,13 @@ def __init__(self, *args, **kwargs): # see https://rustc-dev-guide.rust-lang.org/building/how-to-build-and-run.html#what-is-xpy # note: ConfigureMake.build_step automatically adds '-j ' - self.cfg['build_cmd'] = "./x.py build" - self.cfg['install_cmd'] = "./x.py install -j %(parallel)s" + build_cmd = "./x.py build" + install_cmd = "./x.py install -j %(parallel)s" + if build_option('debug'): + build_cmd += ' --verbose' + install_cmd += ' --verbose' + self.cfg['build_cmd'] = build_cmd + self.cfg['install_cmd'] = install_cmd def _convert_runpaths_to_rpaths(self): """ From 50fd235f2155b84d8ec7f203cda6f42f0dac467d Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 13 Jun 2026 15:20:57 +0200 Subject: [PATCH 47/62] try running 'rustc --version' with $LD_LIBRARY_PATH unset in custom RPATH sanity check for Rust --- easybuild/easyblocks/r/rust.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/easybuild/easyblocks/r/rust.py b/easybuild/easyblocks/r/rust.py index f4c7361fcba..9b3c6fd79ac 100644 --- a/easybuild/easyblocks/r/rust.py +++ b/easybuild/easyblocks/r/rust.py @@ -34,11 +34,12 @@ from easybuild.easyblocks.generic.configuremake import ConfigureMake from easybuild.framework.easyconfig import CUSTOM from easybuild.tools import LooseVersion -from easybuild.tools.build_log import EasyBuildError +from easybuild.tools.build_log import EasyBuildError, EasyBuildExit from easybuild.tools.config import build_option from easybuild.tools.filetools import is_binary, read_file from easybuild.tools.run import run_shell_cmd from easybuild.tools.systemtools import get_shared_lib_ext +from easybuild.tools.utilities import trace_msg DEFAULT_CHANNEL = 'stable' @@ -160,3 +161,27 @@ def sanity_check_step(self): "rustc --version", ] return super().sanity_check_step(custom_paths=custom_paths, custom_commands=custom_commands) + + def sanity_check_rpath(self, *args, **kwargs): + """ + Custom RPATH sanity check for Rust + """ + fails = super().sanity_check_rpath(*args, **kwargs) + + mod_data = super().sanity_check_load_module() + + # check whether rustc binary works when $LD_LIBRARY_PATH is not set, + # see https://github.com/easybuilders/easybuild-easyconfigs/issues/26232 + cmd = "LD_LIBRARY_PATH= rustc --version" + trace_msg(f"Testing '{cmd}'...") + res = run_shell_cmd(cmd, fail_on_error=False, hidden=True) + if res.exit_code == EasyBuildExit.SUCCESS: + trace_msg(f"Result of testing '{cmd}': OK") + else: + trace_msg(f"Testing of '{cmd}' FAILED!") + fails.append(f"Command '{cmd}' failed (exit code {res.exit_code}): {res.output}") + + if mod_data: + self.clean_up_fake_module(mod_data) + + return fails From 0a4bb9b8578cb32988324703d682f3fffe13d78a Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 13 Jun 2026 18:46:15 +0200 Subject: [PATCH 48/62] make sure that correct RPATH linkage is done for binaries in Rust 1.90.0+ installation, where rust-lld linker is used --- easybuild/easyblocks/r/rust.py | 41 ++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/easybuild/easyblocks/r/rust.py b/easybuild/easyblocks/r/rust.py index 9b3c6fd79ac..9d6bbfaeec4 100644 --- a/easybuild/easyblocks/r/rust.py +++ b/easybuild/easyblocks/r/rust.py @@ -31,14 +31,16 @@ import os import re +import easybuild.tools.environment as env from easybuild.easyblocks.generic.configuremake import ConfigureMake from easybuild.framework.easyconfig import CUSTOM from easybuild.tools import LooseVersion from easybuild.tools.build_log import EasyBuildError, EasyBuildExit from easybuild.tools.config import build_option from easybuild.tools.filetools import is_binary, read_file +from easybuild.tools.modules import get_software_root from easybuild.tools.run import run_shell_cmd -from easybuild.tools.systemtools import get_shared_lib_ext +from easybuild.tools.systemtools import X86_64, get_cpu_architecture, get_shared_lib_ext from easybuild.tools.utilities import trace_msg DEFAULT_CHANNEL = 'stable' @@ -106,6 +108,38 @@ def _convert_runpaths_to_rpaths(self): else: self.log.debug(f"No RUNPATH section found in {runpath_file}") + def _control_rpath_link_flags(self): + """ + Helper function to set configure options required to control RPATH link flags + """ + # if rust.lld is used as linker, which is the default for Rust 1.90.0+ on x86_64 + # (see https://blog.rust-lang.org/2025/09/01/rust-lld-on-1.90.0-stable) + # we need to make sure that RPATH is set correctly for the binaries of the Rust compiler, + # see also https://github.com/easybuilders/easybuild-easyconfigs/issues/26232 + rust_version = LooseVersion(self.version) + if rust_version >= '1.90.0' and get_cpu_architecture() == X86_64: + gcc_root = get_software_root('GCCcore') + if not gcc_root: + raise EasyBuildError("Path to GCC installation could not be determined") + gcc_lib_path = os.path.join(gcc_root, 'lib64') + link_args = [ + # inject path to lib64 subdirectory of GCC into RPATH section; + # this is sufficient, no need to inject paths of all dependencies + # (as is done by RPATH wrappers) + f"-Clink-arg=-Wl,-rpath={gcc_lib_path}", + # force use of RPATH linking, rather than RUNPATH + "-Clink-arg=-Wl,--disable-new-dtags", + ] + # we use $RUSTFLAGS_BOOTSTRAP + $RUSTFLAGS_NOT_BOOTSTRAP to inject extra linker flags; + # Rust 1.93.0+ supports specifying rust.rustflags in bootstrap.toml, + # see https://github.com/rust-lang/rust/pull/148795, + # but this can't be set easily via --set option of configure script: + # the rustflags value is not injected into bootstrap.toml as a list, but as a string, + # leading to an error like: + # invalid type: string "[-Clink-arg=-Wl,-rpath=...]", expected a sequence for key rust.rustflags + env.setvar('RUSTFLAGS_BOOTSTRAP', ' '.join(link_args)) + env.setvar('RUSTFLAGS_NOT_BOOTSTRAP', ' '.join(link_args)) + def configure_step(self): """Custom configure step for Rust""" @@ -133,6 +167,9 @@ def configure_step(self): if 'Ninja' not in build_dep_names: self.cfg.update('configopts', "--set=llvm.ninja=false") + if self.toolchain.use_rpath: + self._control_rpath_link_flags() + super().configure_step() # avoid failure when home directory is an NFS mount, @@ -145,7 +182,7 @@ def install_step(self): """Custom install step for Rust""" super().install_step() - if build_option('rpath'): + if self.toolchain.use_rpath: self._convert_runpaths_to_rpaths() def sanity_check_step(self): From 5abe770e11810991e9dc111f36596879cabf6f15 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sat, 13 Jun 2026 19:34:55 +0200 Subject: [PATCH 49/62] inject all paths listed in $LIBRARY_PATH into RPATH section for Rust 1.90.0+ --- easybuild/easyblocks/r/rust.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/easybuild/easyblocks/r/rust.py b/easybuild/easyblocks/r/rust.py index 9d6bbfaeec4..c35f0892234 100644 --- a/easybuild/easyblocks/r/rust.py +++ b/easybuild/easyblocks/r/rust.py @@ -38,7 +38,6 @@ from easybuild.tools.build_log import EasyBuildError, EasyBuildExit from easybuild.tools.config import build_option from easybuild.tools.filetools import is_binary, read_file -from easybuild.tools.modules import get_software_root from easybuild.tools.run import run_shell_cmd from easybuild.tools.systemtools import X86_64, get_cpu_architecture, get_shared_lib_ext from easybuild.tools.utilities import trace_msg @@ -118,18 +117,13 @@ def _control_rpath_link_flags(self): # see also https://github.com/easybuilders/easybuild-easyconfigs/issues/26232 rust_version = LooseVersion(self.version) if rust_version >= '1.90.0' and get_cpu_architecture() == X86_64: - gcc_root = get_software_root('GCCcore') - if not gcc_root: - raise EasyBuildError("Path to GCC installation could not be determined") - gcc_lib_path = os.path.join(gcc_root, 'lib64') - link_args = [ - # inject path to lib64 subdirectory of GCC into RPATH section; - # this is sufficient, no need to inject paths of all dependencies - # (as is done by RPATH wrappers) - f"-Clink-arg=-Wl,-rpath={gcc_lib_path}", - # force use of RPATH linking, rather than RUNPATH - "-Clink-arg=-Wl,--disable-new-dtags", - ] + + lib_paths = [x for x in os.getenv('LIBRARY_PATH', '').split(os.pathsep) if x] + link_args = [f"-Clink-arg=-Wl,-rpath={x}" for x in lib_paths] + + # force use of RPATH linking, rather than RUNPATH + link_args.append("-Clink-arg=-Wl,--disable-new-dtags") + # we use $RUSTFLAGS_BOOTSTRAP + $RUSTFLAGS_NOT_BOOTSTRAP to inject extra linker flags; # Rust 1.93.0+ supports specifying rust.rustflags in bootstrap.toml, # see https://github.com/rust-lang/rust/pull/148795, From 7973eb26c6410d549ab6d012cf8dc4df7b5dceb0 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Sun, 14 Jun 2026 09:30:17 +0200 Subject: [PATCH 50/62] trickle down modified build directory to bundle components in custom easyblock for gnupg-bundle --- easybuild/easyblocks/g/gnupg_bundle.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/easybuild/easyblocks/g/gnupg_bundle.py b/easybuild/easyblocks/g/gnupg_bundle.py index b3b8e833d82..4ad4b099587 100644 --- a/easybuild/easyblocks/g/gnupg_bundle.py +++ b/easybuild/easyblocks/g/gnupg_bundle.py @@ -83,5 +83,9 @@ def __init__(self, *args, **kwargs): if len(self.builddir) > EB_gnupg_minus_bundle.MAX_UNIX_SOCKET_SAFE_BUILD_PATH_LENGTH: self.builddir = EB_gnupg_minus_bundle._get_unix_socket_compliant_buildpath(self) + # trickle down modified build dir to bundle components + for (_, comp_instance) in self.comp_instances: + comp_instance.builddir = self.builddir + print_msg("using modified build path to ensure test UNIX socket can be created: %s ..." % self.builddir) self.log.info("Using modified build path to ensure test UNIX socket can be created: %s", self.builddir) From 2b8f6cba100af87cb053b79d65d5e20aba256568 Mon Sep 17 00:00:00 2001 From: "J. Sassmannshausen" Date: Mon, 15 Jun 2026 13:46:17 +0100 Subject: [PATCH 51/62] configopts added for fine-tuning like GUI on/off --- easybuild/easyblocks/m/mrtrix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/easyblocks/m/mrtrix.py b/easybuild/easyblocks/m/mrtrix.py index 1825c853613..fb32afed65b 100644 --- a/easybuild/easyblocks/m/mrtrix.py +++ b/easybuild/easyblocks/m/mrtrix.py @@ -68,7 +68,7 @@ def configure_step(self): env.setvar('LDLIB', "%s -shared LDLIB_FLAGS OBJECTS -o LIB" % os.getenv('CXX')) env.setvar('QMAKE_CXX', os.getenv('CXX')) - cmd = "python configure -verbose" + cmd = "python configure -verbose " + self.cfg['configopts'] run_shell_cmd(cmd) From ae97fd9dd48cbfa9350e8237ed3173e12ac1e00d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Andr=C3=A9=20Reuter?= Date: Mon, 15 Jun 2026 16:17:42 +0200 Subject: [PATCH 52/62] ignore compiler-rt and lldb test failure for ptrace_scope > 1 --- easybuild/easyblocks/l/llvm.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/easybuild/easyblocks/l/llvm.py b/easybuild/easyblocks/l/llvm.py index a663cb8b6a8..ef63a3cc138 100644 --- a/easybuild/easyblocks/l/llvm.py +++ b/easybuild/easyblocks/l/llvm.py @@ -47,7 +47,7 @@ from easybuild.tools.config import ERROR, IGNORE, SEARCH_PATH_LIB_DIRS, build_option from easybuild.tools.environment import setvar from easybuild.tools.filetools import apply_regex_substitutions, change_dir, copy_dir, adjust_permissions -from easybuild.tools.filetools import mkdir, remove_file, symlink, which, write_file, remove_dir +from easybuild.tools.filetools import mkdir, remove_file, symlink, which, write_file, remove_dir, read_file from easybuild.tools.modules import MODULE_LOAD_ENV_HEADERS, get_software_root, get_software_version from easybuild.tools.run import run_shell_cmd, EasyBuildExit from easybuild.tools.systemtools import AARCH32, AARCH64, POWER, RISCV64, X86_64, POWER_LE @@ -409,6 +409,32 @@ def __init__(self, *args, **kwargs): self._cmakeopts = {} self._cfgopts = list(filter(None, self.cfg.get('configopts', '').split())) + @property + def ptrace_scope(self): + """ + Return if ptrace_scope is set to any value high enough to fail + LLVMs LLDB and compiler-rt tests + """ + try: + ptrace_scope_file = read_file('/proc/sys/kernel/yama/ptrace_scope') + return int(ptrace_scope_file) + except EasyBuildError: + # File might not exist, hence skip a potential error + self.log.info("Could not find or open /proc/sys/kernel/yama/ptrace_scope") + + result = run_shell_cmd("sysctl kernel.yama.ptrace_scope", fail_on_error=False, split_stderr=True) + if result: + try: + # Expected output: kernel.yama.ptrace_scope = 3 + return int(result.stdout.split("=")[-1]) + except ValueError: + self.log.info("Could not determine ptrace_scope value from sysctl output") + else: + self.log.info("Running sysctl kernel.yama.ptrace_scope failed.") + + self.log.info("Could not determine ptrace_scope. Assuming 0.") + return 0 + @property def gcc_prefix(self): """Return the GCC prefix (versioned folder in /lib).""" @@ -1486,6 +1512,12 @@ def test_step(self): if 'rocr-runtime' not in self.deps: self.ignore_patterns += ['amdgcn-amd-amdhsa'] self.log.warning("ROCr-Runtime not in dependencies, ignoring failing tests for AMDGPU target.") + # Ignore compiler-rt and lldb test failures if ptrace_scope is disabled, or higher than 1. + # In this case, debuggers and sanitizers may fail to attach to other processes. + if self.ptrace_scope > 1: + self.ignore_patterns += ['lldb-shell', 'lldb-unit' 'libFuzzer', 'AddressSanitizer', + 'HWAddressSanitizer', 'LeakSanitizer', 'SanitizerCommon', 'UBSan'] + self.log.warning("ptrace_scope > 1 was found, ignoring failing compiler-rt sanitizer and lldb tests.") max_failed = self.cfg['test_suite_max_failed'] if LooseVersion(get_software_version("CMake")) >= '3.19': From c546d71266c4d24f17dacce3cffe93751516aee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Andr=C3=A9=20Reuter?= Date: Mon, 15 Jun 2026 16:20:55 +0200 Subject: [PATCH 53/62] do not abort if ptrace_scope cannot be parsed to number --- easybuild/easyblocks/l/llvm.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/easybuild/easyblocks/l/llvm.py b/easybuild/easyblocks/l/llvm.py index ef63a3cc138..4160af1b16b 100644 --- a/easybuild/easyblocks/l/llvm.py +++ b/easybuild/easyblocks/l/llvm.py @@ -421,6 +421,8 @@ def ptrace_scope(self): except EasyBuildError: # File might not exist, hence skip a potential error self.log.info("Could not find or open /proc/sys/kernel/yama/ptrace_scope") + except ValueError: + self.log.info("Could not parse value of ptrace_scope file") result = run_shell_cmd("sysctl kernel.yama.ptrace_scope", fail_on_error=False, split_stderr=True) if result: From a2d917665398939a406ae27a36e10d1ab021f9f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Andr=C3=A9=20Reuter?= Date: Tue, 16 Jun 2026 06:56:54 +0200 Subject: [PATCH 54/62] fix missing comma for ignore patterns Co-authored-by: Davide Grassano <34096612+Crivella@users.noreply.github.com> --- easybuild/easyblocks/l/llvm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/easyblocks/l/llvm.py b/easybuild/easyblocks/l/llvm.py index 4160af1b16b..71146522485 100644 --- a/easybuild/easyblocks/l/llvm.py +++ b/easybuild/easyblocks/l/llvm.py @@ -1517,7 +1517,7 @@ def test_step(self): # Ignore compiler-rt and lldb test failures if ptrace_scope is disabled, or higher than 1. # In this case, debuggers and sanitizers may fail to attach to other processes. if self.ptrace_scope > 1: - self.ignore_patterns += ['lldb-shell', 'lldb-unit' 'libFuzzer', 'AddressSanitizer', + self.ignore_patterns += ['lldb-shell', 'lldb-unit', 'libFuzzer', 'AddressSanitizer', 'HWAddressSanitizer', 'LeakSanitizer', 'SanitizerCommon', 'UBSan'] self.log.warning("ptrace_scope > 1 was found, ignoring failing compiler-rt sanitizer and lldb tests.") From 2ba26714bd532df585d6b0e7b4fa1b167ffb088b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Andr=C3=A9=20Reuter?= Date: Tue, 16 Jun 2026 08:32:19 +0200 Subject: [PATCH 55/62] Switch to Framework function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jan André Reuter --- easybuild/easyblocks/l/llvm.py | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/easybuild/easyblocks/l/llvm.py b/easybuild/easyblocks/l/llvm.py index 71146522485..0eae85b8fb5 100644 --- a/easybuild/easyblocks/l/llvm.py +++ b/easybuild/easyblocks/l/llvm.py @@ -52,6 +52,7 @@ from easybuild.tools.run import run_shell_cmd, EasyBuildExit from easybuild.tools.systemtools import AARCH32, AARCH64, POWER, RISCV64, X86_64, POWER_LE from easybuild.tools.systemtools import get_cpu_architecture, get_cpu_family, get_shared_lib_ext +from easybuild.tools.systemtools import get_ptrace_scope from easybuild.easyblocks.generic.cmakemake import CMakeMake, get_cmake_python_config_dict @@ -409,34 +410,6 @@ def __init__(self, *args, **kwargs): self._cmakeopts = {} self._cfgopts = list(filter(None, self.cfg.get('configopts', '').split())) - @property - def ptrace_scope(self): - """ - Return if ptrace_scope is set to any value high enough to fail - LLVMs LLDB and compiler-rt tests - """ - try: - ptrace_scope_file = read_file('/proc/sys/kernel/yama/ptrace_scope') - return int(ptrace_scope_file) - except EasyBuildError: - # File might not exist, hence skip a potential error - self.log.info("Could not find or open /proc/sys/kernel/yama/ptrace_scope") - except ValueError: - self.log.info("Could not parse value of ptrace_scope file") - - result = run_shell_cmd("sysctl kernel.yama.ptrace_scope", fail_on_error=False, split_stderr=True) - if result: - try: - # Expected output: kernel.yama.ptrace_scope = 3 - return int(result.stdout.split("=")[-1]) - except ValueError: - self.log.info("Could not determine ptrace_scope value from sysctl output") - else: - self.log.info("Running sysctl kernel.yama.ptrace_scope failed.") - - self.log.info("Could not determine ptrace_scope. Assuming 0.") - return 0 - @property def gcc_prefix(self): """Return the GCC prefix (versioned folder in /lib).""" @@ -1516,7 +1489,7 @@ def test_step(self): self.log.warning("ROCr-Runtime not in dependencies, ignoring failing tests for AMDGPU target.") # Ignore compiler-rt and lldb test failures if ptrace_scope is disabled, or higher than 1. # In this case, debuggers and sanitizers may fail to attach to other processes. - if self.ptrace_scope > 1: + if get_ptrace_scope() > 1: self.ignore_patterns += ['lldb-shell', 'lldb-unit', 'libFuzzer', 'AddressSanitizer', 'HWAddressSanitizer', 'LeakSanitizer', 'SanitizerCommon', 'UBSan'] self.log.warning("ptrace_scope > 1 was found, ignoring failing compiler-rt sanitizer and lldb tests.") From 9e27fc431c97ae47c29a1ff2479f608fa3e69f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Andr=C3=A9=20Reuter?= Date: Tue, 16 Jun 2026 08:33:36 +0200 Subject: [PATCH 56/62] llvm: fix unused read_file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jan André Reuter --- easybuild/easyblocks/l/llvm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/easyblocks/l/llvm.py b/easybuild/easyblocks/l/llvm.py index 0eae85b8fb5..4dfb6d8abdc 100644 --- a/easybuild/easyblocks/l/llvm.py +++ b/easybuild/easyblocks/l/llvm.py @@ -47,7 +47,7 @@ from easybuild.tools.config import ERROR, IGNORE, SEARCH_PATH_LIB_DIRS, build_option from easybuild.tools.environment import setvar from easybuild.tools.filetools import apply_regex_substitutions, change_dir, copy_dir, adjust_permissions -from easybuild.tools.filetools import mkdir, remove_file, symlink, which, write_file, remove_dir, read_file +from easybuild.tools.filetools import mkdir, remove_file, symlink, which, write_file, remove_dir from easybuild.tools.modules import MODULE_LOAD_ENV_HEADERS, get_software_root, get_software_version from easybuild.tools.run import run_shell_cmd, EasyBuildExit from easybuild.tools.systemtools import AARCH32, AARCH64, POWER, RISCV64, X86_64, POWER_LE From cefa75bd73e76f76eca8967929dc5e07cfdeac2f Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Tue, 16 Jun 2026 11:51:42 +0200 Subject: [PATCH 57/62] use elif when checking name of different dependencies in OpenMPI configure step --- easybuild/easyblocks/o/openmpi.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/easybuild/easyblocks/o/openmpi.py b/easybuild/easyblocks/o/openmpi.py index 80d4f8cb7fc..1fab25e4c48 100644 --- a/easybuild/easyblocks/o/openmpi.py +++ b/easybuild/easyblocks/o/openmpi.py @@ -109,15 +109,15 @@ def config_opt_used(key, enable_opt=False): # libfabric option renamed in OpenMPI 3.1.0 to ofi if dep == 'libfabric' and LooseVersion(self.version) >= LooseVersion('3.1'): opt_name = 'ofi' - # needed in easybuild setup as rocm-llvm and hip live in separate dirs - if dep == 'HIP': + elif dep == 'HIP': opt_name = 'rocm' - if dep == 'UCC-ROCm': + elif dep == 'UCC-ROCm': opt_name = 'ucc' - if dep == 'UCX-ROCm': + elif dep == 'UCX-ROCm': opt_name = 'ucx' + # check again if option is already used, using new name if config_opt_used(opt_name): continue From 157b54259251b9bdb58c83919015343e07d3f88b Mon Sep 17 00:00:00 2001 From: crivella Date: Wed, 17 Jun 2026 12:17:47 +0200 Subject: [PATCH 58/62] Fix `--sanity-check-only` for `netcdf4-python` --- easybuild/easyblocks/n/netcdf4_python.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/easybuild/easyblocks/n/netcdf4_python.py b/easybuild/easyblocks/n/netcdf4_python.py index bc3c407e434..e97343d1a77 100644 --- a/easybuild/easyblocks/n/netcdf4_python.py +++ b/easybuild/easyblocks/n/netcdf4_python.py @@ -86,6 +86,9 @@ def test_step(self): def sanity_check_step(self): """Custom sanity check for netcdf4-python""" + # Required to have `self.pylibdir` set in --sanity-check-only mode + if self.python_cmd is None: + self.prepare_python() custom_paths = { 'files': ['bin/nc3tonc4', 'bin/nc4tonc3', 'bin/ncinfo'], 'dirs': [self.pylibdir], From d669926ea00a2b640fbc032e4d54537cf52278e1 Mon Sep 17 00:00:00 2001 From: Samuel Moors Date: Wed, 17 Jun 2026 14:03:32 +0200 Subject: [PATCH 59/62] keep check_names_versions and add strict_check --- easybuild/easyblocks/p/python.py | 101 ++++++++++++++++--------------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/easybuild/easyblocks/p/python.py b/easybuild/easyblocks/p/python.py index a27d6e4eb9b..75767266202 100644 --- a/easybuild/easyblocks/p/python.py +++ b/easybuild/easyblocks/p/python.py @@ -233,14 +233,16 @@ def normalize_pip(name): return REGEX_PIP_NORMALIZE.sub('-', name).lower() -def run_pip_list(pkgs, python_cmd=None, unversioned_packages=None, strict_check=None): +def run_pip_list(pkgs, python_cmd=None, unversioned_packages=None, check_names_versions=True, strict_check=None): """ Run pip list to verify normalized names and versions of installed Python packages :param pkgs: list of package tuples (name, version) as specified in the easyconfig :param python_cmd: Python command to use (if None, 'python' is used) :param unversioned_packages: set of Python packages to exclude in the version existence check - :param strict_check: boolean to indicate whether to raise an error if package names or versions don’t match + :param check_names_versions: boolean to indicate whether name and versions of Python packages should be checked + :param strict_check: boolean to indicate whether to raise an error if package names or versions don’t match, + or emit a warning """ log = fancylogger.getLogger('run_pip_list', fname=False) @@ -318,54 +320,55 @@ def run_pip_list(pkgs, python_cmd=None, unversioned_packages=None, strict_check= msg += "required (check the source for a pyproject.toml and see PEP517 for details on that)." pip_list_errors.append(msg) - normalized_pkgs = [(normalize_pip(name), version) for name, version in pkgs] - - missing_names = [] - missing_versions = [] - - for name, version in normalized_pkgs: - # Skip packages in the unversioned list: they have already been checked - if name in normalized_unversioned: - continue - - # Skip packages in the zero_pkg_names list: they have already been added to pip_list_errors - if name in zero_pkg_names: - continue - - # Check for missing (likely wrong) packages names and propose close matches - if name not in normalized_pip_pkgs: - close_matches = difflib.get_close_matches(name, normalized_pip_pkgs.keys()) - if close_matches: - msg = f"{name} (close matches in 'pip list' output: " + ', '.join(close_matches) + ")" + if check_names_versions: + normalized_pkgs = [(normalize_pip(name), version) for name, version in pkgs] + + missing_names = [] + missing_versions = [] + + for name, version in normalized_pkgs: + # Skip packages in the unversioned list: they have already been checked + if name in normalized_unversioned: + continue + + # Skip packages in the zero_pkg_names list: they have already been added to pip_list_errors + if name in zero_pkg_names: + continue + + # Check for missing (likely wrong) packages names and propose close matches + if name not in normalized_pip_pkgs: + close_matches = difflib.get_close_matches(name, normalized_pip_pkgs.keys()) + if close_matches: + msg = f"{name} (close matches in 'pip list' output: " + ', '.join(close_matches) + ")" + else: + msg = f"{name} (no close matches found in 'pip list' output)" + missing_names.append(msg) + + # Check for missing (likely wrong) package versions + elif version != normalized_pip_pkgs[name]: + missing_versions.append(f"{name} {version} (version in 'pip list' output: {normalized_pip_pkgs[name]})") + + log.info(f"Found {len(missing_names)} missing names and {len(missing_versions)} missing versions " + f"out of {len(pkgs)} packages") + + if missing_names: + missing_names_str = '\n'.join(missing_names) + msg = "The following Python packages were likely specified with a wrong name because they are missing " + msg += f"in the 'pip list' output (causes failure if --upload-test-report is set):\n{missing_names_str}" + if strict_check: + pip_list_errors.append(msg) else: - msg = f"{name} (no close matches found in 'pip list' output)" - missing_names.append(msg) - - # Check for missing (likely wrong) package versions - elif version != normalized_pip_pkgs[name]: - missing_versions.append(f"{name} {version} (version in 'pip list' output: {normalized_pip_pkgs[name]})") - - log.info(f"Found {len(missing_names)} missing names and {len(missing_versions)} missing versions " - f"out of {len(pkgs)} packages") - - if missing_names: - missing_names_str = '\n'.join(missing_names) - msg = "The following Python packages were likely specified with a wrong name because they are missing " - msg += f"in the 'pip list' output (causes failure if --upload-test-report is set):\n{missing_names_str}" - if strict_check: - pip_list_errors.append(msg) - else: - pip_list_warnings.append(msg) - - if missing_versions: - missing_versions_str = '\n'.join(missing_versions) - msg = "The following Python packages were likely specified with a wrong version because they have " - msg += "another version in the 'pip list' output (causes failure if --upload-test-report is set):\n" - msg += missing_versions_str - if strict_check: - pip_list_errors.append(msg) - else: - pip_list_warnings.append(msg) + pip_list_warnings.append(msg) + + if missing_versions: + missing_versions_str = '\n'.join(missing_versions) + msg = "The following Python packages were likely specified with a wrong version because they have " + msg += "another version in the 'pip list' output (causes failure if --upload-test-report is set):\n" + msg += missing_versions_str + if strict_check: + pip_list_errors.append(msg) + else: + pip_list_warnings.append(msg) if pip_list_warnings: print_warning(msg, log=log) From 00052a83f7f54b0bbf050135b865c650fa140c5f Mon Sep 17 00:00:00 2001 From: Sam Moors Date: Wed, 17 Jun 2026 15:43:02 +0200 Subject: [PATCH 60/62] Apply suggestion from @boegel Co-authored-by: Kenneth Hoste --- easybuild/easyblocks/p/python.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/easyblocks/p/python.py b/easybuild/easyblocks/p/python.py index 75767266202..7736ea2080a 100644 --- a/easybuild/easyblocks/p/python.py +++ b/easybuild/easyblocks/p/python.py @@ -371,7 +371,7 @@ def run_pip_list(pkgs, python_cmd=None, unversioned_packages=None, check_names_v pip_list_warnings.append(msg) if pip_list_warnings: - print_warning(msg, log=log) + print_warning('\n'.join(pip_list_warnings), log=log) if pip_list_errors: raise EasyBuildError('\n' + '\n'.join(pip_list_errors)) From fc7b2fda636e69272d7e9e8fc342aaff16f9a30d Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 17 Jun 2026 22:28:59 +0200 Subject: [PATCH 61/62] call set_ulimit in PythonPackage easyblock in prepare step rather than constructor, to fix error due to unresolved templates + fix log message in PythonPackage.set_ulimit --- easybuild/easyblocks/generic/pythonpackage.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/easybuild/easyblocks/generic/pythonpackage.py b/easybuild/easyblocks/generic/pythonpackage.py index fa1b4aa699e..72655daed24 100644 --- a/easybuild/easyblocks/generic/pythonpackage.py +++ b/easybuild/easyblocks/generic/pythonpackage.py @@ -605,7 +605,6 @@ def __init__(self, *args, **kwargs): self.multi_python = 'Python' in self.cfg['multi_deps'] self.determine_install_command() - self.set_ulimit() set_py_env_vars(self.log) @@ -650,7 +649,7 @@ def set_ulimit(self): print_warning(msg % (curr_ulimit_s, self.cfg['ulimit'], max_ulimit_s, max_ulimit_s)) self.cfg['ulimit'] = max_ulimit_s - self.log.info(f"Current stack size limit is {curr_ulimit_s}, limiting stack size to {ULIMIT_DEFAULT}") + self.log.info(f"Current stack size limit is {curr_ulimit_s}, limiting stack size to {self.cfg['ulimit']}") for opt in 'prebuildopts', 'pretestopts', 'preconfigopts': self.cfg.update(opt, "ulimit -s %s && " % self.cfg['ulimit']) @@ -717,6 +716,8 @@ def prepare_python(self): # set Python lib directories self.set_pylibdirs() + self.set_ulimit() + def _should_unpack_source(self): """Determine whether we need to unpack the source(s)""" From c396fc5f57d729a6e123cfc153b8cb74c2742c2a Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Wed, 17 Jun 2026 16:39:16 +0200 Subject: [PATCH 62/62] prepare release notes for EasyBuild v5.3.1 + bump version to 5.3.1 --- RELEASE_NOTES | 44 +++++++++++++++++++++++++++++--- easybuild/easyblocks/__init__.py | 2 +- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 2edfaea71db..637fa180f01 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -3,7 +3,43 @@ For more detailed information, please see the git log. These release notes can also be consulted at https://docs.easybuild.io/release-notes . -The latest version of easybuild-easyblocks provides 209 software-specific easyblocks and 47 generic easyblocks. +The latest version of easybuild-easyblocks provides 210 software-specific easyblocks and 47 generic easyblocks. + + +v5.3.1 (19 Jun 2026) +-------------------- + +update/bugfix release + +- new easyblocks: + - add custom easyblock for ESPResSo (#4066) +- bug fixes: + - fix location of `object_storage` for Dataset easyblock (#4128) + - enhance SAMtools easyblock to specify ncursesw libraries to link to via `CURSES_LIB` (#4149) + - use `sanity_check_load_module` in sanity check step of custom easyblock for GROMACS (#4151) + - fix incorrect plugins path in ABAQUS easyblock, which fixes long startup times (#4152) + - make sure that RPATH section for binaries in Rust 1.90.0+ installation includes path to runtime libraries of GCC compiler (#4156) + - trickle down modified build directory to bundle components in custom easyblock for gnupg-bundle (#4157) + - fix `--sanity-check-only` for netcdf4-python (#4161) +- enhancements: + - add Arm support to NAMD easyblock (#3348) + - enable hwloc, cuFFTMp, and HeFFTe support in GROMACS easyblock (#3531) + - enhance `run_pip_list` function to print warning for mismatched Python package names or versions (#4109) + - adapt sanity checks in OpenMPI easyblock for AMD ROCm (#4119) + - add `$CUDNN_HOME` and `$CUDNN_PATH` as extra environment variables to be set by cuDNN easyblock (#4130) + - enhance custom easyblock for OpenMPI to add ROCm support (#4132) + - support `postinstallcmds` for `Dataset` generic easyblock (#4138) + - add logging if PyTorch tests were successful (#4140) + - enhance `PythonPackage` easyblock to limit ulimit stack size when unlimited (#4144, #4164) + - allow for either Mesa or OpenGL as dependency for VMD (#4147) + - improve error reporting for Python package without any close matches in output of `pip list` (#4155) + - make custom easyblock for MRtrix aware of `configopts` easyconfig parameter (#4158) + - ignore compiler-rt and lldb test failure for `ptrace_scope` > 1 in LLVM easyblock (#4159) +- updates: + - update `Boost` sanity checks for versions 1.89 and newer (#4088) + - update test command in custom easyblock for scipy easyblock for scipy >= 1.17 (#4143) +- code cleanup: + - pythonpackage: fix warning message for max ulimit (#4145) v5.3.0 (10 Apr 2026) @@ -66,7 +102,7 @@ v5.2.1 (20 Feb 2026) - allow to enable more components in Extrae (#4027) - add fallback architecture for zen5 to BLIS easyblock (#4034) - add LLVM support to ParaStationMPI easyblock (#4047) - - allow easier reuse of `compose_install_command` of `PythonPackage`, and add `%(python)s` template and default Python libdir in sanity check (#4050) + - allow easier reuse of `compose_install_command` of `PythonPackage`, and add `%(python)s` template and default Python libdir in sanity check (#4050) - update ELPA easyblock to make it aware of NVHPC toolchain compiler (#4051) - add option to allow missing or additional detected PyTorch test suites (#4052) - update QuantumESPRESSO easyblock - add pretestopts + GPU test cmd (#4053) @@ -77,7 +113,7 @@ v5.2.1 (20 Feb 2026) - update custom easyblock for Gurobi for version 13 (#4060) - other changes: - make `CargoPythonPackage` the default class for extensions/components of `CargoPythonBundle` (#3993) -- code cleanup: +- code cleanup: - enable flake8-comprehension code style check and fix issues (#3989) @@ -224,7 +260,7 @@ update/bugfix release - change `buildenv` easyblock: use `python3` as default path to Python executable in RPATH wrapper scripts (#3910) - limit parallelism of OpenBLAS tests with parallel property (#3936) - remove deprecated license classifier in setup.py (#3942) -- code cleanup: +- code cleanup: - simplify use of `self.cfg.dependencies()` in `PETSc` easyblock (#3813) - stop setting `separate_build_dir` to `True` in various custom easyblocks (#3836, #3837, #3838, #3839, #3840, #3842, #3843, #3844, #3845, #3846, #3847) - remove disabling libunwind on aarch64 for Mesa (#3908) diff --git a/easybuild/easyblocks/__init__.py b/easybuild/easyblocks/__init__.py index fd52841b45d..90553188efb 100644 --- a/easybuild/easyblocks/__init__.py +++ b/easybuild/easyblocks/__init__.py @@ -42,7 +42,7 @@ # recent setuptools versions will *TRANSFORM* something like 'X.Y.Zdev' into 'X.Y.Z.dev0', with a warning like # UserWarning: Normalizing '2.4.0dev' to '2.4.0.dev0' # This causes problems further up the dependency chain... -VERSION = '5.3.1.dev0' +VERSION = '5.3.1' UNKNOWN = 'UNKNOWN'