From 21b0d82e45b5f90fda2fdeea460e362d56e3fd0b Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Thu, 24 Jul 2025 20:02:09 +0200 Subject: [PATCH 01/14] Use EasyBuild hooks to limit toolchains that can be used for specific EESSI version --- .github/workflows/test-eb-hooks.yml | 47 +++++++++++++++++------- eb_hooks.py | 57 ++++++++++++++++++++++++++--- 2 files changed, 84 insertions(+), 20 deletions(-) diff --git a/.github/workflows/test-eb-hooks.yml b/.github/workflows/test-eb-hooks.yml index 57b0d650..eeb82df5 100644 --- a/.github/workflows/test-eb-hooks.yml +++ b/.github/workflows/test-eb-hooks.yml @@ -13,26 +13,27 @@ jobs: matrix: EESSI_VERSION: - '2023.06' + - '2025.06' + include: + # For each EESSI version we need to test different modules + - EESSI_VERSION: '2023.06' + COMPATIBLE_EASYCONFIG: 'M4-1.4.19-GCCcore-13.2.0.eb' + INCOMPATIBLE_EASYCONFIG: 'M4-1.4.19-GCCcore-14.2.0.eb' + - EESSI_VERSION: '2025.06' + COMPATIBLE_EASYCONFIG: 'M4-1.4.19-GCCcore-14.2.0.eb' + INCOMPATIBLE_EASYCONFIG: 'M4-1.4.19-GCCcore-13.2.0.eb' + steps: - name: Check out software-layer repository uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: fetch-depth: 0 # Fetch all history for all branches and tags - - name: Show host system info - run: | - echo "/proc/cpuinfo:" - cat /proc/cpuinfo - echo - echo "lscpu:" - lscpu - - - name: Mount EESSI CernVM-FS pilot repository - uses: cvmfs-contrib/github-action-cvmfs@55899ca74cf78ab874bdf47f5a804e47c198743c # v4.0 + - name: Mount EESSI CernVM-FS repository + uses: eessi/github-action-eessi@v3 with: - cvmfs_config_package: https://github.com/EESSI/filesystem-layer/releases/download/latest/cvmfs-config-eessi_latest_all.deb - cvmfs_http_proxy: DIRECT - cvmfs_repositories: software.eessi.io + eessi_stack_version: ${{matrix.EESSI_VERSION}} + use_eessi_module: true - name: Check that EasyBuild hook is up to date if: ${{ github.event_name == 'pull_request' }} @@ -56,6 +57,24 @@ jobs: sed -i "s//${{matrix.EESSI_VERSION}}/g" "${TEMP_FILE}" # Compare the hooks to what is shipped in the repository - source /cvmfs/software.eessi.io/versions/${{matrix.EESSI_VERSION}}/init/bash module load EESSI-extend diff "$TEMP_FILE" "$EASYBUILD_HOOKS" + + - name: Test that toolchain verification check works + if: ${{ github.event_name == 'pull_request' }} + run: | + # Set up some environment variables + export COMPATIBLE_EASYCONFIG=${{matrix.COMPATIBLE_EASYCONFIG}} + export INCOMPATIBLE_EASYCONFIG=${{matrix.INCOMPATIBLE_EASYCONFIG}} + + # Load specific EESSI-extend vertsion (proxies a version check) + module load EESSI-extend/${{matrix.EESSI_VERSION}}-easybuild + + # Test an easyconfig that should work + eb --hooks=$PWD/eb_hooks.py "$COMPATIBLE_EASYCONFIG" --stop fetch + + # Pick an outdated toolchain for the negative test + eb --hooks=$PWD/eb_hooks.py "$INCOMPATIBLE_EASYCONFIG" --stop fetch 2>&1 1>/dev/null | grep -q "not supported in EESSI" + + # Check the override works + EESSI_OVERRIDE_TOOLCHAIN_CHECK=1 eb --hooks=$PWD/eb_hooks.py "$INCOMPATIBLE_EASYCONFIG" --stop fetch diff --git a/eb_hooks.py b/eb_hooks.py index 91583ce0..97662b41 100644 --- a/eb_hooks.py +++ b/eb_hooks.py @@ -8,6 +8,7 @@ import easybuild.tools.environment as env from easybuild.easyblocks.generic.configuremake import obtain_config_guess from easybuild.framework.easyconfig.constants import EASYCONFIG_CONSTANTS +from easybuild.framework.easyconfig.easyconfig import get_toolchain_hierarchy from easybuild.tools import config from easybuild.tools.build_log import EasyBuildError, print_msg, print_warning from easybuild.tools.config import build_option, install_path, update_build_option @@ -15,6 +16,7 @@ from easybuild.tools.run import run_cmd from easybuild.tools.systemtools import AARCH64, POWER, X86_64, get_cpu_architecture, get_cpu_features from easybuild.tools.toolchain.compiler import OPTARCH_GENERIC +from easybuild.tools.toolchain.toolchain import is_system_toolchain from easybuild.tools.version import VERSION as EASYBUILD_VERSION from easybuild.tools.modules import get_software_root_env_var_name @@ -50,6 +52,18 @@ STACK_REPROD_SUBDIR = 'reprod' +EESSI_SUPPORTED_TOP_LEVEL_TOOLCHAINS = { + '2023.06': [ + {'name': 'foss', 'version': '2022b'}, + {'name': 'foss', 'version': '2023a'}, + {'name': 'foss', 'version': '2023b'}, + ], + '2025.06': [ + {'name': 'foss', 'version': '2024a'}, + {'name': 'foss', 'version': '2025a'}, + ], +} + def is_gcccore_1220_based(**kwargs): # ecname, ecversion, tcname, tcversion): @@ -128,6 +142,34 @@ def parse_hook(ec, *args, **kwargs): ec = inject_gpu_property(ec) +def verify_toolchains_supported_by_eessi_version(easyconfigs): + """Each EESSI version supports a limited set of toolchains, sanity check the easyconfigs for toolchain support.""" + eessi_version = get_eessi_envvar('EESSI_VERSION') + supported_eessi_toolchains = [] + for top_level_toolchain in EESSI_SUPPORTED_TOP_LEVEL_TOOLCHAINS[eessi_version]: + supported_eessi_toolchains += get_toolchain_hierarchy(top_level_toolchain) + for ec in easyconfigs: + toolchain = ec['ec']['toolchain'] + # if it is a system toolchain or appears in the list, we are all good + if is_system_toolchain(toolchain['name']): + continue + elif not any(toolchain.items() <= supported.items() for supported in supported_eessi_toolchains): + raise EasyBuildError( + f"Toolchain {toolchain} (required by {ec['full_mod_name']}) is not supported in EESSI/{eessi_version}\n" + f"Supported toolchains are:\n" + "\n".join(sorted(" " + str(tc) for tc in supported_eessi_toolchains)) + ) + + +def pre_build_and_install_loop_hook(easyconfigs): + """Main pre_build_and_install_loop hook: trigger custom functions before beginning installation loop.""" + + # Always check that toolchain supported by the EESSI version (unless overridden) + if os.getenv("EESSI_OVERRIDE_TOOLCHAIN_CHECK"): + print_warning("Overriding the check that the toolchains are supported by the EESSI version.") + else: + verify_toolchains_supported_by_eessi_version(easyconfigs) + + def post_ready_hook(self, *args, **kwargs): """ Post-ready hook: limit parallellism for selected builds based on software name and CPU target. @@ -135,7 +177,10 @@ def post_ready_hook(self, *args, **kwargs): """ # 'parallel' easyconfig parameter (EB4) or the parallel property (EB5) is set via EasyBlock.set_parallel # in ready step based on available cores - parallel = getattr(self, 'parallel', self.cfg['parallel']) + if hasattr(self, 'parallel'): + parallel = self.parallel + else: + parallel = self.cfg['parallel'] if parallel == 1: return # no need to limit if already using 1 core @@ -167,8 +212,8 @@ def post_ready_hook(self, *args, **kwargs): # apply the limit if it's different from current if new_parallel != parallel: - if EASYBUILD_VERSION >= '5': - self.cfg.parallel = new_parallel + if hasattr(self, 'parallel'): + self.parallel = new_parallel else: self.cfg['parallel'] = new_parallel msg = "limiting parallelism to %s (was %s) for %s on %s to avoid out-of-memory failures during building/testing" @@ -394,7 +439,7 @@ def parse_hook_freeimage_aarch64(ec, *args, **kwargs): https://github.com/EESSI/software-layer/pull/736#issuecomment-2373261889 """ if ec.name == 'FreeImage' and ec.version in ('3.18.0',): - if os.getenv('EESSI_CPU_FAMILY') == 'aarch64': + if get_eessi_envvar('EESSI_CPU_FAMILY') == 'aarch64': # Make sure the toolchainopts key exists, and the value is a dict, # before we add the option to enable PIC and disable PNG_ARM_NEON_OPT if 'toolchainopts' not in ec or ec['toolchainopts'] is None: @@ -1230,7 +1275,7 @@ def replace_non_distributable_files_with_symlinks(log, install_dir, pkg_name, al # CUDA and cu* libraries themselves don't care about compute capability so remove this # duplication from under host_injections (symlink to a single CUDA or cu* library # installation for all compute capabilities) - accel_subdir = os.getenv("EESSI_ACCELERATOR_TARGET") + accel_subdir = get_eessi_envvar("EESSI_ACCELERATOR_TARGET") if accel_subdir: host_inj_path = host_inj_path.replace("/accel/%s" % accel_subdir, '') # make sure source and target of symlink are not the same @@ -1326,7 +1371,7 @@ def post_easyblock_hook(self, *args, **kwargs): # Always trigger this one for EESSI CVMFS/site installations and version 2025.06 or newer, regardless of self.name if os.getenv('EESSI_CVMFS_INSTALL') or os.getenv('EESSI_SITE_INSTALL'): - if os.getenv('EESSI_VERSION') and LooseVersion(os.getenv('EESSI_VERSION')) >= '2025.06': + if get_eessi_envvar('EESSI_VERSION') and LooseVersion(get_eessi_envvar('EESSI_VERSION')) >= '2025.06': post_easyblock_hook_copy_easybuild_subdir(self, *args, **kwargs) else: self.log.debug("No CVMFS/site installation requested, not running post_easyblock_hook_copy_easybuild_subdir.") From b49b1f98b0a402877e63c7f09a7290d5dd8ac8f8 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Thu, 24 Jul 2025 20:06:01 +0200 Subject: [PATCH 02/14] Run the up-to-date check last --- .github/workflows/test-eb-hooks.yml | 38 ++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/test-eb-hooks.yml b/.github/workflows/test-eb-hooks.yml index eeb82df5..9a0a748c 100644 --- a/.github/workflows/test-eb-hooks.yml +++ b/.github/workflows/test-eb-hooks.yml @@ -35,6 +35,25 @@ jobs: eessi_stack_version: ${{matrix.EESSI_VERSION}} use_eessi_module: true + - name: Test that toolchain verification check works + if: ${{ github.event_name == 'pull_request' }} + run: | + # Set up some environment variables + export COMPATIBLE_EASYCONFIG=${{matrix.COMPATIBLE_EASYCONFIG}} + export INCOMPATIBLE_EASYCONFIG=${{matrix.INCOMPATIBLE_EASYCONFIG}} + + # Load specific EESSI-extend vertsion (proxies a version check) + module load EESSI-extend/${{matrix.EESSI_VERSION}}-easybuild + + # Test an easyconfig that should work + eb --hooks=$PWD/eb_hooks.py "$COMPATIBLE_EASYCONFIG" --stop fetch + + # Pick an outdated toolchain for the negative test + eb --hooks=$PWD/eb_hooks.py "$INCOMPATIBLE_EASYCONFIG" --stop fetch 2>&1 1>/dev/null | grep -q "not supported in EESSI" + + # Check the override works + EESSI_OVERRIDE_TOOLCHAIN_CHECK=1 eb --hooks=$PWD/eb_hooks.py "$INCOMPATIBLE_EASYCONFIG" --stop fetch + - name: Check that EasyBuild hook is up to date if: ${{ github.event_name == 'pull_request' }} run: | @@ -59,22 +78,3 @@ jobs: # Compare the hooks to what is shipped in the repository module load EESSI-extend diff "$TEMP_FILE" "$EASYBUILD_HOOKS" - - - name: Test that toolchain verification check works - if: ${{ github.event_name == 'pull_request' }} - run: | - # Set up some environment variables - export COMPATIBLE_EASYCONFIG=${{matrix.COMPATIBLE_EASYCONFIG}} - export INCOMPATIBLE_EASYCONFIG=${{matrix.INCOMPATIBLE_EASYCONFIG}} - - # Load specific EESSI-extend vertsion (proxies a version check) - module load EESSI-extend/${{matrix.EESSI_VERSION}}-easybuild - - # Test an easyconfig that should work - eb --hooks=$PWD/eb_hooks.py "$COMPATIBLE_EASYCONFIG" --stop fetch - - # Pick an outdated toolchain for the negative test - eb --hooks=$PWD/eb_hooks.py "$INCOMPATIBLE_EASYCONFIG" --stop fetch 2>&1 1>/dev/null | grep -q "not supported in EESSI" - - # Check the override works - EESSI_OVERRIDE_TOOLCHAIN_CHECK=1 eb --hooks=$PWD/eb_hooks.py "$INCOMPATIBLE_EASYCONFIG" --stop fetch From 4f5994d04807d179f4ea2178d6ebb617e94c2528 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Thu, 24 Jul 2025 20:36:34 +0200 Subject: [PATCH 03/14] Separate the checks so that we get a more complete set of results --- .github/workflows/test-eb-hooks.yml | 76 +++++++++++++++++------------ 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/.github/workflows/test-eb-hooks.yml b/.github/workflows/test-eb-hooks.yml index 9a0a748c..86b225c9 100644 --- a/.github/workflows/test-eb-hooks.yml +++ b/.github/workflows/test-eb-hooks.yml @@ -1,5 +1,5 @@ # documentation: https://help.github.com/en/articles/workflow-syntax-for-github-actions -name: Check whether eb_hooks.py script is up-to-date +name: Run checks on EasyBuild hooks script on: push: pull_request: @@ -7,7 +7,50 @@ on: permissions: contents: read # to fetch code (actions/checkout) jobs: - check_eb_hooks: + check_eb_hooks_uptodate: + runs-on: ubuntu-24.04 + strategy: + matrix: + EESSI_VERSION: + - '2023.06' + - '2025.06' + + steps: + - name: Check out software-layer repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Mount EESSI CernVM-FS repository + uses: eessi/github-action-eessi@v3 + with: + eessi_stack_version: ${{matrix.EESSI_VERSION}} + use_eessi_module: true + + - name: Check whether eb_hooks.py script is up-to-date + if: ${{ github.event_name == 'pull_request' }} + run: | + FILE="eb_hooks.py" + TEMP_FILE="$(mktemp)" + + # Fetch base branch + git fetch origin ${{ github.base_ref }} + + # Check if the hooks has changed in the PR + if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -q "^$FILE$"; then + echo "Hooks changed in PR. Using PR version." + cp "$FILE" "$TEMP_FILE" + else + echo "File not changed in PR. Using default branch version." + git show origin/${{ github.base_ref }}:$FILE > "$TEMP_FILE" + fi + + # replace placeholder (as is also done in install_scripts.sh) + sed -i "s//${{matrix.EESSI_VERSION}}/g" "${TEMP_FILE}" + + # Compare the hooks to what is shipped in the repository + module load EESSI-extend + diff "$TEMP_FILE" "$EASYBUILD_HOOKS" + + check_eb_hooks_functionality: runs-on: ubuntu-24.04 strategy: matrix: @@ -26,8 +69,6 @@ jobs: steps: - name: Check out software-layer repository uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - with: - fetch-depth: 0 # Fetch all history for all branches and tags - name: Mount EESSI CernVM-FS repository uses: eessi/github-action-eessi@v3 @@ -35,7 +76,7 @@ jobs: eessi_stack_version: ${{matrix.EESSI_VERSION}} use_eessi_module: true - - name: Test that toolchain verification check works + - name: Test that hook toolchain verification check works if: ${{ github.event_name == 'pull_request' }} run: | # Set up some environment variables @@ -53,28 +94,3 @@ jobs: # Check the override works EESSI_OVERRIDE_TOOLCHAIN_CHECK=1 eb --hooks=$PWD/eb_hooks.py "$INCOMPATIBLE_EASYCONFIG" --stop fetch - - - name: Check that EasyBuild hook is up to date - if: ${{ github.event_name == 'pull_request' }} - run: | - FILE="eb_hooks.py" - TEMP_FILE="$(mktemp)" - - # Fetch base branch - git fetch origin ${{ github.base_ref }} - - # Check if the hooks has changed in the PR - if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -q "^$FILE$"; then - echo "Hooks changed in PR. Using PR version." - cp "$FILE" "$TEMP_FILE" - else - echo "File not changed in PR. Using default branch version." - git show origin/${{ github.base_ref }}:$FILE > "$TEMP_FILE" - fi - - # replace placeholder (as is also done in install_scripts.sh) - sed -i "s//${{matrix.EESSI_VERSION}}/g" "${TEMP_FILE}" - - # Compare the hooks to what is shipped in the repository - module load EESSI-extend - diff "$TEMP_FILE" "$EASYBUILD_HOOKS" From 4f9802d40273907b4435e85afe65f5a03f7bc3cd Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Thu, 24 Jul 2025 20:44:07 +0200 Subject: [PATCH 04/14] Fix CI checks --- .github/workflows/scripts/test_init_scripts.sh | 2 +- .github/workflows/test-eb-hooks.yml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/scripts/test_init_scripts.sh b/.github/workflows/scripts/test_init_scripts.sh index 048fba81..bf9abea0 100755 --- a/.github/workflows/scripts/test_init_scripts.sh +++ b/.github/workflows/scripts/test_init_scripts.sh @@ -23,7 +23,7 @@ for shell in ${SHELLS[@]}; do echo -e "\033[33mWe don't now how to test the shell '$shell', PRs are Welcome.\033[0m" else # TEST 1: Source Script and check Module Output - assert "$shell -c 'source init/lmod/$shell' 2>&1 " "EESSI/$EESSI_VERSION loaded successfully" + assert "$shell -c 'source init/lmod/$shell' 2>&1 " "Module for EESSI/$EESSI_VERSION loaded successfully" # TEST 2: Check if module overviews first section is the loaded EESSI module MODULE_SECTIONS=($($shell -c "source init/lmod/$shell 2>/dev/null; module ov 2>&1 | grep -e '---'")) PATTERN="/cvmfs/software\.eessi\.io/versions/$EESSI_VERSION/software/linux/x86_64/(intel/haswell|amd/zen3)/modules/all" diff --git a/.github/workflows/test-eb-hooks.yml b/.github/workflows/test-eb-hooks.yml index 86b225c9..8f0420d2 100644 --- a/.github/workflows/test-eb-hooks.yml +++ b/.github/workflows/test-eb-hooks.yml @@ -18,7 +18,8 @@ jobs: steps: - name: Check out software-layer repository uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - + with: + fetch-depth: 0 # Fetch all history for all branches and tags - name: Mount EESSI CernVM-FS repository uses: eessi/github-action-eessi@v3 with: From e7cf75bf21215e24b58b8965592bba3ab4d7e3b4 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Fri, 25 Jul 2025 09:13:39 +0200 Subject: [PATCH 05/14] Add capability for site supported toolchains --- .github/workflows/test-eb-hooks.yml | 18 ++++++++++++++++++ eb_hooks.py | 21 ++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-eb-hooks.yml b/.github/workflows/test-eb-hooks.yml index 8f0420d2..414d65ea 100644 --- a/.github/workflows/test-eb-hooks.yml +++ b/.github/workflows/test-eb-hooks.yml @@ -63,9 +63,13 @@ jobs: - EESSI_VERSION: '2023.06' COMPATIBLE_EASYCONFIG: 'M4-1.4.19-GCCcore-13.2.0.eb' INCOMPATIBLE_EASYCONFIG: 'M4-1.4.19-GCCcore-14.2.0.eb' + # Pick a site toolchain that will allow the incompatible easyconfig + EESSI_SITE_TOP_LEVEL_TOOLCHAINS: '[{"GCCcore": "14.2.0"}]' - EESSI_VERSION: '2025.06' COMPATIBLE_EASYCONFIG: 'M4-1.4.19-GCCcore-14.2.0.eb' INCOMPATIBLE_EASYCONFIG: 'M4-1.4.19-GCCcore-13.2.0.eb' + # Pick a site toolchain that will allow the incompatible easyconfig + EESSI_SITE_TOP_LEVEL_TOOLCHAINS: '[{"GCCcore": "13.2.0"}]' steps: - name: Check out software-layer repository @@ -89,9 +93,23 @@ jobs: # Test an easyconfig that should work eb --hooks=$PWD/eb_hooks.py "$COMPATIBLE_EASYCONFIG" --stop fetch + echo "Success for hook with easyconfig $COMPATIBLE_EASYCONFIG with EESSI/${{matrix.EESSI_VERSION}}" # Pick an outdated toolchain for the negative test eb --hooks=$PWD/eb_hooks.py "$INCOMPATIBLE_EASYCONFIG" --stop fetch 2>&1 1>/dev/null | grep -q "not supported in EESSI" + echo "Found expected failure for hook with easyconfig $INCOMPATIBLE_EASYCONFIG and EESSI/${{matrix.EESSI_VERSION}}" # Check the override works EESSI_OVERRIDE_TOOLCHAIN_CHECK=1 eb --hooks=$PWD/eb_hooks.py "$INCOMPATIBLE_EASYCONFIG" --stop fetch + echo "Hook ignored via EESSI_OVERRIDE_TOOLCHAIN_CHECK with easyconfig $INCOMPATIBLE_EASYCONFIG and EESSI/${{matrix.EESSI_VERSION}}" + + # Now check if we can set a site list of supported toolchains + export EESSI_SITE_TOP_LEVEL_TOOLCHAINS=${{matrix.EESSI_SITE_TOP_LEVEL_TOOLCHAINS}} + eb --hooks=$PWD/eb_hooks.py "$INCOMPATIBLE_EASYCONFIG" --stop fetch + echo "Site supported toolchain $EESSI_SITE_TOP_LEVEL_TOOLCHAINS successfully used with easyconfig $INCOMPATIBLE_EASYCONFIG and EESSI/${{matrix.EESSI_VERSION}}" + + # Make sure an invalid list of dicts fails + export EESSI_SITE_TOP_LEVEL_TOOLCHAINS="Not a list of dicts" + eb --hooks=$PWD/eb_hooks.py "$INCOMPATIBLE_EASYCONFIG" --stop fetch 2>&1 1>/dev/null | grep -q "does not contain a valid list of dictionaries" + echo "Incorrect format for EESSI_SITE_TOP_LEVEL_TOOLCHAINS caught" + diff --git a/eb_hooks.py b/eb_hooks.py index 97662b41..349c018e 100644 --- a/eb_hooks.py +++ b/eb_hooks.py @@ -1,7 +1,9 @@ # Hooks to customize how EasyBuild installs software in EESSI # see https://docs.easybuild.io/en/latest/Hooks.html +import ast import datetime import glob +import json import os import re @@ -142,10 +144,27 @@ def parse_hook(ec, *args, **kwargs): ec = inject_gpu_property(ec) +def parse_list_of_dicts_env(var_name): + list_string = os.getenv(var_name, '[]') + + list_of_dicts = [] + try: + # Try JSON format first + list_of_dicts = json.loads(list_string) + except json.JSONDecodeError: + try: + # Fall back to Python literal format + list_of_dicts = ast.literal_eval(list_string) + except (ValueError, SyntaxError): + raise ValueError(f"Environment variable '{var_name}' does not contain a valid list of dictionaries.") + + return list_of_dicts + + def verify_toolchains_supported_by_eessi_version(easyconfigs): """Each EESSI version supports a limited set of toolchains, sanity check the easyconfigs for toolchain support.""" eessi_version = get_eessi_envvar('EESSI_VERSION') - supported_eessi_toolchains = [] + supported_eessi_toolchains = parse_list_of_dicts_env('EESSI_SITE_TOP_LEVEL_TOOLCHAINS') for top_level_toolchain in EESSI_SUPPORTED_TOP_LEVEL_TOOLCHAINS[eessi_version]: supported_eessi_toolchains += get_toolchain_hierarchy(top_level_toolchain) for ec in easyconfigs: From 4f11b64a24ab15929314b31dee92f408940360f6 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Fri, 25 Jul 2025 09:17:23 +0200 Subject: [PATCH 06/14] Be more careful when exporting the variable --- .github/workflows/test-eb-hooks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-eb-hooks.yml b/.github/workflows/test-eb-hooks.yml index 414d65ea..62ee00d5 100644 --- a/.github/workflows/test-eb-hooks.yml +++ b/.github/workflows/test-eb-hooks.yml @@ -104,7 +104,7 @@ jobs: echo "Hook ignored via EESSI_OVERRIDE_TOOLCHAIN_CHECK with easyconfig $INCOMPATIBLE_EASYCONFIG and EESSI/${{matrix.EESSI_VERSION}}" # Now check if we can set a site list of supported toolchains - export EESSI_SITE_TOP_LEVEL_TOOLCHAINS=${{matrix.EESSI_SITE_TOP_LEVEL_TOOLCHAINS}} + export EESSI_SITE_TOP_LEVEL_TOOLCHAINS='${{matrix.EESSI_SITE_TOP_LEVEL_TOOLCHAINS}}' eb --hooks=$PWD/eb_hooks.py "$INCOMPATIBLE_EASYCONFIG" --stop fetch echo "Site supported toolchain $EESSI_SITE_TOP_LEVEL_TOOLCHAINS successfully used with easyconfig $INCOMPATIBLE_EASYCONFIG and EESSI/${{matrix.EESSI_VERSION}}" From a1cd808c2f7d0c679ce1ea102fe405f1fa2ac059 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Fri, 25 Jul 2025 09:22:45 +0200 Subject: [PATCH 07/14] One big typo --- .github/workflows/test-eb-hooks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-eb-hooks.yml b/.github/workflows/test-eb-hooks.yml index 62ee00d5..b04f5149 100644 --- a/.github/workflows/test-eb-hooks.yml +++ b/.github/workflows/test-eb-hooks.yml @@ -69,7 +69,7 @@ jobs: COMPATIBLE_EASYCONFIG: 'M4-1.4.19-GCCcore-14.2.0.eb' INCOMPATIBLE_EASYCONFIG: 'M4-1.4.19-GCCcore-13.2.0.eb' # Pick a site toolchain that will allow the incompatible easyconfig - EESSI_SITE_TOP_LEVEL_TOOLCHAINS: '[{"GCCcore": "13.2.0"}]' + EESSI_SITE_TOP_LEVEL_TOOLCHAINS: '[{"name": "GCCcore", "version": "13.2.0"}]' steps: - name: Check out software-layer repository From 9957485fb9520dd0618694e134db310760153811 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Fri, 25 Jul 2025 09:26:59 +0200 Subject: [PATCH 08/14] There were two big typos --- .github/workflows/test-eb-hooks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-eb-hooks.yml b/.github/workflows/test-eb-hooks.yml index b04f5149..0dbd7db1 100644 --- a/.github/workflows/test-eb-hooks.yml +++ b/.github/workflows/test-eb-hooks.yml @@ -64,7 +64,7 @@ jobs: COMPATIBLE_EASYCONFIG: 'M4-1.4.19-GCCcore-13.2.0.eb' INCOMPATIBLE_EASYCONFIG: 'M4-1.4.19-GCCcore-14.2.0.eb' # Pick a site toolchain that will allow the incompatible easyconfig - EESSI_SITE_TOP_LEVEL_TOOLCHAINS: '[{"GCCcore": "14.2.0"}]' + EESSI_SITE_TOP_LEVEL_TOOLCHAINS: '[{"name": "GCCcore", "version": "14.2.0"}]' - EESSI_VERSION: '2025.06' COMPATIBLE_EASYCONFIG: 'M4-1.4.19-GCCcore-14.2.0.eb' INCOMPATIBLE_EASYCONFIG: 'M4-1.4.19-GCCcore-13.2.0.eb' From 541b40aed8818fa588465f3a9ae6b27ea1cc8b87 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Fri, 25 Jul 2025 09:42:09 +0200 Subject: [PATCH 09/14] Allow there to be an environment variable per EESSI version for site top-level toolchains --- .github/workflows/test-eb-hooks.yml | 4 ++-- eb_hooks.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-eb-hooks.yml b/.github/workflows/test-eb-hooks.yml index 0dbd7db1..362e6363 100644 --- a/.github/workflows/test-eb-hooks.yml +++ b/.github/workflows/test-eb-hooks.yml @@ -104,12 +104,12 @@ jobs: echo "Hook ignored via EESSI_OVERRIDE_TOOLCHAIN_CHECK with easyconfig $INCOMPATIBLE_EASYCONFIG and EESSI/${{matrix.EESSI_VERSION}}" # Now check if we can set a site list of supported toolchains - export EESSI_SITE_TOP_LEVEL_TOOLCHAINS='${{matrix.EESSI_SITE_TOP_LEVEL_TOOLCHAINS}}' + export EESSI_SITE_TOP_LEVEL_TOOLCHAINS_"${{matrix.EESSI_VERSION}}"='${{matrix.EESSI_SITE_TOP_LEVEL_TOOLCHAINS}}' eb --hooks=$PWD/eb_hooks.py "$INCOMPATIBLE_EASYCONFIG" --stop fetch echo "Site supported toolchain $EESSI_SITE_TOP_LEVEL_TOOLCHAINS successfully used with easyconfig $INCOMPATIBLE_EASYCONFIG and EESSI/${{matrix.EESSI_VERSION}}" # Make sure an invalid list of dicts fails - export EESSI_SITE_TOP_LEVEL_TOOLCHAINS="Not a list of dicts" + export EESSI_SITE_TOP_LEVEL_TOOLCHAINS_"${{matrix.EESSI_VERSION}}"="Not a list of dicts" eb --hooks=$PWD/eb_hooks.py "$INCOMPATIBLE_EASYCONFIG" --stop fetch 2>&1 1>/dev/null | grep -q "does not contain a valid list of dictionaries" echo "Incorrect format for EESSI_SITE_TOP_LEVEL_TOOLCHAINS caught" diff --git a/eb_hooks.py b/eb_hooks.py index 349c018e..2bf2a381 100644 --- a/eb_hooks.py +++ b/eb_hooks.py @@ -164,8 +164,9 @@ def parse_list_of_dicts_env(var_name): def verify_toolchains_supported_by_eessi_version(easyconfigs): """Each EESSI version supports a limited set of toolchains, sanity check the easyconfigs for toolchain support.""" eessi_version = get_eessi_envvar('EESSI_VERSION') - supported_eessi_toolchains = parse_list_of_dicts_env('EESSI_SITE_TOP_LEVEL_TOOLCHAINS') - for top_level_toolchain in EESSI_SUPPORTED_TOP_LEVEL_TOOLCHAINS[eessi_version]: + supported_eessi_toolchains = [] + site_top_level_toolchains = parse_list_of_dicts_env('EESSI_SITE_TOP_LEVEL_TOOLCHAINS_' + eessi_version) + for top_level_toolchain in EESSI_SUPPORTED_TOP_LEVEL_TOOLCHAINS[eessi_version] + site_top_level_toolchains: supported_eessi_toolchains += get_toolchain_hierarchy(top_level_toolchain) for ec in easyconfigs: toolchain = ec['ec']['toolchain'] From ef953f58ec19a26c4038d79984434d6911a13fd2 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Fri, 25 Jul 2025 09:53:07 +0200 Subject: [PATCH 10/14] Make sure expected envvars have valid syntax --- .github/workflows/test-eb-hooks.yml | 11 +++++++---- eb_hooks.py | 4 +++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-eb-hooks.yml b/.github/workflows/test-eb-hooks.yml index 362e6363..1020b323 100644 --- a/.github/workflows/test-eb-hooks.yml +++ b/.github/workflows/test-eb-hooks.yml @@ -64,12 +64,14 @@ jobs: COMPATIBLE_EASYCONFIG: 'M4-1.4.19-GCCcore-13.2.0.eb' INCOMPATIBLE_EASYCONFIG: 'M4-1.4.19-GCCcore-14.2.0.eb' # Pick a site toolchain that will allow the incompatible easyconfig - EESSI_SITE_TOP_LEVEL_TOOLCHAINS: '[{"name": "GCCcore", "version": "14.2.0"}]' + # (the name will be modified when exported) + SITE_TOP_LEVEL_TOOLCHAINS: '[{"name": "GCCcore", "version": "14.2.0"}]' - EESSI_VERSION: '2025.06' COMPATIBLE_EASYCONFIG: 'M4-1.4.19-GCCcore-14.2.0.eb' INCOMPATIBLE_EASYCONFIG: 'M4-1.4.19-GCCcore-13.2.0.eb' # Pick a site toolchain that will allow the incompatible easyconfig - EESSI_SITE_TOP_LEVEL_TOOLCHAINS: '[{"name": "GCCcore", "version": "13.2.0"}]' + # (the name will be modified when exported) + SITE_TOP_LEVEL_TOOLCHAINS: '[{"name": "GCCcore", "version": "13.2.0"}]' steps: - name: Check out software-layer repository @@ -104,12 +106,13 @@ jobs: echo "Hook ignored via EESSI_OVERRIDE_TOOLCHAIN_CHECK with easyconfig $INCOMPATIBLE_EASYCONFIG and EESSI/${{matrix.EESSI_VERSION}}" # Now check if we can set a site list of supported toolchains - export EESSI_SITE_TOP_LEVEL_TOOLCHAINS_"${{matrix.EESSI_VERSION}}"='${{matrix.EESSI_SITE_TOP_LEVEL_TOOLCHAINS}}' + export SANITIZED_EESSI_VERSION=$(echo "${{ matrix.EESSI_VERSION }}" | sed 's/\./_/g') + export EESSI_SITE_TOP_LEVEL_TOOLCHAINS_"$SANITIZED_EESSI_VERSION"='${{matrix.SITE_TOP_LEVEL_TOOLCHAINS}}' eb --hooks=$PWD/eb_hooks.py "$INCOMPATIBLE_EASYCONFIG" --stop fetch echo "Site supported toolchain $EESSI_SITE_TOP_LEVEL_TOOLCHAINS successfully used with easyconfig $INCOMPATIBLE_EASYCONFIG and EESSI/${{matrix.EESSI_VERSION}}" # Make sure an invalid list of dicts fails - export EESSI_SITE_TOP_LEVEL_TOOLCHAINS_"${{matrix.EESSI_VERSION}}"="Not a list of dicts" + export EESSI_SITE_TOP_LEVEL_TOOLCHAINS_"$SANITIZED_EESSI_VERSION"="Not a list of dicts" eb --hooks=$PWD/eb_hooks.py "$INCOMPATIBLE_EASYCONFIG" --stop fetch 2>&1 1>/dev/null | grep -q "does not contain a valid list of dictionaries" echo "Incorrect format for EESSI_SITE_TOP_LEVEL_TOOLCHAINS caught" diff --git a/eb_hooks.py b/eb_hooks.py index 2bf2a381..3525a81e 100644 --- a/eb_hooks.py +++ b/eb_hooks.py @@ -165,7 +165,9 @@ def verify_toolchains_supported_by_eessi_version(easyconfigs): """Each EESSI version supports a limited set of toolchains, sanity check the easyconfigs for toolchain support.""" eessi_version = get_eessi_envvar('EESSI_VERSION') supported_eessi_toolchains = [] - site_top_level_toolchains = parse_list_of_dicts_env('EESSI_SITE_TOP_LEVEL_TOOLCHAINS_' + eessi_version) + # Environment variable can't have a '.' so replace by '_' + site_top_level_toolchains_envvar = 'EESSI_SITE_TOP_LEVEL_TOOLCHAINS_' + eessi_version.replace('.', '_') + site_top_level_toolchains = parse_list_of_dicts_env(site_top_level_toolchains_envvar) for top_level_toolchain in EESSI_SUPPORTED_TOP_LEVEL_TOOLCHAINS[eessi_version] + site_top_level_toolchains: supported_eessi_toolchains += get_toolchain_hierarchy(top_level_toolchain) for ec in easyconfigs: From bfe97777e291a1943309dbb76320c0b66d3f8b00 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Fri, 25 Jul 2025 09:55:11 +0200 Subject: [PATCH 11/14] Add a check to verify the name of the environment variable --- eb_hooks.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/eb_hooks.py b/eb_hooks.py index 3525a81e..034272ce 100644 --- a/eb_hooks.py +++ b/eb_hooks.py @@ -145,6 +145,11 @@ def parse_hook(ec, *args, **kwargs): def parse_list_of_dicts_env(var_name): + """Parse a list of dicts that are stored in an environment variable string""" + + # Check if the environment variable name is valid (letters, numbers, underscores, and doesn't start with a digit) + if not re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', var_name): + raise ValueError(f"Invalid environment variable name: {var_name}") list_string = os.getenv(var_name, '[]') list_of_dicts = [] From 3c1c429f2887abf68f38f4c5f0921318512b9b61 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Fri, 25 Jul 2025 10:07:03 +0200 Subject: [PATCH 12/14] Update some CI comments --- .github/workflows/test-eb-hooks.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-eb-hooks.yml b/.github/workflows/test-eb-hooks.yml index 1020b323..416cd99c 100644 --- a/.github/workflows/test-eb-hooks.yml +++ b/.github/workflows/test-eb-hooks.yml @@ -97,7 +97,7 @@ jobs: eb --hooks=$PWD/eb_hooks.py "$COMPATIBLE_EASYCONFIG" --stop fetch echo "Success for hook with easyconfig $COMPATIBLE_EASYCONFIG with EESSI/${{matrix.EESSI_VERSION}}" - # Pick an outdated toolchain for the negative test + # Now ensure an incompatible easyconfig does not work eb --hooks=$PWD/eb_hooks.py "$INCOMPATIBLE_EASYCONFIG" --stop fetch 2>&1 1>/dev/null | grep -q "not supported in EESSI" echo "Found expected failure for hook with easyconfig $INCOMPATIBLE_EASYCONFIG and EESSI/${{matrix.EESSI_VERSION}}" @@ -109,7 +109,7 @@ jobs: export SANITIZED_EESSI_VERSION=$(echo "${{ matrix.EESSI_VERSION }}" | sed 's/\./_/g') export EESSI_SITE_TOP_LEVEL_TOOLCHAINS_"$SANITIZED_EESSI_VERSION"='${{matrix.SITE_TOP_LEVEL_TOOLCHAINS}}' eb --hooks=$PWD/eb_hooks.py "$INCOMPATIBLE_EASYCONFIG" --stop fetch - echo "Site supported toolchain $EESSI_SITE_TOP_LEVEL_TOOLCHAINS successfully used with easyconfig $INCOMPATIBLE_EASYCONFIG and EESSI/${{matrix.EESSI_VERSION}}" + echo "Site supported toolchain from $EESSI_SITE_TOP_LEVEL_TOOLCHAINS successfully used with easyconfig $INCOMPATIBLE_EASYCONFIG and EESSI/${{matrix.EESSI_VERSION}}" # Make sure an invalid list of dicts fails export EESSI_SITE_TOP_LEVEL_TOOLCHAINS_"$SANITIZED_EESSI_VERSION"="Not a list of dicts" From ef560294ff6e231599f2f48fdda279007960ed04 Mon Sep 17 00:00:00 2001 From: ocaisa Date: Fri, 25 Jul 2025 10:09:41 +0200 Subject: [PATCH 13/14] Update eb_hooks.py --- eb_hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eb_hooks.py b/eb_hooks.py index 034272ce..41895eb5 100644 --- a/eb_hooks.py +++ b/eb_hooks.py @@ -240,7 +240,7 @@ def post_ready_hook(self, *args, **kwargs): # apply the limit if it's different from current if new_parallel != parallel: if hasattr(self, 'parallel'): - self.parallel = new_parallel + self.cfg.parallel = new_parallel else: self.cfg['parallel'] = new_parallel msg = "limiting parallelism to %s (was %s) for %s on %s to avoid out-of-memory failures during building/testing" From 9dabe2cecd0eef6dfdf0306fefb4766b41609590 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Fri, 25 Jul 2025 10:51:31 +0200 Subject: [PATCH 14/14] Add a comment for strange looking dict comparison --- eb_hooks.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/eb_hooks.py b/eb_hooks.py index 41895eb5..bdf8f49b 100644 --- a/eb_hooks.py +++ b/eb_hooks.py @@ -180,6 +180,9 @@ def verify_toolchains_supported_by_eessi_version(easyconfigs): # if it is a system toolchain or appears in the list, we are all good if is_system_toolchain(toolchain['name']): continue + # This check verifies that the toolchain dict is in the list of supported toolchains. + # It uses <= as there may be other dict entries in the values returned from get_toolchain_hierarchy() + # but we only care that the toolchain dict (which has 'name' and 'version') appear. elif not any(toolchain.items() <= supported.items() for supported in supported_eessi_toolchains): raise EasyBuildError( f"Toolchain {toolchain} (required by {ec['full_mod_name']}) is not supported in EESSI/{eessi_version}\n"