Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/test-eb-hooks-toolchains.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# documentation: https://help.github.com/en/articles/workflow-syntax-for-github-actions
name: Unit tests for supported toolchains in EasyBuild hooks
on:
push:
paths:
- eb_hooks.py
- eessi_supported_toolchains.toml
- tests/eb_hooks/**
- .github/workflows/test-eb-hooks-toolchains.yml
pull_request:
paths:
- eb_hooks.py
- eessi_supported_toolchains.toml
- tests/eb_hooks/**
- .github/workflows/test-eb-hooks-toolchains.yml
workflow_dispatch:
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
test_supported_toolchains:
runs-on: ubuntu-24.04
steps:
- name: Check out software-layer repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

- name: Install EasyBuild and pytest
run: |
python3 -m venv "$RUNNER_TEMP/venv"
"$RUNNER_TEMP/venv/bin/pip" install easybuild pytest

- name: Run unit tests
run: |
"$RUNNER_TEMP/venv/bin/python" -m pytest -v tests/eb_hooks
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# documentation: https://help.github.com/en/articles/workflow-syntax-for-github-actions
name: Run checks on EasyBuild hooks script
name: Run checks on EasyBuild hooks script and related init files
on:
push:
pull_request:
Expand All @@ -26,30 +26,36 @@ jobs:
eessi_stack_version: ${{matrix.EESSI_VERSION}}
use_eessi_module: true

- name: Check whether eb_hooks.py script is up-to-date
- name: Check whether eb_hooks.py and related files are up-to-date
if: ${{ github.event_name == 'pull_request' }}
run: |
FILE="eb_hooks.py"
TEMP_FILE="$(mktemp)"
# Files that are installed in the same directory as the hooks, and have to be up-to-date
FILES=(eb_hooks.py eessi_supported_toolchains.toml)

# 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 <EESSI_VERSION> placeholder (as is also done in install_scripts.sh)
sed -i "s/<EESSI_VERSION>/${{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"

for FILE in "${FILES[@]}"; do
echo "Checking whether $FILE is up-to-date"
TEMP_FILE="$(mktemp)"

# Check if the file has changed in the PR
if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -q "^$FILE$"; then
echo "$FILE 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 <EESSI_VERSION> placeholder (as is also done in install_scripts.sh)
sed -i "s/<EESSI_VERSION>/${{matrix.EESSI_VERSION}}/g" "${TEMP_FILE}"

# Compare to what is shipped in the repository (all files are installed in the same directory as the hooks)
diff "$TEMP_FILE" "$(dirname "$EASYBUILD_HOOKS")/$FILE"
done

check_eb_hooks_functionality:
runs-on: ubuntu-24.04
Expand Down
13 changes: 13 additions & 0 deletions EESSI-install-software.sh
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,19 @@ else
echo_green ">> Using ${EESSI_SOFTWARE_SUBDIR} as software subdirectory!"
fi

# Check that the compilers of all supported toolchains translate the native architecture flag into exactly the same
# target flags on this build host as on other build hosts for the same CPU target, to ensure that binaries built on
# different build hosts for the same CPU target are compatible
if [[ ${GENERIC} -eq 1 ]]; then
echo ">> Skipping check of native compiler flags, since this is a generic build"
elif [[ -n "${EESSI_SKIP_NATIVE_FLAGS_CHECK}" ]]; then
echo_yellow ">> Skipping check of native compiler flags, since \$EESSI_SKIP_NATIVE_FLAGS_CHECK is set"
else
${TOPDIR}/scripts/native_flags/check_native_flags.sh
check_exit_code $? ">> Native compiler flags on this build host are OK" \
"Native compiler flags check failed, refusing to build on this host (set \$EESSI_SKIP_NATIVE_FLAGS_CHECK to override)"
fi

# avoid that pyc files for EasyBuild are stored in EasyBuild installation directory
export PYTHONPYCACHEPREFIX=$TMPDIR/pycache
# force Python's stdout and stderr streams to be unbuffered
Expand Down
85 changes: 61 additions & 24 deletions eb_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@
import tarfile
from typing import NamedTuple

try:
import tomllib # in the standard library since Python 3.11
except ImportError:
try:
import tomli as tomllib # backport for older Python versions
except ImportError:
tomllib = None

import easybuild.tools.environment as env
from easybuild.easyblocks.generic.configuremake import obtain_config_guess
from easybuild.framework.easyconfig.constants import EASYCONFIG_CONSTANTS
Expand Down Expand Up @@ -67,31 +75,60 @@

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'},
{'name': 'foss', 'version': '2025b'},
],
'2026.06': [
{'name': 'foss', 'version': '2026.1'},
{'name': 'lfoss', 'version': '2026.1'},
],
}
if EASYBUILD_VERSION >= '5.2.0':
EESSI_SUPPORTED_TOP_LEVEL_TOOLCHAINS['2025.06'].append(
{'name': 'lfoss', 'version': '2025b'}
)
# Environment variable that can be used to point to a custom TOML file with the supported top-level toolchains
SUPPORTED_TOOLCHAINS_FILE_ENVVAR = 'EESSI_SUPPORTED_TOOLCHAINS_FILE'

if EASYBUILD_VERSION >= '5.3.1':
EESSI_SUPPORTED_TOP_LEVEL_TOOLCHAINS['2025.06'].append(
{'name': 'rompi', 'version': '2025a'}
)

def load_supported_top_level_toolchains():
"""
Load the supported top-level toolchains per EESSI version from a TOML file.

The location of the TOML file can be set through the environment variable EESSI_SUPPORTED_TOOLCHAINS_FILE.
If that is not set, eessi_supported_toolchains.toml is expected next to this hooks file (both in the
software-layer-scripts repository, and when installed in <EESSI prefix>/init/easybuild).

Toolchains that require a more recent EasyBuild version than the one being used (as specified via
'min_easybuild_version') are left out.

Returns:
supported_toolchains (dict): maps each EESSI version to a list of dicts with the 'name' and 'version'
of a supported top-level toolchain
"""
default_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'eessi_supported_toolchains.toml')
toolchains_file = os.getenv(SUPPORTED_TOOLCHAINS_FILE_ENVVAR)
envvar_msg = f" (set via ${SUPPORTED_TOOLCHAINS_FILE_ENVVAR})"
if not toolchains_file:
toolchains_file = default_file
envvar_msg = ""

if tomllib is None:
raise EasyBuildError("Parsing TOML files requires Python 3.11 or newer, or the 'tomli' Python package")

try:
with open(toolchains_file, 'rb') as fh:
toolchains = tomllib.load(fh)
except OSError as err:
msg = (f"Failed to read the file with supported toolchains {toolchains_file}{envvar_msg}: {err}. "
f"By default, it is expected next to the EasyBuild hooks file; its location can be configured "
f"through the environment variable {SUPPORTED_TOOLCHAINS_FILE_ENVVAR}.")
if envvar_msg and os.path.isfile(default_file):
msg += (f" Note that a file with supported toolchains does exist in the default location {default_file}. "
f"If that is the file you intended to use, unset {SUPPORTED_TOOLCHAINS_FILE_ENVVAR}.")
raise EasyBuildError(msg)
except ValueError as err:
raise EasyBuildError(f"The file with supported toolchains {toolchains_file}{envvar_msg} "
f"does not contain valid TOML: {err}")

return {
eessi_version: [
{'name': tc['name'], 'version': tc['version']} for tc in tcs
if EASYBUILD_VERSION >= tc.get('min_easybuild_version', '0')
]
for eessi_version, tcs in toolchains.items()
}


EESSI_SUPPORTED_TOP_LEVEL_TOOLCHAINS = load_supported_top_level_toolchains()

# Supported compute capabilities by CUDA toolkit version
# Obtained by installing all CUDAs from 12.0.0 to 13.3.0, then using:
Expand Down
28 changes: 28 additions & 0 deletions eessi_supported_toolchains.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Top-level toolchains that are supported for installations in each EESSI version.
# This file is read by eb_hooks.py (see load_supported_top_level_toolchains()), and has to be installed next to it.
#
# Each key is an EESSI version, with a list of toolchains ('name' and 'version').
# An optional 'min_easybuild_version' can be used for toolchains that are only supported when
# a sufficiently recent EasyBuild version is used (e.g. because older versions don't know the toolchain).

# If you need to use this from a shell and don't have yq available, you can convert it to json on the fly using
# python -c "import tomllib, json; print(json.dumps(tomllib.load(open('input.toml','rb')), indent=2))" > output.json

"2023.06" = [
{ name = "foss", version = "2022b" },
{ name = "foss", version = "2023a" },
{ name = "foss", version = "2023b" },
]

"2025.06" = [
{ name = "foss", version = "2024a" },
{ name = "foss", version = "2025a" },
{ name = "foss", version = "2025b" },
{ name = "lfoss", version = "2025b", min_easybuild_version = "5.2.0" },
{ name = "rompi", version = "2025a", min_easybuild_version = "5.3.1" },
]

"2026.06" = [
{ name = "foss", version = "2026.1" },
{ name = "lfoss", version = "2026.1" },
]
7 changes: 4 additions & 3 deletions install_scripts.sh
Original file line number Diff line number Diff line change
Expand Up @@ -222,11 +222,12 @@ if [[ -d ${host_injections_easystacks_dir} ]]; then
copy_files_by_list ${host_injections_easystacks_dir} ${INSTALL_PREFIX}/scripts/gpu_support/nvidia/easystacks "${host_injections_easystacks[@]}"
fi

# Copy over EasyBuild hooks file used for installations
hook_files=(
# Copy over EasyBuild hooks file used for installations (and the files it needs, which have to be located next to it)
easybuild_init_files=(
eb_hooks.py
eessi_supported_toolchains.toml
)
copy_files_by_list ${TOPDIR} ${INSTALL_PREFIX}/init/easybuild "${hook_files[@]}"
copy_files_by_list ${TOPDIR} ${INSTALL_PREFIX}/init/easybuild "${easybuild_init_files[@]}"

# replace version placeholders in scripts;
# note: the commands below are always run, regardless of whether the scripts were changed,
Expand Down
Loading
Loading