@@ -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