Skip to content

Commit 6e68abe

Browse files
authored
Merge pull request #4769 from Flamefire/multi-class-easyblocks
Ignore other classes if software specific easyblock class was found
2 parents c0e5aaa + 8c0e500 commit 6e68abe

File tree

7 files changed

+76
-53
lines changed

7 files changed

+76
-53
lines changed

easybuild/framework/easyconfig/tools.py

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
from easybuild.tools.build_log import EasyBuildError, print_error, print_msg, print_warning
5959
from easybuild.tools.config import build_option
6060
from easybuild.tools.environment import restore_env
61-
from easybuild.tools.filetools import find_easyconfigs, is_patch_file, locate_files
61+
from easybuild.tools.filetools import EASYBLOCK_CLASS_PREFIX, find_easyconfigs, is_patch_file, locate_files
6262
from easybuild.tools.filetools import read_file, resolve_path, which, write_file
6363
from easybuild.tools.github import GITHUB_EASYCONFIGS_REPO
6464
from easybuild.tools.github import det_pr_labels, det_pr_title, download_repo, fetch_easyconfigs_from_commit
@@ -758,7 +758,7 @@ def avail_easyblocks():
758758
"""Return a list of all available easyblocks."""
759759

760760
module_regexp = re.compile(r"^([^_].*)\.py$")
761-
class_regex = re.compile(r"^class ([^(]*)\(", re.M)
761+
class_regex = re.compile(r"^class ([^(:]*)\(", re.M)
762762

763763
# finish initialisation of the toolchain module (ie set the TC_CONSTANT constants)
764764
search_toolchain('')
@@ -768,33 +768,43 @@ def avail_easyblocks():
768768
__import__(pkg)
769769

770770
# determine paths for this package
771-
paths = sys.modules[pkg].__path__
771+
paths = [path for path in sys.modules[pkg].__path__ if os.path.exists(path)]
772772

773773
# import all modules in these paths
774774
for path in paths:
775-
if os.path.exists(path):
776-
for fn in os.listdir(path):
777-
res = module_regexp.match(fn)
778-
if res:
779-
easyblock_mod_name = '%s.%s' % (pkg, res.group(1))
780-
781-
if easyblock_mod_name not in easyblocks:
782-
__import__(easyblock_mod_name)
783-
easyblock_loc = os.path.join(path, fn)
784-
785-
class_names = class_regex.findall(read_file(easyblock_loc))
786-
if len(class_names) == 1:
787-
easyblock_class = class_names[0]
788-
elif class_names:
789-
raise EasyBuildError("Found multiple class names for easyblock %s: %s",
790-
easyblock_loc, class_names)
791-
else:
792-
raise EasyBuildError("Failed to determine easyblock class name for %s", easyblock_loc)
793-
794-
easyblocks[easyblock_mod_name] = {'class': easyblock_class, 'loc': easyblock_loc}
775+
for fn in os.listdir(path):
776+
res = module_regexp.match(fn)
777+
if not res:
778+
continue
779+
easyblock_mod_name = res.group(1)
780+
easyblock_full_mod_name = '%s.%s' % (pkg, easyblock_mod_name)
781+
782+
if easyblock_full_mod_name in easyblocks:
783+
_log.debug("%s already imported from %s, ignoring %s",
784+
easyblock_full_mod_name, easyblocks[easyblock_full_mod_name]['loc'], path)
785+
else:
786+
__import__(easyblock_full_mod_name)
787+
easyblock_loc = os.path.join(path, fn)
788+
789+
class_names = class_regex.findall(read_file(easyblock_loc))
790+
if len(class_names) > 1:
791+
if pkg.endswith('.generic'):
792+
# In generic easyblocks we have e.g. ConfigureMake in configuremake.py
793+
sw_specific_class_names = [name for name in class_names
794+
if name.lower() == easyblock_mod_name.lower()]
795795
else:
796-
_log.debug("%s already imported from %s, ignoring %s",
797-
easyblock_mod_name, easyblocks[easyblock_mod_name]['loc'], path)
796+
# If there is exactly one software specific easyblock we use that
797+
sw_specific_class_names = [name for name in class_names
798+
if name.startswith(EASYBLOCK_CLASS_PREFIX)]
799+
if len(sw_specific_class_names) == 1:
800+
class_names = sw_specific_class_names
801+
if len(class_names) == 1:
802+
easyblocks[easyblock_full_mod_name] = {'class': class_names[0], 'loc': easyblock_loc}
803+
elif class_names:
804+
raise EasyBuildError("Found multiple class names for easyblock %s: %s",
805+
easyblock_loc, class_names)
806+
else:
807+
raise EasyBuildError("Failed to determine easyblock class name for %s", easyblock_loc)
798808

799809
return easyblocks
800810

easybuild/scripts/mk_tmpl_easyblock_for.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
import sys
3737
from optparse import OptionParser
3838

39-
from easybuild.tools.filetools import encode_class_name
39+
from easybuild.tools.filetools import encode_class_name, EASYBLOCK_CLASS_PREFIX
4040

4141
# parse options
4242
parser = OptionParser()
@@ -83,8 +83,9 @@
8383
# determine parent easyblock class
8484
parent_import = "from easybuild.framework.easyblock import EasyBlock"
8585
if not options.parent == "EasyBlock":
86-
if options.parent.startswith('EB_'):
87-
ebmod = options.parent[3:].lower() # FIXME: here we should actually decode the encoded class name
86+
if options.parent.startswith(EASYBLOCK_CLASS_PREFIX):
87+
# FIXME: here we should actually decode the encoded class name
88+
ebmod = options.parent[len(EASYBLOCK_CLASS_PREFIX):].lower()
8889
else:
8990
ebmod = "generic.%s" % options.parent.lower()
9091
parent_import = "from easybuild.easyblocks.%s import %s" % (ebmod, options.parent)

easybuild/tools/docs.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,15 +1313,19 @@ def get_easyblock_classes(package_name):
13131313
"""
13141314
Get list of all easyblock classes in specified easyblocks.* package
13151315
"""
1316-
easyblocks = []
1316+
easyblocks = set()
13171317
modules = import_available_modules(package_name)
13181318

13191319
for mod in modules:
1320+
easyblock_found = False
13201321
for name, _ in inspect.getmembers(mod, inspect.isclass):
13211322
eb_class = getattr(mod, name)
13221323
# skip imported classes that are not easyblocks
1323-
if eb_class.__module__.startswith(package_name) and eb_class not in easyblocks:
1324-
easyblocks.append(eb_class)
1324+
if eb_class.__module__.startswith(package_name) and EasyBlock in inspect.getmro(eb_class):
1325+
easyblocks.add(eb_class)
1326+
easyblock_found = True
1327+
if not easyblock_found:
1328+
raise RuntimeError("No easyblocks found in module: %s", mod.__name__)
13251329

13261330
return easyblocks
13271331

easybuild/tools/include.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737

3838
from easybuild.base import fancylogger
3939
from easybuild.tools.build_log import EasyBuildError
40-
from easybuild.tools.filetools import expand_glob_paths, read_file, symlink
40+
from easybuild.tools.filetools import expand_glob_paths, read_file, symlink, EASYBLOCK_CLASS_PREFIX
4141
# these are imported just to we can reload them later
4242
import easybuild.tools.module_naming_scheme
4343
import easybuild.toolchains
@@ -157,7 +157,8 @@ def verify_imports(pymods, pypkg, from_path):
157157

158158
def is_software_specific_easyblock(module):
159159
"""Determine whether Python module at specified location is a software-specific easyblock."""
160-
return bool(re.search(r'^class EB_.*\(.*\):\s*$', read_file(module), re.M))
160+
# All software-specific easyblocks start with the prefix and derive from another class, at least EasyBlock
161+
return bool(re.search(r"^class %s[^(:]+\([^)]+\):\s*$" % EASYBLOCK_CLASS_PREFIX, read_file(module), re.M))
161162

162163

163164
def include_easyblocks(tmpdir, paths):

test/framework/docs.py

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
"""
2626
Unit tests for docs.py.
2727
"""
28-
import inspect
2928
import os
3029
import re
3130
import sys
@@ -38,7 +37,7 @@
3837
from easybuild.tools.docs import list_easyblocks, list_software, list_toolchains
3938
from easybuild.tools.docs import md_title_and_table, rst_title_and_table
4039
from easybuild.tools.options import EasyBuildOptions
41-
from easybuild.tools.utilities import import_available_modules, mk_md_table, mk_rst_table
40+
from easybuild.tools.utilities import mk_md_table, mk_rst_table
4241
from test.framework.utilities import EnhancedTestCase, TestLoaderFiltered, init_config
4342

4443

@@ -520,7 +519,7 @@ def test_get_easyblock_classes(self):
520519
def test_gen_easyblocks_overview(self):
521520
""" Test gen_easyblocks_overview_* functions """
522521
gen_easyblocks_pkg = 'easybuild.easyblocks.generic'
523-
modules = import_available_modules(gen_easyblocks_pkg)
522+
names = [eb_class.__name__ for eb_class in get_easyblock_classes(gen_easyblocks_pkg)]
524523
common_params = {
525524
'ConfigureMake': ['configopts', 'buildopts', 'installopts'],
526525
}
@@ -564,15 +563,9 @@ def test_gen_easyblocks_overview(self):
564563
])
565564

566565
self.assertIn(check_configuremake, ebdoc)
567-
names = []
568566

569-
for mod in modules:
570-
for name, _ in inspect.getmembers(mod, inspect.isclass):
571-
eb_class = getattr(mod, name)
572-
# skip imported classes that are not easyblocks
573-
if eb_class.__module__.startswith(gen_easyblocks_pkg):
574-
self.assertIn(name, ebdoc)
575-
names.append(name)
567+
for name in names:
568+
self.assertIn(name, ebdoc)
576569

577570
toc = [":ref:`" + n + "`" for n in sorted(set(names))]
578571
pattern = " - ".join(toc)
@@ -610,17 +603,11 @@ def test_gen_easyblocks_overview(self):
610603
])
611604

612605
self.assertIn(check_configuremake, ebdoc)
613-
names = []
614606

615-
for mod in modules:
616-
for name, _ in inspect.getmembers(mod, inspect.isclass):
617-
eb_class = getattr(mod, name)
618-
# skip imported classes that are not easyblocks
619-
if eb_class.__module__.startswith(gen_easyblocks_pkg):
620-
self.assertIn(name, ebdoc)
621-
names.append(name)
607+
for name in names:
608+
self.assertIn(name, ebdoc)
622609

623-
toc = ["\\[" + n + "\\]\\(#" + n.lower() + "\\)" for n in sorted(set(names))]
610+
toc = ["\\[" + n + "\\]\\(#" + n.lower() + "\\)" for n in sorted(names)]
624611
pattern = " - ".join(toc)
625612
regex = re.compile(pattern)
626613
self.assertTrue(re.search(regex, ebdoc), "Pattern %s found in %s" % (regex.pattern, ebdoc))

test/framework/sandbox/easybuild/easyblocks/f/foofoo.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@
3232
from easybuild.framework.easyconfig import CUSTOM, MANDATORY
3333

3434

35+
class dummy1:
36+
"""Only to verify that unrelated classes in software specific easyblocks are ignored"""
37+
38+
39+
class dummy2(dummy1):
40+
"""Same but with inheritance"""
41+
42+
3543
class EB_foofoo(EB_foo):
3644
"""Support for building/installing foofoo."""
3745

test/framework/sandbox/easybuild/easyblocks/generic/bar.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@
3232
from easybuild.framework.easyconfig import CUSTOM, MANDATORY
3333

3434

35+
class dummy1:
36+
"""Only to verify that unrelated classes in software specific easyblocks are ignored"""
37+
38+
39+
class dummy2(dummy1):
40+
"""Same but with inheritance"""
41+
42+
43+
class dummy3:
44+
"""Class without inheritance before the real easyblock to verify the regex not being too greedy"""
45+
46+
3547
class bar(EasyBlock):
3648
"""Generic support for building/installing bar."""
3749

0 commit comments

Comments
 (0)