5757from easybuild .tools .utilities import get_subclasses , nub
5858
5959
60+ MODULE_LOAD_ENV_HEADERS = 'HEADERS'
61+
6062# software root/version environment variable name prefixes
6163ROOT_ENV_VAR_NAME_PREFIX = "EBROOT"
6264VERSION_ENV_VAR_NAME_PREFIX = "EBVERSION"
@@ -241,18 +243,38 @@ def is_path(self):
241243
242244
243245class ModuleLoadEnvironment :
244- """Changes to environment variables that should be made when environment module is loaded"""
246+ """
247+ Changes to environment variables that should be made when environment module is loaded.
248+ - Environment variables are defined as ModuleEnvironmentVariables instances
249+ with attribute name equal to environment variable name.
250+ - Aliases are arbitrary names that serve to apply changes to lists of
251+ environment variables
252+ - Only environment variables attributes are public. Other attributes like
253+ aliases are private.
254+ """
245255
246- def __init__ (self ):
256+ def __init__ (self , aliases = None ):
247257 """
248258 Initialize default environment definition
249259 Paths are relative to root of installation directory
260+
261+ :aliases: dict defining environment variables aliases
250262 """
263+ self ._aliases = {}
264+ if aliases is not None :
265+ try :
266+ for alias_name , alias_vars in aliases .items ():
267+ self .update_alias (alias_name , alias_vars )
268+ except AttributeError as err :
269+ raise EasyBuildError (
270+ "Wrong format for aliases defitions passed to ModuleLoadEnvironment. "
271+ f"Expected a dictionary but got: { type (aliases )} ."
272+ ) from err
273+
251274 self .ACLOCAL_PATH = [os .path .join ('share' , 'aclocal' )]
252275 self .CLASSPATH = ['*.jar' ]
253276 self .CMAKE_LIBRARY_PATH = ['lib64' ] # only needed for installations with standalone lib64
254277 self .CMAKE_PREFIX_PATH = ['' ]
255- self .CPATH = SEARCH_PATH_HEADER_DIRS
256278 self .GI_TYPELIB_PATH = [os .path .join (x , 'girepository-*' ) for x in SEARCH_PATH_LIB_DIRS ]
257279 self .LD_LIBRARY_PATH = SEARCH_PATH_LIB_DIRS
258280 self .LIBRARY_PATH = SEARCH_PATH_LIB_DIRS
@@ -261,11 +283,29 @@ def __init__(self):
261283 self .PKG_CONFIG_PATH = [os .path .join (x , 'pkgconfig' ) for x in SEARCH_PATH_LIB_DIRS + ['share' ]]
262284 self .XDG_DATA_DIRS = ['share' ]
263285
286+ # environment variables with known aliases
287+ # e.g. search paths to C/C++ headers
288+ for envar_name in self ._aliases .get (MODULE_LOAD_ENV_HEADERS , []):
289+ setattr (self , envar_name , SEARCH_PATH_HEADER_DIRS )
290+
264291 def __setattr__ (self , name , value ):
265292 """
266293 Specific restrictions for ModuleLoadEnvironment attributes:
294+ - public attributes are instances of ModuleEnvironmentVariable with uppercase names
295+ - private attributes are allowed with any name
296+ """
297+ if name .startswith ('_' ):
298+ # do not control protected/private attributes
299+ return super ().__setattr__ (name , value )
300+
301+ return self .__set_module_environment_variable (name , value )
302+
303+ def __set_module_environment_variable (self , name , value ):
304+ """
305+ Specific restrictions for ModuleEnvironmentVariable attributes:
267306 - attribute names are uppercase
268- - attributes are instances of ModuleEnvironmentVariable
307+ - dictionaries are unpacked into arguments of ModuleEnvironmentVariable
308+ - controls variables with special types (e.g. PATH, LD_LIBRARY_PATH)
269309 """
270310 if name != name .upper ():
271311 raise EasyBuildError (f"Names of ModuleLoadEnvironment attributes must be uppercase, got '{ name } '" )
@@ -284,17 +324,24 @@ def __setattr__(self, name, value):
284324
285325 return super ().__setattr__ (name , ModuleEnvironmentVariable (contents , ** kwargs ))
286326
327+ @property
328+ def vars (self ):
329+ """Return list of public ModuleEnvironmentVariable"""
330+
331+ return [envar for envar in self .__dict__ if not str (envar ).startswith ('_' )]
332+
287333 def __iter__ (self ):
288334 """Make the class iterable"""
289- yield from self .__dict__
335+ yield from self .vars
290336
291337 def items (self ):
292338 """
293339 Return key-value pairs for each attribute that is a ModuleEnvironmentVariable
294340 - key = attribute name
295341 - value = its "contents" attribute
296342 """
297- return self .__dict__ .items ()
343+ for attr in self .vars :
344+ yield attr , getattr (self , attr )
298345
299346 def update (self , new_env ):
300347 """Update contents of environment from given dictionary"""
@@ -304,6 +351,20 @@ def update(self, new_env):
304351 except AttributeError as err :
305352 raise EasyBuildError ("Cannot update ModuleLoadEnvironment from a non-dict variable" ) from err
306353
354+ def replace (self , new_env ):
355+ """Replace contents of environment with given dictionary"""
356+ for var in self .vars :
357+ self .remove (var )
358+ self .update (new_env )
359+
360+ def remove (self , var_name ):
361+ """
362+ Remove ModuleEnvironmentVariable attribute from instance
363+ Silently goes through if attribute is already missing
364+ """
365+ if var_name in self .vars :
366+ delattr (self , var_name )
367+
307368 @property
308369 def as_dict (self ):
309370 """
@@ -319,6 +380,48 @@ def environ(self):
319380 """
320381 return {envar_name : str (envar_contents ) for envar_name , envar_contents in self .items ()}
321382
383+ def alias (self , alias ):
384+ """
385+ Return iterator to search path variables for given alias
386+ """
387+ try :
388+ yield from [getattr (self , envar ) for envar in self ._aliases [alias ]]
389+ except KeyError as err :
390+ raise EasyBuildError (f"Unknown search path alias: { alias } " ) from err
391+ except AttributeError as err :
392+ raise EasyBuildError (f"Missing environment variable in '{ alias } alias" ) from err
393+
394+ def alias_vars (self , alias ):
395+ """
396+ Return list of environment variable names aliased by given alias
397+ """
398+ try :
399+ return self ._aliases [alias ]
400+ except KeyError as err :
401+ raise EasyBuildError (f"Unknown search path alias: { alias } " ) from err
402+
403+ def update_alias (self , alias , value ):
404+ """
405+ Update existing or non-existing alias with given search paths variables
406+ """
407+ if isinstance (value , str ):
408+ value = [value ]
409+
410+ try :
411+ self ._aliases [alias ] = [str (envar ) for envar in value ]
412+ except TypeError as err :
413+ raise TypeError ("ModuleLoadEnvironment aliases must be a list of strings" ) from err
414+
415+ def set_alias_vars (self , alias , value ):
416+ """
417+ Set value of search paths variables for given alias
418+ """
419+ try :
420+ for envar_name in self ._aliases [alias ]:
421+ setattr (self , envar_name , value )
422+ except KeyError as err :
423+ raise EasyBuildError (f"Unknown search path alias: { alias } " ) from err
424+
322425
323426class ModulesTool (object ):
324427 """An abstract interface to a tool that deals with modules."""
@@ -1013,7 +1116,6 @@ def run_module(self, *args, **kwargs):
10131116 # stdout will contain python code (to change environment etc)
10141117 # stderr will contain text (just like the normal module command)
10151118 stdout , stderr = res .output , res .stderr
1016- self .log .debug ("Output of module command '%s': stdout: %s; stderr: %s" , cmd , stdout , stderr )
10171119
10181120 # also catch and check exit code
10191121 if kwargs .get ('check_exit_code' , True ) and res .exit_code != EasyBuildExit .SUCCESS :
0 commit comments