Skip to content

Commit

Permalink
Allow unresolved templates in _finalize_dependencies, extension opt…
Browse files Browse the repository at this point in the history
…ions & `asdict`

Some dependencies may use templates referring to other dependencies
which cannot yet be resolved. Hence add an opt-out to the enforcment
of resolved templates via `expect_resolved=False` and use that.

This allows use of `%(installdir)s` or `%(ext_name)s` and similar in the
options.
  • Loading branch information
Flamefire committed Apr 25, 2024
1 parent b2dadbd commit 4a73b73
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 14 deletions.
32 changes: 19 additions & 13 deletions easybuild/framework/easyconfig/easyconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -1623,8 +1623,9 @@ def _finalize_dependencies(self):
filter_deps_specs = self.parse_filter_deps()

for key in DEPENDENCY_PARAMETERS:
# loop over a *copy* of dependency dicts (with resolved templates);
deps = self[key]
# loop over a *copy* of dependency dicts with resolved templates,
# although some templates may not resolve yet (e.g. those relying on dependencies like %(pyver)s)
deps = resolve_template(self.get_ref(key), self.template_values, expect_resolved=False)

# to update the original dep dict, we need to get a reference with templating disabled...
deps_ref = self.get_ref(key)
Expand Down Expand Up @@ -1833,7 +1834,8 @@ def asdict(self):
if self.enable_templating:
if not self.template_values:
self.generate_template_values()
value = resolve_template(value, self.template_values)
# Not all values can be resolved, e.g. %(installdir)s
value = resolve_template(value, self.template_values, expect_resolved=False)
res[key] = value
return res

Expand Down Expand Up @@ -1968,10 +1970,11 @@ def get_module_path(name, generic=None, decode=True):
return '.'.join(modpath + [module_name])


def resolve_template(value, tmpl_dict):
def resolve_template(value, tmpl_dict, expect_resolved=True):
"""Given a value, try to susbstitute the templated strings with actual values.
- value: some python object (supported are string, tuple/list, dict or some mix thereof)
- tmpl_dict: template dictionary
- expect_resolved: Expects that all templates get resolved
"""
if isinstance(value, str):
# simple escaping, making all '%foo', '%%foo', '%%%foo' post-templates values available,
Expand Down Expand Up @@ -2002,12 +2005,14 @@ def resolve_template(value, tmpl_dict):
try:
value = value % tmpl_dict
except KeyError:
msg = ('Failed to resolve template value %s with dict %s. ' % (value, tmpl_dict) +
'This might cause failures or unexpected behavior, check for correct escaping if this is intended!')
if build_option('allow_unresolved_templates'):
print_warning(msg)
else:
raise EasyBuildError(msg)
if expect_resolved:
msg = (f'Failed to resolve template value {value} with dict {tmpl_dict}. '
'This might cause failures or unexpected behavior, '
'check for correct escaping if this is intended!')
if build_option('allow_unresolved_templates'):
print_warning(msg)
else:
raise EasyBuildError(msg)
else:
# this block deals with references to objects and returns other references
# for reading this is ok, but for self['x'] = {}
Expand All @@ -2021,11 +2026,12 @@ def resolve_template(value, tmpl_dict):
# self._config['x']['y'] = z
# it can not be intercepted with __setitem__ because the set is done at a deeper level
if isinstance(value, list):
value = [resolve_template(val, tmpl_dict) for val in value]
value = [resolve_template(val, tmpl_dict, expect_resolved) for val in value]
elif isinstance(value, tuple):
value = tuple(resolve_template(list(value), tmpl_dict))
value = tuple(resolve_template(list(value), tmpl_dict, expect_resolved))
elif isinstance(value, dict):
value = {resolve_template(k, tmpl_dict): resolve_template(v, tmpl_dict) for k, v in value.items()}
value = {resolve_template(k, tmpl_dict, expect_resolved): resolve_template(v, tmpl_dict, expect_resolved)
for k, v in value.items()}

return value

Expand Down
5 changes: 4 additions & 1 deletion easybuild/framework/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,10 @@ def __init__(self, mself, ext, extra_params=None):
self.src = resolve_template(self.ext.get('src', []), self.cfg.template_values)
self.src_extract_cmd = self.ext.get('extract_cmd', None)
self.patches = resolve_template(self.ext.get('patches', []), self.cfg.template_values)
self.options = resolve_template(copy.deepcopy(self.ext.get('options', {})), self.cfg.template_values)
# Some options may not be resolvable yet
self.options = resolve_template(copy.deepcopy(self.ext.get('options', {})),
self.cfg.template_values,
expect_resolved=False)

if extra_params:
self.cfg.extend_params(extra_params, overwrite=False)
Expand Down

0 comments on commit 4a73b73

Please sign in to comment.