Skip to content

Commit eb40cb2

Browse files
committed
Add option to make sanity_check_paths arch dependent
1 parent b288b59 commit eb40cb2

File tree

5 files changed

+154
-33
lines changed

5 files changed

+154
-33
lines changed

easybuild/framework/easyblock.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@
9292
from easybuild.tools.package.utilities import package
9393
from easybuild.tools.py2vs3 import extract_method_name, string_type
9494
from easybuild.tools.repository.repository import init_repository
95-
from easybuild.tools.systemtools import check_linked_shared_libs, det_parallelism, get_shared_lib_ext, use_group
95+
from easybuild.tools.systemtools import check_linked_shared_libs, det_parallelism, get_shared_lib_ext
96+
from easybuild.tools.systemtools import pick_system_specific_value, use_group
9697
from easybuild.tools.utilities import INDENT_4SPACES, get_class_for, nub, quote_str
9798
from easybuild.tools.utilities import remove_unwanted_chars, time2str, trace_msg
9899
from easybuild.tools.version import this_is_easybuild, VERBOSE_VERSION, VERSION
@@ -2907,6 +2908,15 @@ def _sanity_check_step_common(self, custom_paths, custom_commands):
29072908
error_msg += "values should be lists (at least one non-empty)."
29082909
raise EasyBuildError(error_msg % ', '.join("'%s'" % k for k in known_keys))
29092910

2911+
# Resolve arch specific entries
2912+
for key, values in paths.items():
2913+
new_values = []
2914+
for value in values:
2915+
value = pick_system_specific_value('sanity_check_paths', value, allow_none=True)
2916+
if value is not None:
2917+
new_values.append(value)
2918+
values[:] = new_values
2919+
29102920
# if enhance_sanity_check is not enabled, only sanity_check_commands specified in the easyconfig file are used,
29112921
# the ones provided by the easyblock (via custom_commands) are ignored
29122922
if ec_commands and not enhance_sanity_check:

easybuild/framework/easyconfig/types.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,43 @@ def to_list_of_strings_and_tuples(spec):
349349
return str_tup_list
350350

351351

352+
def to_sanity_check_paths_entry(spec):
353+
"""
354+
Convert a 'list of lists and strings' to a 'list of tuples and strings' while allowing dicts of lists or strings
355+
356+
Example:
357+
['foo', ['bar', 'baz'], {'f42': ['a', 'b']}]
358+
to
359+
['foo', ('bar', 'baz'), {'f42': ('a', 'b')}]
360+
"""
361+
result = []
362+
363+
if not isinstance(spec, (list, tuple)):
364+
raise EasyBuildError("Expected value to be a list, found %s (%s)", spec, type(spec))
365+
366+
for elem in spec:
367+
if isinstance(elem, (string_type, tuple)):
368+
result.append(elem)
369+
elif isinstance(elem, list):
370+
result.append(tuple(elem))
371+
elif isinstance(elem, dict):
372+
result.append({})
373+
for key, value in elem.items():
374+
if not isinstance(key, string_type):
375+
raise EasyBuildError("Expected keys to be of type string, got %s (%s)", key, type(key))
376+
if isinstance(value, (string_type, tuple)):
377+
result[-1][key] = value
378+
elif isinstance(value, list):
379+
result[-1][key] = tuple(value)
380+
else:
381+
raise EasyBuildError("Expected elements to be of type string, tuple or list, got %s (%s)",
382+
value, type(value))
383+
else:
384+
raise EasyBuildError("Expected elements to be of type string, tuple or list, got %s (%s)", elem, type(elem))
385+
386+
return result
387+
388+
352389
def to_sanity_check_paths_dict(spec):
353390
"""
354391
Convert a sanity_check_paths dict as received by yaml (a dict with list values that contain either lists or strings)
@@ -363,7 +400,7 @@ def to_sanity_check_paths_dict(spec):
363400

364401
sanity_check_dict = {}
365402
for key in spec:
366-
sanity_check_dict[key] = to_list_of_strings_and_tuples(spec[key])
403+
sanity_check_dict[key] = to_sanity_check_paths_entry(spec[key])
367404
return sanity_check_dict
368405

369406

easybuild/tools/systemtools.py

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,6 +1106,46 @@ def check_python_version():
11061106
return (python_maj_ver, python_min_ver)
11071107

11081108

1109+
def pick_system_specific_value(description, options_or_value, allow_none=False):
1110+
"""Pick an entry for the current system when the input has multiple options
1111+
1112+
:param description: Descriptive string about the value to be retrieved. Used for logging.
1113+
:param options_or_value: Either a dictionary with options to choose from or a value of any other type
1114+
:param allow_none: When True and no matching arch key was found, return None instead of an error
1115+
1116+
:return options_or_value when it is not a dictionary or the matching entry (if existing)
1117+
"""
1118+
result = options_or_value
1119+
if isinstance(options_or_value, dict):
1120+
if not options_or_value:
1121+
raise EasyBuildError("Found empty dict as %s!", description)
1122+
other_keys = [x for x in options_or_value.keys() if not x.startswith(ARCH_KEY_PREFIX)]
1123+
if other_keys:
1124+
other_keys = ','.join(sorted(other_keys))
1125+
raise EasyBuildError("Unexpected keys in %s: %s (only '%s' keys are supported)",
1126+
description, other_keys, ARCH_KEY_PREFIX)
1127+
host_arch_key = ARCH_KEY_PREFIX + get_cpu_architecture()
1128+
star_arch_key = ARCH_KEY_PREFIX + '*'
1129+
# check for specific 'arch=' key first
1130+
try:
1131+
result = options_or_value[host_arch_key]
1132+
_log.info("Selected %s from %s for %s (using key %s)",
1133+
result, options_or_value, description, host_arch_key)
1134+
except KeyError:
1135+
# fall back to 'arch=*'
1136+
try:
1137+
result = options_or_value[star_arch_key]
1138+
_log.info("Selected %s from %s for %s (using fallback key %s)",
1139+
result, options_or_value, description, star_arch_key)
1140+
except KeyError:
1141+
if allow_none:
1142+
result = None
1143+
else:
1144+
raise EasyBuildError("No matches for %s in %s (looking for %s)",
1145+
description, options_or_value, host_arch_key)
1146+
return result
1147+
1148+
11091149
def pick_dep_version(dep_version):
11101150
"""
11111151
Pick the correct dependency version to use for this system.
@@ -1115,39 +1155,14 @@ def pick_dep_version(dep_version):
11151155
11161156
Return value is the version to use.
11171157
"""
1118-
if isinstance(dep_version, string_type):
1119-
_log.debug("Version is already a string ('%s'), OK", dep_version)
1120-
result = dep_version
1121-
1122-
elif dep_version is None:
1158+
if dep_version is None:
11231159
_log.debug("Version is None, OK")
11241160
result = None
1125-
1126-
elif isinstance(dep_version, dict):
1127-
arch_keys = [x for x in dep_version.keys() if x.startswith(ARCH_KEY_PREFIX)]
1128-
other_keys = [x for x in dep_version.keys() if x not in arch_keys]
1129-
if other_keys:
1130-
other_keys = ','.join(sorted(other_keys))
1131-
raise EasyBuildError("Unexpected keys in version: %s (only 'arch=' keys are supported)", other_keys)
1132-
if arch_keys:
1133-
host_arch_key = ARCH_KEY_PREFIX + get_cpu_architecture()
1134-
star_arch_key = ARCH_KEY_PREFIX + '*'
1135-
# check for specific 'arch=' key first
1136-
if host_arch_key in dep_version:
1137-
result = dep_version[host_arch_key]
1138-
_log.info("Version selected from %s using key %s: %s", dep_version, host_arch_key, result)
1139-
# fall back to 'arch=*'
1140-
elif star_arch_key in dep_version:
1141-
result = dep_version[star_arch_key]
1142-
_log.info("Version selected for %s using fallback key %s: %s", dep_version, star_arch_key, result)
1143-
else:
1144-
raise EasyBuildError("No matches for version in %s (looking for %s)", dep_version, host_arch_key)
1145-
else:
1146-
raise EasyBuildError("Found empty dict as version!")
1147-
11481161
else:
1149-
typ = type(dep_version)
1150-
raise EasyBuildError("Unknown value type for version: %s (%s), should be string value", typ, dep_version)
1162+
result = pick_system_specific_value("version", dep_version)
1163+
if not isinstance(result, string_type):
1164+
typ = type(dep_version)
1165+
raise EasyBuildError("Unknown value type for version: %s (%s), should be string value", typ, dep_version)
11511166

11521167
return result
11531168

test/framework/easyblock.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from test.framework.utilities import EnhancedTestCase, TestLoaderFiltered, init_config
3838
from unittest import TextTestRunner
3939

40+
import easybuild.tools.systemtools as st
4041
from easybuild.framework.easyblock import EasyBlock, get_easyblock_instance
4142
from easybuild.framework.easyconfig import CUSTOM
4243
from easybuild.framework.easyconfig.easyconfig import EasyConfig
@@ -2263,6 +2264,32 @@ def test_avail_easyblocks(self):
22632264
self.assertEqual(hpl['class'], 'EB_HPL')
22642265
self.assertTrue(hpl['loc'].endswith('sandbox/easybuild/easyblocks/h/hpl.py'))
22652266

2267+
def test_arch_specific_sanity_check(self):
2268+
"""Tests that the correct version is chosen for this architecture"""
2269+
2270+
my_arch = st.get_cpu_architecture()
2271+
2272+
self.contents = '\n'.join([
2273+
'easyblock = "ConfigureMake"',
2274+
'name = "test"',
2275+
'version = "0.2"',
2276+
'homepage = "https://example.com"',
2277+
'description = "test"',
2278+
'toolchain = SYSTEM',
2279+
'sanity_check_paths = {',
2280+
" 'files': [{'arch=%s': 'correct.a'}, 'default.a']," % my_arch,
2281+
" 'dirs': [{'arch=%s': ('correct', 'alternative')}, {'arch=no-arch': 'not-used'}]," % my_arch,
2282+
'}',
2283+
])
2284+
self.writeEC()
2285+
ec = EasyConfig(self.eb_file)
2286+
eb = EasyBlock(ec)
2287+
paths, _, _ = eb._sanity_check_step_common(None, None)
2288+
2289+
self.assertEqual(set(paths.keys()), set(('files', 'dirs')))
2290+
self.assertEqual(paths['files'], ['correct.a', 'default.a'])
2291+
self.assertEqual(paths['dirs'], [('correct', 'alternative')])
2292+
22662293
def test_sanity_check_paths_verification(self):
22672294
"""Test verification of sanity_check_paths w.r.t. keys & values."""
22682295

test/framework/systemtools.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
from easybuild.tools.systemtools import get_cpu_family, get_cpu_features, get_cpu_model, get_cpu_speed, get_cpu_vendor
5252
from easybuild.tools.systemtools import get_gcc_version, get_glibc_version, get_os_type, get_os_name, get_os_version
5353
from easybuild.tools.systemtools import get_platform_name, get_shared_lib_ext, get_system_info, get_total_memory
54-
from easybuild.tools.systemtools import find_library_path, locate_solib, pick_dep_version
54+
from easybuild.tools.systemtools import find_library_path, locate_solib, pick_dep_version, pick_system_specific_value
5555

5656

5757
PROC_CPUINFO_TXT = None
@@ -910,6 +910,38 @@ def test_pick_dep_version(self):
910910
error_pattern = r"Unknown value type for version: .* \(1.23\), should be string value"
911911
self.assertErrorRegex(EasyBuildError, error_pattern, pick_dep_version, 1.23)
912912

913+
def test_pick_system_specific_value(self):
914+
"""Test pick_system_specific_value function."""
915+
916+
self.assertEqual(pick_system_specific_value('test-desc', None), None)
917+
self.assertEqual(pick_system_specific_value('test-desc', '1.2.3'), '1.2.3')
918+
self.assertEqual(pick_system_specific_value('test-desc', (42, 'foobar')), (42, 'foobar'))
919+
920+
option_dict = {
921+
'arch=x86_64': '1.2.3-amd64',
922+
'arch=POWER': '1.2.3-ppc64le',
923+
'arch=*': '1.2.3-other',
924+
}
925+
926+
st.get_cpu_architecture = lambda: X86_64
927+
self.assertEqual(pick_system_specific_value('test-desc', option_dict), '1.2.3-amd64')
928+
929+
st.get_cpu_architecture = lambda: POWER
930+
self.assertEqual(pick_system_specific_value('test-desc', option_dict), '1.2.3-ppc64le')
931+
932+
st.get_cpu_architecture = lambda: "NON_EXISTING_ARCH"
933+
self.assertEqual(pick_system_specific_value('test-desc', option_dict), '1.2.3-other')
934+
935+
error_pattern = "Found empty dict as test-desc"
936+
self.assertErrorRegex(EasyBuildError, error_pattern, pick_system_specific_value, 'test-desc', {})
937+
938+
error_pattern = r"Unexpected keys in test-desc: foo \(only 'arch=' keys are supported\)"
939+
self.assertErrorRegex(EasyBuildError, error_pattern, pick_system_specific_value, 'test-desc',
940+
{'foo': '1'})
941+
error_pattern = r"Unexpected keys in test-desc: foo \(only 'arch=' keys are supported\)"
942+
self.assertErrorRegex(EasyBuildError, error_pattern, pick_system_specific_value, 'test-desc',
943+
{'foo': '1', 'arch=POWER': '2'})
944+
913945
def test_check_os_dependency(self):
914946
"""Test check_os_dependency."""
915947

0 commit comments

Comments
 (0)