1010from collections import defaultdict , OrderedDict
1111from datetime import datetime , timezone
1212from easybuild .tools .version import VERSION as EASYBUILD_VERSION
13- from easybuild .framework .easyconfig .easyconfig import process_easyconfig , get_toolchain_hierarchy
13+ from easybuild .framework .easyconfig .easyconfig import (
14+ process_easyconfig ,
15+ get_toolchain_hierarchy ,
16+ )
1417from easybuild .tools .options import set_up_configuration
1518from easybuild .tools .include import include_easyblocks
1619from contextlib import contextmanager
1720
1821VALID_EESSI_VERSIONS = ["2025.06" , "2023.06" ]
1922
20- EESSI_REFERENCE_ARCHITECTURE = "x86_64/intel/icelake"
21-
2223# Give order to my toolchains so I can easily figure out what "latest" means
2324EESSI_SUPPORTED_TOP_LEVEL_TOOLCHAINS = OrderedDict (
2425 {
@@ -49,7 +50,11 @@ def suppress_stdout():
4950
5051def module_dict_from_module_string (module ):
5152 module_name , module_version = module .split ("/" , 1 )
52- module_dict = {"module_name" : module_name , "module_version" : module_version , "full_module_name" : module }
53+ module_dict = {
54+ "module_name" : module_name ,
55+ "module_version" : module_version ,
56+ "full_module_name" : module ,
57+ }
5358
5459 return module_dict
5560
@@ -182,6 +187,16 @@ def collect_eb_files(base_path):
182187 return dict (eb_files_by_version )
183188
184189
190+ def merge_dicts (d1 , d2 ):
191+ merged = defaultdict (list )
192+
193+ for d in (d1 , d2 ):
194+ for key , value in d .items ():
195+ merged [key ].extend (value )
196+
197+ return dict (merged )
198+
199+
185200if __name__ == "__main__" :
186201 # The EESSI version is provided as an argument
187202 parser = argparse .ArgumentParser (description = "EESSI version to scan." )
@@ -199,10 +214,23 @@ def collect_eb_files(base_path):
199214 print (f"Using EESSI version: { eessi_version } " )
200215
201216 # We use a single architecture path to gather information about the software versions
202- base_path = (
203- f"/cvmfs/software.eessi.io/versions/{ eessi_version } /software/linux/{ EESSI_REFERENCE_ARCHITECTURE } /software/"
204- )
205- result = collect_eb_files (base_path )
217+ eessi_reference_architecture = os .getenv ("EESSI_ARCHDETECT_OPTIONS_OVERRIDE" , False )
218+ if not eessi_reference_architecture :
219+ print ("You must have selected a CPU architecture via EESSI_ARCHDETECT_OPTIONS_OVERRIDE" )
220+ exit ()
221+ base_path = f"/cvmfs/software.eessi.io/versions/{ eessi_version } /software/linux/{ eessi_reference_architecture } "
222+ cpu_easyconfig_files_dict = collect_eb_files (os .path .join (base_path , "software" ))
223+ # We also gather all the acclerator installations for NVIDIA-enabled packages
224+ # We're not typically running this script on a node with a GPU so an override must have been set
225+ eessi_reference_nvidia_architecture = os .getenv ("EESSI_ACCELERATOR_TARGET_OVERRIDE" , False )
226+ if not eessi_reference_nvidia_architecture :
227+ print ("You must have selected a GPU architecture via EESSI_ACCELERATOR_TARGET_OVERRIDE" )
228+ exit ()
229+ accel_base_path = os .path .join (base_path , eessi_reference_nvidia_architecture )
230+ accel_easyconfig_files_dict = collect_eb_files (os .path .join (accel_base_path , "software" ))
231+
232+ # Merge the easyconfig files
233+ easyconfig_files_dict = merge_dicts (cpu_easyconfig_files_dict , accel_easyconfig_files_dict )
206234
207235 set_up_configuration (args = "" )
208236 tmpdir = tempfile .mkdtemp ()
@@ -224,23 +252,23 @@ def collect_eb_files(base_path):
224252 {"name" : "system" , "version" : "system" }
225253 ] + get_toolchain_hierarchy (top_level_toolchain )
226254
227- for eb_version_of_install , files in sorted (result .items ()):
255+ for eb_version_of_install , easyconfigs in sorted (easyconfig_files_dict .items ()):
228256 print (f"Major version { eb_version_of_install } :" )
229257 if eb_version_of_install == str (EASYBUILD_VERSION .version [0 ]):
230- total_files = len (files )
231- for i , file in enumerate (files , start = 1 ):
232- percent = (i / total_files ) * 100
233- print (f"{ percent :.1f} % - { file } " )
258+ total_easyconfigs = len (easyconfigs )
259+ for i , easyconfig in enumerate (easyconfigs , start = 1 ):
260+ percent = (i / total_easyconfigs ) * 100
261+ print (f"{ percent :.1f} % - { easyconfig } " )
234262
235263 # Don't try to parse an EasyBuild easyconfig that is not the same major release
236- if "/software/EasyBuild/" in file and f"/EasyBuild/{ eb_version_of_install } " not in file :
264+ if "/software/EasyBuild/" in easyconfig and f"/EasyBuild/{ eb_version_of_install } " not in easyconfig :
237265 continue
238266 # print(process_easyconfig(path)[0]['ec'].asdict())
239267
240- eb_hooks_path = use_timestamped_reprod_if_exists (f"{ os .path .dirname (file )} /reprod/easyblocks" )
268+ eb_hooks_path = use_timestamped_reprod_if_exists (f"{ os .path .dirname (easyconfig )} /reprod/easyblocks" )
241269 easyblocks_dir = include_easyblocks (tmpdir , [eb_hooks_path + "/*.py" ])
242270 with suppress_stdout ():
243- parsed_ec = process_easyconfig (file )[0 ]
271+ parsed_ec = process_easyconfig (easyconfig )[0 ]
244272 # included easyblocks are the first entry in sys.path, so just pop them but keep a list of what was used
245273 sys .path .pop (0 )
246274 easyblocks_used = [
@@ -252,26 +280,29 @@ def collect_eb_files(base_path):
252280
253281 # Store everything we now know about the installation as a dict
254282 # Use the path as the key since we know it is unique
255- eessi_software ["eessi_version" ][eessi_version ][file ] = parsed_ec ["ec" ].asdict ()
256- eessi_software ["eessi_version" ][eessi_version ][file ]["mtime" ] = os .path .getmtime (file )
283+ eessi_software ["eessi_version" ][eessi_version ][easyconfig ] = parsed_ec ["ec" ].asdict ()
284+ eessi_software ["eessi_version" ][eessi_version ][easyconfig ]["mtime" ] = os .path .getmtime (easyconfig )
257285
258286 # Make sure we can load the module before adding it's information to the main dict
259287 try :
260- eessi_software ["eessi_version" ][eessi_version ][file ]["required_modules" ] = load_and_list_modules (
261- parsed_ec ["full_mod_name" ]
288+ eessi_software ["eessi_version" ][eessi_version ][easyconfig ]["required_modules" ] = (
289+ load_and_list_modules ( parsed_ec ["full_mod_name" ])
262290 )
263291 except RuntimeError as e :
264- print (f"Ignoring { file } due to error processing module: { e } " )
265- eessi_software ["eessi_version" ][eessi_version ].pop (file )
292+ print (f"Ignoring { easyconfig } due to error processing module: { e } " )
293+ eessi_software ["eessi_version" ][eessi_version ].pop (easyconfig )
266294 continue
267295
268296 # Add important data that is related to the module environment
269- eessi_software ["eessi_version" ][eessi_version ][file ]["module" ] = module_dict_from_module_string (
297+ eessi_software ["eessi_version" ][eessi_version ][easyconfig ]["module" ] = module_dict_from_module_string (
270298 parsed_ec ["full_mod_name" ]
271299 )
272300 # Retain the easyblocks used so we can use a heuristic to figure out the type of extensions (R, Python, Perl)
273- eessi_software ["eessi_version" ][eessi_version ][file ]["easyblocks" ] = easyblocks_used
301+ eessi_software ["eessi_version" ][eessi_version ][easyconfig ]["easyblocks" ] = easyblocks_used
274302
275303 # Store the result
276- with open (f"eessi_software_{ eessi_version } -eb{ str (EASYBUILD_VERSION .version [0 ])} .yaml" , "w" ) as f :
304+ with open (
305+ f"eessi_software_{ eessi_version } -eb{ str (EASYBUILD_VERSION .version [0 ])} .yaml" ,
306+ "w" ,
307+ ) as f :
277308 yaml .dump (eessi_software , f )
0 commit comments