5959from easybuild .framework .easyconfig .format .format import DEPENDENCY_PARAMETERS
6060from easybuild .framework .easyconfig .format .one import EB_FORMAT_EXTENSION , retrieve_blocks_in_spec
6161from easybuild .framework .easyconfig .licenses import EASYCONFIG_LICENSES_DICT
62- from easybuild .framework .easyconfig .parser import DEPRECATED_PARAMETERS , REPLACED_PARAMETERS
62+ from easybuild .framework .easyconfig .parser import ALTERNATE_PARAMETERS , DEPRECATED_PARAMETERS , REPLACED_PARAMETERS
6363from easybuild .framework .easyconfig .parser import EasyConfigParser , fetch_parameters_from_easyconfig
64- from easybuild .framework .easyconfig .templates import TEMPLATE_CONSTANTS , TEMPLATE_NAMES_DYNAMIC , template_constant_dict
64+ from easybuild .framework .easyconfig .templates import ALTERNATE_TEMPLATES , DEPRECATED_TEMPLATES , TEMPLATE_CONSTANTS
65+ from easybuild .framework .easyconfig .templates import TEMPLATE_NAMES_DYNAMIC , template_constant_dict
6566from easybuild .tools import LooseVersion
66- from easybuild .tools .build_log import EasyBuildError , print_warning , print_msg
67+ from easybuild .tools .build_log import EasyBuildError , EasyBuildExit , print_warning , print_msg
6768from easybuild .tools .config import GENERIC_EASYBLOCK_PKG , LOCAL_VAR_NAMING_CHECK_ERROR , LOCAL_VAR_NAMING_CHECK_LOG
6869from easybuild .tools .config import LOCAL_VAR_NAMING_CHECK_WARN
6970from easybuild .tools .config import Singleton , build_option , get_module_naming_scheme
@@ -118,11 +119,13 @@ def handle_deprecated_or_replaced_easyconfig_parameters(ec_method):
118119 def new_ec_method (self , key , * args , ** kwargs ):
119120 """Check whether any replace easyconfig parameters are still used"""
120121 # map deprecated parameters to their replacements, issue deprecation warning(/error)
121- if key in DEPRECATED_PARAMETERS :
122+ if key in ALTERNATE_PARAMETERS :
123+ key = ALTERNATE_PARAMETERS [key ]
124+ elif key in DEPRECATED_PARAMETERS :
122125 depr_key = key
123126 key , ver = DEPRECATED_PARAMETERS [depr_key ]
124127 _log .deprecated ("Easyconfig parameter '%s' is deprecated, use '%s' instead" % (depr_key , key ), ver )
125- if key in REPLACED_PARAMETERS :
128+ elif key in REPLACED_PARAMETERS :
126129 _log .nosupport ("Easyconfig parameter '%s' is replaced by '%s'" % (key , REPLACED_PARAMETERS [key ]), '2.0' )
127130 return ec_method (self , key , * args , ** kwargs )
128131
@@ -179,7 +182,7 @@ def triage_easyconfig_params(variables, ec):
179182
180183 for key in variables :
181184 # validations are skipped, just set in the config
182- if key in ec or key in DEPRECATED_PARAMETERS .keys ():
185+ if any ( key in d for d in ( ec , DEPRECATED_PARAMETERS .keys (), ALTERNATE_PARAMETERS . keys ()) ):
183186 ec_params [key ] = variables [key ]
184187 _log .debug ("setting config option %s: value %s (type: %s)" , key , ec_params [key ], type (ec_params [key ]))
185188 elif key in REPLACED_PARAMETERS :
@@ -658,7 +661,7 @@ def set_keys(self, params):
658661 with self .disable_templating ():
659662 for key in sorted (params .keys ()):
660663 # validations are skipped, just set in the config
661- if key in self . _config . keys () or key in DEPRECATED_PARAMETERS . keys ( ):
664+ if any ( key in x . keys () for x in ( self . _config , ALTERNATE_PARAMETERS , DEPRECATED_PARAMETERS ) ):
662665 self [key ] = params [key ]
663666 self .log .info ("setting easyconfig parameter %s: value %s (type: %s)" ,
664667 key , self [key ], type (self [key ]))
@@ -827,7 +830,7 @@ def check_deprecated(self, path):
827830 if depr_msgs :
828831 depr_msg = ', ' .join (depr_msgs )
829832
830- depr_maj_ver = int (str (VERSION ).split ('.' )[0 ]) + 1
833+ depr_maj_ver = int (str (VERSION ).split ('.' , maxsplit = 1 )[0 ]) + 1
831834 depr_ver = '%s.0' % depr_maj_ver
832835
833836 more_info_depr_ec = " (see also https://docs.easybuild.io/deprecated-easyconfigs)"
@@ -842,8 +845,8 @@ def validate(self, check_osdeps=True):
842845 - check license
843846 """
844847 self .log .info ("Validating easyconfig" )
845- for attr in self .validations :
846- self ._validate (attr , self . validations [ attr ] )
848+ for attr , valid_values in self .validations . items () :
849+ self ._validate (attr , valid_values )
847850
848851 if check_osdeps :
849852 self .log .info ("Checking OS dependencies" )
@@ -899,9 +902,12 @@ def validate_os_deps(self):
899902 not_found .append (dep )
900903
901904 if not_found :
902- raise EasyBuildError ("One or more OS dependencies were not found: %s" , not_found , exit_code = 8 )
903- else :
904- self .log .info ("OS dependencies ok: %s" % self ['osdependencies' ])
905+ raise EasyBuildError (
906+ "One or more OS dependencies were not found: %s" , not_found ,
907+ exit_code = EasyBuildExit .MISS_SYSTEM_DEPENDENCY
908+ )
909+
910+ self .log .info ("OS dependencies ok: %s" % self ['osdependencies' ])
905911
906912 return True
907913
@@ -1207,8 +1213,8 @@ def dump(self, fp, always_overwrite=True, backup=False, explicit_toolchains=Fals
12071213 # templated values should be dumped unresolved
12081214 with self .disable_templating ():
12091215 # build dict of default values
1210- default_values = {key : DEFAULT_CONFIG [ key ][ 0 ] for key in DEFAULT_CONFIG }
1211- default_values .update ({key : self . extra_options [ key ][ 0 ] for key in self .extra_options })
1216+ default_values = {key : value [ 0 ] for key , value in DEFAULT_CONFIG . items () }
1217+ default_values .update ({key : value [ 0 ] for key , value in self .extra_options . items () })
12121218
12131219 self .generate_template_values ()
12141220 templ_const = {quote_py_str (const [1 ]): const [0 ] for const in TEMPLATE_CONSTANTS }
@@ -1264,7 +1270,10 @@ def _validate(self, attr, values): # private method
12641270 if values is None :
12651271 values = []
12661272 if self [attr ] and self [attr ] not in values :
1267- raise EasyBuildError ("%s provided '%s' is not valid: %s" , attr , self [attr ], values , exit_code = 12 )
1273+ raise EasyBuildError (
1274+ "%s provided '%s' is not valid: %s" , attr , self [attr ], values ,
1275+ exit_code = EasyBuildExit .SYNTAX_ERROR
1276+ )
12681277
12691278 def probe_external_module_metadata (self , mod_name , existing_metadata = None ):
12701279 """
@@ -1911,14 +1920,17 @@ def get_easyblock_class(easyblock, name=None, error_on_failed_import=True, error
19111920 modname = modulepath .replace ('easybuild.easyblocks.' , '' )
19121921 error_re = re .compile (r"No module named '?.*/?%s'?" % modname )
19131922 _log .debug ("error regexp for ImportError on '%s' easyblock: %s" , modname , error_re .pattern )
1914- if error_re .match (str (err )):
1915- if error_on_missing_easyblock :
1916- raise EasyBuildError (
1917- "No software-specific easyblock '%s' found for %s" , class_name , name , exit_code = 4 )
1918- elif error_on_failed_import :
1919- raise EasyBuildError ("Failed to import %s easyblock: %s" , class_name , err , exit_code = 5 )
1920- else :
1921- _log .debug ("Failed to import easyblock for %s, but ignoring it: %s" % (class_name , err ))
1923+ if error_re .match (str (err )) and error_on_missing_easyblock :
1924+ raise EasyBuildError (
1925+ "No software-specific easyblock '%s' found for %s" , class_name , name ,
1926+ exit_code = EasyBuildExit .MISS_EASYBLOCK
1927+ )
1928+ if error_on_failed_import :
1929+ raise EasyBuildError (
1930+ "Failed to import %s easyblock: %s" , class_name , err ,
1931+ exit_code = EasyBuildExit .EASYBLOCK_ERROR
1932+ )
1933+ _log .debug ("Failed to import easyblock for %s, but ignoring it: %s" % (class_name , err ))
19221934
19231935 if cls is not None :
19241936 _log .info ("Successfully obtained class '%s' for easyblock '%s' (software name '%s')" ,
@@ -1933,7 +1945,9 @@ def get_easyblock_class(easyblock, name=None, error_on_failed_import=True, error
19331945 raise err
19341946 except Exception as err :
19351947 raise EasyBuildError (
1936- "Failed to obtain class for %s easyblock (not available?): %s" , easyblock , err , exit_code = 6 )
1948+ "Failed to obtain class for %s easyblock (not available?): %s" , easyblock , err ,
1949+ exit_code = EasyBuildExit .EASYBLOCK_ERROR
1950+ )
19371951
19381952
19391953def get_module_path (name , generic = None , decode = True ):
@@ -1991,12 +2005,41 @@ def resolve_template(value, tmpl_dict):
19912005 # '%(name)s' -> '%(name)s'
19922006 # '%%(name)s' -> '%%(name)s'
19932007 if '%' in value :
2008+ raw_value = value
19942009 value = re .sub (re .compile (r'(%)(?!%*\(\w+\)s)' ), r'\1\1' , value )
19952010
19962011 try :
19972012 value = value % tmpl_dict
19982013 except KeyError :
1999- _log .warning ("Unable to resolve template value %s with dict %s" , value , tmpl_dict )
2014+ # check if any alternate and/or deprecated templates resolve
2015+ try :
2016+ orig_value = value
2017+ # map old templates to new values for alternate and deprecated templates
2018+ alt_map = {old_tmpl : tmpl_dict [new_tmpl ] for (old_tmpl , new_tmpl ) in
2019+ ALTERNATE_TEMPLATES .items () if new_tmpl in tmpl_dict .keys ()}
2020+ alt_map2 = {new_tmpl : tmpl_dict [old_tmpl ] for (old_tmpl , new_tmpl ) in
2021+ ALTERNATE_TEMPLATES .items () if old_tmpl in tmpl_dict .keys ()}
2022+ depr_map = {old_tmpl : tmpl_dict [new_tmpl ] for (old_tmpl , (new_tmpl , ver )) in
2023+ DEPRECATED_TEMPLATES .items () if new_tmpl in tmpl_dict .keys ()}
2024+
2025+ # try templating with alternate and deprecated templates included
2026+ value = value % {** tmpl_dict , ** alt_map , ** alt_map2 , ** depr_map }
2027+
2028+ for old_tmpl , val in depr_map .items ():
2029+ # check which deprecated templates were replaced, and issue deprecation warnings
2030+ if old_tmpl in orig_value and val in value :
2031+ new_tmpl , ver = DEPRECATED_TEMPLATES [old_tmpl ]
2032+ _log .deprecated (f"Easyconfig template '{ old_tmpl } ' is deprecated, use '{ new_tmpl } ' instead" ,
2033+ ver )
2034+ except KeyError :
2035+ _log .warning (f"Unable to resolve template value { value } with dict { tmpl_dict } " )
2036+ value = raw_value # Undo "%"-escaping
2037+
2038+ for key in tmpl_dict :
2039+ if key in DEPRECATED_TEMPLATES :
2040+ new_key , ver = DEPRECATED_TEMPLATES [key ]
2041+ _log .deprecated (f"Easyconfig template '{ key } ' is deprecated, use '{ new_key } ' instead" , ver )
2042+
20002043 else :
20012044 # this block deals with references to objects and returns other references
20022045 # for reading this is ok, but for self['x'] = {}
@@ -2050,10 +2093,10 @@ def process_easyconfig(path, build_specs=None, validate=True, parse_only=False,
20502093 ec = EasyConfig (spec , build_specs = build_specs , validate = validate , hidden = hidden )
20512094 except EasyBuildError as err :
20522095 try :
2053- err .exit_code
2096+ exit_code = err .exit_code
20542097 except AttributeError :
2055- err . exit_code = 1
2056- raise EasyBuildError ("Failed to process easyconfig %s: %s" , spec , err .msg , exit_code = err . exit_code )
2098+ exit_code = EasyBuildExit . EASYCONFIG_ERROR
2099+ raise EasyBuildError ("Failed to process easyconfig %s: %s" , spec , err .msg , exit_code = exit_code )
20572100
20582101 name = ec ['name' ]
20592102
0 commit comments