4444from vsc .utils .patterns import Singleton
4545
4646import easybuild .tools .environment as env
47- from easybuild .tools .autopep8 import fix_code
4847from easybuild .tools .build_log import EasyBuildError
4948from easybuild .tools .config import build_option , get_module_naming_scheme
5049from easybuild .tools .filetools import decode_class_name , encode_class_name , read_file , write_file
5655from easybuild .tools .systemtools import check_os_dependency
5756from easybuild .tools .toolchain import DUMMY_TOOLCHAIN_NAME , DUMMY_TOOLCHAIN_VERSION
5857from easybuild .tools .toolchain .utilities import get_toolchain
59- from easybuild .tools .utilities import remove_unwanted_chars , quote_str
58+ from easybuild .tools .utilities import quote_py_str , quote_str , remove_unwanted_chars
6059from easybuild .framework .easyconfig import MANDATORY
6160from easybuild .framework .easyconfig .constants import EXTERNAL_MODULE_MARKER
6261from easybuild .framework .easyconfig .default import DEFAULT_CONFIG
7675# set of configure/build/install options that can be provided as lists for an iterated build
7776ITERATE_OPTIONS = ['preconfigopts' , 'configopts' , 'prebuildopts' , 'buildopts' , 'preinstallopts' , 'installopts' ]
7877
79- # values for these keys will not be templated in dump()
80- EXCLUDED_KEYS_REPLACE_TEMPLATES = ['easyblock' , 'name' , 'version' , 'description' , 'homepage' , 'toolchain' ]
81-
82-
83- # ordered groups of keys to obtain a nice looking easyconfig file
84- GROUPED_PARAMS = [
85- ['easyblock' ],
86- ['name' , 'version' , 'versionprefix' , 'versionsuffix' ],
87- ['homepage' , 'description' ],
88- ['toolchain' , 'toolchainopts' ],
89- ['sources' , 'source_urls' ],
90- ['patches' ],
91- ['builddependencies' , 'dependencies' , 'hiddendependencies' ],
92- ['osdependencies' ],
93- ['preconfigopts' , 'configopts' ],
94- ['prebuildopts' , 'buildopts' ],
95- ['preinstallopts' , 'installopts' ],
96- ['parallel' , 'maxparallel' ],
97- ]
98- LAST_PARAMS = ['sanity_check_paths' , 'moduleclass' ]
78+
79+ try :
80+ import autopep8
81+ except ImportError as err :
82+ _log .warning ("Failed to import autopep8, dumping easyconfigs with reformatting enabled will not work: %s" , err )
83+
9984
10085_easyconfig_files_cache = {}
10186_easyconfigs_cache = {}
@@ -197,6 +182,7 @@ def __init__(self, path, extra_options=None, build_specs=None, validate=True, hi
197182
198183 # parse easyconfig file
199184 self .build_specs = build_specs
185+ self .parser = EasyConfigParser (filename = self .path , rawcontent = self .rawtxt )
200186 self .parse ()
201187
202188 # handle allowed system dependencies
@@ -260,13 +246,10 @@ def parse(self):
260246 self .log .debug ("Obtained specs dict %s" % arg_specs )
261247
262248 self .log .info ("Parsing easyconfig file %s with rawcontent: %s" % (self .path , self .rawtxt ))
263- parser = EasyConfigParser (filename = self .path , rawcontent = self .rawtxt )
264- parser .set_specifications (arg_specs )
265- local_vars = parser .get_config_dict ()
249+ self .parser .set_specifications (arg_specs )
250+ local_vars = self .parser .get_config_dict ()
266251 self .log .debug ("Parsed easyconfig as a dictionary: %s" % local_vars )
267252
268- self .comments = parser .get_comments ()
269-
270253 # make sure all mandatory parameters are defined
271254 # this includes both generic mandatory parameters and software-specific parameters defined via extra_options
272255 missing_mandatory_keys = [key for key in self .mandatory if key not in local_vars ]
@@ -492,12 +475,14 @@ def toolchain(self):
492475 self .log .debug ("Initialized toolchain: %s (opts: %s)" % (tc_dict , self ['toolchainopts' ]))
493476 return self ._toolchain
494477
495- def dump (self , fp , formatting = True ):
478+ def dump (self , fp ):
496479 """
497480 Dump this easyconfig to file, with the given filename.
498481 """
499482 orig_enable_templating = self .enable_templating
500- self .enable_templating = False # templated values should be dumped unresolved
483+
484+ # templated values should be dumped unresolved
485+ self .enable_templating = False
501486
502487 # build dict of default values
503488 default_values = dict ([(key , DEFAULT_CONFIG [key ][0 ]) for key in DEFAULT_CONFIG ])
@@ -510,125 +495,22 @@ def dump(self, fp, formatting=True):
510495 keys = sorted (self .template_values , key = lambda k : len (self .template_values [k ]), reverse = True )
511496 templ_val = OrderedDict ([(self .template_values [k ], k ) for k in keys if len (self .template_values [k ]) > 2 ])
512497
513- def add_key_and_comments (key , val ):
514- """
515- Add key + value and comments (if any) to txt to be dumped.
516- """
517- if key in self .comments ['inline' ]:
518- ebtxt .append ("%s = %s %s" % (key , val , self .comments ['inline' ][key ]))
519- else :
520- if key in self .comments ['above' ]:
521- ebtxt .extend (self .comments ['above' ][key ])
522- ebtxt .append ("%s = %s" % (key , val ))
523-
524- def include_defined_parameters (ebtxt , keyset ):
525- """
526- Internal function to include parameters in the dumped easyconfig file which have a non-default value.
527- """
528- for group in keyset :
529- printed = False
530- for key in group :
531- val = self [key ]
532- if val != default_values [key ]:
533- # dependency easyconfig parameters were parsed, so these need special care to 'unparse' them
534- if key in ['builddependencies' , 'dependencies' , 'hiddendependencies' ]:
535- dumped_deps = [self ._dump_dependency (d ) for d in val ]
536- val = dumped_deps
537- else :
538- val = quote_py_str (val )
539-
540- ebtxt = self ._add_key_and_comments (ebtxt , key , val , templ_const , templ_val , formatting )
541-
542- printed_keys .append (key )
543- printed = True
544- if printed :
545- ebtxt .append ('' )
546-
547- ebtxt = []
548- printed_keys = []
549-
550- # add header comments
551- ebtxt .extend (self .comments ['header' ])
552-
553- # print easyconfig parameters ordered and in groups specified above
554- include_defined_parameters (ebtxt , GROUPED_PARAMS )
555-
556- # print other easyconfig parameters at the end
557- keys_to_ignore = printed_keys + LAST_PARAMS
558- for key in default_values :
559- if key not in keys_to_ignore and self [key ] != default_values [key ]:
560- ebtxt = self ._add_key_and_comments (ebtxt , key , quote_py_str (self [key ]), templ_const , templ_val , formatting )
561- ebtxt .append ('' )
562-
563- # print last parameters
564- include_defined_parameters (ebtxt , [[k ] for k in LAST_PARAMS ])
565-
566- write_file (fp , ('\n ' .join (ebtxt )).strip ()) # strip for newlines at the end
567-
568- dumped_text = ('\n ' .join (ebtxt ))
569- write_file (fp , (fix_code (dumped_text , options = {'aggressive' : 1 , 'max_line_length' :120 })).strip ())
570- self .enable_templating = orig_enable_templating
498+ ectxt = self .parser .dump (self , default_values , templ_const , templ_val )
499+ self .log .debug ("Dumped easyconfig: %s" , ectxt )
571500
572- def _add_key_and_comments (self , ebtxt , key , val , templ_const , templ_val , formatting ):
573- """ Add key, value pair and comments (if there are any) to the dump file (helper method for dump()) """
574- if formatting :
575- val = self ._format (key , val , True , dict ())
576- else :
577- val = str (val )
501+ if build_option ('dump_autopep8' ):
502+ autopep8_opts = {
503+ 'aggressive' : 1 , # enable non-whitespace changes, but don't be too aggressive
504+ 'max_line_length' : 120 ,
505+ }
506+ self .log .info ("Reformatting dumped easyconfig using autopep8 (options: %s)" , autopep8_opts )
507+ print ("Reformatting dumped easyconfig using autopep8 (options: %s)" , autopep8_opts )
508+ ectxt = autopep8 .fix_code (ectxt , options = autopep8_opts )
509+ self .log .debug ("Dumped easyconfig after autopep8 reformatting: %s" , ectxt )
578510
579- # templates
580- if key not in EXCLUDED_KEYS_REPLACE_TEMPLATES :
581- new_val = to_template_str (val , templ_const , templ_val )
582- if not r'%(' + key in new_val :
583- val = new_val
511+ write_file (fp , ectxt .strip ())
584512
585- if key in self .comments ['inline' ]:
586- ebtxt .append ("%s = %s%s" % (key , val , self .comments ['inline' ][key ]))
587- else :
588- if key in self .comments ['above' ]:
589- ebtxt .extend (self .comments ['above' ][key ])
590- ebtxt .append ("%s = %s" % (key , val ))
591-
592- return ebtxt
593-
594- def _format (self , key , value , outer , comment ):
595- """ Returns string version of the value, including comments and newlines in lists, tuples and dicts """
596- str_value = ''
597-
598- for k , v in self .comments ['list_value' ].get (key , {}).items ():
599- if str (value ) in k :
600- comment [str (value )] = v
601-
602- if outer :
603- if isinstance (value , list ):
604- str_value += '[\n '
605- for el in value :
606- str_value += self ._format (key , el , False , comment )
607- str_value += ',' + comment .get (str (el ), '' ) + '\n '
608- str_value += ']'
609- elif isinstance (value , tuple ):
610- str_value += '(\n '
611- for el in value :
612- str_value += self ._format (key , el , False , comment )
613- str_value += ',' + comment .get (str (el ), '' ) + '\n '
614- str_value += ')'
615- elif isinstance (value , dict ):
616- str_value += '{\n '
617- for k , v in value .items ():
618- str_value += quote_py_str (k ) + ': ' + self ._format (key , v , False , comment )
619- str_value += ',' + comment .get (str (v ), '' ) + '\n '
620- str_value += '}'
621-
622- value = str_value or str (value )
623-
624- else :
625- # dependencies are already dumped as strings, so they do not need to be quoted again
626- if isinstance (value , basestring ) and key not in ['builddependencies' , 'dependencies' , 'hiddendependencies' ]:
627- value = quote_py_str (value )
628- else :
629- value = str (value )
630-
631- return value
513+ self .enable_templating = orig_enable_templating
632514
633515 def _validate (self , attr , values ): # private method
634516 """
@@ -764,26 +646,6 @@ def _parse_dependency(self, dep, hidden=False):
764646
765647 return dependency
766648
767- def _dump_dependency (self , dep ):
768- """Dump parsed dependency in tuple format"""
769-
770- if dep ['external_module' ]:
771- res = "(%s, EXTERNAL_MODULE)" % quote_py_str (dep ['full_mod_name' ])
772- else :
773- # mininal spec: (name, version)
774- tup = (dep ['name' ], dep ['version' ])
775- if dep ['toolchain' ] != self ['toolchain' ]:
776- if dep ['dummy' ]:
777- tup += (dep ['versionsuffix' ], True )
778- else :
779- tup += (dep ['versionsuffix' ], (dep ['toolchain' ]['name' ], dep ['toolchain' ]['version' ]))
780-
781- elif dep ['versionsuffix' ]:
782- tup += (dep ['versionsuffix' ],)
783-
784- res = str (tup )
785- return res
786-
787649 def generate_template_values (self ):
788650 """Try to generate all template values."""
789651 # TODO proper recursive code https://github.com/hpcugent/easybuild-framework/issues/474
@@ -1042,34 +904,6 @@ def resolve_template(value, tmpl_dict):
1042904 return value
1043905
1044906
1045- def quote_py_str (val ):
1046- """Version of quote_str specific for generating use in Python context (e.g., easyconfig parameters)."""
1047- return quote_str (val , escape_newline = True , prefer_single_quotes = True )
1048-
1049-
1050- def to_template_str (value , templ_const , templ_val ):
1051- """
1052- Insert template values where possible
1053- - value is a string
1054- - templ_const is a dictionary of template strings (constants)
1055- - templ_val is an ordered dictionary of template strings specific for this easyconfig file
1056- """
1057- old_value = None
1058- while value != old_value :
1059- old_value = value
1060- # check for constant values
1061- for tval , tname in templ_const .items ():
1062- value = re .sub (r'(^|\W)' + re .escape (tval ) + r'(\W|$)' , r'\1' + tname + r'\2' , value )
1063-
1064- for tval , tname in templ_val .items ():
1065- # only replace full words with templates: word to replace should be at the beginning of a line
1066- # or be preceded by a non-alphanumeric (\W). It should end at the end of a line or be succeeded
1067- # by another non-alphanumeric.
1068- value = re .sub (r'(^|\W)' + re .escape (tval ) + r'(\W|$)' , r'\1%(' + tname + r')s\2' , value )
1069-
1070- return value
1071-
1072-
1073907def process_easyconfig (path , build_specs = None , validate = True , parse_only = False , hidden = None ):
1074908 """
1075909 Process easyconfig, returning some information for each block
@@ -1088,7 +922,7 @@ def process_easyconfig(path, build_specs=None, validate=True, parse_only=False,
1088922 if build_specs is None :
1089923 cache_key = (path , validate , hidden , parse_only )
1090924 if cache_key in _easyconfigs_cache :
1091- return copy . deepcopy ( _easyconfigs_cache [cache_key ])
925+ return [ e . copy () for e in _easyconfigs_cache [cache_key ]]
1092926
1093927 easyconfigs = []
1094928 for spec in blocks :
@@ -1147,7 +981,7 @@ def process_easyconfig(path, build_specs=None, validate=True, parse_only=False,
1147981 easyconfig ['unresolved_deps' ] = copy .deepcopy (easyconfig ['dependencies' ])
1148982
1149983 if cache_key is not None :
1150- _easyconfigs_cache [cache_key ] = copy . deepcopy ( easyconfigs )
984+ _easyconfigs_cache [cache_key ] = [ e . copy () for e in easyconfigs ]
1151985
1152986 return easyconfigs
1153987
0 commit comments