|
45 | 45 | import os |
46 | 46 | import re |
47 | 47 | from distutils.version import LooseVersion |
| 48 | +from contextlib import contextmanager |
48 | 49 |
|
49 | 50 | import easybuild.tools.filetools as filetools |
50 | 51 | from easybuild.base import fancylogger |
@@ -383,6 +384,23 @@ def get_toolchain_hierarchy(parent_toolchain, incl_capabilities=False): |
383 | 384 | return toolchain_hierarchy |
384 | 385 |
|
385 | 386 |
|
| 387 | +@contextmanager |
| 388 | +def disable_templating(ec): |
| 389 | + """Temporarily disable templating on the given EasyConfig |
| 390 | +
|
| 391 | + Usage: |
| 392 | + with disable_templating(ec): |
| 393 | + # Do what you want without templating |
| 394 | + # Templating set to previous value |
| 395 | + """ |
| 396 | + old_enable_templating = ec.enable_templating |
| 397 | + ec.enable_templating = False |
| 398 | + try: |
| 399 | + yield old_enable_templating |
| 400 | + finally: |
| 401 | + ec.enable_templating = old_enable_templating |
| 402 | + |
| 403 | + |
386 | 404 | class EasyConfig(object): |
387 | 405 | """ |
388 | 406 | Class which handles loading, reading, validation of easyconfigs |
@@ -592,18 +610,15 @@ def set_keys(self, params): |
592 | 610 | """ |
593 | 611 | # disable templating when setting easyconfig parameters |
594 | 612 | # required to avoid problems with values that need more parsing to be done (e.g. dependencies) |
595 | | - prev_enable_templating = self.enable_templating |
596 | | - self.enable_templating = False |
597 | | - |
598 | | - for key in sorted(params.keys()): |
599 | | - # validations are skipped, just set in the config |
600 | | - if key in self._config.keys(): |
601 | | - self[key] = params[key] |
602 | | - self.log.info("setting easyconfig parameter %s: value %s (type: %s)", key, self[key], type(self[key])) |
603 | | - else: |
604 | | - raise EasyBuildError("Unknown easyconfig parameter: %s (value '%s')", key, params[key]) |
605 | | - |
606 | | - self.enable_templating = prev_enable_templating |
| 613 | + with disable_templating(self): |
| 614 | + for key in sorted(params.keys()): |
| 615 | + # validations are skipped, just set in the config |
| 616 | + if key in self._config.keys(): |
| 617 | + self[key] = params[key] |
| 618 | + self.log.info("setting easyconfig parameter %s: value %s (type: %s)", |
| 619 | + key, self[key], type(self[key])) |
| 620 | + else: |
| 621 | + raise EasyBuildError("Unknown easyconfig parameter: %s (value '%s')", key, params[key]) |
607 | 622 |
|
608 | 623 | def parse(self): |
609 | 624 | """ |
@@ -647,42 +662,39 @@ def parse(self): |
647 | 662 |
|
648 | 663 | # templating is disabled when parse_hook is called to allow for easy updating of mutable easyconfig parameters |
649 | 664 | # (see also comment in resolve_template) |
650 | | - prev_enable_templating = self.enable_templating |
651 | | - self.enable_templating = False |
652 | | - |
653 | | - # if any lists of dependency versions are specified over which we should iterate, |
654 | | - # deal with them now, before calling parse hook, parsing of dependencies & iterative easyconfig parameters... |
655 | | - self.handle_multi_deps() |
656 | | - |
657 | | - parse_hook_msg = None |
658 | | - if self.path: |
659 | | - parse_hook_msg = "Running %s hook for %s..." % (PARSE, os.path.basename(self.path)) |
660 | | - |
661 | | - # trigger parse hook |
662 | | - hooks = load_hooks(build_option('hooks')) |
663 | | - run_hook(PARSE, hooks, args=[self], msg=parse_hook_msg) |
664 | | - |
665 | | - # parse dependency specifications |
666 | | - # it's important that templating is still disabled at this stage! |
667 | | - self.log.info("Parsing dependency specifications...") |
668 | | - self['dependencies'] = [self._parse_dependency(dep) for dep in self['dependencies']] |
669 | | - self['hiddendependencies'] = [self._parse_dependency(dep, hidden=True) for dep in self['hiddendependencies']] |
670 | | - |
671 | | - # need to take into account that builddependencies may need to be iterated over, |
672 | | - # i.e. when the value is a list of lists of tuples |
673 | | - builddeps = self['builddependencies'] |
674 | | - if builddeps and all(isinstance(x, (list, tuple)) for b in builddeps for x in b): |
675 | | - self.iterate_options.append('builddependencies') |
676 | | - builddeps = [[self._parse_dependency(dep, build_only=True) for dep in x] for x in builddeps] |
677 | | - else: |
678 | | - builddeps = [self._parse_dependency(dep, build_only=True) for dep in builddeps] |
679 | | - self['builddependencies'] = builddeps |
| 665 | + with disable_templating(self): |
| 666 | + # if any lists of dependency versions are specified over which we should iterate, |
| 667 | + # deal with them now, before calling parse hook, parsing of dependencies & iterative easyconfig parameters |
| 668 | + self.handle_multi_deps() |
680 | 669 |
|
681 | | - # keep track of parsed multi deps, they'll come in handy during sanity check & module steps... |
682 | | - self.multi_deps = self.get_parsed_multi_deps() |
| 670 | + parse_hook_msg = None |
| 671 | + if self.path: |
| 672 | + parse_hook_msg = "Running %s hook for %s..." % (PARSE, os.path.basename(self.path)) |
| 673 | + |
| 674 | + # trigger parse hook |
| 675 | + hooks = load_hooks(build_option('hooks')) |
| 676 | + run_hook(PARSE, hooks, args=[self], msg=parse_hook_msg) |
| 677 | + |
| 678 | + # parse dependency specifications |
| 679 | + # it's important that templating is still disabled at this stage! |
| 680 | + self.log.info("Parsing dependency specifications...") |
| 681 | + self['dependencies'] = [self._parse_dependency(dep) for dep in self['dependencies']] |
| 682 | + self['hiddendependencies'] = [ |
| 683 | + self._parse_dependency(dep, hidden=True) for dep in self['hiddendependencies'] |
| 684 | + ] |
| 685 | + |
| 686 | + # need to take into account that builddependencies may need to be iterated over, |
| 687 | + # i.e. when the value is a list of lists of tuples |
| 688 | + builddeps = self['builddependencies'] |
| 689 | + if builddeps and all(isinstance(x, (list, tuple)) for b in builddeps for x in b): |
| 690 | + self.iterate_options.append('builddependencies') |
| 691 | + builddeps = [[self._parse_dependency(dep, build_only=True) for dep in x] for x in builddeps] |
| 692 | + else: |
| 693 | + builddeps = [self._parse_dependency(dep, build_only=True) for dep in builddeps] |
| 694 | + self['builddependencies'] = builddeps |
683 | 695 |
|
684 | | - # restore templating |
685 | | - self.enable_templating = prev_enable_templating |
| 696 | + # keep track of parsed multi deps, they'll come in handy during sanity check & module steps... |
| 697 | + self.multi_deps = self.get_parsed_multi_deps() |
686 | 698 |
|
687 | 699 | # update templating dictionary |
688 | 700 | self.generate_template_values() |
@@ -1108,63 +1120,57 @@ def dump(self, fp, always_overwrite=True, backup=False, explicit_toolchains=Fals |
1108 | 1120 | :param always_overwrite: overwrite existing file at specified location without use of --force |
1109 | 1121 | :param backup: create backup of existing file before overwriting it |
1110 | 1122 | """ |
1111 | | - orig_enable_templating = self.enable_templating |
1112 | | - |
1113 | 1123 | # templated values should be dumped unresolved |
1114 | | - self.enable_templating = False |
1115 | | - |
1116 | | - # build dict of default values |
1117 | | - default_values = dict([(key, DEFAULT_CONFIG[key][0]) for key in DEFAULT_CONFIG]) |
1118 | | - default_values.update(dict([(key, self.extra_options[key][0]) for key in self.extra_options])) |
| 1124 | + with disable_templating(self): |
| 1125 | + # build dict of default values |
| 1126 | + default_values = dict([(key, DEFAULT_CONFIG[key][0]) for key in DEFAULT_CONFIG]) |
| 1127 | + default_values.update(dict([(key, self.extra_options[key][0]) for key in self.extra_options])) |
| 1128 | + |
| 1129 | + self.generate_template_values() |
| 1130 | + templ_const = dict([(quote_py_str(const[1]), const[0]) for const in TEMPLATE_CONSTANTS]) |
| 1131 | + |
| 1132 | + # create reverse map of templates, to inject template values where possible |
| 1133 | + # longer template values are considered first, shorter template keys get preference over longer ones |
| 1134 | + sorted_keys = sorted(self.template_values, key=lambda k: (len(self.template_values[k]), -len(k)), |
| 1135 | + reverse=True) |
| 1136 | + templ_val = OrderedDict([]) |
| 1137 | + for key in sorted_keys: |
| 1138 | + # shortest template 'key' is retained in case of duplicates |
| 1139 | + # ('namelower' is preferred over 'github_account') |
| 1140 | + # only template values longer than 2 characters are retained |
| 1141 | + if self.template_values[key] not in templ_val and len(self.template_values[key]) > 2: |
| 1142 | + templ_val[self.template_values[key]] = key |
| 1143 | + |
| 1144 | + toolchain_hierarchy = None |
| 1145 | + if not explicit_toolchains: |
| 1146 | + try: |
| 1147 | + toolchain_hierarchy = get_toolchain_hierarchy(self['toolchain']) |
| 1148 | + except EasyBuildError as err: |
| 1149 | + # don't fail hard just because we can't get the hierarchy |
| 1150 | + self.log.warning('Could not generate toolchain hierarchy for %s to use in easyconfig dump method, ' |
| 1151 | + 'error:\n%s', self['toolchain'], str(err)) |
1119 | 1152 |
|
1120 | | - self.generate_template_values() |
1121 | | - templ_const = dict([(quote_py_str(const[1]), const[0]) for const in TEMPLATE_CONSTANTS]) |
1122 | | - |
1123 | | - # create reverse map of templates, to inject template values where possible |
1124 | | - # longer template values are considered first, shorter template keys get preference over longer ones |
1125 | | - sorted_keys = sorted(self.template_values, key=lambda k: (len(self.template_values[k]), -len(k)), reverse=True) |
1126 | | - templ_val = OrderedDict([]) |
1127 | | - for key in sorted_keys: |
1128 | | - # shortest template 'key' is retained in case of duplicates ('namelower' is preferred over 'github_account') |
1129 | | - # only template values longer than 2 characters are retained |
1130 | | - if self.template_values[key] not in templ_val and len(self.template_values[key]) > 2: |
1131 | | - templ_val[self.template_values[key]] = key |
1132 | | - |
1133 | | - toolchain_hierarchy = None |
1134 | | - if not explicit_toolchains: |
1135 | 1153 | try: |
1136 | | - toolchain_hierarchy = get_toolchain_hierarchy(self['toolchain']) |
1137 | | - except EasyBuildError as err: |
1138 | | - # don't fail hard just because we can't get the hierarchy |
1139 | | - self.log.warning('Could not generate toolchain hierarchy for %s to use in easyconfig dump method, ' |
1140 | | - 'error:\n%s', self['toolchain'], str(err)) |
| 1154 | + ectxt = self.parser.dump(self, default_values, templ_const, templ_val, |
| 1155 | + toolchain_hierarchy=toolchain_hierarchy) |
| 1156 | + except NotImplementedError as err: |
| 1157 | + raise NotImplementedError(err) |
1141 | 1158 |
|
1142 | | - try: |
1143 | | - ectxt = self.parser.dump(self, default_values, templ_const, templ_val, |
1144 | | - toolchain_hierarchy=toolchain_hierarchy) |
1145 | | - except NotImplementedError as err: |
1146 | | - # need to restore enable_templating value in case this method is caught in a try/except block and ignored |
1147 | | - # (the ability to dump is not a hard requirement for build success) |
1148 | | - self.enable_templating = orig_enable_templating |
1149 | | - raise NotImplementedError(err) |
| 1159 | + self.log.debug("Dumped easyconfig: %s", ectxt) |
1150 | 1160 |
|
1151 | | - self.log.debug("Dumped easyconfig: %s", ectxt) |
| 1161 | + if build_option('dump_autopep8'): |
| 1162 | + autopep8_opts = { |
| 1163 | + 'aggressive': 1, # enable non-whitespace changes, but don't be too aggressive |
| 1164 | + 'max_line_length': 120, |
| 1165 | + } |
| 1166 | + self.log.info("Reformatting dumped easyconfig using autopep8 (options: %s)", autopep8_opts) |
| 1167 | + ectxt = autopep8.fix_code(ectxt, options=autopep8_opts) |
| 1168 | + self.log.debug("Dumped easyconfig after autopep8 reformatting: %s", ectxt) |
1152 | 1169 |
|
1153 | | - if build_option('dump_autopep8'): |
1154 | | - autopep8_opts = { |
1155 | | - 'aggressive': 1, # enable non-whitespace changes, but don't be too aggressive |
1156 | | - 'max_line_length': 120, |
1157 | | - } |
1158 | | - self.log.info("Reformatting dumped easyconfig using autopep8 (options: %s)", autopep8_opts) |
1159 | | - ectxt = autopep8.fix_code(ectxt, options=autopep8_opts) |
1160 | | - self.log.debug("Dumped easyconfig after autopep8 reformatting: %s", ectxt) |
| 1170 | + if not ectxt.endswith('\n'): |
| 1171 | + ectxt += '\n' |
1161 | 1172 |
|
1162 | | - if not ectxt.endswith('\n'): |
1163 | | - ectxt += '\n' |
1164 | | - |
1165 | | - write_file(fp, ectxt, always_overwrite=always_overwrite, backup=backup, verbose=backup) |
1166 | | - |
1167 | | - self.enable_templating = orig_enable_templating |
| 1173 | + write_file(fp, ectxt, always_overwrite=always_overwrite, backup=backup, verbose=backup) |
1168 | 1174 |
|
1169 | 1175 | def _validate(self, attr, values): # private method |
1170 | 1176 | """ |
@@ -1473,7 +1479,7 @@ def _parse_dependency(self, dep, hidden=False, build_only=False): |
1473 | 1479 |
|
1474 | 1480 | # (true) boolean value simply indicates that a system toolchain is used |
1475 | 1481 | elif isinstance(tc_spec, bool) and tc_spec: |
1476 | | - tc = {'name': SYSTEM_TOOLCHAIN_NAME, 'version': ''} |
| 1482 | + tc = {'name': SYSTEM_TOOLCHAIN_NAME, 'version': ''} |
1477 | 1483 |
|
1478 | 1484 | # two-element list/tuple value indicates custom toolchain specification |
1479 | 1485 | elif isinstance(tc_spec, (list, tuple,)): |
@@ -1593,27 +1599,21 @@ def _generate_template_values(self, ignore=None): |
1593 | 1599 |
|
1594 | 1600 | # step 1-3 work with easyconfig.templates constants |
1595 | 1601 | # disable templating with creating dict with template values to avoid looping back to here via __getitem__ |
1596 | | - prev_enable_templating = self.enable_templating |
1597 | | - |
1598 | | - self.enable_templating = False |
1599 | | - |
1600 | | - if self.template_values is None: |
1601 | | - # if no template values are set yet, initiate with a minimal set of template values; |
1602 | | - # this is important for easyconfig that use %(version_minor)s to define 'toolchain', |
1603 | | - # which is a pretty weird use case, but fine... |
1604 | | - self.template_values = template_constant_dict(self, ignore=ignore) |
1605 | | - |
1606 | | - self.enable_templating = prev_enable_templating |
| 1602 | + with disable_templating(self): |
| 1603 | + if self.template_values is None: |
| 1604 | + # if no template values are set yet, initiate with a minimal set of template values; |
| 1605 | + # this is important for easyconfig that use %(version_minor)s to define 'toolchain', |
| 1606 | + # which is a pretty weird use case, but fine... |
| 1607 | + self.template_values = template_constant_dict(self, ignore=ignore) |
1607 | 1608 |
|
1608 | 1609 | # grab toolchain instance with templating support enabled, |
1609 | 1610 | # which is important in case the Toolchain instance was not created yet |
1610 | 1611 | toolchain = self.toolchain |
1611 | 1612 |
|
1612 | 1613 | # get updated set of template values, now with toolchain instance |
1613 | 1614 | # (which is used to define the %(mpi_cmd_prefix)s template) |
1614 | | - self.enable_templating = False |
1615 | | - template_values = template_constant_dict(self, ignore=ignore, toolchain=toolchain) |
1616 | | - self.enable_templating = prev_enable_templating |
| 1615 | + with disable_templating(self): |
| 1616 | + template_values = template_constant_dict(self, ignore=ignore, toolchain=toolchain) |
1617 | 1617 |
|
1618 | 1618 | # update the template_values dict |
1619 | 1619 | self.template_values.update(template_values) |
@@ -1656,13 +1656,8 @@ def get_ref(self, key): |
1656 | 1656 | # see also comments in resolve_template |
1657 | 1657 |
|
1658 | 1658 | # temporarily disable templating |
1659 | | - prev_enable_templating = self.enable_templating |
1660 | | - self.enable_templating = False |
1661 | | - |
1662 | | - ref = self[key] |
1663 | | - |
1664 | | - # restore previous value for 'enable_templating' |
1665 | | - self.enable_templating = prev_enable_templating |
| 1659 | + with disable_templating(self): |
| 1660 | + ref = self[key] |
1666 | 1661 |
|
1667 | 1662 | return ref |
1668 | 1663 |
|
|
0 commit comments