Skip to content

Commit 218685e

Browse files
authored
Merge pull request #3261 from ocaisa/toolchain_hierarchy_aware_dump
make EasyConfig.dump aware of toolchain hierarchy, to avoid hardcoded subtoolchains in dependencies easyconfig parameters
2 parents 14fa5aa + 8e9f2a8 commit 218685e

File tree

7 files changed

+91
-17
lines changed

7 files changed

+91
-17
lines changed

easybuild/framework/easyblock.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3330,7 +3330,7 @@ def reproduce_build(app, reprod_dir_root):
33303330
reprod_dir = find_backup_name_candidate(os.path.join(reprod_dir_root, REPROD))
33313331
reprod_spec = os.path.join(reprod_dir, ec_filename)
33323332
try:
3333-
app.cfg.dump(reprod_spec)
3333+
app.cfg.dump(reprod_spec, explicit_toolchains=True)
33343334
_log.info("Dumped easyconfig instance to %s", reprod_spec)
33353335
except NotImplementedError as err:
33363336
_log.warning("Unable to dump easyconfig instance to %s: %s", reprod_spec, err)

easybuild/framework/easyconfig/easyconfig.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,7 +1101,7 @@ def all_dependencies(self):
11011101

11021102
return self._all_dependencies
11031103

1104-
def dump(self, fp, always_overwrite=True, backup=False):
1104+
def dump(self, fp, always_overwrite=True, backup=False, explicit_toolchains=False):
11051105
"""
11061106
Dump this easyconfig to file, with the given filename.
11071107
@@ -1130,8 +1130,18 @@ def dump(self, fp, always_overwrite=True, backup=False):
11301130
if self.template_values[key] not in templ_val and len(self.template_values[key]) > 2:
11311131
templ_val[self.template_values[key]] = key
11321132

1133+
toolchain_hierarchy = None
1134+
if not explicit_toolchains:
1135+
try:
1136+
toolchain_hierarchy = get_toolchain_hierarchy(self['toolchain'])
1137+
except EasyBuildError as err:
1138+
# don't fail hard just because we can't get the hierarchy
1139+
self.log.warning('Could not generate toolchain hierarchy for %s to use in easyconfig dump method, '
1140+
'error:\n%s', self['toolchain'], str(err))
1141+
11331142
try:
1134-
ectxt = self.parser.dump(self, default_values, templ_const, templ_val)
1143+
ectxt = self.parser.dump(self, default_values, templ_const, templ_val,
1144+
toolchain_hierarchy=toolchain_hierarchy)
11351145
except NotImplementedError as err:
11361146
# need to restore enable_templating value in case this method is caught in a try/except block and ignored
11371147
# (the ability to dump is not a hard requirement for build success)

easybuild/framework/easyconfig/format/format.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -632,8 +632,8 @@ def parse(self, txt, **kwargs):
632632
"""Parse the txt according to this format. This is highly version specific"""
633633
raise NotImplementedError
634634

635-
def dump(self, ecfg, default_values, templ_const, templ_val):
636-
"""Dump easyconfig according to this format. This is higly version specific"""
635+
def dump(self, ecfg, default_values, templ_const, templ_val, toolchain_hierarchy=None):
636+
"""Dump easyconfig according to this format. This is highly version specific"""
637637
raise NotImplementedError
638638

639639
def extract_comments(self, rawtxt):

easybuild/framework/easyconfig/format/one.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,17 @@
6565
_log = fancylogger.getLogger('easyconfig.format.one', fname=False)
6666

6767

68-
def dump_dependency(dep, toolchain):
68+
def dump_dependency(dep, toolchain, toolchain_hierarchy=None):
6969
"""Dump parsed dependency in tuple format"""
70+
if not toolchain_hierarchy:
71+
toolchain_hierarchy = [toolchain]
7072

7173
if dep['external_module']:
7274
res = "(%s, EXTERNAL_MODULE)" % quote_py_str(dep['full_mod_name'])
7375
else:
7476
# minimal spec: (name, version)
7577
tup = (dep['name'], dep['version'])
76-
if dep['toolchain'] != toolchain:
78+
if all(dep['toolchain'] != subtoolchain for subtoolchain in toolchain_hierarchy):
7779
if dep[SYSTEM_TOOLCHAIN_NAME]:
7880
tup += (dep['versionsuffix'], True)
7981
else:
@@ -260,7 +262,7 @@ def _find_param_with_comments(self, key, val, templ_const, templ_val):
260262

261263
return res
262264

263-
def _find_defined_params(self, ecfg, keyset, default_values, templ_const, templ_val):
265+
def _find_defined_params(self, ecfg, keyset, default_values, templ_const, templ_val, toolchain_hierarchy=None):
264266
"""
265267
Determine parameters in the dumped easyconfig file which have a non-default value.
266268
"""
@@ -279,12 +281,18 @@ def _find_defined_params(self, ecfg, keyset, default_values, templ_const, templ_
279281
# the way that builddependencies are constructed with multi_deps
280282
# we just need to dump the first entry without the dependencies
281283
# that are listed in multi_deps
282-
valstr = [dump_dependency(d, ecfg['toolchain']) for d in val[0]
283-
if d['name'] not in ecfg['multi_deps']]
284+
valstr = [
285+
dump_dependency(d, ecfg['toolchain'], toolchain_hierarchy=toolchain_hierarchy)
286+
for d in val[0] if d['name'] not in ecfg['multi_deps']
287+
]
284288
else:
285-
valstr = [[dump_dependency(d, ecfg['toolchain']) for d in dep] for dep in val]
289+
valstr = [
290+
[dump_dependency(d, ecfg['toolchain'], toolchain_hierarchy=toolchain_hierarchy)
291+
for d in dep] for dep in val
292+
]
286293
else:
287-
valstr = [dump_dependency(d, ecfg['toolchain']) for d in val]
294+
valstr = [dump_dependency(d, ecfg['toolchain'], toolchain_hierarchy=toolchain_hierarchy)
295+
for d in val]
288296
elif key == 'toolchain':
289297
valstr = "{'name': '%(name)s', 'version': '%(version)s'}" % ecfg[key]
290298
else:
@@ -299,20 +307,22 @@ def _find_defined_params(self, ecfg, keyset, default_values, templ_const, templ_
299307

300308
return eclines, printed_keys
301309

302-
def dump(self, ecfg, default_values, templ_const, templ_val):
310+
def dump(self, ecfg, default_values, templ_const, templ_val, toolchain_hierarchy=None):
303311
"""
304312
Dump easyconfig in format v1.
305313
306314
:param ecfg: EasyConfig instance
307315
:param default_values: default values for easyconfig parameters
308316
:param templ_const: known template constants
309317
:param templ_val: known template values
318+
:param toolchain_hierarchy: hierarchy of toolchains for easyconfig
310319
"""
311320
# include header comments first
312321
dump = self.comments['header'][:]
313322

314323
# print easyconfig parameters ordered and in groups specified above
315-
params, printed_keys = self._find_defined_params(ecfg, GROUPED_PARAMS, default_values, templ_const, templ_val)
324+
params, printed_keys = self._find_defined_params(ecfg, GROUPED_PARAMS, default_values, templ_const, templ_val,
325+
toolchain_hierarchy=toolchain_hierarchy)
316326
dump.extend(params)
317327

318328
# print other easyconfig parameters at the end

easybuild/framework/easyconfig/format/yeb.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def _inject_constants_dict(self, txt):
126126

127127
return full_txt
128128

129-
def dump(self, ecfg, default_values, templ_const, templ_val):
129+
def dump(self, ecfg, default_values, templ_const, templ_val, toolchain_hierarchy=None):
130130
"""Dump parsed easyconfig in .yeb format"""
131131
raise NotImplementedError("Dumping of .yeb easyconfigs not supported yet")
132132

easybuild/framework/easyconfig/parser.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ def get_config_dict(self, validate=True):
226226

227227
return cfg
228228

229-
def dump(self, ecfg, default_values, templ_const, templ_val):
229+
def dump(self, ecfg, default_values, templ_const, templ_val, toolchain_hierarchy=None):
230230
"""Dump easyconfig in format it was parsed from."""
231-
return self._formatter.dump(ecfg, default_values, templ_const, templ_val)
231+
return self._formatter.dump(ecfg, default_values, templ_const, templ_val,
232+
toolchain_hierarchy=toolchain_hierarchy)

test/framework/easyconfig.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1816,6 +1816,59 @@ def test_dump(self):
18161816
if param in ec:
18171817
self.assertEqual(ec[param], dumped_ec[param])
18181818

1819+
def test_toolchain_hierarchy_aware_dump(self):
1820+
"""Test that EasyConfig's dump() method is aware of the toolchain hierarchy."""
1821+
test_ecs_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs')
1822+
build_options = {
1823+
'check_osdeps': False,
1824+
'robot_path': [test_ecs_dir],
1825+
'valid_module_classes': module_classes(),
1826+
}
1827+
init_config(build_options=build_options)
1828+
rawtxt = '\n'.join([
1829+
"easyblock = 'EB_foo'",
1830+
'',
1831+
"name = 'foo'",
1832+
"version = '0.0.1'",
1833+
'',
1834+
"toolchain = {'name': 'foss', 'version': '2018a'}",
1835+
'',
1836+
"homepage = 'http://foo.com/'",
1837+
'description = "foo description"',
1838+
'',
1839+
'sources = [SOURCE_TAR_GZ]',
1840+
'source_urls = ["http://example.com"]',
1841+
'checksums = ["6af6ab95ce131c2dd467d2ebc8270e9c265cc32496210b069e51d3749f335f3d"]',
1842+
'',
1843+
"dependencies = [",
1844+
" ('toy', '0.0', '', ('gompi', '2018a')),",
1845+
" ('bar', '1.0'),",
1846+
" ('foobar/1.2.3', EXTERNAL_MODULE),",
1847+
"]",
1848+
'',
1849+
"foo_extra1 = 'foobar'",
1850+
'',
1851+
'moduleclass = "tools"',
1852+
])
1853+
1854+
test_ec = os.path.join(self.test_prefix, 'test.eb')
1855+
ec = EasyConfig(None, rawtxt=rawtxt)
1856+
ecdict = ec.asdict()
1857+
ec.dump(test_ec)
1858+
# dict representation of EasyConfig instance should not change after dump
1859+
self.assertEqual(ecdict, ec.asdict())
1860+
ectxt = read_file(test_ec)
1861+
dumped_ec = EasyConfig(test_ec)
1862+
self.assertEqual(ecdict, dumped_ec.asdict())
1863+
self.assertTrue(r"'toy', '0.0')," in ectxt)
1864+
# test case where we ask for explicit toolchains
1865+
ec.dump(test_ec, explicit_toolchains=True)
1866+
self.assertEqual(ecdict, ec.asdict())
1867+
ectxt = read_file(test_ec)
1868+
dumped_ec = EasyConfig(test_ec)
1869+
self.assertEqual(ecdict, dumped_ec.asdict())
1870+
self.assertTrue(r"'toy', '0.0', '', ('gompi', '2018a'))," in ectxt)
1871+
18191872
def test_dump_order(self):
18201873
"""Test order of easyconfig parameters in dumped easyconfig."""
18211874
rawtxt = '\n'.join([

0 commit comments

Comments
 (0)