Repository: easybuilders/easybuild-framework

Issue:
rename unclear ExtensionEasyblock.run() method
The `run()` method from extensioneasyblock is the one carrying out the installation of any extension. Its name should be changed to something more meaningful that reflects its function more clearly. For instance `run_extension_steps()`.


Discussion:

Resolving patch:
diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py
--- a/easybuild/framework/easyblock.py
+++ b/easybuild/framework/easyblock.py
@@ -1857,7 +1857,15 @@ def skip_extensions_parallel(self, exts_filter):
 
         self.ext_instances = retained_ext_instances
 
-    def install_extensions(self, install=True):
+    def install_extensions(self, *args, **kwargs):
+        """[DEPRECATED] Install extensions."""
+        self.log.deprecated(
+            "EasyBlock.install_extensions() is deprecated, use EasyBlock.install_all_extensions() instead.",
+            '6.0',
+        )
+        self.install_all_extensions(*args, **kwargs)
+
+    def install_all_extensions(self, install=True):
         """
         Install extensions.
 
@@ -1921,15 +1929,15 @@ def install_extensions_sequential(self, install=True):
                 ext.toolchain.prepare(onlymod=self.cfg['onlytcmod'], silent=True, loadmod=False,
                                       rpath_filter_dirs=self.rpath_filter_dirs)
 
-            # real work
+            # actual installation of the extension
             if install:
                 try:
-                    ext.prerun()
+                    ext.install_extension_substep("pre_install_extension")
                     with self.module_generator.start_module_creation():
-                        txt = ext.run()
+                        txt = ext.install_extension_substep("install_extension")
                     if txt:
                         self.module_extra_extensions += txt
-                    ext.postrun()
+                    ext.install_extension_substep("post_install_extension")
                 finally:
                     if not self.dry_run:
                         ext_duration = datetime.now() - start_time
@@ -1996,7 +2004,7 @@ def update_exts_progress_bar_helper(running_exts, progress_size):
                             self.log.info(f"Installation of extension {ext.name} completed!")
                             # run post-install method for extension from same working dir as installation of extension
                             cwd = change_dir(res.work_dir)
-                            ext.postrun()
+                            ext.install_extension_substep("post_install_extension")
                             change_dir(cwd)
                             running_exts.remove(ext)
                             installed_ext_names.append(ext.name)
@@ -2070,8 +2078,8 @@ def update_exts_progress_bar_helper(running_exts, progress_size):
                     ext.toolchain.prepare(onlymod=self.cfg['onlytcmod'], silent=True, loadmod=False,
                                           rpath_filter_dirs=self.rpath_filter_dirs)
                     if install:
-                        ext.prerun()
-                        ext.async_cmd_task = ext.run_async(thread_pool)
+                        ext.install_extension_substep("pre_install_extension")
+                        ext.async_cmd_task = ext.install_extension_substep("install_extension_async", thread_pool)
                         running_exts.append(ext)
                         self.log.info(f"Started installation of extension {ext.name} in the background...")
                         update_exts_progress_bar_helper(running_exts, 0)
@@ -2904,7 +2912,7 @@ def extensions_step(self, fetch=False, install=True):
         if self.skip:
             self.skip_extensions()
 
-        self.install_extensions(install=install)
+        self.install_all_extensions(install=install)
 
         # cleanup (unload fake module, remove fake module dir)
         if fake_mod_data:
@@ -4475,7 +4483,7 @@ def copy_easyblocks_for_reprod(easyblock_instances, reprod_dir):
         for easyblock_class in inspect.getmro(type(easyblock_instance)):
             easyblock_path = inspect.getsourcefile(easyblock_class)
             # if we reach EasyBlock, Extension or ExtensionEasyBlock class, we are done
-            # (Extension and ExtensionEasyblock are hardcoded to avoid a cyclical import)
+            # (Extension and ExtensionEasyBlock are hardcoded to avoid a cyclical import)
             if easyblock_class.__name__ in [EasyBlock.__name__, 'Extension', 'ExtensionEasyBlock']:
                 break
             else:
diff --git a/easybuild/framework/extension.py b/easybuild/framework/extension.py
--- a/easybuild/framework/extension.py
+++ b/easybuild/framework/extension.py
@@ -167,29 +167,99 @@ def version(self):
         return self.ext.get('version', None)
 
     def prerun(self):
+        """
+        [DEPRECATED][6.0] Stuff to do before installing a extension.
+        """
+        # Deprecation warning triggered by Extension.install_extension_substep()
+        self.pre_install_extension()
+
+    def pre_install_extension(self):
         """
         Stuff to do before installing a extension.
         """
         pass
 
     def run(self, *args, **kwargs):
+        """
+        [DEPRECATED][6.0] Actual installation of an extension.
+        """
+        # Deprecation warning triggered by Extension.install_extension_substep()
+        self.install_extension(*args, **kwargs)
+
+    def install_extension(self, *args, **kwargs):
         """
         Actual installation of an extension.
         """
         pass
 
     def run_async(self, *args, **kwargs):
+        """
+        [DEPRECATED][6.0] Asynchronous installation of an extension.
+        """
+        # Deprecation warning triggered by Extension.install_extension_substep()
+        self.install_extension_async(*args, **kwargs)
+
+    def install_extension_async(self, *args, **kwargs):
         """
         Asynchronous installation of an extension.
         """
         raise NotImplementedError
 
     def postrun(self):
+        """
+        [DEPRECATED][6.0] Stuff to do after installing a extension.
+        """
+        # Deprecation warning triggered by Extension.install_extension_substep()
+        self.post_install_extension()
+
+    def post_install_extension(self):
         """
         Stuff to do after installing a extension.
         """
         self.master.run_post_install_commands(commands=self.cfg.get('postinstallcmds', []))
 
+    def install_extension_substep(self, substep, *args, **kwargs):
+        """
+        Carry out extension installation substep allowing use of deprecated
+        methods on those extensions using an older EasyBlock
+        """
+        substeps_mapping = {
+            'pre_install_extension': 'prerun',
+            'install_extension': 'run',
+            'install_extension_async': 'run_async',
+            'post_install_extension': 'postrun',
+        }
+
+        deprecated_substep = substeps_mapping.get(substep)
+        if deprecated_substep is None:
+            raise EasyBuildError("Unknown extension installation substep: %s", substep)
+
+        try:
+            substep_method = getattr(self, deprecated_substep)
+        except AttributeError:
+            log_msg = f"EasyBlock does not implement deprecated method '{deprecated_substep}' "
+            log_msg += f"for installation substep {substep}"
+            self.log.debug(log_msg)
+            substep_method = getattr(self, substep)
+        else:
+            # Qualified method name contains class defining the method (PEP 3155)
+            substep_method_name = substep_method.__qualname__
+            self.log.debug(f"Found deprecated method in EasyBlock: {substep_method_name}")
+
+            base_method_name = f"Extension.{deprecated_substep}"
+            if substep_method_name == base_method_name:
+                # No custom method in child Easyblock, deprecated method is defined by base Extension class
+                # Switch to non-deprecated substep method
+                substep_method = getattr(self, substep)
+            else:
+                # Custom deprecated method used by child Easyblock
+                self.log.deprecated(
+                    f"{substep_method_name}() is deprecated, use {substep}() instead.",
+                    "6.0",
+                )
+
+        return substep_method(*args, **kwargs)
+
     @property
     def required_deps(self):
         """Return list of required dependencies for this extension."""
diff --git a/easybuild/framework/extensioneasyblock.py b/easybuild/framework/extensioneasyblock.py
--- a/easybuild/framework/extensioneasyblock.py
+++ b/easybuild/framework/extensioneasyblock.py
@@ -133,7 +133,7 @@ def _set_start_dir(self):
             self.log.warning(warn_msg)
             print_warning(warn_msg, silent=build_option('silent'))
 
-    def run(self, unpack_src=False):
+    def install_extension(self, unpack_src=False):
         """Common operations for extensions: unpacking sources, patching, ..."""
 
         # unpack file if desired


Test patch:
diff --git a/test/framework/docs.py b/test/framework/docs.py
--- a/test/framework/docs.py
+++ b/test/framework/docs.py
@@ -59,6 +59,10 @@
 |-- EB_toy_buggy
 |-- ExtensionEasyBlock
 |   |-- DummyExtension
+|   |   |-- CustomDummyExtension
+|   |   |   |-- ChildCustomDummyExtension
+|   |   |-- DeprecatedDummyExtension
+|   |   |   |-- ChildDeprecatedDummyExtension
 |   |-- EB_toy
 |   |   |-- EB_toy_eula
 |   |   |-- EB_toytoy
@@ -69,10 +73,14 @@
 Extension
 |-- ExtensionEasyBlock
 |   |-- DummyExtension
+|   |   |-- CustomDummyExtension
+|   |   |   |-- ChildCustomDummyExtension
+|   |   |-- DeprecatedDummyExtension
+|   |   |   |-- ChildDeprecatedDummyExtension
 |   |-- EB_toy
 |   |   |-- EB_toy_eula
 |   |   |-- EB_toytoy
-|   |-- Toy_Extension"""
+|   |-- Toy_Extension"""  # noqa
 
 LIST_EASYBLOCKS_DETAILED_TXT = """EasyBlock (easybuild.framework.easyblock)
 |-- bar (easybuild.easyblocks.generic.bar @ %(topdir)s/generic/bar.py)
@@ -91,6 +99,10 @@
 |-- EB_toy_buggy (easybuild.easyblocks.toy_buggy @ %(topdir)s/t/toy_buggy.py)
 |-- ExtensionEasyBlock (easybuild.framework.extensioneasyblock )
 |   |-- DummyExtension (easybuild.easyblocks.generic.dummyextension @ %(topdir)s/generic/dummyextension.py)
+|   |   |-- CustomDummyExtension (easybuild.easyblocks.generic.customdummyextension @ %(topdir)s/generic/customdummyextension.py)
+|   |   |   |-- ChildCustomDummyExtension (easybuild.easyblocks.generic.childcustomdummyextension @ %(topdir)s/generic/childcustomdummyextension.py)
+|   |   |-- DeprecatedDummyExtension (easybuild.easyblocks.generic.deprecateddummyextension @ %(topdir)s/generic/deprecateddummyextension.py)
+|   |   |   |-- ChildDeprecatedDummyExtension (easybuild.easyblocks.generic.childdeprecateddummyextension @ %(topdir)s/generic/childdeprecateddummyextension.py)
 |   |-- EB_toy (easybuild.easyblocks.toy @ %(topdir)s/t/toy.py)
 |   |   |-- EB_toy_eula (easybuild.easyblocks.toy_eula @ %(topdir)s/t/toy_eula.py)
 |   |   |-- EB_toytoy (easybuild.easyblocks.toytoy @ %(topdir)s/t/toytoy.py)
@@ -101,10 +113,14 @@
 Extension (easybuild.framework.extension)
 |-- ExtensionEasyBlock (easybuild.framework.extensioneasyblock )
 |   |-- DummyExtension (easybuild.easyblocks.generic.dummyextension @ %(topdir)s/generic/dummyextension.py)
+|   |   |-- CustomDummyExtension (easybuild.easyblocks.generic.customdummyextension @ %(topdir)s/generic/customdummyextension.py)
+|   |   |   |-- ChildCustomDummyExtension (easybuild.easyblocks.generic.childcustomdummyextension @ %(topdir)s/generic/childcustomdummyextension.py)
+|   |   |-- DeprecatedDummyExtension (easybuild.easyblocks.generic.deprecateddummyextension @ %(topdir)s/generic/deprecateddummyextension.py)
+|   |   |   |-- ChildDeprecatedDummyExtension (easybuild.easyblocks.generic.childdeprecateddummyextension @ %(topdir)s/generic/childdeprecateddummyextension.py)
 |   |-- EB_toy (easybuild.easyblocks.toy @ %(topdir)s/t/toy.py)
 |   |   |-- EB_toy_eula (easybuild.easyblocks.toy_eula @ %(topdir)s/t/toy_eula.py)
 |   |   |-- EB_toytoy (easybuild.easyblocks.toytoy @ %(topdir)s/t/toytoy.py)
-|   |-- Toy_Extension (easybuild.easyblocks.generic.toy_extension @ %(topdir)s/generic/toy_extension.py)"""
+|   |-- Toy_Extension (easybuild.easyblocks.generic.toy_extension @ %(topdir)s/generic/toy_extension.py)"""  # noqa
 
 LIST_EASYBLOCKS_SIMPLE_RST = """* **EasyBlock**
 
@@ -129,6 +145,16 @@
   * ExtensionEasyBlock
 
     * DummyExtension
+
+      * CustomDummyExtension
+
+        * ChildCustomDummyExtension
+
+      * DeprecatedDummyExtension
+
+        * ChildDeprecatedDummyExtension
+
+
     * EB_toy
 
       * EB_toy_eula
@@ -145,6 +171,16 @@
   * ExtensionEasyBlock
 
     * DummyExtension
+
+      * CustomDummyExtension
+
+        * ChildCustomDummyExtension
+
+      * DeprecatedDummyExtension
+
+        * ChildDeprecatedDummyExtension
+
+
     * EB_toy
 
       * EB_toy_eula
@@ -152,7 +188,7 @@
 
     * Toy_Extension
 
-"""
+"""  # noqa
 
 LIST_EASYBLOCKS_DETAILED_RST = """* **EasyBlock** (easybuild.framework.easyblock)
 
@@ -177,6 +213,16 @@
   * ExtensionEasyBlock (easybuild.framework.extensioneasyblock )
 
     * DummyExtension (easybuild.easyblocks.generic.dummyextension @ %(topdir)s/generic/dummyextension.py)
+
+      * CustomDummyExtension (easybuild.easyblocks.generic.customdummyextension @ %(topdir)s/generic/customdummyextension.py)
+
+        * ChildCustomDummyExtension (easybuild.easyblocks.generic.childcustomdummyextension @ %(topdir)s/generic/childcustomdummyextension.py)
+
+      * DeprecatedDummyExtension (easybuild.easyblocks.generic.deprecateddummyextension @ %(topdir)s/generic/deprecateddummyextension.py)
+
+        * ChildDeprecatedDummyExtension (easybuild.easyblocks.generic.childdeprecateddummyextension @ %(topdir)s/generic/childdeprecateddummyextension.py)
+
+
     * EB_toy (easybuild.easyblocks.toy @ %(topdir)s/t/toy.py)
 
       * EB_toy_eula (easybuild.easyblocks.toy_eula @ %(topdir)s/t/toy_eula.py)
@@ -193,6 +239,16 @@
   * ExtensionEasyBlock (easybuild.framework.extensioneasyblock )
 
     * DummyExtension (easybuild.easyblocks.generic.dummyextension @ %(topdir)s/generic/dummyextension.py)
+
+      * CustomDummyExtension (easybuild.easyblocks.generic.customdummyextension @ %(topdir)s/generic/customdummyextension.py)
+
+        * ChildCustomDummyExtension (easybuild.easyblocks.generic.childcustomdummyextension @ %(topdir)s/generic/childcustomdummyextension.py)
+
+      * DeprecatedDummyExtension (easybuild.easyblocks.generic.deprecateddummyextension @ %(topdir)s/generic/deprecateddummyextension.py)
+
+        * ChildDeprecatedDummyExtension (easybuild.easyblocks.generic.childdeprecateddummyextension @ %(topdir)s/generic/childdeprecateddummyextension.py)
+
+
     * EB_toy (easybuild.easyblocks.toy @ %(topdir)s/t/toy.py)
 
       * EB_toy_eula (easybuild.easyblocks.toy_eula @ %(topdir)s/t/toy_eula.py)
@@ -200,7 +256,7 @@
 
     * Toy_Extension (easybuild.easyblocks.generic.toy_extension @ %(topdir)s/generic/toy_extension.py)
 
-"""
+"""  # noqa
 
 LIST_EASYBLOCKS_SIMPLE_MD = """- **EasyBlock**
   - bar
@@ -219,6 +275,10 @@
   - EB_toy_buggy
   - ExtensionEasyBlock
     - DummyExtension
+      - CustomDummyExtension
+        - ChildCustomDummyExtension
+      - DeprecatedDummyExtension
+        - ChildDeprecatedDummyExtension
     - EB_toy
       - EB_toy_eula
       - EB_toytoy
@@ -229,10 +289,14 @@
 - **Extension**
   - ExtensionEasyBlock
     - DummyExtension
+      - CustomDummyExtension
+        - ChildCustomDummyExtension
+      - DeprecatedDummyExtension
+        - ChildDeprecatedDummyExtension
     - EB_toy
       - EB_toy_eula
       - EB_toytoy
-    - Toy_Extension"""
+    - Toy_Extension"""  # noqa
 
 LIST_EASYBLOCKS_DETAILED_MD = """- **EasyBlock** (easybuild.framework.easyblock)
   - bar (easybuild.easyblocks.generic.bar @ %(topdir)s/generic/bar.py)
@@ -251,6 +315,10 @@
   - EB_toy_buggy (easybuild.easyblocks.toy_buggy @ %(topdir)s/t/toy_buggy.py)
   - ExtensionEasyBlock (easybuild.framework.extensioneasyblock )
     - DummyExtension (easybuild.easyblocks.generic.dummyextension @ %(topdir)s/generic/dummyextension.py)
+      - CustomDummyExtension (easybuild.easyblocks.generic.customdummyextension @ %(topdir)s/generic/customdummyextension.py)
+        - ChildCustomDummyExtension (easybuild.easyblocks.generic.childcustomdummyextension @ %(topdir)s/generic/childcustomdummyextension.py)
+      - DeprecatedDummyExtension (easybuild.easyblocks.generic.deprecateddummyextension @ %(topdir)s/generic/deprecateddummyextension.py)
+        - ChildDeprecatedDummyExtension (easybuild.easyblocks.generic.childdeprecateddummyextension @ %(topdir)s/generic/childdeprecateddummyextension.py)
     - EB_toy (easybuild.easyblocks.toy @ %(topdir)s/t/toy.py)
       - EB_toy_eula (easybuild.easyblocks.toy_eula @ %(topdir)s/t/toy_eula.py)
       - EB_toytoy (easybuild.easyblocks.toytoy @ %(topdir)s/t/toytoy.py)
@@ -261,14 +329,18 @@
 - **Extension** (easybuild.framework.extension)
   - ExtensionEasyBlock (easybuild.framework.extensioneasyblock )
     - DummyExtension (easybuild.easyblocks.generic.dummyextension @ %(topdir)s/generic/dummyextension.py)
+      - CustomDummyExtension (easybuild.easyblocks.generic.customdummyextension @ %(topdir)s/generic/customdummyextension.py)
+        - ChildCustomDummyExtension (easybuild.easyblocks.generic.childcustomdummyextension @ %(topdir)s/generic/childcustomdummyextension.py)
+      - DeprecatedDummyExtension (easybuild.easyblocks.generic.deprecateddummyextension @ %(topdir)s/generic/deprecateddummyextension.py)
+        - ChildDeprecatedDummyExtension (easybuild.easyblocks.generic.childdeprecateddummyextension @ %(topdir)s/generic/childdeprecateddummyextension.py)
     - EB_toy (easybuild.easyblocks.toy @ %(topdir)s/t/toy.py)
       - EB_toy_eula (easybuild.easyblocks.toy_eula @ %(topdir)s/t/toy_eula.py)
       - EB_toytoy (easybuild.easyblocks.toytoy @ %(topdir)s/t/toytoy.py)
-    - Toy_Extension (easybuild.easyblocks.generic.toy_extension @ %(topdir)s/generic/toy_extension.py)"""
+    - Toy_Extension (easybuild.easyblocks.generic.toy_extension @ %(topdir)s/generic/toy_extension.py)"""  # noqa
 
 LIST_SOFTWARE_SIMPLE_TXT = """
 * GCC
-* gzip"""
+* gzip"""  # noqa
 
 GCC_DESCR = "The GNU Compiler Collection includes front ends for C, C++, Objective-C, Fortran, Java, and Ada, "
 GCC_DESCR += "as well as libraries for these languages (libstdc++, libgcj,...)."
@@ -291,7 +363,7 @@
 
   * gzip v1.4: GCC/4.6.3, system
   * gzip v1.5: foss/2018a, intel/2018a
-""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR}
+""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR}  # noqa
 
 LIST_SOFTWARE_SIMPLE_RST = """List of supported software
 ==========================
@@ -307,7 +379,7 @@
 ---
 
 * GCC
-* gzip"""
+* gzip"""  # noqa
 
 LIST_SOFTWARE_DETAILED_RST = """List of supported software
 ==========================
@@ -357,7 +429,7 @@
 ``1.4``    ``GCC/4.6.3``, ``system``
 ``1.5``    ``foss/2018a``, ``intel/2018a``
 =======    ===============================
-""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR}
+""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR}  # noqa
 
 LIST_SOFTWARE_SIMPLE_MD = """# List of supported software
 
@@ -369,7 +441,7 @@
 ## G
 
 * GCC
-* gzip"""
+* gzip"""  # noqa
 
 LIST_SOFTWARE_DETAILED_MD = """# List of supported software
 
@@ -403,7 +475,7 @@
 version|toolchain
 -------|-------------------------------
 ``1.4``|``GCC/4.6.3``, ``system``
-``1.5``|``foss/2018a``, ``intel/2018a``""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR}
+``1.5``|``foss/2018a``, ``intel/2018a``""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR}  # noqa
 
 LIST_SOFTWARE_SIMPLE_MD = """# List of supported software
 
@@ -415,7 +487,7 @@
 ## G
 
 * GCC
-* gzip"""
+* gzip"""  # noqa
 
 LIST_SOFTWARE_DETAILED_MD = """# List of supported software
 
@@ -449,7 +521,7 @@
 version|toolchain
 -------|-------------------------------
 ``1.4``|``GCC/4.6.3``, ``system``
-``1.5``|``foss/2018a``, ``intel/2018a``""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR}
+``1.5``|``foss/2018a``, ``intel/2018a``""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR}  # noqa
 
 LIST_SOFTWARE_SIMPLE_JSON = """[
 {
@@ -458,7 +530,7 @@
 {
     "name": "gzip"
 }
-]"""
+]"""  # noqa
 
 LIST_SOFTWARE_DETAILED_JSON = """[
 {
@@ -501,7 +573,7 @@
     "version": "1.5",
     "versionsuffix": ""
 }
-]""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR}
+]""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR}  # noqa
 
 
 class DocsTest(EnhancedTestCase):
@@ -513,8 +585,20 @@ def test_get_easyblock_classes(self):
         # result should correspond with test easyblocks in test/framework/sandbox/easybuild/easyblocks/generic
         eb_classes = get_easyblock_classes('easybuild.easyblocks.generic')
         eb_names = [x.__name__ for x in eb_classes]
-        expected = ['ConfigureMake', 'DummyExtension', 'MakeCp', 'ModuleRC',
-                    'PythonBundle', 'Toolchain', 'Toy_Extension', 'bar']
+        expected = [
+            'ChildCustomDummyExtension',
+            'ChildDeprecatedDummyExtension',
+            'ConfigureMake',
+            'CustomDummyExtension',
+            'DeprecatedDummyExtension',
+            'DummyExtension',
+            'MakeCp',
+            'ModuleRC',
+            'PythonBundle',
+            'Toolchain',
+            'Toy_Extension',
+            'bar',
+        ]
         self.assertEqual(sorted(eb_names), expected)
 
     def test_gen_easyblocks_overview(self):
diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py
--- a/test/framework/easyblock.py
+++ b/test/framework/easyblock.py
@@ -1038,6 +1038,64 @@ def test_extensions_step(self):
         eb.close_log()
         os.remove(eb.logfile)
 
+    def test_extensions_step_deprecations(self):
+        """Test extension install with deprecated substeps."""
+        install_substeps = ["pre_install_extension", "install_extension", "post_install_extension"]
+
+        test_ec = os.path.join(self.test_prefix, 'test.eb')
+        test_ec_txt = '\n'.join([
+            'easyblock = "ConfigureMake"',
+            'name = "pi"',
+            'version = "3.14"',
+            'homepage = "http://example.com"',
+            'description = "test easyconfig"',
+            'toolchain = SYSTEM',
+            'exts_defaultclass = "DummyExtension"',
+            'exts_list = ["ext1"]',
+            'exts_list = [',
+            '    "dummy_ext",',
+            '    ("custom_ext", "0.0", {"easyblock": "CustomDummyExtension"}),',
+            '    ("deprec_ext", "0.0", {"easyblock": "DeprecatedDummyExtension"}),',
+            '    ("childcustom_ext", "0.0", {"easyblock": "ChildCustomDummyExtension"}),',
+            '    ("childdeprec_ext", "0.0", {"easyblock": "ChildDeprecatedDummyExtension"}),',
+            ']',
+        ])
+        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()
+
+        # Default DummyExtension without deprecated or custom install substeps
+        ext = eb.ext_instances[0]
+        self.assertEqual(ext.__class__.__name__, "DummyExtension")
+        for substep in install_substeps:
+            self.assertEqual(ext.install_extension_substep(substep), None)
+        # CustomDummyExtension
+        ext = eb.ext_instances[1]
+        self.assertEqual(ext.__class__.__name__, "CustomDummyExtension")
+        for substep in install_substeps:
+            expected_return = f"Extension installed with custom {substep}()"
+            self.assertEqual(ext.install_extension_substep(substep), expected_return)
+        # DeprecatedDummyExtension
+        ext = eb.ext_instances[2]
+        self.assertEqual(ext.__class__.__name__, "DeprecatedDummyExtension")
+        for substep in install_substeps:
+            expected_error = rf"DEPRECATED \(since v6.0\).*use {substep}\(\) instead.*"
+            self.assertErrorRegex(EasyBuildError, expected_error, ext.install_extension_substep, substep)
+        # ChildCustomDummyExtension
+        ext = eb.ext_instances[3]
+        self.assertEqual(ext.__class__.__name__, "ChildCustomDummyExtension")
+        for substep in install_substeps:
+            expected_return = f"Extension installed with custom {substep}()"
+            self.assertEqual(ext.install_extension_substep(substep), expected_return)
+        # ChildDeprecatedDummyExtension
+        ext = eb.ext_instances[4]
+        self.assertEqual(ext.__class__.__name__, "ChildDeprecatedDummyExtension")
+        for substep in install_substeps:
+            expected_error = rf"DEPRECATED \(since v6.0\).*use {substep}\(\) instead.*"
+            self.assertErrorRegex(EasyBuildError, expected_error, ext.install_extension_substep, substep)
+
     def test_init_extensions(self):
         """Test creating extension instances."""
 
@@ -2230,7 +2288,7 @@ def check_ext_start_dir(expected_start_dir, unpack_src=True):
             eb.extensions_step(fetch=True, install=False)
             # extract sources of the extension
             ext = eb.ext_instances[-1]
-            ext.run(unpack_src=unpack_src)
+            ext.install_extension(unpack_src=unpack_src)
 
             if expected_start_dir is None:
                 self.assertIsNone(ext.start_dir)
diff --git a/test/framework/options.py b/test/framework/options.py
--- a/test/framework/options.py
+++ b/test/framework/options.py
@@ -923,37 +923,46 @@ def test_000_list_easyblocks(self):
             logtxt = read_file(self.logfile)
 
             expected = '\n'.join([
-                r'EasyBlock',
-                r'\|-- bar',
-                r'\|-- ConfigureMake',
-                r'\|   \|-- MakeCp',
-                r'\|-- EB_EasyBuildMeta',
-                r'\|-- EB_FFTW',
-                r'\|-- EB_foo',
-                r'\|   \|-- EB_foofoo',
-                r'\|-- EB_GCC',
-                r'\|-- EB_HPL',
-                r'\|-- EB_libtoy',
-                r'\|-- EB_OpenBLAS',
-                r'\|-- EB_OpenMPI',
-                r'\|-- EB_ScaLAPACK',
-                r'\|-- EB_toy_buggy',
-                r'\|-- ExtensionEasyBlock',
-                r'\|   \|-- DummyExtension',
-                r'\|   \|-- EB_toy',
-                r'\|   \|   \|-- EB_toy_eula',
-                r'\|   \|   \|-- EB_toytoy',
-                r'\|   \|-- Toy_Extension',
-                r'\|-- ModuleRC',
-                r'\|-- PythonBundle',
-                r'\|-- Toolchain',
-                r'Extension',
-                r'\|-- ExtensionEasyBlock',
-                r'\|   \|-- DummyExtension',
-                r'\|   \|-- EB_toy',
-                r'\|   \|   \|-- EB_toy_eula',
-                r'\|   \|   \|-- EB_toytoy',
-                r'\|   \|-- Toy_Extension',
+                "EasyBlock",
+                "|-- bar",
+                "|-- ConfigureMake",
+                "|   |-- MakeCp",
+                "|-- EB_EasyBuildMeta",
+                "|-- EB_FFTW",
+                "|-- EB_foo",
+                "|   |-- EB_foofoo",
+                "|-- EB_GCC",
+                "|-- EB_HPL",
+                "|-- EB_libtoy",
+                "|-- EB_OpenBLAS",
+                "|-- EB_OpenMPI",
+                "|-- EB_ScaLAPACK",
+                "|-- EB_toy_buggy",
+                "|-- ExtensionEasyBlock",
+                "|   |-- DummyExtension",
+                "|   |   |-- CustomDummyExtension",
+                "|   |   |   |-- ChildCustomDummyExtension",
+                "|   |   |-- DeprecatedDummyExtension",
+                "|   |   |   |-- ChildDeprecatedDummyExtension",
+                "|   |-- EB_toy",
+                "|   |   |-- EB_toy_eula",
+                "|   |   |-- EB_toytoy",
+                "|   |-- Toy_Extension",
+                "|-- ModuleRC",
+                "|-- PythonBundle",
+                "|-- Toolchain",
+                "Extension",
+                "|-- ExtensionEasyBlock",
+                "|   |-- DummyExtension",
+                "|   |   |-- CustomDummyExtension",
+                "|   |   |   |-- ChildCustomDummyExtension",
+                "|   |   |-- DeprecatedDummyExtension",
+                "|   |   |   |-- ChildDeprecatedDummyExtension",
+                "|   |-- EB_toy",
+                "|   |   |-- EB_toy_eula",
+                "|   |   |-- EB_toytoy",
+                "|   |-- Toy_Extension",
+                "",
             ])
             regex = re.compile(expected, re.M)
             self.assertTrue(regex.search(logtxt), "Pattern '%s' found in: %s" % (regex.pattern, logtxt))
diff --git a/test/framework/sandbox/easybuild/easyblocks/generic/childcustomdummyextension.py b/test/framework/sandbox/easybuild/easyblocks/generic/childcustomdummyextension.py
new file mode 100644
--- /dev/null
+++ b/test/framework/sandbox/easybuild/easyblocks/generic/childcustomdummyextension.py
@@ -0,0 +1,35 @@
+##
+# Copyright 2009-2023 Ghent University
+#
+# This file is part of EasyBuild,
+# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),
+# with support of Ghent University (http://ugent.be/hpc),
+# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be),
+# Flemish Research Foundation (FWO) (http://www.fwo.be/en)
+# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).
+#
+# https://github.com/easybuilders/easybuild
+#
+# EasyBuild is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation v2.
+#
+# EasyBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with EasyBuild.  If not, see <http://www.gnu.org/licenses/>.
+##
+"""
+Test EasyBlocks building and installing dummy extensions with customized methods
+
+@author: Alex Domingo (Vrije Universiteit Brussel)
+"""
+
+from easybuild.easyblocks.generic.customdummyextension import CustomDummyExtension
+
+
+class ChildCustomDummyExtension(CustomDummyExtension):
+    """Extension EasyBlock inheriting customized install step"""
diff --git a/test/framework/sandbox/easybuild/easyblocks/generic/childdeprecateddummyextension.py b/test/framework/sandbox/easybuild/easyblocks/generic/childdeprecateddummyextension.py
new file mode 100644
--- /dev/null
+++ b/test/framework/sandbox/easybuild/easyblocks/generic/childdeprecateddummyextension.py
@@ -0,0 +1,35 @@
+##
+# Copyright 2009-2023 Ghent University
+#
+# This file is part of EasyBuild,
+# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),
+# with support of Ghent University (http://ugent.be/hpc),
+# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be),
+# Flemish Research Foundation (FWO) (http://www.fwo.be/en)
+# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).
+#
+# https://github.com/easybuilders/easybuild
+#
+# EasyBuild is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation v2.
+#
+# EasyBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with EasyBuild.  If not, see <http://www.gnu.org/licenses/>.
+##
+"""
+Test EasyBlocks building and installing dummy extensions with deprecated methods
+
+@author: Alex Domingo (Vrije Universiteit Brussel)
+"""
+
+from easybuild.easyblocks.generic.deprecateddummyextension import DeprecatedDummyExtension
+
+
+class ChildDeprecatedDummyExtension(DeprecatedDummyExtension):
+    """Extension EasyBlock inheriting deprecated install step"""
diff --git a/test/framework/sandbox/easybuild/easyblocks/generic/customdummyextension.py b/test/framework/sandbox/easybuild/easyblocks/generic/customdummyextension.py
new file mode 100644
--- /dev/null
+++ b/test/framework/sandbox/easybuild/easyblocks/generic/customdummyextension.py
@@ -0,0 +1,47 @@
+##
+# Copyright 2009-2023 Ghent University
+#
+# This file is part of EasyBuild,
+# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),
+# with support of Ghent University (http://ugent.be/hpc),
+# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be),
+# Flemish Research Foundation (FWO) (http://www.fwo.be/en)
+# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).
+#
+# https://github.com/easybuilders/easybuild
+#
+# EasyBuild is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation v2.
+#
+# EasyBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with EasyBuild.  If not, see <http://www.gnu.org/licenses/>.
+##
+"""
+Test EasyBlocks building and installing dummy extensions with customized methods
+
+@author: Alex Domingo (Vrije Universiteit Brussel)
+"""
+
+from easybuild.easyblocks.generic.dummyextension import DummyExtension
+
+
+class CustomDummyExtension(DummyExtension):
+    """Extension EasyBlock with customized install step"""
+
+    def pre_install_extension(self):
+
+        return "Extension installed with custom pre_install_extension()"
+
+    def install_extension(self):
+
+        return "Extension installed with custom install_extension()"
+
+    def post_install_extension(self):
+
+        return "Extension installed with custom post_install_extension()"
diff --git a/test/framework/sandbox/easybuild/easyblocks/generic/deprecateddummyextension.py b/test/framework/sandbox/easybuild/easyblocks/generic/deprecateddummyextension.py
new file mode 100644
--- /dev/null
+++ b/test/framework/sandbox/easybuild/easyblocks/generic/deprecateddummyextension.py
@@ -0,0 +1,47 @@
+##
+# Copyright 2009-2023 Ghent University
+#
+# This file is part of EasyBuild,
+# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),
+# with support of Ghent University (http://ugent.be/hpc),
+# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be),
+# Flemish Research Foundation (FWO) (http://www.fwo.be/en)
+# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).
+#
+# https://github.com/easybuilders/easybuild
+#
+# EasyBuild is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation v2.
+#
+# EasyBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with EasyBuild.  If not, see <http://www.gnu.org/licenses/>.
+##
+"""
+Test EasyBlocks building and installing dummy extensions with deprecated methods
+
+@author: Alex Domingo (Vrije Universiteit Brussel)
+"""
+
+from easybuild.easyblocks.generic.dummyextension import DummyExtension
+
+
+class DeprecatedDummyExtension(DummyExtension):
+    """Extension EasyBlock with deprecated install step"""
+
+    def prerun(self):
+
+        return "Extension installed with custom prerun()"
+
+    def run(self):
+
+        return "Extension installed with custom run()"
+
+    def postrun(self):
+
+        return "Extension installed with custom postrun()"
diff --git a/test/framework/sandbox/easybuild/easyblocks/generic/toy_extension.py b/test/framework/sandbox/easybuild/easyblocks/generic/toy_extension.py
--- a/test/framework/sandbox/easybuild/easyblocks/generic/toy_extension.py
+++ b/test/framework/sandbox/easybuild/easyblocks/generic/toy_extension.py
@@ -60,7 +60,17 @@ def required_deps(self):
         else:
             raise EasyBuildError("Dependencies for %s are unknown!", self.name)
 
-    def run(self, *args, **kwargs):
+    def pre_install_extension(self):
+        """
+        Prepare installation of toy extension.
+        """
+        super(Toy_Extension, self).pre_install_extension()
+
+        if self.src:
+            super(Toy_Extension, self).install_extension(unpack_src=True)
+            EB_toy.configure_step(self.master, name=self.name, cfg=self.cfg)
+
+    def install_extension(self, *args, **kwargs):
         """
         Install toy extension.
         """
@@ -72,17 +82,7 @@ def run(self, *args, **kwargs):
 
             return self.module_generator.set_environment('TOY_EXT_%s' % self.name.upper().replace('-', '_'), self.name)
 
-    def prerun(self):
-        """
-        Prepare installation of toy extension.
-        """
-        super(Toy_Extension, self).prerun()
-
-        if self.src:
-            super(Toy_Extension, self).run(unpack_src=True)
-            EB_toy.configure_step(self.master, name=self.name, cfg=self.cfg)
-
-    def run_async(self, thread_pool):
+    def install_extension_async(self, thread_pool):
         """
         Install toy extension asynchronously.
         """
@@ -95,11 +95,12 @@ def run_async(self, thread_pool):
         return thread_pool.submit(run_shell_cmd, cmd, asynchronous=True, env=os.environ.copy(),
                                   fail_on_error=False, task_id=task_id, work_dir=os.getcwd())
 
-    def postrun(self):
+    def post_install_extension(self):
         """
         Wrap up installation of toy extension.
         """
-        super(Toy_Extension, self).postrun()
+        super(Toy_Extension, self).post_install_extension()
+
         EB_toy.install_step(self.master, name=self.name)
 
     def sanity_check_step(self, *args, **kwargs):
diff --git a/test/framework/sandbox/easybuild/easyblocks/t/toy.py b/test/framework/sandbox/easybuild/easyblocks/t/toy.py
--- a/test/framework/sandbox/easybuild/easyblocks/t/toy.py
+++ b/test/framework/sandbox/easybuild/easyblocks/t/toy.py
@@ -150,20 +150,20 @@ def required_deps(self):
         else:
             raise EasyBuildError("Dependencies for %s are unknown!", self.name)
 
-    def prerun(self):
+    def pre_install_extension(self):
         """
         Prepare installation of toy as extension.
         """
-        super(EB_toy, self).run(unpack_src=True)
+        super(EB_toy, self).install_extension(unpack_src=True)
         self.configure_step()
 
-    def run(self):
+    def install_extension(self):
         """
         Install toy as extension.
         """
         self.build_step()
 
-    def run_async(self, thread_pool):
+    def install_extension_async(self, thread_pool):
         """
         Asynchronous installation of toy as extension.
         """
@@ -172,7 +172,7 @@ def run_async(self, thread_pool):
         return thread_pool.submit(run_shell_cmd, cmd, asynchronous=True, env=os.environ.copy(),
                                   fail_on_error=False, task_id=task_id, work_dir=os.getcwd())
 
-    def postrun(self):
+    def post_install_extension(self):
         """
         Wrap up installation of toy as extension.
         """