Skip to content

Commit c32f1ec

Browse files
committed
add hooks for improving reproducibility
1 parent a676158 commit c32f1ec

1 file changed

Lines changed: 53 additions & 2 deletions

File tree

eb_hooks.py

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
# Hooks to customize how EasyBuild installs software in EESSI
22
# see https://docs.easybuild.io/en/latest/Hooks.html
3+
import datetime
34
import glob
45
import os
56
import re
67

78
import easybuild.tools.environment as env
89
from easybuild.easyblocks.generic.configuremake import obtain_config_guess
910
from easybuild.framework.easyconfig.constants import EASYCONFIG_CONSTANTS
11+
from easybuild.tools import config
1012
from easybuild.tools.build_log import EasyBuildError, print_msg
11-
from easybuild.tools.config import build_option, update_build_option
12-
from easybuild.tools.filetools import apply_regex_substitutions, copy_file, remove_file, symlink, which
13+
from easybuild.tools.config import build_option, install_path, update_build_option
14+
from easybuild.tools.filetools import apply_regex_substitutions, copy_dir, copy_file, remove_file, symlink, which
1315
from easybuild.tools.run import run_cmd
1416
from easybuild.tools.systemtools import AARCH64, POWER, X86_64, get_cpu_architecture, get_cpu_features
1517
from easybuild.tools.toolchain.compiler import OPTARCH_GENERIC
@@ -46,6 +48,8 @@
4648
# Make sure a single environment variable name is used for this throughout the hooks
4749
EESSI_IGNORE_ZEN4_GCC1220_ENVVAR="EESSI_IGNORE_LMOD_ERROR_ZEN4_GCC1220"
4850

51+
STACK_REPROD_SUBDIR = 'reprod'
52+
4953
def is_gcccore_1220_based(**kwargs):
5054
# ecname, ecversion, tcname, tcversion):
5155
"""
@@ -516,6 +520,26 @@ def post_module_hook_zen4_gcccore1220(self, *args, **kwargs):
516520
del self.initial_environ[EESSI_IGNORE_ZEN4_GCC1220_ENVVAR]
517521

518522

523+
def post_easyblock_hook_copy_exts_patches_to_easybuild_subdir(self, *args, **kwargs):
524+
"""Copy the patches of extensions to the easybuild subdir of the installation directory."""
525+
526+
# Temporary fix for https://github.com/easybuilders/easybuild-framework/issues/4864:
527+
# make sure that the patches of extensions are copied to the easybuild subdirectory
528+
for ext in self.exts:
529+
for patch in ext.get('patches', []):
530+
new_log_dir = os.path.join(self.installdir, config.log_path(ec=self.cfg))
531+
target = os.path.join(new_log_dir, os.path.basename(patch['path']))
532+
copy_file(patch['path'], target)
533+
534+
# Now we simply copy the entire "easybuild" subdirectory of the installed application
535+
# to a timestamped subdirectory of the stack's central reprod directory, e.g.:
536+
#
537+
stack_reprod_dir = os.path.join(os.path.dirname(install_path()), STACK_REPROD_SUBDIR)
538+
now_utc_timestamp = datetime.datetime.now(datetime.UTC).isoformat('T', 'seconds').replace('+00:00', 'Z')
539+
app_reprod_dir = os.path.join(stack_reprod_dir, self.install_subdir, now_utc_timestamp)
540+
copy_dir(new_log_dir, app_reprod_dir)
541+
542+
519543
# Modules for dependencies are loaded in the prepare step. Thus, that's where we need this variable to be set
520544
# so that the modules can be succesfully loaded without printing the error (so that we can create a module
521545
# _with_ the warning for the current software being installed)
@@ -1297,6 +1321,31 @@ def post_module_hook(self, *args, **kwargs):
12971321
post_module_hook_zen4_gcccore1220(self, *args, **kwargs)
12981322

12991323

1324+
def post_easyblock_hook(self, *args, **kwargs):
1325+
"""Main post easyblock hook: trigger custom functions based on software name."""
1326+
if self.name in POST_EASYBLOCK_HOOKS:
1327+
POST_EASYBLOCK_HOOKS[self.name](self, *args, **kwargs)
1328+
1329+
# Always trigger this one, regardless of self.name
1330+
post_easyblock_hook_copy_exts_patches_to_easybuild_subdir(self, *args, **kwargs)
1331+
1332+
1333+
def post_build_and_install_loop_hook(ecs, *args, **kwargs):
1334+
"""
1335+
Post build and install loop hook that copies the easybuild subdirectory of every installed application
1336+
to a central location in the root of the software stack, e.g.:
1337+
/path/to/stack/reprod/20250102T12:34:56Z/MyApp/1.2-foss-2025a
1338+
"""
1339+
1340+
stack_reprod_dir = os.path.join(os.path.dirname(install_path()), STACK_REPROD_SUBDIR)
1341+
for (ec, status) in ecs:
1342+
if status['success']:
1343+
now_timestamp = datetime.datetime.now(datetime.UTC).isoformat('T', 'seconds').replace('+00:00', 'Z')
1344+
app_easybuild_dir = os.path.dirname(status['log_file'])
1345+
app_reprod_dir = os.path.join(stack_reprod_dir, ec['short_mod_name'], now_timestamp, 'easybuild')
1346+
copy_dir(app_easybuild_dir, app_reprod_dir)
1347+
1348+
13001349
PARSE_HOOKS = {
13011350
'casacore': parse_hook_casacore_disable_vectorize,
13021351
'CGAL': parse_hook_cgal_toolchainopts_precise,
@@ -1365,6 +1414,8 @@ def post_module_hook(self, *args, **kwargs):
13651414

13661415
POST_MODULE_HOOKS = {}
13671416

1417+
POST_EASYBLOCK_HOOKS = {}
1418+
13681419
# Define parallelism limit operations
13691420
def divide_by_factor(parallel, factor):
13701421
"""Divide parallelism by given factor"""

0 commit comments

Comments
 (0)