Skip to content

Commit 9c92996

Browse files
authored
Merge pull request #3083 from boegel/external_modules_metadata_hmns
take into account that external modules may not be visible directly (due to module hierarchy)
2 parents 314cf7c + adc9c3f commit 9c92996

File tree

5 files changed

+67
-6
lines changed

5 files changed

+67
-6
lines changed

easybuild/framework/easyconfig/tools.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,10 @@ def find_resolved_modules(easyconfigs, avail_modules, modtool, retain_all_deps=F
125125
for dep in easyconfig['dependencies']:
126126
dep_mod_name = dep.get('full_mod_name', ActiveMNS().det_full_module_name(dep))
127127

128-
# treat external modules as resolved when retain_all_deps is enabled (e.g., under --dry-run),
128+
# always treat external modules as resolved,
129129
# since no corresponding easyconfig can be found for them
130-
if retain_all_deps and dep.get('external_module', False):
131-
_log.debug("Treating dependency marked as external dependency as resolved: %s", dep_mod_name)
130+
if dep.get('external_module', False):
131+
_log.debug("Treating dependency marked as external module as resolved: %s", dep_mod_name)
132132

133133
elif retain_all_deps and dep_mod_name not in avail_modules:
134134
# if all dependencies should be retained, include dep unless it has been already

easybuild/tools/options.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,8 +1523,16 @@ def parse_external_modules_metadata(cfgs):
15231523
except ConfigObjError as err:
15241524
raise EasyBuildError("Failed to parse %s with external modules metadata: %s", cfg, err)
15251525

1526+
known_metadata_keys = ['name', 'prefix', 'version']
1527+
unknown_keys = {}
1528+
15261529
# make sure name/version values are always lists, make sure they're equal length
15271530
for mod, entry in parsed_metadata.items():
1531+
# make sure only known keys are used
1532+
for key in entry.keys():
1533+
if key not in known_metadata_keys:
1534+
unknown_keys.setdefault(mod, []).append(key)
1535+
15281536
for key in ['name', 'version']:
15291537
if isinstance(entry.get(key), string_type):
15301538
entry[key] = [entry[key]]
@@ -1537,6 +1545,12 @@ def parse_external_modules_metadata(cfgs):
15371545
raise EasyBuildError("Different length for lists of names/versions in metadata for external module %s: "
15381546
"names: %s; versions: %s", mod, names, versions)
15391547

1548+
if unknown_keys:
1549+
error_msg = "Found metadata entries with unknown keys:"
1550+
for mod in sorted(unknown_keys.keys()):
1551+
error_msg += "\n* %s: %s" % (mod, ', '.join(sorted(unknown_keys[mod])))
1552+
raise EasyBuildError(error_msg)
1553+
15401554
_log.debug("External modules metadata: %s", parsed_metadata)
15411555
return parsed_metadata
15421556

easybuild/tools/toolchain/toolchain.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,16 @@ def _check_dependencies(self, dependencies):
451451
if dep_exists:
452452
deps.append(dep)
453453
self.log.devel("_check_dependencies: added toolchain dependency %s", str(dep))
454+
elif dep['external_module']:
455+
# external modules may be organised hierarchically,
456+
# so not all modules may be directly available for loading;
457+
# we assume here that the required modules are either provided by the toolchain,
458+
# or are listed earlier as dependency
459+
# examples from OpenHPC:
460+
# - openmpi3 module provided by OpenHPC requires that gnu7, gnu8 or intel module is loaded first
461+
# - fftw module provided by OpenHPC requires that compiler + MPI module are loaded first
462+
self.log.info("Assuming non-visible external module %s is available", dep['full_mod_name'])
463+
deps.append(dep)
454464
else:
455465
missing_dep_mods.append(dep_mod_name)
456466

test/framework/options.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3571,6 +3571,24 @@ def test_parse_external_modules_metadata(self):
35713571
self.assertEqual(metadata['two/2.1'], {'name': ['two'], 'version': ['2.1']})
35723572
self.assertEqual(metadata['three/3.0'], {'name': ['three'], 'version': ['3.0']})
35733573

3574+
# check whether entries with unknown keys result in an error
3575+
cfg1 = os.path.join(self.test_prefix, 'broken_cfg1.cfg')
3576+
write_file(cfg1, "[one/1.0]\nname = one\nversion = 1.0\nfoo = bar")
3577+
cfg2 = os.path.join(self.test_prefix, 'cfg2.cfg')
3578+
write_file(cfg2, "[two/2.0]\nname = two\nversion = 2.0")
3579+
cfg3 = os.path.join(self.test_prefix, 'broken_cfg3.cfg')
3580+
write_file(cfg3, "[three/3.0]\nnaem = three\nzzz=zzz\nvresion = 3.0\naaa = aaa")
3581+
cfg4 = os.path.join(self.test_prefix, 'broken_cfg4.cfg')
3582+
write_file(cfg4, "[four/4]\nprfeix = /software/four/4")
3583+
broken_cfgs = [cfg1, cfg2, cfg3, cfg4]
3584+
error_pattern = '\n'.join([
3585+
r"Found metadata entries with unknown keys:",
3586+
r"\* four/4: prfeix",
3587+
r"\* one/1.0: foo",
3588+
r"\* three/3.0: aaa, naem, vresion, zzz",
3589+
])
3590+
self.assertErrorRegex(EasyBuildError, error_pattern, parse_external_modules_metadata, broken_cfgs)
3591+
35743592
def test_zip_logs(self):
35753593
"""Test use of --zip-logs"""
35763594

test/framework/toy_build.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
@author: Kenneth Hoste (Ghent University)
2929
@author: Damian Alvarez (Forschungszentrum Juelich GmbH)
3030
"""
31+
import copy
3132
import glob
3233
import grp
3334
import os
@@ -1287,6 +1288,7 @@ def test_toy_module_fulltxt(self):
12871288

12881289
def test_external_dependencies(self):
12891290
"""Test specifying external (build) dependencies."""
1291+
12901292
topdir = os.path.dirname(os.path.abspath(__file__))
12911293
ectxt = read_file(os.path.join(topdir, 'easyconfigs', 'test_ecs', 't', 'toy', 'toy-0.0-deps.eb'))
12921294
toy_ec = os.path.join(self.test_prefix, 'toy-0.0-external-deps.eb')
@@ -1303,26 +1305,43 @@ def test_external_dependencies(self):
13031305
mkdir(os.path.join(modulepath, os.path.dirname(mod)), parents=True)
13041306
write_file(os.path.join(modulepath, mod), "#%Module")
13051307

1306-
self.reset_modulepath([modulepath, os.path.join(self.test_installpath, 'modules', 'all')])
1308+
installed_test_modules = os.path.join(self.test_installpath, 'modules', 'all')
1309+
self.reset_modulepath([modulepath, installed_test_modules])
1310+
1311+
start_env = copy.deepcopy(os.environ)
1312+
13071313
self.test_toy_build(ec_file=toy_ec, versionsuffix='-external-deps', verbose=True, raise_error=True)
13081314

13091315
self.modtool.load(['toy/0.0-external-deps'])
13101316
# note build dependency is not loaded
13111317
mods = ['intel/2018a', 'GCC/6.4.0-2.28', 'foobar/1.2.3', 'toy/0.0-external-deps']
13121318
self.assertEqual([x['mod_name'] for x in self.modtool.list()], mods)
13131319

1314-
# check behaviour when a non-existing external (build) dependency is included
1315-
err_msg = "Missing modules for dependencies marked as external modules:"
1320+
# restore original environment (to undo 'module load' done above)
1321+
modify_env(os.environ, start_env, verbose=False)
13161322

1323+
# check behaviour when a non-existing external (build) dependency is included
13171324
extraectxt = "\nbuilddependencies = [('nosuchbuilddep/0.0.0', EXTERNAL_MODULE)]"
13181325
extraectxt += "\nversionsuffix = '-external-deps-broken1'"
13191326
write_file(toy_ec, ectxt + extraectxt)
1327+
1328+
if isinstance(self.modtool, Lmod):
1329+
err_msg = r"Module command \\'module load nosuchbuilddep/0.0.0\\' failed"
1330+
else:
1331+
err_msg = r"Unable to locate a modulefile for 'nosuchbuilddep/0.0.0'"
1332+
13201333
self.assertErrorRegex(EasyBuildError, err_msg, self.test_toy_build, ec_file=toy_ec,
13211334
raise_error=True, verbose=False)
13221335

13231336
extraectxt = "\ndependencies += [('nosuchmodule/1.2.3', EXTERNAL_MODULE)]"
13241337
extraectxt += "\nversionsuffix = '-external-deps-broken2'"
13251338
write_file(toy_ec, ectxt + extraectxt)
1339+
1340+
if isinstance(self.modtool, Lmod):
1341+
err_msg = r"Module command \\'module load nosuchmodule/1.2.3\\' failed"
1342+
else:
1343+
err_msg = r"Unable to locate a modulefile for 'nosuchmodule/1.2.3'"
1344+
13261345
self.assertErrorRegex(EasyBuildError, err_msg, self.test_toy_build, ec_file=toy_ec,
13271346
raise_error=True, verbose=False)
13281347

0 commit comments

Comments
 (0)