diff --git a/easybuild/tools/systemtools.py b/easybuild/tools/systemtools.py index d58cfc0abf..6a3aa83861 100644 --- a/easybuild/tools/systemtools.py +++ b/easybuild/tools/systemtools.py @@ -73,6 +73,8 @@ POWER = 'POWER' X86_64 = 'x86_64' +ARCH_KEY_PREFIX = 'arch=' + # Vendor constants AMD = 'AMD' APM = 'Applied Micro' @@ -921,20 +923,29 @@ def pick_dep_version(dep_version): result = None elif isinstance(dep_version, dict): - # figure out matches based on dict keys (after splitting on '=') - my_arch_key = 'arch=%s' % get_cpu_architecture() - arch_keys = [x for x in dep_version.keys() if x.startswith('arch=')] + arch_keys = [x for x in dep_version.keys() if x.startswith(ARCH_KEY_PREFIX)] other_keys = [x for x in dep_version.keys() if x not in arch_keys] if other_keys: - raise EasyBuildError("Unexpected keys in version: %s. Only 'arch=' keys are supported", other_keys) + other_keys = ','.join(sorted(other_keys)) + raise EasyBuildError("Unexpected keys in version: %s (only 'arch=' keys are supported)", other_keys) if arch_keys: - if my_arch_key in dep_version: - result = dep_version[my_arch_key] - _log.info("Version selected from %s using key %s: %s", dep_version, my_arch_key, result) + host_arch_key = ARCH_KEY_PREFIX + get_cpu_architecture() + star_arch_key = ARCH_KEY_PREFIX + '*' + # check for specific 'arch=' key first + if host_arch_key in dep_version: + result = dep_version[host_arch_key] + _log.info("Version selected from %s using key %s: %s", dep_version, host_arch_key, result) + # fall back to 'arch=*' + elif star_arch_key in dep_version: + result = dep_version[star_arch_key] + _log.info("Version selected for %s using fallback key %s: %s", dep_version, star_arch_key, result) else: - raise EasyBuildError("No matches for version in %s (looking for %s)", dep_version, my_arch_key) + raise EasyBuildError("No matches for version in %s (looking for %s)", dep_version, host_arch_key) + else: + raise EasyBuildError("Found empty dict as version!") else: - raise EasyBuildError("Unknown value type for version: %s", dep_version) + typ = type(dep_version) + raise EasyBuildError("Unknown value type for version: %s (%s), should be string value", typ, dep_version) return result diff --git a/test/framework/easyconfig.py b/test/framework/easyconfig.py index d4011f124f..d5d6458ef0 100644 --- a/test/framework/easyconfig.py +++ b/test/framework/easyconfig.py @@ -72,7 +72,7 @@ from easybuild.tools.options import parse_external_modules_metadata from easybuild.tools.py2vs3 import OrderedDict, reload from easybuild.tools.robot import resolve_dependencies -from easybuild.tools.systemtools import get_shared_lib_ext +from easybuild.tools.systemtools import AARCH64, POWER, X86_64, get_cpu_architecture, get_shared_lib_ext from easybuild.tools.toolchain.utilities import search_toolchain from easybuild.tools.utilities import quote_str, quote_py_str from test.framework.utilities import find_full_path @@ -104,6 +104,7 @@ class EasyConfigTest(EnhancedTestCase): def setUp(self): """Set up everything for running a unit test.""" super(EasyConfigTest, self).setUp() + self.orig_get_cpu_architecture = st.get_cpu_architecture self.cwd = os.getcwd() self.all_stops = [x[0] for x in EasyBlock.get_steps()] @@ -122,6 +123,7 @@ def prep(self): def tearDown(self): """ make sure to remove the temporary file """ + st.get_cpu_architecture = self.orig_get_cpu_architecture super(EasyConfigTest, self).tearDown() if os.path.exists(self.eb_file): os.remove(self.eb_file) @@ -305,6 +307,69 @@ def test_dependency(self): self.assertErrorRegex(EasyBuildError, err_msg, eb._parse_dependency, (EXTERNAL_MODULE_MARKER,)) self.assertErrorRegex(EasyBuildError, err_msg, eb._parse_dependency, ('foo', '1.2.3', EXTERNAL_MODULE_MARKER)) + def test_false_dep_version(self): + """ + Test use False as dependency version via dict using 'arch=' keys, + which should result in filtering the dependency. + """ + # silence warnings about missing easyconfigs for dependencies, we don't care + init_config(build_options={'silent': True}) + + arch = get_cpu_architecture() + + self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', + 'name = "pi"', + 'version = "3.14"', + 'versionsuffix = "-test"', + 'homepage = "http://example.com"', + 'description = "test easyconfig"', + 'toolchain = {"name":"GCC", "version": "4.6.3"}', + 'builddependencies = [', + ' ("first_build", {"arch=%s": False}),' % arch, + ' ("second_build", "2.0"),', + ']', + 'dependencies = [' + ' ("first", "1.0"),', + ' ("second", {"arch=%s": False}),' % arch, + ']', + ]) + self.prep() + eb = EasyConfig(self.eb_file) + deps = eb.dependencies() + self.assertEqual(len(deps), 2) + self.assertEqual(deps[0]['name'], 'second_build') + self.assertEqual(deps[1]['name'], 'first') + + # more realistic example: only filter dep for POWER + self.contents = '\n'.join([ + 'easyblock = "ConfigureMake"', + 'name = "pi"', + 'version = "3.14"', + 'versionsuffix = "-test"', + 'homepage = "http://example.com"', + 'description = "test easyconfig"', + 'toolchain = {"name":"GCC", "version": "4.6.3"}', + 'dependencies = [' + ' ("not_on_power", {"arch=*": "1.2.3", "arch=POWER": False}),', + ']', + ]) + self.prep() + + # only non-POWER arch, dependency is retained + for arch in (AARCH64, X86_64): + st.get_cpu_architecture = lambda: arch + eb = EasyConfig(self.eb_file) + deps = eb.dependencies() + self.assertEqual(len(deps), 1) + self.assertEqual(deps[0]['name'], 'not_on_power') + + # only power, dependency gets filtered + st.get_cpu_architecture = lambda: POWER + eb = EasyConfig(self.eb_file) + deps = eb.dependencies() + self.assertEqual(deps, []) + def test_extra_options(self): """ extra_options should allow other variables to be stored """ init_config(build_options={'silent': True}) diff --git a/test/framework/systemtools.py b/test/framework/systemtools.py index eba1625a17..3654882086 100644 --- a/test/framework/systemtools.py +++ b/test/framework/systemtools.py @@ -916,6 +916,24 @@ def test_pick_dep_version(self): error_pattern = "Unknown value type for version" self.assertErrorRegex(EasyBuildError, error_pattern, pick_dep_version, ('1.2.3', '4.5.6')) + # check support for using 'arch=*' as fallback key + dep_ver_dict = { + 'arch=*': '1.2.3', + 'arch=foo': '1.2.3-foo', + 'arch=POWER': '1.2.3-ppc64le', + } + self.assertEqual(pick_dep_version(dep_ver_dict), '1.2.3-ppc64le') + + del dep_ver_dict['arch=POWER'] + self.assertEqual(pick_dep_version(dep_ver_dict), '1.2.3') + + # check how faulty input is handled + self.assertErrorRegex(EasyBuildError, "Found empty dict as version!", pick_dep_version, {}) + error_pattern = r"Unexpected keys in version: bar,foo \(only 'arch=' keys are supported\)" + self.assertErrorRegex(EasyBuildError, error_pattern, pick_dep_version, {'foo': '1.2', 'bar': '2.3'}) + error_pattern = r"Unknown value type for version: .* \(1.23\), should be string value" + self.assertErrorRegex(EasyBuildError, error_pattern, pick_dep_version, 1.23) + def test_check_os_dependency(self): """Test check_os_dependency."""