From 9f81a26b661f03cb418ae1841e1b580a00a01188 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Wed, 15 Apr 2026 09:55:16 +0200 Subject: [PATCH 1/7] Make sure to restore state after using include_easyblocks() --- .github/workflows/docs.yml | 2 +- scripts/generate_data_files.py | 41 +++++++++++++++++++++++++--------- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 66e8171..399f7ef 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -43,7 +43,7 @@ jobs: pid1=$! # then 2023.06 for EB 5 export EESSI_ACCELERATOR_TARGET_OVERRIDE="accel/nvidia/cc90" - ( module load EESSI/2023.06 && module load EasyBuild/5.2 && module load EESSI-extend && python scripts/generate_data_files.py --eessi-version=2023.06 ) & + ( module load EESSI/2023.06 && module load EasyBuild/5 && module load EESSI-extend && python scripts/generate_data_files.py --eessi-version=2023.06 ) & pid2=$! # then 2025.06 for EB 5 (does not have EB4) export EESSI_ACCELERATOR_TARGET_OVERRIDE="accel/nvidia/cc90" diff --git a/scripts/generate_data_files.py b/scripts/generate_data_files.py index 30026bf..17a0283 100644 --- a/scripts/generate_data_files.py +++ b/scripts/generate_data_files.py @@ -266,17 +266,36 @@ def merge_dicts(d1, d2): # print(process_easyconfig(path)[0]['ec'].asdict()) eb_hooks_path = use_timestamped_reprod_if_exists(f"{os.path.dirname(easyconfig)}/reprod/easyblocks") - easyblocks_dir = include_easyblocks(tmpdir, [eb_hooks_path + "/*.py"]) - with suppress_stdout(): - parsed_ec = process_easyconfig(easyconfig)[0] - # included easyblocks are the first entry in sys.path, so just pop them but keep a list of what was used - sys.path.pop(0) - easyblocks_used = [ - os.path.basename(f) - for f in glob.glob(f"{easyblocks_dir}/**/*.py", recursive=True) - if os.path.basename(f) != "__init__.py" - ] - shutil.rmtree(easyblocks_dir) + + # Store our easyblock-related state before including easyblocks (which modify all these) + orig_sys_path = list(sys.path) + import easybuild.easyblocks + import easybuild.easyblocks.generic + orig_easyblocks_path = list(easybuild.easyblocks.__path__) + orig_generic_easyblocks_path = list(easybuild.easyblocks.generic.__path__) + + easyblocks_dir = None + try: + easyblocks_dir = include_easyblocks(tmpdir, [eb_hooks_path + "/*.py"]) + with suppress_stdout(): + parsed_ec = process_easyconfig(easyconfig)[0] + easyblocks_used = [ + os.path.basename(f) + for f in glob.glob(f"{easyblocks_dir}/**/*.py", recursive=True) + if os.path.basename(f) != "__init__.py" + ] + except Exception: + raise # or should we break? + finally: + # ALWAYS restore + for module in list(sys.modules): + if module.startswith("easybuild.easyblocks"): + del sys.modules[module] + sys.path[:] = orig_sys_path + easybuild.easyblocks.__path__[:] = orig_easyblocks_path + easybuild.easyblocks.generic.__path__[:] = orig_generic_easyblocks_path + + shutil.rmtree(easyblocks_dir, ignore_errors=True) # Store everything we now know about the installation as a dict # Use the path as the key since we know it is unique From d643d084fc9528ccc1e32befff23dd71d67f2504 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Wed, 15 Apr 2026 10:03:34 +0200 Subject: [PATCH 2/7] Test the one that was problematic --- .github/workflows/prs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prs.yml b/.github/workflows/prs.yml index da0d590..04104de 100644 --- a/.github/workflows/prs.yml +++ b/.github/workflows/prs.yml @@ -34,7 +34,7 @@ jobs: export EESSI_ACCELERATOR_TARGET_OVERRIDE="accel/nvidia/cc90" export EESSI_OVERRIDE_GPU_CHECK=1 # Only do 2023.06 for EB 5 since this is just a test - ( module load EESSI/2023.06 && module load EasyBuild/5 && module load EESSI-extend && python scripts/generate_data_files.py --eessi-version=2023.06 ) & + ( module load EESSI/2025.06 && module load EasyBuild/5 && module load EESSI-extend && python scripts/generate_data_files.py --eessi-version=2025.06 ) & # Merge all these results together wait python scripts/merge_data_files.py out.yaml eessi*.yaml From a37d3d2c379ab3a2d0dd5918e75a63c7bd41f336 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Wed, 15 Apr 2026 10:50:33 +0200 Subject: [PATCH 3/7] Retry the parse with the current EB version if we have a failure --- scripts/generate_data_files.py | 36 +++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/scripts/generate_data_files.py b/scripts/generate_data_files.py index 17a0283..48c9c57 100644 --- a/scripts/generate_data_files.py +++ b/scripts/generate_data_files.py @@ -274,19 +274,41 @@ def merge_dicts(d1, d2): orig_easyblocks_path = list(easybuild.easyblocks.__path__) orig_generic_easyblocks_path = list(easybuild.easyblocks.generic.__path__) - easyblocks_dir = None + easyblocks_dir = include_easyblocks(tmpdir, [eb_hooks_path + "/*.py"]) + parsed_using_fallback = False try: - easyblocks_dir = include_easyblocks(tmpdir, [eb_hooks_path + "/*.py"]) with suppress_stdout(): parsed_ec = process_easyconfig(easyconfig)[0] + except Exception: + # There are cases where a an easyblock inherits from a class but also imports + # something from another easyblock which inherits from the same class, the import + # easyblock is not included in the reproducibility dir as it is not an inherited + # class. This can mean it may reference something that + # is not available in the "legacy" easyblock included by include_easyblock(). + # Example is Tkinter, which inherits from EB_Python but also imports from + # pythonpackage (which also imports from EB_Python). pythonpackage is being + # picked up from the EasyBuild release being used for the parsing. + + # Restore the original env and retry without include_easyblocks + for module in list(sys.modules): + if module.startswith("easybuild.easyblocks"): + del sys.modules[module] + sys.path[:] = orig_sys_path + easybuild.easyblocks.__path__[:] = orig_easyblocks_path + easybuild.easyblocks.generic.__path__[:] = orig_generic_easyblocks_path + try: + with suppress_stdout(): + parsed_ec = process_easyconfig(easyconfig)[0] + parsed_using_fallback = True + except Exception: + print(f"Fallback parsing of {easyconfig} without using include_easyblocks() failed!") + raise # or should we break? + finally: easyblocks_used = [ os.path.basename(f) for f in glob.glob(f"{easyblocks_dir}/**/*.py", recursive=True) if os.path.basename(f) != "__init__.py" ] - except Exception: - raise # or should we break? - finally: # ALWAYS restore for module in list(sys.modules): if module.startswith("easybuild.easyblocks"): @@ -300,6 +322,10 @@ def merge_dicts(d1, d2): # Store everything we now know about the installation as a dict # Use the path as the key since we know it is unique eessi_software["eessi_version"][eessi_version][easyconfig] = parsed_ec["ec"].asdict() + if parsed_using_fallback: + eessi_software["eessi_version"][eessi_version][easyconfig]["parsed_with_eb_version_fallback"] = EASYBUILD_VERSION + else: + eessi_software["eessi_version"][eessi_version][easyconfig]["parsed_with_eb_version_fallback"] = False eessi_software["eessi_version"][eessi_version][easyconfig]["mtime"] = os.path.getmtime(easyconfig) # Make sure we can load the module before adding it's information to the main dict From efc723ed1437bce18a66627c25f9fc37fcba65e9 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Wed, 15 Apr 2026 10:51:54 +0200 Subject: [PATCH 4/7] Retry the parse with the current EB version if we have a failure --- scripts/generate_data_files.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/generate_data_files.py b/scripts/generate_data_files.py index 48c9c57..736e1e3 100644 --- a/scripts/generate_data_files.py +++ b/scripts/generate_data_files.py @@ -299,6 +299,7 @@ def merge_dicts(d1, d2): try: with suppress_stdout(): parsed_ec = process_easyconfig(easyconfig)[0] + print(f"Parsed {easyconfig} using fallback as using include_easyblocks() failed") parsed_using_fallback = True except Exception: print(f"Fallback parsing of {easyconfig} without using include_easyblocks() failed!") From 1cbf2a20885631bfc60c359c754ffd2946846d63 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Wed, 15 Apr 2026 11:08:57 +0200 Subject: [PATCH 5/7] Be more careful about what we are try-ing --- scripts/generate_data_files.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scripts/generate_data_files.py b/scripts/generate_data_files.py index 736e1e3..f2dec16 100644 --- a/scripts/generate_data_files.py +++ b/scripts/generate_data_files.py @@ -274,9 +274,10 @@ def merge_dicts(d1, d2): orig_easyblocks_path = list(easybuild.easyblocks.__path__) orig_generic_easyblocks_path = list(easybuild.easyblocks.generic.__path__) - easyblocks_dir = include_easyblocks(tmpdir, [eb_hooks_path + "/*.py"]) parsed_using_fallback = False + include_easyblocks_dir = None try: + include_easyblocks_dir = include_easyblocks(tmpdir, [eb_hooks_path + "/*.py"]) with suppress_stdout(): parsed_ec = process_easyconfig(easyconfig)[0] except Exception: @@ -305,6 +306,13 @@ def merge_dicts(d1, d2): print(f"Fallback parsing of {easyconfig} without using include_easyblocks() failed!") raise # or should we break? finally: + if parsed_using_fallback: + # Let's still report the easyblocks used by the actual installation + easyblocks_dir = eb_hooks_path + else: + # Means include_easyblocks must have worked + easyblocks_dir = include_easyblocks_dir + easyblocks_used = [ os.path.basename(f) for f in glob.glob(f"{easyblocks_dir}/**/*.py", recursive=True) @@ -318,7 +326,8 @@ def merge_dicts(d1, d2): easybuild.easyblocks.__path__[:] = orig_easyblocks_path easybuild.easyblocks.generic.__path__[:] = orig_generic_easyblocks_path - shutil.rmtree(easyblocks_dir, ignore_errors=True) + if include_easyblocks_dir: + shutil.rmtree(include_easyblocks_dir, ignore_errors=True) # Store everything we now know about the installation as a dict # Use the path as the key since we know it is unique From d0018aa5d6190a7748d9f27fecb8df0fbf97181a Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Wed, 15 Apr 2026 11:25:39 +0200 Subject: [PATCH 6/7] Add some logging and cast EASYBUILD_VERSION to string for yaml --- scripts/generate_data_files.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/generate_data_files.py b/scripts/generate_data_files.py index f2dec16..6d7568d 100644 --- a/scripts/generate_data_files.py +++ b/scripts/generate_data_files.py @@ -252,6 +252,7 @@ def merge_dicts(d1, d2): {"name": "system", "version": "system"} ] + get_toolchain_hierarchy(top_level_toolchain) + failed_include_easyblocks = [] for eb_version_of_install, easyconfigs in sorted(easyconfig_files_dict.items()): print(f"Major version {eb_version_of_install}:") if eb_version_of_install == str(EASYBUILD_VERSION.version[0]): @@ -333,7 +334,8 @@ def merge_dicts(d1, d2): # Use the path as the key since we know it is unique eessi_software["eessi_version"][eessi_version][easyconfig] = parsed_ec["ec"].asdict() if parsed_using_fallback: - eessi_software["eessi_version"][eessi_version][easyconfig]["parsed_with_eb_version_fallback"] = EASYBUILD_VERSION + failed_include_easyblocks.append(easyconfig) + eessi_software["eessi_version"][eessi_version][easyconfig]["parsed_with_eb_version_fallback"] = str(EASYBUILD_VERSION) else: eessi_software["eessi_version"][eessi_version][easyconfig]["parsed_with_eb_version_fallback"] = False eessi_software["eessi_version"][eessi_version][easyconfig]["mtime"] = os.path.getmtime(easyconfig) @@ -361,3 +363,6 @@ def merge_dicts(d1, d2): "w", ) as f: yaml.dump(eessi_software, f) + + if failed_include_easyblocks: + print(f"Failed to include_easyblocks() for {failed_include_easyblocks}") From ac76c61ce5e8d31b3088f03d109f44c1b80cbc9b Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Wed, 15 Apr 2026 11:43:20 +0200 Subject: [PATCH 7/7] Restore the quicker test --- .github/workflows/prs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prs.yml b/.github/workflows/prs.yml index 04104de..da0d590 100644 --- a/.github/workflows/prs.yml +++ b/.github/workflows/prs.yml @@ -34,7 +34,7 @@ jobs: export EESSI_ACCELERATOR_TARGET_OVERRIDE="accel/nvidia/cc90" export EESSI_OVERRIDE_GPU_CHECK=1 # Only do 2023.06 for EB 5 since this is just a test - ( module load EESSI/2025.06 && module load EasyBuild/5 && module load EESSI-extend && python scripts/generate_data_files.py --eessi-version=2025.06 ) & + ( module load EESSI/2023.06 && module load EasyBuild/5 && module load EESSI-extend && python scripts/generate_data_files.py --eessi-version=2023.06 ) & # Merge all these results together wait python scripts/merge_data_files.py out.yaml eessi*.yaml