Skip to content

Commit 2035f5d

Browse files
Merge pull request #3383 from easybuilders/4.2.x
release EasyBuild v4.2.2
2 parents 766128e + fec3b14 commit 2035f5d

File tree

16 files changed

+687
-123
lines changed

16 files changed

+687
-123
lines changed

RELEASE_NOTES

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,26 @@ For more detailed information, please see the git log.
33

44
These release notes can also be consulted at https://easybuild.readthedocs.io/en/latest/Release_notes.html.
55

6+
v4.2.2 (July 8th 2020)
7+
----------------------
8+
9+
update/bugfix release
10+
11+
- various enhancements, including:
12+
- add support for using 'sources' and 'git_config' for extensions in 'exts_list' (#3294)
13+
- add support for software minver template (like %(pyminver)s, etc.) (#3344, #3345)
14+
- add support for updating dictionary or tuple easyconfig parameters with self.cfg.update (#3356)
15+
- various bug fixes, including:
16+
- fix crash in --avail-easyconfig-constants when using --output-format=rst + ensure sorted output (#3341)
17+
- always take into account builddependencies when generating template values, also when we're not iterating over builddependencies (#3346)
18+
- fix running command as 'easybuild' user in generated Singularity definition file (#3347)
19+
- allow ignoring versionsuffix in --try-update-deps (#3350, #3353)
20+
- retain order of paths when generating prepend_path statements for module file (don't sort them alphabetically) (#3367)
21+
- also put easyblocks used by extensions in 'reprod' directory (#3375)
22+
- also copy template values in EasyConfig.copy method to ensure they are defined when installing extensions (#3377)
23+
- skip lines that start with 'module-version' when determining whether a module exists in ModulesTool.exist (#3379)
24+
25+
626
v4.2.1 (May 20th 2020)
727
----------------------
828

easybuild/framework/easyblock.py

Lines changed: 137 additions & 64 deletions
Large diffs are not rendered by default.

easybuild/framework/easyconfig/easyconfig.py

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -570,34 +570,57 @@ def copy(self, validate=None):
570570
if self.path:
571571
ec.path = self.path
572572

573+
# also copy template values, since re-generating them may not give the same set of template values straight away
574+
ec.template_values = copy.deepcopy(self.template_values)
575+
573576
return ec
574577

575578
def update(self, key, value, allow_duplicate=True):
576579
"""
577-
Update a string configuration value with a value (i.e. append to it).
580+
Update an easyconfig parameter with the specified value (i.e. append to it).
581+
Note: For dictionary easyconfig parameters, 'allow_duplicate' is ignored (since it's meaningless).
578582
"""
579583
if isinstance(value, string_type):
580-
lval = [value]
581-
elif isinstance(value, list):
582-
lval = value
584+
inval = [value]
585+
elif isinstance(value, (list, dict, tuple)):
586+
inval = value
583587
else:
584-
msg = "Can't update configuration value for %s, because the "
585-
msg += "attempted update value, '%s', is not a string or list."
588+
msg = "Can't update configuration value for %s, because the attempted"
589+
msg += " update value, '%s', is not a string, list, tuple or dictionary."
586590
raise EasyBuildError(msg, key, value)
587591

588-
param_value = self[key]
592+
# For easyconfig parameters that are dictionaries, input value must also be a dictionary
593+
if isinstance(self[key], dict) and not isinstance(value, dict):
594+
msg = "Can't update configuration value for %s, because the attempted"
595+
msg += "update value (%s), is not a dictionary (type: %s)."
596+
raise EasyBuildError(msg, key, value, type(value))
597+
598+
# Grab current parameter value so we can modify it
599+
param_value = copy.deepcopy(self[key])
600+
589601
if isinstance(param_value, string_type):
590-
for item in lval:
602+
for item in inval:
591603
# re.search: only add value to string if it's not there yet (surrounded by whitespace)
592604
if allow_duplicate or (not re.search(r'(^|\s+)%s(\s+|$)' % re.escape(item), param_value)):
593605
param_value = param_value + ' %s ' % item
594-
elif isinstance(param_value, list):
595-
for item in lval:
606+
607+
elif isinstance(param_value, (list, tuple)):
608+
# make sure we have a list value so we can just append to it
609+
param_value = list(param_value)
610+
for item in inval:
596611
if allow_duplicate or item not in param_value:
597-
param_value = param_value + [item]
612+
param_value.append(item)
613+
# cast back to tuple if original value was a tuple
614+
if isinstance(self[key], tuple):
615+
param_value = tuple(param_value)
616+
617+
elif isinstance(param_value, dict):
618+
param_value.update(inval)
598619
else:
599-
raise EasyBuildError("Can't update configuration value for %s, because it's not a string or list.", key)
620+
msg = "Can't update configuration value for %s, because it's not a string, list, tuple or dictionary."
621+
raise EasyBuildError(msg, key)
600622

623+
# Overwrite easyconfig parameter value with updated value, preserving type
601624
self[key] = param_value
602625

603626
def set_keys(self, params):
@@ -1573,6 +1596,7 @@ def _finalize_dependencies(self):
15731596
def generate_template_values(self):
15741597
"""Try to generate all template values."""
15751598

1599+
self.log.info("Generating template values...")
15761600
self._generate_template_values()
15771601

15781602
# recursive call, until there are no more changes to template values;
@@ -1591,6 +1615,8 @@ def generate_template_values(self):
15911615
# KeyError's may occur when not all templates are defined yet, but these are safe to ignore
15921616
pass
15931617

1618+
self.log.info("Template values: %s", ', '.join("%s='%s'" % x for x in sorted(self.template_values.items())))
1619+
15941620
def _generate_template_values(self, ignore=None):
15951621
"""Actual code to generate the template values"""
15961622

easybuild/framework/easyconfig/templates.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,25 @@ def template_constant_dict(config, ignore=None, skip_lower=None, toolchain=None)
216216
# copy to avoid changing original list below
217217
deps = copy.copy(config.get('dependencies', []))
218218

219-
# only consider build dependencies for defining *ver and *shortver templates if we're in iterative mode
220-
if hasattr(config, 'iterating') and config.iterating:
221-
deps += config.get('builddependencies', [])
219+
# also consider build dependencies for *ver and *shortver templates;
220+
# we need to be a bit careful here, because for iterative installations
221+
# (when multi_deps is used for example) the builddependencies value may be a list of lists
222+
223+
# first, determine if we have an EasyConfig instance
224+
# (indirectly by checking for 'iterating' and 'iterate_options' attributes,
225+
# because we can't import the EasyConfig class here without introducing
226+
# a cyclic import...);
227+
# we need to know to determine whether we're iterating over a list of build dependencies
228+
is_easyconfig = hasattr(config, 'iterating') and hasattr(config, 'iterate_options')
229+
230+
if is_easyconfig:
231+
# if we're iterating over different lists of build dependencies,
232+
# only consider build dependencies when we're actually in iterative mode!
233+
if 'builddependencies' in config.iterate_options:
234+
if config.iterating:
235+
deps += config.get('builddependencies', [])
236+
else:
237+
deps += config.get('builddependencies', [])
222238

223239
for dep in deps:
224240
if isinstance(dep, dict):
@@ -245,6 +261,8 @@ def template_constant_dict(config, ignore=None, skip_lower=None, toolchain=None)
245261
template_values['%sver' % pref] = dep_version
246262
dep_version_parts = dep_version.split('.')
247263
template_values['%smajver' % pref] = dep_version_parts[0]
264+
if len(dep_version_parts) > 1:
265+
template_values['%sminver' % pref] = dep_version_parts[1]
248266
template_values['%sshortver' % pref] = '.'.join(dep_version_parts[:2])
249267
break
250268

easybuild/framework/easyconfig/tweak.py

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,8 @@
6464
from easybuild.tools.toolchain.toolchain import TOOLCHAIN_CAPABILITIES
6565
from easybuild.tools.utilities import flatten, nub, quote_str
6666

67-
6867
_log = fancylogger.getLogger('easyconfig.tweak', fname=False)
6968

70-
7169
EASYCONFIG_TEMPLATE = "TEMPLATE"
7270

7371

@@ -126,6 +124,10 @@ def tweak(easyconfigs, build_specs, modtool, targetdirs=None):
126124
pruned_build_specs = copy.copy(build_specs)
127125

128126
update_dependencies = pruned_build_specs.pop('update_deps', None)
127+
ignore_versionsuffixes = pruned_build_specs.pop('ignore_versionsuffixes', None)
128+
if ignore_versionsuffixes and not update_dependencies:
129+
print_warning("--try-ignore-versionsuffixes is ignored if --try-update-deps is not True")
130+
ignore_versionsuffixes = False
129131
if 'toolchain' in pruned_build_specs:
130132
target_toolchain = pruned_build_specs.pop('toolchain')
131133
pruned_build_specs.pop('toolchain_name', '')
@@ -197,7 +199,8 @@ def tweak(easyconfigs, build_specs, modtool, targetdirs=None):
197199
new_ec_file = map_easyconfig_to_target_tc_hierarchy(orig_ec['spec'], src_to_dst_tc_mapping,
198200
targetdir=tweaked_ecs_path,
199201
update_build_specs=pruned_build_specs,
200-
update_dep_versions=update_dependencies)
202+
update_dep_versions=update_dependencies,
203+
ignore_versionsuffixes=ignore_versionsuffixes)
201204
# Need to update the toolchain in the build_specs to match the toolchain mapping
202205
keys = verification_build_specs.keys()
203206
if 'toolchain_name' in keys:
@@ -219,7 +222,8 @@ def tweak(easyconfigs, build_specs, modtool, targetdirs=None):
219222
# Note pruned_build_specs are not passed down for dependencies
220223
map_easyconfig_to_target_tc_hierarchy(orig_ec['spec'], src_to_dst_tc_mapping,
221224
targetdir=tweaked_ecs_deps_path,
222-
update_dep_versions=update_dependencies)
225+
update_dep_versions=update_dependencies,
226+
ignore_versionsuffixes=ignore_versionsuffixes)
223227
else:
224228
tweak_one(orig_ec['spec'], None, build_specs, targetdir=tweaked_ecs_deps_path)
225229

@@ -277,6 +281,7 @@ def tweak_one(orig_ec, tweaked_ec, tweaks, targetdir=None):
277281

278282
class TcDict(dict):
279283
"""A special dict class that represents trivial toolchains properly."""
284+
280285
def __repr__(self):
281286
return "{'name': '%(name)s', 'version': '%(version)s'}" % self
282287

@@ -899,7 +904,7 @@ def map_common_versionsuffixes(software_name, original_toolchain, toolchain_mapp
899904
'versionsuffix': versionsuffix or '',
900905
}
901906
# See what this dep would be mapped to
902-
version_matches = find_potential_version_mappings(software_as_dep, toolchain_mapping)
907+
version_matches = find_potential_version_mappings(software_as_dep, toolchain_mapping, quiet=True)
903908
if version_matches:
904909
target_version = version_matches[0]['version']
905910
if LooseVersion(target_version) > LooseVersion(version):
@@ -940,7 +945,7 @@ def get_matching_easyconfig_candidates(prefix_stub, toolchain):
940945

941946

942947
def map_easyconfig_to_target_tc_hierarchy(ec_spec, toolchain_mapping, targetdir=None, update_build_specs=None,
943-
update_dep_versions=False):
948+
update_dep_versions=False, ignore_versionsuffixes=False):
944949
"""
945950
Take an easyconfig spec, parse it, map it to a target toolchain and dump it out
946951
@@ -961,6 +966,7 @@ def map_easyconfig_to_target_tc_hierarchy(ec_spec, toolchain_mapping, targetdir=
961966
if update_dep_versions and (list_deps_versionsuffixes(ec_spec) or parsed_ec['versionsuffix']):
962967
# We may need to update the versionsuffix if it is like, for example, `-Python-2.7.8`
963968
versonsuffix_mapping = map_common_versionsuffixes('Python', parsed_ec['toolchain'], toolchain_mapping)
969+
versonsuffix_mapping.update(map_common_versionsuffixes('Perl', parsed_ec['toolchain'], toolchain_mapping))
964970

965971
if update_build_specs is not None:
966972
if 'version' in update_build_specs:
@@ -1033,21 +1039,25 @@ def map_easyconfig_to_target_tc_hierarchy(ec_spec, toolchain_mapping, targetdir=
10331039
elif update_dep_versions:
10341040
# search for available updates for this dependency:
10351041
# first get highest version candidate paths for this (include search through subtoolchains)
1036-
potential_version_mappings = find_potential_version_mappings(dep, toolchain_mapping,
1037-
versionsuffix_mapping=versonsuffix_mapping)
1042+
potential_version_mappings = find_potential_version_mappings(
1043+
dep,
1044+
toolchain_mapping,
1045+
versionsuffix_mapping=versonsuffix_mapping,
1046+
ignore_versionsuffixes=ignore_versionsuffixes
1047+
)
10381048
# only highest version match is retained by default in potential_version_mappings,
10391049
# compare that version to the original version and replace if appropriate (upgrades only).
10401050
if potential_version_mappings:
10411051
highest_version_match = potential_version_mappings[0]['version']
1052+
highest_versionsuffix_match = potential_version_mappings[0]['versionsuffix']
10421053
if LooseVersion(highest_version_match) > LooseVersion(dep['version']):
10431054
_log.info("Updating version of %s dependency from %s to %s", dep['name'], dep['version'],
10441055
highest_version_match)
10451056
_log.info("Depending on your configuration, this will be resolved with one of the following "
10461057
"easyconfigs: \n%s", '\n'.join(cand['path'] for cand in potential_version_mappings))
10471058
orig_dep['version'] = highest_version_match
1048-
if orig_dep['versionsuffix'] in versonsuffix_mapping:
1049-
dep['versionsuffix'] = versonsuffix_mapping[orig_dep['versionsuffix']]
1050-
orig_dep['versionsuffix'] = versonsuffix_mapping[orig_dep['versionsuffix']]
1059+
dep['versionsuffix'] = highest_versionsuffix_match
1060+
orig_dep['versionsuffix'] = highest_versionsuffix_match
10511061
dep_changed = True
10521062

10531063
if dep_changed:
@@ -1090,7 +1100,8 @@ def list_deps_versionsuffixes(ec_spec):
10901100
return list(set(versionsuffix_list))
10911101

10921102

1093-
def find_potential_version_mappings(dep, toolchain_mapping, versionsuffix_mapping=None, highest_versions_only=True):
1103+
def find_potential_version_mappings(dep, toolchain_mapping, versionsuffix_mapping=None, highest_versions_only=True,
1104+
ignore_versionsuffixes=False, quiet=False):
10941105
"""
10951106
Find potential version mapping for a dependency in a new hierarchy
10961107
@@ -1139,7 +1150,9 @@ def find_potential_version_mappings(dep, toolchain_mapping, versionsuffix_mappin
11391150
if len(version_components) > 1: # Have at least major.minor
11401151
candidate_ver_list.append(r'%s\..*' % major_version)
11411152
candidate_ver_list.append(r'.*') # Include a major version search
1142-
potential_version_mappings, highest_version = [], None
1153+
potential_version_mappings = []
1154+
highest_version = None
1155+
highest_version_ignoring_versionsuffix = None
11431156

11441157
for candidate_ver in candidate_ver_list:
11451158

@@ -1152,7 +1165,8 @@ def find_potential_version_mappings(dep, toolchain_mapping, versionsuffix_mappin
11521165
toolchain_suffix = ''
11531166
else:
11541167
toolchain_suffix = '-%s-%s' % (toolchain['name'], toolchain['version'])
1155-
full_versionsuffix = toolchain_suffix + versionsuffix + EB_FORMAT_EXTENSION
1168+
# Search for any version suffix but only use what we are allowed to
1169+
full_versionsuffix = toolchain_suffix + r'.*' + EB_FORMAT_EXTENSION
11561170
depver = '^' + prefix_to_version + candidate_ver + full_versionsuffix
11571171
cand_paths = search_easyconfigs(depver, consider_extra_paths=False, print_result=False,
11581172
case_sensitive=True)
@@ -1178,14 +1192,46 @@ def find_potential_version_mappings(dep, toolchain_mapping, versionsuffix_mappin
11781192

11791193
# add what is left to the possibilities
11801194
for path in cand_paths:
1181-
version = fetch_parameters_from_easyconfig(read_file(path), ['version'])[0]
1195+
version, newversionsuffix = fetch_parameters_from_easyconfig(read_file(path), ['version',
1196+
'versionsuffix'])
1197+
if not newversionsuffix:
1198+
newversionsuffix = ''
11821199
if version:
1183-
if highest_version is None or LooseVersion(version) > LooseVersion(highest_version):
1184-
highest_version = version
1200+
if versionsuffix == newversionsuffix:
1201+
if highest_version is None or LooseVersion(version) > LooseVersion(highest_version):
1202+
highest_version = version
1203+
else:
1204+
if highest_version_ignoring_versionsuffix is None or \
1205+
LooseVersion(version) > LooseVersion(highest_version_ignoring_versionsuffix):
1206+
highest_version_ignoring_versionsuffix = version
11851207
else:
11861208
raise EasyBuildError("Failed to determine version from contents of %s", path)
11871209

1188-
potential_version_mappings.append({'path': path, 'toolchain': toolchain, 'version': version})
1210+
potential_version_mappings.append({'path': path, 'toolchain': toolchain, 'version': version,
1211+
'versionsuffix': newversionsuffix})
1212+
1213+
ignored_versionsuffix_greater = \
1214+
highest_version_ignoring_versionsuffix is not None and highest_version is None or \
1215+
(highest_version_ignoring_versionsuffix is not None and highest_version is not None and
1216+
LooseVersion(highest_version_ignoring_versionsuffix) > LooseVersion(highest_version))
1217+
1218+
exclude_alternate_versionsuffixes = False
1219+
if ignored_versionsuffix_greater:
1220+
if ignore_versionsuffixes:
1221+
highest_version = highest_version_ignoring_versionsuffix
1222+
else:
1223+
if not quiet:
1224+
print_warning(
1225+
"There may be newer version(s) of dep '%s' available with a different versionsuffix to '%s': %s",
1226+
dep['name'], versionsuffix, [d['path'] for d in potential_version_mappings if
1227+
d['version'] == highest_version_ignoring_versionsuffix])
1228+
# exclude candidates with a different versionsuffix
1229+
exclude_alternate_versionsuffixes = True
1230+
else:
1231+
# If the other version suffixes are not greater, then just ignore them
1232+
exclude_alternate_versionsuffixes = True
1233+
if exclude_alternate_versionsuffixes:
1234+
potential_version_mappings = [d for d in potential_version_mappings if d['versionsuffix'] == versionsuffix]
11891235

11901236
if highest_versions_only and highest_version is not None:
11911237
potential_version_mappings = [d for d in potential_version_mappings if d['version'] == highest_version]

0 commit comments

Comments
 (0)