Skip to content

Make sure cached ECs don't get modifed #4818

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions easybuild/framework/easyconfig/easyconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -2207,6 +2207,22 @@ def resolve_template(value, tmpl_dict, expect_resolved=True):
return value


def _copy_ec_dict(easyconfig):
"""Copy an easyconfig dict as (initially) parsed"""
ec = easyconfig.pop('ec') # Don't copy this via deepcopy
try:
new_easyconfig = copy.deepcopy(easyconfig) # Copy the rest of the dict
finally:
easyconfig['ec'] = ec # Put back
new_easyconfig['ec'] = ec.copy()
return new_easyconfig


def _copy_ec_dicts(easyconfigs):
"""Copy list of easyconfig dicts as (initially) parsed"""
return [_copy_ec_dict(ec) for ec in easyconfigs]


def process_easyconfig(path, build_specs=None, validate=True, parse_only=False, hidden=None):
"""
Process easyconfig, returning some information for each block
Expand All @@ -2226,7 +2242,7 @@ def process_easyconfig(path, build_specs=None, validate=True, parse_only=False,
if not build_specs:
cache_key = (path, validate, hidden, parse_only)
if cache_key in _easyconfigs_cache:
return [e.copy() for e in _easyconfigs_cache[cache_key]]
return _copy_ec_dicts(_easyconfigs_cache[cache_key])

easyconfigs = []
for spec in blocks:
Expand Down Expand Up @@ -2281,7 +2297,7 @@ def process_easyconfig(path, build_specs=None, validate=True, parse_only=False,
easyconfig['dependencies'].append(tc)

if cache_key is not None:
_easyconfigs_cache[cache_key] = [e.copy() for e in easyconfigs]
_easyconfigs_cache[cache_key] = _copy_ec_dicts(easyconfigs)

return easyconfigs

Expand Down
18 changes: 18 additions & 0 deletions test/framework/easyconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -5187,6 +5187,24 @@ def test_easyconfigs_caches(self):
self.assertIsInstance(ec2['ec'].toolchain, SystemToolchain)
self.assertTrue(os.path.samefile(ec2['ec'].path, toy_ec))

# Returned easyconfigs are independent as-if there was no caching
ec3 = process_easyconfig(toy_ec)[0]
ec3['ec']['name'] = 'newname'
ec3['ec']['version'] = '99.1234'
ec3['spec'] = 'non-existing.eb'
ec3['dependencies'].append('Dummy')
self.assertEqual(ec3['ec'].name, 'newname')
self.assertEqual(ec3['ec'].version, '99.1234')
self.assertEqual(ec3['spec'], 'non-existing.eb')
self.assertEqual(ec3['dependencies'], ['Dummy'])
# Neither the previously returned nor newly requested ECs are modified by the above
ec2_2 = process_easyconfig(toy_ec)[0]
for orig_ec in (ec2, ec2_2):
self.assertEqual(orig_ec['ec'].name, 'toy')
self.assertEqual(orig_ec['ec'].version, '0.0')
self.assertEqual(orig_ec['spec'], toy_ec)
self.assertEqual(orig_ec['dependencies'], [])

# also check whether easyconfigs cache works with end-to-end test
args = [libtoy_ec, '--trace']
self.mock_stdout(True)
Expand Down
11 changes: 7 additions & 4 deletions test/framework/toy_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -3234,7 +3234,7 @@ def start_hook():
print('start hook triggered')

def parse_hook(ec):
print('%s %s' % (ec.name, ec.version))
print('Parse Hook %s %s' % (ec.name, ec.version))
# print sources value to check that raw untemplated strings are exposed in parse_hook
print(ec['sources'])
# try appending to postinstallcmd to see whether the modification is actually picked up
Expand Down Expand Up @@ -3319,7 +3319,10 @@ def post_run_shell_cmd_hook(cmd, *args, **kwargs):
# - for devel module file
expected_output = textwrap.dedent(f"""
start hook triggered
toy 0.0
Parse Hook toy 0.0
['%(name)s-%(version)s.tar.gz']
echo toy
Parse Hook toy 0.0
['%(name)s-%(version)s.tar.gz']
echo toy
pre-configure: toy.source: True
Expand All @@ -3329,10 +3332,10 @@ def post_run_shell_cmd_hook(cmd, *args, **kwargs):
in post-install hook for toy v0.0
bin, lib
in module-write hook hook for {mod_name}
toy 0.0
Parse Hook toy 0.0
['%(name)s-%(version)s.tar.gz']
echo toy
toy 0.0
Parse Hook toy 0.0
['%(name)s-%(version)s.tar.gz']
echo toy
installing of extension bar is done!
Expand Down
Loading