From 35fdf6cde5669cb3c9bb90c89c715bfde4da5d84 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Fri, 5 Aug 2022 12:40:19 +0200 Subject: [PATCH 1/9] Include 'sources' key to entries in list returned by `collect_exts_file_info` The returned dict per extension has a 'patches' key but no 'sources'. Add this incoorporating `source_tmpl` or the default value. Add test for that and also for `nosource: True` and `sources` being a list. --- easybuild/framework/easyblock.py | 6 ++++ test/framework/easyblock.py | 47 ++++++++++++++++++++++++++++---- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 20d69ac066..e30fb2feae 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -725,6 +725,11 @@ def collect_exts_file_info(self, fetch_files=True, verify_checksums=True): # copy 'path' entry to 'src' for use with extensions 'src': src['path'], }) + filename = src['name'] + else: + filename = source.get('filename') + if filename is not None: + ext_src['sources'] = [filename] else: @@ -738,6 +743,7 @@ def collect_exts_file_info(self, fetch_files=True, verify_checksums=True): raise EasyBuildError(error_msg, type(src_fn).__name__, src_fn) src_fn = resolve_template(src_fn, template_values) + ext_src['sources'] = [src_fn] if fetch_files: src_path = self.obtain_file(src_fn, extension=True, urls=source_urls, diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index 35af52cbcc..f55883c100 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -2350,17 +2350,25 @@ def test_collect_exts_file_info(self): toy_sources = os.path.join(testdir, 'sandbox', 'sources', 'toy') toy_ext_sources = os.path.join(toy_sources, 'extensions') toy_ec_file = os.path.join(testdir, 'easyconfigs', 'test_ecs', 't', 'toy', 'toy-0.0-gompi-2018a-test.eb') - toy_ec = process_easyconfig(toy_ec_file)[0] + + test_ec = os.path.join(self.test_prefix, 'test.eb') + new_ext_txt = "('baz', '0.0', {'nosource': True})," # With nosource option + new_ext_txt += "('barbar', '0.0', {'sources': [SOURCE_TAR_GZ]})," # With sources containing a list + test_ectxt = re.sub(r'\(name, version', new_ext_txt+r"\g<0>", read_file(toy_ec_file)) + write_file(test_ec, test_ectxt) + + toy_ec = process_easyconfig(test_ec)[0] toy_eb = EasyBlock(toy_ec['ec']) exts_file_info = toy_eb.collect_exts_file_info() self.assertIsInstance(exts_file_info, list) - self.assertEqual(len(exts_file_info), 4) + self.assertEqual(len(exts_file_info), 6) self.assertEqual(exts_file_info[0], {'name': 'ulimit'}) self.assertEqual(exts_file_info[1]['name'], 'bar') + self.assertEqual(exts_file_info[1]['sources'], ['bar-0.0.tar.gz']) self.assertEqual(exts_file_info[1]['src'], os.path.join(toy_ext_sources, 'bar-0.0.tar.gz')) bar_patch1 = 'bar-0.0_fix-silly-typo-in-printf-statement.patch' self.assertEqual(exts_file_info[1]['patches'][0]['name'], bar_patch1) @@ -2370,22 +2378,36 @@ def test_collect_exts_file_info(self): self.assertEqual(exts_file_info[1]['patches'][1]['path'], os.path.join(toy_ext_sources, bar_patch2)) self.assertEqual(exts_file_info[2]['name'], 'barbar') + self.assertEqual(exts_file_info[2]['sources'], ['barbar-1.2.tar.gz']) self.assertEqual(exts_file_info[2]['src'], os.path.join(toy_ext_sources, 'barbar-1.2.tar.gz')) self.assertNotIn('patches', exts_file_info[2]) - self.assertEqual(exts_file_info[3]['name'], 'toy') - self.assertEqual(exts_file_info[3]['src'], os.path.join(toy_sources, 'toy-0.0.tar.gz')) + self.assertEqual(exts_file_info[3]['name'], 'baz') + self.assertNotIn('sources', exts_file_info[3]) + self.assertNotIn('sources', exts_file_info[3]['options']) + self.assertNotIn('src', exts_file_info[3]) self.assertNotIn('patches', exts_file_info[3]) + self.assertEqual(exts_file_info[4]['name'], 'barbar') + self.assertEqual(exts_file_info[4]['sources'], ['barbar-0.0.tar.gz']) + self.assertEqual(exts_file_info[4]['src'], os.path.join(toy_ext_sources, 'barbar-0.0.tar.gz')) + self.assertNotIn('patches', exts_file_info[4]) + + self.assertEqual(exts_file_info[5]['name'], 'toy') + self.assertEqual(exts_file_info[5]['sources'], ['toy-0.0.tar.gz']) + self.assertEqual(exts_file_info[5]['src'], os.path.join(toy_sources, 'toy-0.0.tar.gz')) + self.assertNotIn('patches', exts_file_info[5]) + # location of files is missing when fetch_files is set to False exts_file_info = toy_eb.collect_exts_file_info(fetch_files=False, verify_checksums=False) self.assertIsInstance(exts_file_info, list) - self.assertEqual(len(exts_file_info), 4) + self.assertEqual(len(exts_file_info), 6) self.assertEqual(exts_file_info[0], {'name': 'ulimit'}) self.assertEqual(exts_file_info[1]['name'], 'bar') + self.assertEqual(exts_file_info[1]['sources'], ['bar-0.0.tar.gz']) self.assertNotIn('src', exts_file_info[1]) self.assertEqual(exts_file_info[1]['patches'][0]['name'], bar_patch1) self.assertNotIn('path', exts_file_info[1]['patches'][0]) @@ -2393,13 +2415,26 @@ def test_collect_exts_file_info(self): self.assertNotIn('path', exts_file_info[1]['patches'][1]) self.assertEqual(exts_file_info[2]['name'], 'barbar') + self.assertEqual(exts_file_info[2]['sources'], ['barbar-1.2.tar.gz']) self.assertNotIn('src', exts_file_info[2]) self.assertNotIn('patches', exts_file_info[2]) - self.assertEqual(exts_file_info[3]['name'], 'toy') + self.assertEqual(exts_file_info[3]['name'], 'baz') + self.assertNotIn('sources', exts_file_info[3]) + self.assertNotIn('sources', exts_file_info[3]['options']) self.assertNotIn('src', exts_file_info[3]) self.assertNotIn('patches', exts_file_info[3]) + self.assertEqual(exts_file_info[4]['name'], 'barbar') + self.assertEqual(exts_file_info[4]['sources'], ['barbar-0.0.tar.gz']) + self.assertNotIn('src', exts_file_info[4]) + self.assertNotIn('patches', exts_file_info[4]) + + self.assertEqual(exts_file_info[5]['name'], 'toy') + self.assertEqual(exts_file_info[5]['sources'], ['toy-0.0.tar.gz']) + self.assertNotIn('src', exts_file_info[5]) + self.assertNotIn('patches', exts_file_info[5]) + error_msg = "Can't verify checksums for extension files if they are not being fetched" self.assertErrorRegex(EasyBuildError, error_msg, toy_eb.collect_exts_file_info, fetch_files=False) From 9497665b7e2bcbf7614a326faa42e5ac9140a659 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Wed, 8 Oct 2025 14:33:19 +0200 Subject: [PATCH 2/9] Use chain.from_iterable --- easybuild/framework/easyblock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 20d69ac066..aaf4bc68f4 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -593,7 +593,7 @@ def fetch_patches(self, patch_specs=None, extension=False, checksums=None): if not isinstance(patch_specs, tuple) or len(patch_specs) != 2: raise EasyBuildError('Patch specs must be a tuple of (patches, post-install patches) or a list') post_install_patches = patch_specs[1] - patch_specs = itertools.chain(*patch_specs) + patch_specs = itertools.chain.from_iterable(patch_specs) patches = [] for index, patch_spec in enumerate(patch_specs): @@ -781,7 +781,7 @@ def collect_exts_file_info(self, fetch_files=True, verify_checksums=True): if fetch_files: ext_patches = self.fetch_patches(patch_specs=ext_patch_specs, extension=True) else: - ext_patches = [create_patch_info(p) for p in itertools.chain(*ext_patch_specs)] + ext_patches = [create_patch_info(p) for p in itertools.chain.from_iterable(ext_patch_specs)] if ext_patches: self.log.debug('Found patches for extension %s: %s', ext_name, ext_patches) From cee6e58ab772744d383cd54b20e3b44ef7e4f7e9 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Wed, 8 Oct 2025 16:13:53 +0200 Subject: [PATCH 3/9] Use template-disable decorator instead of repeated `get_ref` --- easybuild/framework/easyblock.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index aaf4bc68f4..a16a1183fc 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -2748,10 +2748,11 @@ def check_checksums_for(self, ent, sub='', source_cnt=None): checksums = ent.get('checksums', []) except EasyBuildError: if isinstance(ent, EasyConfig): - sources = ent.get_ref('sources') - data_sources = ent.get_ref('data_sources') - patches = ent.get_ref('patches') + ent.get_ref('postinstallpatches') - checksums = ent.get_ref('checksums') + with ent.disable_templating(): + sources = ent['sources'] + data_sources = ent['data_sources'] + patches = ent['patches'] + ent['postinstallpatches'] + checksums = ent['checksums'] # Single source should be re-wrapped as a list, and checksums with it if isinstance(sources, dict): From def93c3123dfda73dbdaf4590c61a5189201497f Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Wed, 8 Oct 2025 12:16:18 +0200 Subject: [PATCH 4/9] Fix `check_checksums` to handle patches specified as dicts In `sources` the keyname is "filename" while in `patches` it is "name". --- easybuild/framework/easyblock.py | 35 +++++++++++++++---------- test/framework/easyblock.py | 45 +++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 15 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index a16a1183fc..5867e8fcc6 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -42,8 +42,10 @@ * Caspar van Leeuwen (SURF) * Jan Andre Reuter (Juelich Supercomputing Centre) * Jasper Grimm (UoY) +* Alexander Grund (TU Dresden) """ import concurrent +import contextlib import copy import functools import glob @@ -2762,25 +2764,30 @@ def check_checksums_for(self, ent, sub='', source_cnt=None): if isinstance(checksums, str): checksums = [checksums] - sources = sources + data_sources + def get_name(fn, key): + # if the filename is a tuple, the actual source file name is the first element + if isinstance(fn, tuple): + fn = fn[0] + # if the filename is a dict, the actual source file name is inside + if isinstance(fn, dict): + fn = fn[key] + return fn - if not checksums: + sources = [get_name(src, 'filename') for src in itertools.chain(sources, data_sources)] + patches = [get_name(patch, 'name') for patch in patches] + + if source_cnt is None: + source_cnt = len(sources) + patch_cnt = len(patches) + + if not checksums and (source_cnt + patch_cnt) > 0: checksums_from_json = self.get_checksums_from_json() # recreate a list of checksums. If each filename is found, the generated list of checksums should match # what is expected in list format - for fn in sources + patches: - # if the filename is a tuple, the actual source file name is the first element - if isinstance(fn, tuple): - fn = fn[0] - # if the filename is a dict, the actual source file name is the "filename" element - if isinstance(fn, dict): - fn = fn["filename"] - if fn in checksums_from_json.keys(): - checksums += [checksums_from_json[fn]] + with contextlib.suppress(KeyError): + checksums.extend(checksums_from_json[fn] for fn in sources + patches) - if source_cnt is None: - source_cnt = len(sources) - patch_cnt, checksum_cnt = len(patches), len(checksums) + checksum_cnt = len(checksums) if (source_cnt + patch_cnt) != checksum_cnt: if sub: diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index 35af52cbcc..ea58de8731 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -3431,9 +3431,52 @@ def run_checks(): eb.json_checksums = None self.assertEqual(eb.check_checksums(), []) + self.contents = textwrap.dedent(""" + easyblock = "ConfigureMake" + name = "Uniq_1" + version = "3" + homepage = "http://example.com" + description = "test" + toolchain = SYSTEM + # Different ways of specifying sources, patches, template usages to make sure they are resolved correctly + exts_list = [ + 'ulimit', # extension that is part of "standard library" + ('ext1', '0.0'), # Default source filename + ('ext2', '1.2', { + 'source_tmpl': "%(name)s.zip", + 'patches': ['%(name)s.patch'], + }), + ('ext3', '1.2', { + 'sources': "%(name)s.zip", + 'postinstallpatches': ['%(name)s.patch'], + }), + ('ext-%(namelower)s', version + '.14', { + 'sources': {'filename': '%(name)s-%(version)s.zip', 'download_filename': 'foo.tar'}, + 'patches': [{'name': '%(name)s.patch'}], + }), + ('ext-ok1', version, { + 'checksums': '44332000aa33b99ad1e00cbd1a7da769220d74647060a10e807b916d73ea27bc' + }), + ('ext-ok2', version, { + 'data_sources': {'filename': '%(name)s-%(version)s.zip', 'download_filename': 'bar.tar'}, + 'checksums': '44332000aa33b99ad1e00cbd1a7da769220d74647060a10e807b916d73ea27bc' + }), + ('ext-ok3', version, { + 'nosource': True + }), + ] + """) + self.writeEC() + eb = EasyBlock(EasyConfig(self.eb_file)) + res = eb.check_checksums() + self.assertEqual(len(res), 4) + extensions = ['ext1', 'ext2', 'ext3', 'ext-uniq_1'] + for ext, line in zip(extensions, res): + self.assertIn(ext_error_tmpl % ext, line) + # more checks for check_checksums_for method, which also takes regular dict as input self.assertEqual(eb.check_checksums_for({}), []) - expected = "Checksums missing for one or more sources/patches in test.eb: " + expected = f"Checksums missing for one or more sources/patches in {os.path.basename(self.eb_file)}: " expected += "found 1 sources + 0 patches vs 0 checksums" self.assertEqual(eb.check_checksums_for({'sources': ['test.tar.gz']}), [expected]) From 0ada96ef9724aa8b56de11ada8b556ae1c016bf8 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Wed, 8 Oct 2025 10:22:36 +0200 Subject: [PATCH 5/9] Add test for `check_checksums` using template in extension --- test/framework/easyblock.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index ea58de8731..9c696fadee 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -37,6 +37,7 @@ import shutil import sys import tempfile +import textwrap from inspect import cleandoc from test.framework.github import requires_github_access from test.framework.utilities import EnhancedTestCase, TestLoaderFiltered, init_config @@ -3337,6 +3338,8 @@ def run_checks(): self.assertEqual(res[0], expected) self.assertTrue(res[1].startswith("Non-SHA256 checksum(s) found for toy-0.0.tar.gz:")) + ext_error_tmpl = "Checksums missing for one or more sources/patches of extension %s in " + # check for main sources/patches should reveal two issues with checksums res = eb.check_checksums_for(eb.cfg) self.assertEqual(len(res), 2) @@ -3347,11 +3350,8 @@ def run_checks(): self.assertEqual(len(res), 4) run_checks() - idx = 2 - for ext in ['bar', 'barbar']: - expected = "Checksums missing for one or more sources/patches of extension %s in " % ext - self.assertTrue(res[idx].startswith(expected)) - idx += 1 + for ext, line in zip(('bar', 'barbar'), res[2:]): + self.assertIn(ext_error_tmpl % ext, line) # check whether tuple of alternative SHA256 checksums is correctly recognized toy_ec = os.path.join(testdir, 'easyconfigs', 'test_ecs', 't', 'toy', 'toy-0.0.eb') @@ -3414,6 +3414,24 @@ def run_checks(): )] self.assertEqual(eb.check_checksums(), []) + self.contents = textwrap.dedent(""" + easyblock = "ConfigureMake" + name = "Uniq_1" + version = "3.14" + homepage = "http://example.com" + description = "test" + toolchain = SYSTEM + # Templates of parent used in extensions + exts_list = [ + ('%(namelower)s', version), + ] + """) + self.writeEC() + eb = EasyBlock(EasyConfig(self.eb_file)) + res = eb.check_checksums() + self.assertEqual(len(res), 1) + self.assertIn(ext_error_tmpl % 'uniq_1', res[0]) + # no checksums in easyconfig, then picked up from checksums.json next to easyconfig file test_ec = os.path.join(self.test_prefix, 'test.eb') copy_file(toy_ec, test_ec) @@ -3426,7 +3444,7 @@ def run_checks(): expected += "found 1 sources + 2 patches vs 0 checksums" self.assertEqual(res[0], expected) - # all is fine is checksums.json is also copied + # all is fine if checksums.json is also copied copy_file(os.path.join(os.path.dirname(toy_ec), 'checksums.json'), self.test_prefix) eb.json_checksums = None self.assertEqual(eb.check_checksums(), []) From ff560f4b9c1d4e51e8617ea84872ec7f7d398b51 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Wed, 8 Oct 2025 10:23:10 +0200 Subject: [PATCH 6/9] Revert "don't require that all templates can be resolved for 'exts_list' easyconfig parameter value in EasyBlock.check_checksums" This reverts commit 7112e406dc8ee75fd3d5257648d23d801d77e813. --- easybuild/framework/easyblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 5867e8fcc6..88bfaaad41 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -2843,7 +2843,7 @@ def check_checksums(self): checksum_issues.extend(self.check_checksums_for(self.cfg)) # also check checksums for extensions - for ext in self.cfg.get_ref('exts_list'): + for ext in self.cfg['exts_list']: # just skip extensions for which only a name is specified # those are just there to check for things that are in the "standard library" if not isinstance(ext, str): From 78a6392b43f3d978a7a0ca9d9da43116201b73c4 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Wed, 8 Oct 2025 10:28:53 +0200 Subject: [PATCH 7/9] Fix `check_checksums` when using templates in extensions --- easybuild/framework/easyblock.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 88bfaaad41..3c04242de0 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -2843,7 +2843,9 @@ def check_checksums(self): checksum_issues.extend(self.check_checksums_for(self.cfg)) # also check checksums for extensions - for ext in self.cfg['exts_list']: + with self.cfg.allow_unresolved_templates(): + exts_list = self.cfg['exts_list'] + for ext in exts_list: # just skip extensions for which only a name is specified # those are just there to check for things that are in the "standard library" if not isinstance(ext, str): From 050cdb2acd41b032ef40f6b4517ebd5becad267a Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Wed, 8 Oct 2025 13:36:43 +0200 Subject: [PATCH 8/9] Fix `check_checksums` when checksums are specified in JSON file This requires correctly resolving the templates in `exts_list` sources/patches to be able to fetch them from the JSON file. --- easybuild/framework/easyblock.py | 16 +++------------- test/framework/easyblock.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 3c04242de0..fa6f7b9778 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -2843,19 +2843,9 @@ def check_checksums(self): checksum_issues.extend(self.check_checksums_for(self.cfg)) # also check checksums for extensions - with self.cfg.allow_unresolved_templates(): - exts_list = self.cfg['exts_list'] - for ext in exts_list: - # just skip extensions for which only a name is specified - # those are just there to check for things that are in the "standard library" - if not isinstance(ext, str): - ext_name = ext[0] - # take into account that extension may be a 2-tuple with just name/version - ext_opts = ext[2] if len(ext) == 3 else {} - # only a single source per extension is supported (see source_tmpl) - source_cnt = 1 if not ext_opts.get('nosource') else 0 - res = self.check_checksums_for(ext_opts, sub="of extension %s" % ext_name, source_cnt=source_cnt) - checksum_issues.extend(res) + for ext in self.collect_exts_file_info(fetch_files=False, verify_checksums=False): + res = self.check_checksums_for(ext, sub=f"of extension {ext['name']}") + checksum_issues.extend(res) return checksum_issues diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index 9c696fadee..520a97c66b 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -29,9 +29,11 @@ @author: Kenneth Hoste (Ghent University) @author: Maxime Boissonneault (Compute Canada) @author: Jan Andre Reuter (Juelich Supercomputing Centre) +@author: Alexander Grund (TU Dresden) """ import copy import fileinput +import itertools import os import re import shutil @@ -3346,6 +3348,7 @@ def run_checks(): run_checks() # full check also catches checksum issues with extensions + eb.json_checksums = {} # Avoid picking up checksums from JSON file res = eb.check_checksums() self.assertEqual(len(res), 4) run_checks() @@ -3482,6 +3485,12 @@ def run_checks(): ('ext-ok3', version, { 'nosource': True }), + ('ext-ok1', version, { + 'checksums': ['44332000aa33b99ad1e00cbd1a7da769220d74647060a10e807b916d73ea27bc'] + }), + ('ext-ok2', version, { + 'nosource': True + }), ] """) self.writeEC() @@ -3492,6 +3501,27 @@ def run_checks(): for ext, line in zip(extensions, res): self.assertIn(ext_error_tmpl % ext, line) + # Gradually add checksums to JSON dict and test that the associated extension checksums are now fine + sha256_cs = '81a3accc894592152f81814fbf133d39afad52885ab52c25018722c7bda92487' # Valid format only + eb.json_checksums = {'ext1-0.0.tar.gz': sha256_cs} + for ext, line in itertools.zip_longest(extensions[1:], eb.check_checksums(), fillvalue=''): + self.assertIn(ext_error_tmpl % ext, line) + eb.json_checksums['ext2.zip'] = sha256_cs + for ext, line in itertools.zip_longest(extensions[1:], eb.check_checksums(), fillvalue=''): + self.assertIn(ext_error_tmpl % ext, line) + eb.json_checksums['ext2.patch'] = sha256_cs + for ext, line in itertools.zip_longest(extensions[2:], eb.check_checksums(), fillvalue=''): + self.assertIn(ext_error_tmpl % ext, line) + eb.json_checksums['ext3.zip'] = sha256_cs + for ext, line in itertools.zip_longest(extensions[2:], eb.check_checksums(), fillvalue=''): + self.assertIn(ext_error_tmpl % ext, line) + eb.json_checksums['ext3.patch'] = sha256_cs + for ext, line in itertools.zip_longest(extensions[3:], eb.check_checksums(), fillvalue=''): + self.assertIn(ext_error_tmpl % ext, line) + eb.json_checksums['ext-uniq_1-3.14.zip'] = sha256_cs + eb.json_checksums['ext-uniq_1.patch'] = sha256_cs + self.assertEqual(eb.check_checksums(), []) + # more checks for check_checksums_for method, which also takes regular dict as input self.assertEqual(eb.check_checksums_for({}), []) expected = f"Checksums missing for one or more sources/patches in {os.path.basename(self.eb_file)}: " From f037b850805c1cdf32f40c6f24b2f592109a23a0 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Wed, 8 Oct 2025 14:32:49 +0200 Subject: [PATCH 9/9] Store checksums in extension_info --- easybuild/framework/easyblock.py | 1 + test/framework/easyblock.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index fa6f7b9778..a5cfccaa78 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -685,6 +685,7 @@ def collect_exts_file_info(self, fetch_files=True, verify_checksums=True): source_urls = resolve_template(ext_options.get('source_urls', []), template_values) checksums = resolve_template(ext_options.get('checksums', []), template_values) + ext_src['checksums'] = checksums download_instructions = resolve_template(ext_options.get('download_instructions'), template_values) diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index 520a97c66b..c150102eb5 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -2371,14 +2371,17 @@ def test_collect_exts_file_info(self): bar_patch2 = 'bar-0.0_fix-very-silly-typo-in-printf-statement.patch' self.assertEqual(exts_file_info[1]['patches'][1]['name'], bar_patch2) self.assertEqual(exts_file_info[1]['patches'][1]['path'], os.path.join(toy_ext_sources, bar_patch2)) + self.assertEqual(len(exts_file_info[1]['checksums']), 1) self.assertEqual(exts_file_info[2]['name'], 'barbar') self.assertEqual(exts_file_info[2]['src'], os.path.join(toy_ext_sources, 'barbar-1.2.tar.gz')) self.assertNotIn('patches', exts_file_info[2]) + self.assertEqual(len(exts_file_info[2]['checksums']), 0) self.assertEqual(exts_file_info[3]['name'], 'toy') self.assertEqual(exts_file_info[3]['src'], os.path.join(toy_sources, 'toy-0.0.tar.gz')) self.assertNotIn('patches', exts_file_info[3]) + self.assertEqual(len(exts_file_info[3]['checksums']), 1) # location of files is missing when fetch_files is set to False exts_file_info = toy_eb.collect_exts_file_info(fetch_files=False, verify_checksums=False)