Skip to content

Commit a4fcae2

Browse files
authored
Merge pull request #3353 from ocaisa/allow_ignoring_versionsuffix
Allow ignoring versionsuffix in try-update-deps
2 parents 4208302 + b844b7f commit a4fcae2

File tree

4 files changed

+153
-19
lines changed

4 files changed

+153
-19
lines changed

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]

easybuild/tools/options.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,8 @@ def software_options(self):
320320
opts['try-update-deps'] = ("Try to update versions of the dependencies of an easyconfig based on what is "
321321
"available in the robot path",
322322
None, 'store_true', False)
323+
opts['try-ignore-versionsuffixes'] = ("Ignore versionsuffix differences when --try-update-deps is used",
324+
None, 'store_true', False)
323325

324326
self.log.debug("software_options: descr %s opts %s" % (descr, opts))
325327
self.add_group_parser(opts, descr)
@@ -1490,7 +1492,8 @@ def process_software_build_specs(options):
14901492
'version': options.try_software_version,
14911493
'toolchain_name': options.try_toolchain_name,
14921494
'toolchain_version': options.try_toolchain_version,
1493-
'update_deps': options.try_update_deps
1495+
'update_deps': options.try_update_deps,
1496+
'ignore_versionsuffixes': options.try_ignore_versionsuffixes,
14941497
}
14951498

14961499
# process easy options

test/framework/options.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1315,6 +1315,57 @@ def test_try_update_deps(self):
13151315
regex = re.compile(pattern, re.M)
13161316
self.assertTrue(regex.search(outtxt), "Pattern '%s' should be found in: %s" % (regex.pattern, outtxt))
13171317

1318+
# construct another toy easyconfig that is well suited for testing ignoring versionsuffix
1319+
test_ectxt = '\n'.join([
1320+
"easyblock = 'ConfigureMake'",
1321+
'',
1322+
"name = 'test'",
1323+
"version = '1.2.3'",
1324+
''
1325+
"homepage = 'https://test.org'",
1326+
"description = 'this is just a test'",
1327+
'',
1328+
"toolchain = {'name': 'GCC', 'version': '4.8.2'}",
1329+
'',
1330+
"dependencies = [('OpenBLAS', '0.2.8', '-LAPACK-3.4.2')]",
1331+
])
1332+
write_file(test_ec, test_ectxt)
1333+
self.mock_stderr(True)
1334+
outtxt = self.eb_main(args, raise_error=True, do_build=True)
1335+
errtxt = self.get_stderr()
1336+
warning_stub = "\nWARNING: There may be newer version(s) of dep 'OpenBLAS' available with a different " \
1337+
"versionsuffix to '-LAPACK-3.4.2'"
1338+
self.mock_stderr(False)
1339+
self.assertTrue(warning_stub in errtxt)
1340+
patterns = [
1341+
# toolchain got updated
1342+
r"^ \* \[x\] .*/test_ecs/g/GCC/GCC-6.4.0-2.28.eb \(module: GCC/6.4.0-2.28\)$",
1343+
# no version update for OpenBLAS (because there's no corresponding ec using GCC/6.4.0-2.28 (sub)toolchain)
1344+
r"^ \* \[ \] .*/tweaked_dep_easyconfigs/OpenBLAS-0.2.8-GCC-6.4.0-2.28-LAPACK-3.4.2.eb "
1345+
r"\(module: OpenBLAS/0.2.8-GCC-6.4.0-2.28-LAPACK-3.4.2\)$",
1346+
# also generated easyconfig for test/1.2.3 with expected toolchain
1347+
r"^ \* \[ \] .*/tweaked_easyconfigs/test-1.2.3-GCC-6.4.0-2.28.eb \(module: test/1.2.3-GCC-6.4.0-2.28\)$",
1348+
]
1349+
for pattern in patterns:
1350+
regex = re.compile(pattern, re.M)
1351+
self.assertTrue(regex.search(outtxt), "Pattern '%s' should be found in: %s" % (regex.pattern, outtxt))
1352+
1353+
# Now verify that we can ignore versionsuffixes
1354+
args.append('--try-ignore-versionsuffixes')
1355+
outtxt = self.eb_main(args, raise_error=True, do_build=True)
1356+
patterns = [
1357+
# toolchain got updated
1358+
r"^ \* \[x\] .*/test_ecs/g/GCC/GCC-6.4.0-2.28.eb \(module: GCC/6.4.0-2.28\)$",
1359+
# no version update for OpenBLAS (because there's no corresponding ec using GCC/6.4.0-2.28 (sub)toolchain)
1360+
r"^ \* \[x\] .*/test_ecs/o/OpenBLAS/OpenBLAS-0.2.20-GCC-6.4.0-2.28.eb "
1361+
r"\(module: OpenBLAS/0.2.20-GCC-6.4.0-2.28\)$",
1362+
# also generated easyconfig for test/1.2.3 with expected toolchain
1363+
r"^ \* \[ \] .*/tweaked_easyconfigs/test-1.2.3-GCC-6.4.0-2.28.eb \(module: test/1.2.3-GCC-6.4.0-2.28\)$",
1364+
]
1365+
for pattern in patterns:
1366+
regex = re.compile(pattern, re.M)
1367+
self.assertTrue(regex.search(outtxt), "Pattern '%s' should be found in: %s" % (regex.pattern, outtxt))
1368+
13181369
def test_dry_run_hierarchical(self):
13191370
"""Test dry run using a hierarchical module naming scheme."""
13201371
fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log')

test/framework/tweak.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,40 @@ def test_find_potential_version_mappings(self):
378378
'path': os.path.join(test_easyconfigs, 'g', 'gzip', 'gzip-1.6-iccifort-2016.1.150-GCC-4.9.3-2.25.eb'),
379379
'toolchain': {'name': 'iccifort', 'version': '2016.1.150-GCC-4.9.3-2.25'},
380380
'version': '1.6',
381+
'versionsuffix': '',
382+
}
383+
self.assertEqual(potential_versions[0], expected)
384+
385+
# Test that we can override respecting the versionsuffix
386+
387+
# Create toolchain mapping for OpenBLAS
388+
gcc_4_tc = {'name': 'GCC', 'version': '4.8.2'}
389+
gcc_6_tc = {'name': 'GCC', 'version': '6.4.0-2.28'}
390+
tc_mapping = map_toolchain_hierarchies(gcc_4_tc, gcc_6_tc, self.modtool)
391+
# Create a dep with the necessary params (including versionsuffix)
392+
openblas_dep = {
393+
'toolchain': {'version': '4.8.2', 'name': 'GCC'},
394+
'name': 'OpenBLAS',
395+
'system': False,
396+
'versionsuffix': '-LAPACK-3.4.2',
397+
'version': '0.2.8'
398+
}
399+
400+
self.mock_stderr(True)
401+
potential_versions = find_potential_version_mappings(openblas_dep, tc_mapping)
402+
errtxt = self.get_stderr()
403+
warning_stub = "\nWARNING: There may be newer version(s) of dep 'OpenBLAS' available with a different " \
404+
"versionsuffix to '-LAPACK-3.4.2'"
405+
self.mock_stderr(False)
406+
self.assertTrue(errtxt.startswith(warning_stub))
407+
self.assertEqual(len(potential_versions), 0)
408+
potential_versions = find_potential_version_mappings(openblas_dep, tc_mapping, ignore_versionsuffixes=True)
409+
self.assertEqual(len(potential_versions), 1)
410+
expected = {
411+
'path': os.path.join(test_easyconfigs, 'o', 'OpenBLAS', 'OpenBLAS-0.2.20-GCC-6.4.0-2.28.eb'),
412+
'toolchain': {'version': '6.4.0-2.28', 'name': 'GCC'},
413+
'version': '0.2.20',
414+
'versionsuffix': '',
381415
}
382416
self.assertEqual(potential_versions[0], expected)
383417

0 commit comments

Comments
 (0)