Skip to content
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

honor specified easyblock for extensions #3762

Merged
merged 2 commits into from
Jul 3, 2021
Merged
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
27 changes: 22 additions & 5 deletions easybuild/framework/easyblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
from easybuild.framework.easyconfig.style import MAX_LINE_LENGTH
from easybuild.framework.easyconfig.tools import get_paths_for
from easybuild.framework.easyconfig.templates import TEMPLATE_NAMES_EASYBLOCK_RUN_STEP, template_constant_dict
from easybuild.framework.extension import resolve_exts_filter_template
from easybuild.framework.extension import Extension, resolve_exts_filter_template
from easybuild.tools import config, run
from easybuild.tools.build_details import get_build_stats
from easybuild.tools.build_log import EasyBuildError, dry_run_msg, dry_run_warning, dry_run_set_dirs
Expand Down Expand Up @@ -540,6 +540,10 @@ def fetch_extension_sources(self, skip_checksums=False):
'options': ext_options,
}

# if a particular easyblock is specified, make sure it's used
# (this is picked up by init_ext_instances)
ext_src['easyblock'] = ext_options.get('easyblock', None)

# construct dictionary with template values;
# inherited from parent, except for name/version templates which are specific to this extension
template_values = copy.deepcopy(self.cfg.template_values)
Expand Down Expand Up @@ -2295,18 +2299,31 @@ def init_ext_instances(self):
ext_name = ext['name']
self.log.debug("Creating class instance for extension %s...", ext_name)

# if a specific easyblock is specified for this extension, honor it;
# just passing this to get_easyblock_class is sufficient
easyblock = ext.get('easyblock', None)
if easyblock:
class_name = easyblock
mod_path = get_module_path(class_name)
else:
class_name = encode_class_name(ext_name)
mod_path = get_module_path(class_name, generic=False)

cls, inst = None, None
class_name = encode_class_name(ext_name)
mod_path = get_module_path(class_name, generic=False)

# try instantiating extension-specific class
# try instantiating extension-specific class, or honor specified easyblock
try:
# no error when importing class fails, in case we run into an existing easyblock
# with a similar name (e.g., Perl Extension 'GO' vs 'Go' for which 'EB_Go' is available)
cls = get_easyblock_class(None, name=ext_name, error_on_failed_import=False,
cls = get_easyblock_class(easyblock, name=ext_name, error_on_failed_import=False,
error_on_missing_easyblock=False)

self.log.debug("Obtained class %s for extension %s", cls, ext_name)
if cls is not None:
# make sure that this easyblock can be used to install extensions
if not issubclass(cls, Extension):
raise EasyBuildError("%s easyblock can not be used to install extensions!", cls.__name__)

inst = cls(self, ext)
except (ImportError, NameError) as err:
self.log.debug("Failed to use extension-specific class for extension %s: %s", ext_name, err)
Expand Down
36 changes: 36 additions & 0 deletions test/framework/easyblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,42 @@ def test_extensions_step(self):
eb.close_log()
os.remove(eb.logfile)

def test_init_extensions(self):
"""Test creating extension instances."""

testdir = os.path.abspath(os.path.dirname(__file__))
toy_ec_file = os.path.join(testdir, 'easyconfigs', 'test_ecs', 't', 'toy', 'toy-0.0-gompi-2018a-test.eb')
toy_ec_txt = read_file(toy_ec_file)

test_ec = os.path.join(self.test_prefix, 'test.eb')
test_ec_txt = toy_ec_txt.replace("('barbar', '0.0', {", "('barbar', '0.0', {'easyblock': 'DummyExtension',")
write_file(test_ec, test_ec_txt)
ec = process_easyconfig(test_ec)[0]
eb = get_easyblock_instance(ec)

eb.prepare_for_extensions()
eb.init_ext_instances()
ext_inst_class_names = [x.__class__.__name__ for x in eb.ext_instances]
expected = [
'Toy_Extension', # 'ls' extension
'Toy_Extension', # 'bar' extension
'DummyExtension', # 'barbar' extension
'EB_toy', # 'toy' extension
]
self.assertEqual(ext_inst_class_names, expected)

# check what happen if we specify an easyblock that doesn't derive from Extension,
# and hence can't be used to install extensions...
test_ec = os.path.join(self.test_prefix, 'test_broken.eb')
test_ec_txt = test_ec_txt.replace('DummyExtension', 'ConfigureMake')
write_file(test_ec, test_ec_txt)
ec = process_easyconfig(test_ec)[0]
eb = get_easyblock_instance(ec)

eb.prepare_for_extensions()
error_pattern = "ConfigureMake easyblock can not be used to install extensions"
self.assertErrorRegex(EasyBuildError, error_pattern, eb.init_ext_instances)

def test_skip_extensions_step(self):
"""Test the skip_extensions_step"""

Expand Down