Skip to content

Commit c7e838f

Browse files
authored
Merge pull request #22 from EESSI/fix_include_easyblocks
Make sure to restore state after using `include_easyblocks()`
2 parents 43666a1 + ac76c61 commit c7e838f

2 files changed

Lines changed: 72 additions & 12 deletions

File tree

.github/workflows/docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
pid1=$!
4444
# then 2023.06 for EB 5
4545
export EESSI_ACCELERATOR_TARGET_OVERRIDE="accel/nvidia/cc90"
46-
( module load EESSI/2023.06 && module load EasyBuild/5.2 && module load EESSI-extend && python scripts/generate_data_files.py --eessi-version=2023.06 ) &
46+
( module load EESSI/2023.06 && module load EasyBuild/5 && module load EESSI-extend && python scripts/generate_data_files.py --eessi-version=2023.06 ) &
4747
pid2=$!
4848
# then 2025.06 for EB 5 (does not have EB4)
4949
export EESSI_ACCELERATOR_TARGET_OVERRIDE="accel/nvidia/cc90"

scripts/generate_data_files.py

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ def merge_dicts(d1, d2):
252252
{"name": "system", "version": "system"}
253253
] + get_toolchain_hierarchy(top_level_toolchain)
254254

255+
failed_include_easyblocks = []
255256
for eb_version_of_install, easyconfigs in sorted(easyconfig_files_dict.items()):
256257
print(f"Major version {eb_version_of_install}:")
257258
if eb_version_of_install == str(EASYBUILD_VERSION.version[0]):
@@ -266,21 +267,77 @@ def merge_dicts(d1, d2):
266267
# print(process_easyconfig(path)[0]['ec'].asdict())
267268

268269
eb_hooks_path = use_timestamped_reprod_if_exists(f"{os.path.dirname(easyconfig)}/reprod/easyblocks")
269-
easyblocks_dir = include_easyblocks(tmpdir, [eb_hooks_path + "/*.py"])
270-
with suppress_stdout():
271-
parsed_ec = process_easyconfig(easyconfig)[0]
272-
# included easyblocks are the first entry in sys.path, so just pop them but keep a list of what was used
273-
sys.path.pop(0)
274-
easyblocks_used = [
275-
os.path.basename(f)
276-
for f in glob.glob(f"{easyblocks_dir}/**/*.py", recursive=True)
277-
if os.path.basename(f) != "__init__.py"
278-
]
279-
shutil.rmtree(easyblocks_dir)
270+
271+
# Store our easyblock-related state before including easyblocks (which modify all these)
272+
orig_sys_path = list(sys.path)
273+
import easybuild.easyblocks
274+
import easybuild.easyblocks.generic
275+
orig_easyblocks_path = list(easybuild.easyblocks.__path__)
276+
orig_generic_easyblocks_path = list(easybuild.easyblocks.generic.__path__)
277+
278+
parsed_using_fallback = False
279+
include_easyblocks_dir = None
280+
try:
281+
include_easyblocks_dir = include_easyblocks(tmpdir, [eb_hooks_path + "/*.py"])
282+
with suppress_stdout():
283+
parsed_ec = process_easyconfig(easyconfig)[0]
284+
except Exception:
285+
# There are cases where a an easyblock inherits from a class but also imports
286+
# something from another easyblock which inherits from the same class, the import
287+
# easyblock is not included in the reproducibility dir as it is not an inherited
288+
# class. This can mean it may reference something that
289+
# is not available in the "legacy" easyblock included by include_easyblock().
290+
# Example is Tkinter, which inherits from EB_Python but also imports from
291+
# pythonpackage (which also imports from EB_Python). pythonpackage is being
292+
# picked up from the EasyBuild release being used for the parsing.
293+
294+
# Restore the original env and retry without include_easyblocks
295+
for module in list(sys.modules):
296+
if module.startswith("easybuild.easyblocks"):
297+
del sys.modules[module]
298+
sys.path[:] = orig_sys_path
299+
easybuild.easyblocks.__path__[:] = orig_easyblocks_path
300+
easybuild.easyblocks.generic.__path__[:] = orig_generic_easyblocks_path
301+
try:
302+
with suppress_stdout():
303+
parsed_ec = process_easyconfig(easyconfig)[0]
304+
print(f"Parsed {easyconfig} using fallback as using include_easyblocks() failed")
305+
parsed_using_fallback = True
306+
except Exception:
307+
print(f"Fallback parsing of {easyconfig} without using include_easyblocks() failed!")
308+
raise # or should we break?
309+
finally:
310+
if parsed_using_fallback:
311+
# Let's still report the easyblocks used by the actual installation
312+
easyblocks_dir = eb_hooks_path
313+
else:
314+
# Means include_easyblocks must have worked
315+
easyblocks_dir = include_easyblocks_dir
316+
317+
easyblocks_used = [
318+
os.path.basename(f)
319+
for f in glob.glob(f"{easyblocks_dir}/**/*.py", recursive=True)
320+
if os.path.basename(f) != "__init__.py"
321+
]
322+
# ALWAYS restore
323+
for module in list(sys.modules):
324+
if module.startswith("easybuild.easyblocks"):
325+
del sys.modules[module]
326+
sys.path[:] = orig_sys_path
327+
easybuild.easyblocks.__path__[:] = orig_easyblocks_path
328+
easybuild.easyblocks.generic.__path__[:] = orig_generic_easyblocks_path
329+
330+
if include_easyblocks_dir:
331+
shutil.rmtree(include_easyblocks_dir, ignore_errors=True)
280332

281333
# Store everything we now know about the installation as a dict
282334
# Use the path as the key since we know it is unique
283335
eessi_software["eessi_version"][eessi_version][easyconfig] = parsed_ec["ec"].asdict()
336+
if parsed_using_fallback:
337+
failed_include_easyblocks.append(easyconfig)
338+
eessi_software["eessi_version"][eessi_version][easyconfig]["parsed_with_eb_version_fallback"] = str(EASYBUILD_VERSION)
339+
else:
340+
eessi_software["eessi_version"][eessi_version][easyconfig]["parsed_with_eb_version_fallback"] = False
284341
eessi_software["eessi_version"][eessi_version][easyconfig]["mtime"] = os.path.getmtime(easyconfig)
285342

286343
# Make sure we can load the module before adding it's information to the main dict
@@ -306,3 +363,6 @@ def merge_dicts(d1, d2):
306363
"w",
307364
) as f:
308365
yaml.dump(eessi_software, f)
366+
367+
if failed_include_easyblocks:
368+
print(f"Failed to include_easyblocks() for {failed_include_easyblocks}")

0 commit comments

Comments
 (0)