52
52
from easybuild .tools .modules import ROOT_ENV_VAR_NAME_PREFIX , EnvironmentModulesC , Lmod , modules_tool
53
53
from easybuild .tools .utilities import get_subclasses , nub , quote_str
54
54
55
-
56
55
_log = fancylogger .getLogger ('module_generator' , fname = False )
57
56
58
57
@@ -133,6 +132,15 @@ class ModuleGenerator:
133
132
# a single level of indentation
134
133
INDENTATION = ' ' * 4
135
134
135
+ # shell environment variable name: ${__}VAR_NAME_00_SUFFIX
136
+ REGEX_SHELL_VAR_PATTERN = r'[A-Z_]+[A-Z0-9_]+'
137
+ REGEX_SHELL_VAR = re .compile (rf'\$({ REGEX_SHELL_VAR_PATTERN } )' )
138
+ REGEX_QUOTE_SHELL_VAR = re .compile (rf'[\"\']\$({ REGEX_SHELL_VAR_PATTERN } )[\"\']' )
139
+
140
+ # default options for modextravars
141
+ DEFAULT_MODEXTRAVARS_USE_PUSHENV = False
142
+ DEFAULT_MODEXTRAVARS_RESOLVE_ENV_VARS = True
143
+
136
144
def __init__ (self , application , fake = False ):
137
145
"""ModuleGenerator constructor."""
138
146
self .app = application
@@ -422,28 +430,38 @@ def det_installdir(self, modfile):
422
430
423
431
return res
424
432
425
- def unpack_setenv_value (self , env_var_name , env_var_val ):
433
+ def unpack_setenv_value (self , * args , ** kwargs ):
434
+ """
435
+ DEPRECATED method, should not be used.
436
+ Replaced with (internal) _unpack_setenv_value method.
437
+ """
438
+ self .log .deprecated ("unpack_setenv_value should not be used directly (replaced by internal method)" , '6.0' )
439
+ value , use_pushenv , _ = self ._unpack_setenv_value (* args , ** kwargs )
440
+ return value , use_pushenv
441
+
442
+ def _unpack_setenv_value (self , env_var_name , env_var_val ):
426
443
"""
427
444
Unpack value that specifies how to define an environment variable with specified name.
428
445
"""
429
- use_pushenv = False
446
+ use_pushenv = self .DEFAULT_MODEXTRAVARS_USE_PUSHENV
447
+ resolve_env_vars = self .DEFAULT_MODEXTRAVARS_RESOLVE_ENV_VARS
430
448
431
449
# value may be specified as a string, or as a dict for special cases
432
450
if isinstance (env_var_val , str ):
433
451
value = env_var_val
434
-
435
452
elif isinstance (env_var_val , dict ):
436
- use_pushenv = env_var_val .get ('pushenv' , False )
453
+ use_pushenv = env_var_val .get ('pushenv' , self .DEFAULT_MODEXTRAVARS_USE_PUSHENV )
454
+ resolve_env_vars = env_var_val .get ('resolve_env_vars' , self .DEFAULT_MODEXTRAVARS_RESOLVE_ENV_VARS )
437
455
try :
438
456
value = env_var_val ['value' ]
439
- except KeyError :
457
+ except KeyError as err :
440
458
raise EasyBuildError ("Required key 'value' is missing in dict that specifies how to set $%s: %s" ,
441
- env_var_name , env_var_val )
459
+ env_var_name , env_var_val ) from err
442
460
else :
443
461
raise EasyBuildError ("Incorrect value type for setting $%s environment variable (%s): %s" ,
444
462
env_var_name , type (env_var_val ), env_var_val )
445
463
446
- return value , use_pushenv
464
+ return value , use_pushenv , resolve_env_vars
447
465
448
466
# From this point on just not implemented methods
449
467
@@ -1056,19 +1074,19 @@ def set_environment(self, key, value, relpath=False):
1056
1074
self .log .info ("Not including statement to define environment variable $%s, as specified" , key )
1057
1075
return ''
1058
1076
1059
- value , use_pushenv = self .unpack_setenv_value (key , value )
1077
+ set_value , use_pushenv , resolve_env_vars = self ._unpack_setenv_value (key , value )
1060
1078
1061
- # quotes are needed, to ensure smooth working of EBDEVEL* modulefiles
1062
1079
if relpath :
1063
- if value :
1064
- val = quote_str (os .path .join ('$root' , value ), tcl = True )
1065
- else :
1066
- val = '"$root"'
1067
- else :
1068
- val = quote_str (value , tcl = True )
1080
+ set_value = os .path .join ('$root' , set_value ) if set_value else '$root'
1081
+
1082
+ if resolve_env_vars :
1083
+ set_value = self .REGEX_SHELL_VAR .sub (r'$::env(\1)' , set_value )
1084
+
1085
+ # quotes are needed, to ensure smooth working of EBDEVEL* modulefiles
1086
+ set_value = quote_str (set_value , tcl = True )
1069
1087
1070
1088
env_setter = 'pushenv' if use_pushenv else 'setenv'
1071
- return '%s \t %s \t \t %s \n ' % ( env_setter , key , val )
1089
+ return f' { env_setter } \t { key } \t \t { set_value } \n '
1072
1090
1073
1091
def swap_module (self , mod_name_out , mod_name_in , guarded = True ):
1074
1092
"""
@@ -1152,12 +1170,14 @@ class ModuleGeneratorLua(ModuleGenerator):
1152
1170
LOAD_TEMPLATE_DEPENDS_ON = 'depends_on("%(mod_name)s")'
1153
1171
IS_LOADED_TEMPLATE = 'isloaded("%s")'
1154
1172
1173
+ OS_GETENV_TEMPLATE = r'os.getenv("%s")'
1155
1174
PATH_JOIN_TEMPLATE = 'pathJoin(root, "%s")'
1156
1175
UPDATE_PATH_TEMPLATE = '%s_path("%s", %s)'
1157
1176
UPDATE_PATH_TEMPLATE_DELIM = '%s_path("%s", %s, "%s")'
1158
1177
1159
1178
START_STR = '[==['
1160
1179
END_STR = ']==]'
1180
+ CONCAT_STR = ' .. '
1161
1181
1162
1182
def __init__ (self , * args , ** kwargs ):
1163
1183
"""ModuleGeneratorLua constructor."""
@@ -1167,6 +1187,20 @@ def __init__(self, *args, **kwargs):
1167
1187
if self .modules_tool .version and LooseVersion (self .modules_tool .version ) >= LooseVersion ('7.7.38' ):
1168
1188
self .DOT_MODULERC = '.modulerc.lua'
1169
1189
1190
+ @staticmethod
1191
+ def _path_join_cmd (path ):
1192
+ "Return 'pathJoin' command for given path string"
1193
+ path_components = [quote_str (p ) for p in path .split (os .path .sep ) if p ]
1194
+
1195
+ path_root = quote_str (os .path .sep ) if os .path .isabs (path ) else 'root'
1196
+ path_components .insert (0 , path_root )
1197
+
1198
+ if len (path_components ) > 1 :
1199
+ return 'pathJoin(' + ', ' .join (path_components ) + ')'
1200
+
1201
+ # no need for a pathJoin for single component paths
1202
+ return path_components [0 ]
1203
+
1170
1204
def check_version (self , minimal_version_maj , minimal_version_min , minimal_version_patch = '0' ):
1171
1205
"""
1172
1206
Check the minimal version of the moduletool in the module file
@@ -1292,10 +1326,9 @@ def getenv_cmd(self, envvar, default=None):
1292
1326
"""
1293
1327
Return module-syntax specific code to get value of specific environment variable.
1294
1328
"""
1295
- if default is None :
1296
- cmd = 'os.getenv("%s")' % envvar
1297
- else :
1298
- cmd = 'os.getenv("%s") or "%s"' % (envvar , default )
1329
+ cmd = self .OS_GETENV_TEMPLATE % envvar
1330
+ if default is not None :
1331
+ cmd += f' or "{ default } "'
1299
1332
return cmd
1300
1333
1301
1334
def load_module (self , mod_name , recursive_unload = None , depends_on = None , unload_modules = None , multi_dep_mods = None ):
@@ -1448,7 +1481,7 @@ def update_paths(self, key, paths, prepend=True, allow_abs=False, expand_relpath
1448
1481
# use pathJoin for (non-empty) relative paths
1449
1482
if path :
1450
1483
if expand_relpaths :
1451
- abspaths .append (self .PATH_JOIN_TEMPLATE % path )
1484
+ abspaths .append (self ._path_join_cmd ( path ) )
1452
1485
else :
1453
1486
abspaths .append (quote_str (path ))
1454
1487
else :
@@ -1513,19 +1546,28 @@ def set_environment(self, key, value, relpath=False):
1513
1546
self .log .info ("Not including statement to define environment variable $%s, as specified" , key )
1514
1547
return ''
1515
1548
1516
- value , use_pushenv = self .unpack_setenv_value (key , value )
1549
+ set_value , use_pushenv , resolve_env_vars = self ._unpack_setenv_value (key , value )
1517
1550
1518
1551
if relpath :
1519
- if value :
1520
- val = self .PATH_JOIN_TEMPLATE % value
1521
- else :
1522
- val = 'root'
1552
+ set_value = self ._path_join_cmd (set_value )
1553
+ if resolve_env_vars :
1554
+ # replace quoted substring with env var with os.getenv statement
1555
+ # example: pathJoin(root, "$HOME") -> pathJoin(root, os.getenv("HOME"))
1556
+ set_value = self .REGEX_QUOTE_SHELL_VAR .sub (self .OS_GETENV_TEMPLATE % r"\1" , set_value )
1523
1557
else :
1524
- val = quote_str (value )
1558
+ if resolve_env_vars :
1559
+ # replace env var with os.getenv statement
1560
+ # example: $HOME -> os.getenv("HOME")
1561
+ concat_getenv = self .CONCAT_STR + self .OS_GETENV_TEMPLATE % r"\1" + self .CONCAT_STR
1562
+ set_value = self .REGEX_SHELL_VAR .sub (concat_getenv , set_value )
1563
+ set_value = self .CONCAT_STR .join ([
1564
+ # quote any substrings that are not a os.getenv Lua statement
1565
+ x if x .startswith (self .OS_GETENV_TEMPLATE [:10 ]) else quote_str (x )
1566
+ for x in set_value .strip (self .CONCAT_STR ).split (self .CONCAT_STR )
1567
+ ])
1525
1568
1526
1569
env_setter = 'pushenv' if use_pushenv else 'setenv'
1527
-
1528
- return '%s("%s", %s)\n ' % (env_setter , key , val )
1570
+ return f'{ env_setter } ("{ key } ", { set_value } )\n '
1529
1571
1530
1572
def swap_module (self , mod_name_out , mod_name_in , guarded = True ):
1531
1573
"""
0 commit comments