4141 pass
4242_log = fancylogger .getLogger ('easystack' , fname = False )
4343
44+ EASYSTACK_DOC_URL = 'https://docs.easybuild.io/en/latest/Easystack-files.html'
45+
4446
4547def check_value (value , context ):
4648 """
@@ -68,10 +70,16 @@ def __init__(self):
6870 self .easybuild_version = None
6971 self .robot = False
7072 self .software_list = []
73+ self .easyconfigs = [] # A list of easyconfig names. May or may not include .eb extension
74+ # A dict where keys are easyconfig names, values are dictionary of options that should be
75+ # applied for that easyconfig
76+ self .ec_opts = {}
7177
7278 def compose_ec_filenames (self ):
7379 """Returns a list of all easyconfig names"""
7480 ec_filenames = []
81+
82+ # entries specified via 'software' top-level key
7583 for sw in self .software_list :
7684 full_ec_version = det_full_ec_version ({
7785 'toolchain' : {'name' : sw .toolchain_name , 'version' : sw .toolchain_version },
@@ -80,6 +88,10 @@ def compose_ec_filenames(self):
8088 })
8189 ec_filename = '%s-%s.eb' % (sw .name , full_ec_version )
8290 ec_filenames .append (ec_filename )
91+
92+ # entries specified via 'easyconfigs' top-level key
93+ for ec in self .easyconfigs :
94+ ec_filenames .append (ec )
8395 return ec_filenames
8496
8597 # flags applicable to all sw (i.e. robot)
@@ -108,21 +120,89 @@ class EasyStackParser(object):
108120
109121 @staticmethod
110122 def parse (filepath ):
111- """Parses YAML file and assigns obtained values to SW config instances as well as general config instance"""
123+ """
124+ Parses YAML file and assigns obtained values to SW config instances as well as general config instance"""
112125 yaml_txt = read_file (filepath )
113126
114127 try :
115128 easystack_raw = yaml .safe_load (yaml_txt )
116129 except yaml .YAMLError as err :
117130 raise EasyBuildError ("Failed to parse %s: %s" % (filepath , err ))
118131
132+ easystack_data = None
133+ top_keys = ('easyconfigs' , 'software' )
134+ keys_found = []
135+ for key in top_keys :
136+ if key in easystack_raw :
137+ keys_found .append (key )
138+ # For now, we don't support mixing multiple top_keys, so check that only one was defined
139+ if len (keys_found ) > 1 :
140+ keys_string = ', ' .join (keys_found )
141+ msg = "Specifying multiple top level keys (%s) " % keys_string
142+ msg += "in one EasyStack file is currently not supported"
143+ msg += ", see %s for documentation." % EASYSTACK_DOC_URL
144+ raise EasyBuildError (msg )
145+ elif len (keys_found ) == 0 :
146+ msg = "Not a valid EasyStack YAML file: no 'easyconfigs' or 'software' top-level key found"
147+ msg += ", see %s for documentation." % EASYSTACK_DOC_URL
148+ raise EasyBuildError (msg )
149+ else :
150+ key = keys_found [0 ]
151+ easystack_data = easystack_raw [key ]
152+
153+ parse_method_name = 'parse_by_' + key
154+ parse_method = getattr (EasyStackParser , 'parse_by_%s' % key , None )
155+ if parse_method is None :
156+ raise EasyBuildError ("Easystack parse method '%s' not found!" , parse_method_name )
157+
158+ # assign general easystack attributes
159+ easybuild_version = easystack_raw .get ('easybuild_version' , None )
160+ robot = easystack_raw .get ('robot' , False )
161+
162+ return parse_method (filepath , easystack_data , easybuild_version = easybuild_version , robot = robot )
163+
164+ @staticmethod
165+ def parse_by_easyconfigs (filepath , easyconfigs , easybuild_version = None , robot = False ):
166+ """
167+ Parse easystack file with 'easyconfigs' as top-level key.
168+ """
169+
119170 easystack = EasyStack ()
120171
121- try :
122- software = easystack_raw ["software" ]
123- except KeyError :
124- wrong_structure_file = "Not a valid EasyStack YAML file: no 'software' key found"
125- raise EasyBuildError (wrong_structure_file )
172+ for easyconfig in easyconfigs :
173+ if isinstance (easyconfig , str ):
174+ if not easyconfig .endswith ('.eb' ):
175+ easyconfig = easyconfig + '.eb'
176+ easystack .easyconfigs .append (easyconfig )
177+ elif isinstance (easyconfig , dict ):
178+ if len (easyconfig ) == 1 :
179+ # Get single key from dictionary 'easyconfig'
180+ easyconf_name = list (easyconfig .keys ())[0 ]
181+ # Add easyconfig name to the list
182+ if not easyconf_name .endswith ('.eb' ):
183+ easyconf_name_with_eb = easyconf_name + '.eb'
184+ else :
185+ easyconf_name_with_eb = easyconf_name
186+ easystack .easyconfigs .append (easyconf_name_with_eb )
187+ # Add options to the ec_opts dict
188+ if 'options' in easyconfig [easyconf_name ].keys ():
189+ easystack .ec_opts [easyconf_name_with_eb ] = easyconfig [easyconf_name ]['options' ]
190+ else :
191+ dict_keys = ', ' .join (easyconfig .keys ())
192+ msg = "Failed to parse easystack file: expected a dictionary with one key (the EasyConfig name), "
193+ msg += "instead found keys: %s" % dict_keys
194+ msg += ", see %s for documentation." % EASYSTACK_DOC_URL
195+ raise EasyBuildError (msg )
196+
197+ return easystack
198+
199+ @staticmethod
200+ def parse_by_software (filepath , software , easybuild_version = None , robot = False ):
201+ """
202+ Parse easystack file with 'software' as top-level key.
203+ """
204+
205+ easystack = EasyStack ()
126206
127207 # assign software-specific easystack attributes
128208 for name in software :
@@ -224,8 +304,8 @@ def parse(filepath):
224304 easystack .software_list .append (sw )
225305
226306 # assign general easystack attributes
227- easystack .easybuild_version = easystack_raw . get ( ' easybuild_version' , None )
228- easystack .robot = easystack_raw . get ( ' robot' , False )
307+ easystack .easybuild_version = easybuild_version
308+ easystack .robot = robot
229309
230310 return easystack
231311
@@ -243,12 +323,16 @@ def parse_easystack(filepath):
243323
244324 easyconfig_names = easystack .compose_ec_filenames ()
245325
246- general_options = easystack .get_general_options ()
326+ # Disabled general options for now. We weren't using them, and first want support for EasyConfig-specific options.
327+ # Then, we need a method to resolve conflicts (specific options should win)
328+ # general_options = easystack.get_general_options()
247329
248330 _log .debug ("EasyStack parsed. Proceeding to install these Easyconfigs: %s" % ', ' .join (sorted (easyconfig_names )))
249- if len (general_options ) != 0 :
250- _log .debug ("General options for installation are: \n %s" % str (general_options ))
251- else :
252- _log .debug ("No general options were specified in easystack" )
253-
254- return easyconfig_names , general_options
331+ _log .debug ("Using EasyConfig specific options based on the following dict:" )
332+ _log .debug (easystack .ec_opts )
333+ # if len(general_options) != 0:
334+ # _log.debug("General options for installation are: \n%s" % str(general_options))
335+ # else:
336+ # _log.debug("No general options were specified in easystack")
337+
338+ return easyconfig_names , easystack .ec_opts
0 commit comments