Skip to content

Commit 64bd214

Browse files
authored
Merge pull request #4724 from boegel/5.0.x
sync with develop (20241218) + version bump to `5.0.0beta1`
2 parents 7666088 + a3e31e6 commit 64bd214

File tree

10 files changed

+189
-23
lines changed

10 files changed

+189
-23
lines changed

easybuild/framework/easyblock.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3187,21 +3187,24 @@ def post_processing_step(self):
31873187
)
31883188
return self.post_install_step()
31893189

3190+
def _dispatch_sanity_check_step(self, *args, **kwargs):
3191+
"""Decide whether to run the dry-run or the real version of the sanity-check step"""
3192+
if self.dry_run:
3193+
self._sanity_check_step_dry_run(*args, **kwargs)
3194+
else:
3195+
self._sanity_check_step(*args, **kwargs)
3196+
31903197
def sanity_check_step(self, *args, **kwargs):
31913198
"""
31923199
Do a sanity check on the installation
31933200
- if *any* of the files/subdirectories in the installation directory listed
31943201
in sanity_check_paths are non-existent (or empty), the sanity check fails
31953202
"""
3196-
if self.dry_run:
3197-
self._sanity_check_step_dry_run(*args, **kwargs)
3198-
31993203
# handling of extensions that were installed for multiple dependency versions is done in ExtensionEasyBlock
3200-
elif self.cfg['multi_deps'] and not self.is_extension:
3204+
if self.cfg['multi_deps'] and not self.is_extension:
32013205
self._sanity_check_step_multi_deps(*args, **kwargs)
3202-
32033206
else:
3204-
self._sanity_check_step(*args, **kwargs)
3207+
self._dispatch_sanity_check_step(*args, **kwargs)
32053208

32063209
def _sanity_check_step_multi_deps(self, *args, **kwargs):
32073210
"""Perform sanity check for installations that iterate over a list a versions for particular dependencies."""
@@ -3233,7 +3236,7 @@ def _sanity_check_step_multi_deps(self, *args, **kwargs):
32333236
self.log.info(info_msg)
32343237

32353238
kwargs['extra_modules'] = extra_modules
3236-
self._sanity_check_step(*args, **kwargs)
3239+
self._dispatch_sanity_check_step(*args, **kwargs)
32373240

32383241
# restore list of lists of build dependencies & stop iterating again
32393242
self.cfg['builddependencies'] = builddeps
@@ -3509,7 +3512,7 @@ def _sanity_check_step_common(self, custom_paths, custom_commands):
35093512
# if no sanity_check_paths are specified in easyconfig,
35103513
# we fall back to the ones provided by the easyblock via custom_paths
35113514
if custom_paths:
3512-
paths = custom_paths
3515+
paths = self.cfg.resolve_template(custom_paths)
35133516
self.log.info("Using customized sanity check paths: %s", paths)
35143517
# if custom_paths is empty, we fall back to a generic set of paths:
35153518
# non-empty bin/ + /lib or /lib64 directories
@@ -3523,14 +3526,13 @@ def _sanity_check_step_common(self, custom_paths, custom_commands):
35233526
# if enhance_sanity_check is enabled *and* sanity_check_paths are specified in the easyconfig,
35243527
# those paths are used to enhance the paths provided by the easyblock
35253528
if enhance_sanity_check and ec_paths:
3526-
for key in ec_paths:
3527-
val = ec_paths[key]
3529+
for key, val in ec_paths.items():
35283530
if isinstance(val, list):
35293531
paths[key] = paths.get(key, []) + val
35303532
else:
3531-
error_pattern = "Incorrect value type in sanity_check_paths, should be a list: "
3532-
error_pattern += "%s (type: %s)" % (val, type(val))
3533-
raise EasyBuildError(error_pattern)
3533+
error_msg = "Incorrect value type in sanity_check_paths, should be a list: "
3534+
error_msg += "%s (type: %s)" % (val, type(val))
3535+
raise EasyBuildError(error_msg)
35343536
self.log.info("Enhanced sanity check paths after taking into account easyconfig file: %s", paths)
35353537

35363538
sorted_keys = sorted(paths.keys())
@@ -3560,7 +3562,7 @@ def _sanity_check_step_common(self, custom_paths, custom_commands):
35603562
self.log.info("Using (only) sanity check commands specified by easyconfig file: %s", commands)
35613563
else:
35623564
if custom_commands:
3563-
commands = custom_commands
3565+
commands = self.cfg.resolve_template(custom_commands)
35643566
self.log.info("Using customised sanity check commands: %s", commands)
35653567
else:
35663568
commands = []

easybuild/framework/easyconfig/easyconfig.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1769,6 +1769,12 @@ def _generate_template_values(self, ignore=None):
17691769
if self.template_values[key] is None:
17701770
del self.template_values[key]
17711771

1772+
def resolve_template(self, value):
1773+
"""Resolve all templates in the given value using this easyconfig"""
1774+
if not self.template_values:
1775+
self.generate_template_values()
1776+
return resolve_template(value, self.template_values)
1777+
17721778
@handle_deprecated_or_replaced_easyconfig_parameters
17731779
def __contains__(self, key):
17741780
"""Check whether easyconfig parameter is defined"""
@@ -1784,9 +1790,7 @@ def __getitem__(self, key):
17841790
raise EasyBuildError("Use of unknown easyconfig parameter '%s' when getting parameter value", key)
17851791

17861792
if self.enable_templating:
1787-
if self.template_values is None or len(self.template_values) == 0:
1788-
self.generate_template_values()
1789-
value = resolve_template(value, self.template_values)
1793+
value = self.resolve_template(value)
17901794

17911795
return value
17921796

easybuild/tools/include.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,8 @@ def include_easyblocks(tmpdir, paths):
198198

199199
# hard inject location to included (generic) easyblocks into Python search path
200200
# only prepending to sys.path is not enough due to 'pkgutil.extend_path' in easybuild/easyblocks/__init__.py
201-
new_path = os.path.join(easyblocks_path, 'easybuild', 'easyblocks')
202-
easybuild.easyblocks.__path__.insert(0, new_path)
203-
new_path = os.path.join(new_path, 'generic')
204-
easybuild.easyblocks.generic.__path__.insert(0, new_path)
201+
easybuild.easyblocks.__path__.insert(0, easyblocks_dir)
202+
easybuild.easyblocks.generic.__path__.insert(0, os.path.join(easyblocks_dir, 'generic'))
205203

206204
# sanity check: verify that included easyblocks can be imported (from expected location)
207205
for subdir, ebs in [('', included_ebs), ('generic', included_generic_ebs)]:

easybuild/tools/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
# recent setuptools versions will *TRANSFORM* something like 'X.Y.Zdev' into 'X.Y.Z.dev0', with a warning like
4646
# UserWarning: Normalizing '2.4.0dev' to '2.4.0.dev0'
4747
# This causes problems further up the dependency chain...
48-
VERSION = LooseVersion('5.0.0.dev0')
48+
VERSION = LooseVersion('5.0.0beta1')
4949
UNKNOWN = 'UNKNOWN'
5050
UNKNOWN_EASYBLOCKS_VERSION = '0.0.UNKNOWN.EASYBLOCKS'
5151

test/framework/easyconfig.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,6 +1279,29 @@ def test_template_constant_import(self):
12791279
self.assertEqual(GNU_SOURCE, TEMPLATE_CONSTANTS['GNU_SOURCE'][0])
12801280
self.assertEqual(SHLIB_EXT, get_shared_lib_ext())
12811281

1282+
def test_ec_method_resolve_template(self):
1283+
"""Test the `resolve_template` method of easyconfig instances."""
1284+
# don't use any escaping insanity here, since it is templated itself
1285+
self.contents = textwrap.dedent("""
1286+
easyblock = "ConfigureMake"
1287+
name = "PI"
1288+
version = "3.14"
1289+
homepage = "http://example.com"
1290+
description = "test easyconfig %(name)s version %(version_major)s"
1291+
toolchain = SYSTEM
1292+
""")
1293+
self.prep()
1294+
ec = EasyConfig(self.eb_file, validate=False)
1295+
1296+
# We can resolve anything with values from the EC
1297+
self.assertEqual(ec.resolve_template('%(namelower)s %(version_major)s begins with %(nameletterlower)s'),
1298+
'pi 3 begins with p')
1299+
1300+
# `resolve_template` does basically the same resolving any value on acccess
1301+
description = ec.get('description', resolve=False)
1302+
self.assertIn('%', description, 'Description needs a template for the next test')
1303+
self.assertEqual(ec.resolve_template(description), ec['description'])
1304+
12821305
def test_templating_cuda_toolchain(self):
12831306
"""Test templates via toolchain component, like setting %(cudaver)s with fosscuda toolchain."""
12841307

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
easyblock = 'ConfigureMake'
2+
3+
name = 'Python'
4+
version = '2.7.15'
5+
6+
homepage = 'http://python.org/'
7+
description = """Python is a programming language that lets you work more quickly and integrate your systems
8+
more effectively."""
9+
10+
toolchain = SYSTEM
11+
12+
source_urls = ['http://www.python.org/ftp/%(namelower)s/%(version)s/']
13+
sources = [SOURCE_TGZ]
14+
15+
# This just serves to have a Python as a dependency to test e.g. the Python version templates
16+
# So all dependencies and extensions are removed
17+
dependencies = []
18+
19+
osdependencies = []
20+
21+
exts_list = []
22+
23+
moduleclass = 'lang'
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
easyblock = 'ConfigureMake'
2+
3+
name = 'Python'
4+
version = '3.7.2'
5+
6+
homepage = 'http://python.org/'
7+
description = """Python is a programming language that lets you work more quickly and integrate your systems
8+
more effectively."""
9+
10+
toolchain = SYSTEM
11+
12+
source_urls = ['http://www.python.org/ftp/%(namelower)s/%(version)s/']
13+
sources = [SOURCE_TGZ]
14+
15+
# This just serves to have a Python as a dependency to test e.g. the Python version templates
16+
# So all dependencies and extensions are removed
17+
dependencies = []
18+
19+
osdependencies = []
20+
21+
exts_list = []
22+
23+
moduleclass = 'lang'

test/framework/filetools.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2545,7 +2545,7 @@ def test_index_functions(self):
25452545
# test with specified path with and without trailing '/'s
25462546
for path in [test_ecs, test_ecs + '/', test_ecs + '//']:
25472547
index = ft.create_index(path)
2548-
self.assertEqual(len(index), 92)
2548+
self.assertEqual(len(index), 94)
25492549

25502550
expected = [
25512551
os.path.join('b', 'bzip2', 'bzip2-1.0.6-GCC-4.9.2.eb'),
21.8 MB
Binary file not shown.

test/framework/toy_build.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2673,6 +2673,99 @@ def test_toy_build_enhanced_sanity_check(self):
26732673

26742674
del sys.modules['easybuild.easyblocks.toy']
26752675

2676+
def test_toy_build_enhanced_sanity_check_templated_multi_dep(self):
2677+
"""Test enhancing of sanity check by easyblocks with templates and in the presence of multi_deps."""
2678+
2679+
# if toy easyblock was imported, get rid of corresponding entry in sys.modules,
2680+
# to avoid that it messes up the use of --include-easyblocks=toy.py below...
2681+
if 'easybuild.easyblocks.toy' in sys.modules:
2682+
del sys.modules['easybuild.easyblocks.toy']
2683+
2684+
test_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)))
2685+
toy_ec = os.path.join(test_dir, 'easyconfigs', 'test_ecs', 't', 'toy', 'toy-0.0.eb')
2686+
toy_ec_txt = read_file(toy_ec)
2687+
2688+
test_ec = os.path.join(self.test_prefix, 'test.eb')
2689+
2690+
# get rid of custom sanity check paths in test easyconfig
2691+
regex = re.compile(r'^sanity_check_paths\s*=\s*{[^}]+}', re.M)
2692+
test_ec_txt = regex.sub('', toy_ec_txt)
2693+
self.assertNotIn('sanity_check_', test_ec_txt)
2694+
2695+
test_ec_txt += "\nmulti_deps = {'Python': ['3.7.2', '2.7.15']}"
2696+
write_file(test_ec, test_ec_txt)
2697+
2698+
# create custom easyblock for toy that has a custom sanity_check_step
2699+
toy_easyblock = os.path.join(test_dir, 'sandbox', 'easybuild', 'easyblocks', 't', 'toy.py')
2700+
2701+
toy_easyblock_txt = read_file(toy_easyblock)
2702+
2703+
toy_custom_sanity_check_step = textwrap.dedent("""
2704+
# Add to class to indent
2705+
def sanity_check_step(self):
2706+
paths = {
2707+
'files': ['bin/python%(pyshortver)s'],
2708+
'dirs': ['lib/py-%(pyshortver)s'],
2709+
}
2710+
cmds = ['python%(pyshortver)s']
2711+
return super(EB_toy, self).sanity_check_step(custom_paths=paths, custom_commands=cmds)
2712+
""")
2713+
test_toy_easyblock = os.path.join(self.test_prefix, 'toy.py')
2714+
write_file(test_toy_easyblock, toy_easyblock_txt + toy_custom_sanity_check_step)
2715+
2716+
eb_args = [
2717+
'--extended-dry-run',
2718+
'--include-easyblocks=%s' % test_toy_easyblock,
2719+
]
2720+
2721+
# by default, sanity check commands & paths specified by easyblock are used
2722+
with self.mocked_stdout_stderr():
2723+
self._test_toy_build(ec_file=test_ec, extra_args=eb_args, verify=False, testing=False, raise_error=True)
2724+
stdout = self.get_stdout()
2725+
# Cut output to start of the toy-ec, after the Python installations
2726+
stdout = stdout[stdout.index(test_ec):]
2727+
2728+
pattern_template = textwrap.dedent(r"""
2729+
Sanity check paths - file.*
2730+
\s*\* bin/python{pyshortver}
2731+
Sanity check paths - \(non-empty\) directory.*
2732+
\s*\* lib/py-{pyshortver}
2733+
Sanity check commands
2734+
\s*\* python{pyshortver}
2735+
""")
2736+
for pyshortver in ('2.7', '3.7'):
2737+
regex = re.compile(pattern_template.format(pyshortver=pyshortver), re.M)
2738+
self.assertTrue(regex.search(stdout), "Pattern '%s' should be found in: %s" % (regex.pattern, stdout))
2739+
2740+
# Enhance sanity check by extra paths to check for, the ones from the easyblock should be kept
2741+
test_ec_txt += textwrap.dedent("""
2742+
enhance_sanity_check = True
2743+
sanity_check_paths = {
2744+
'files': ['bin/pip%(pyshortver)s'],
2745+
'dirs': ['bin'],
2746+
}
2747+
""")
2748+
write_file(test_ec, test_ec_txt)
2749+
with self.mocked_stdout_stderr():
2750+
self._test_toy_build(ec_file=test_ec, extra_args=eb_args, verify=False, testing=False, raise_error=True)
2751+
stdout = self.get_stdout()
2752+
# Cut output to start of the toy-ec, after the Python installations
2753+
stdout = stdout[stdout.index(test_ec):]
2754+
2755+
pattern_template = textwrap.dedent(r"""
2756+
Sanity check paths - file.*
2757+
\s*\* bin/pip{pyshortver}
2758+
\s*\* bin/python{pyshortver}
2759+
Sanity check paths - \(non-empty\) directory.*
2760+
\s*\* bin
2761+
\s*\* lib/py-{pyshortver}
2762+
Sanity check commands
2763+
\s*\* python{pyshortver}
2764+
""")
2765+
for pyshortver in ('2.7', '3.7'):
2766+
regex = re.compile(pattern_template.format(pyshortver=pyshortver), re.M)
2767+
self.assertTrue(regex.search(stdout), "Pattern '%s' should be found in: %s" % (regex.pattern, stdout))
2768+
26762769
def test_toy_dumped_easyconfig(self):
26772770
""" Test dumping of file in eb_filerepo in both .eb format """
26782771
filename = 'toy-0.0'

0 commit comments

Comments
 (0)