Entrypoints¶
Entrypoints are a mechanism that can be used by Python packages to expose functionalities. The most common use case is to expose a command line interface (CLI) to the user, but they can also be used to expose functions or classes that can be used by other Python packages.
Easybuild makes use of entrypoints for extending the following:
- Hooks (
easybuild.hooks): Allow injecting multiple hooks for each allowed step. The order of execution can be enforced by setting a priority for each hook (higher priority will execute first). When the same priority is set, the order of execution is determined by the hook name. The entrypoints hooks can be ran in conjunction with standard hooks detected using--hookswhere the latter will always take precedence, regardless of the priority of the entrypoint hooks. - Easyblocks (
easybuild.easyblocks): Allow extending the set of available easyblocks, which are used to build and install software. - Toolchains (
easybuild.toolchains): Allow extending the set of available toolchains, which are used to build and install software.
The entrypoints needs to be decorated with the appropriate class in order for them to be recognized and used:
- For hooks:
EntrypointHook - For easyblocks:
EntrypointEasyblock - For toolchains:
EntrypointToolchain
Validation¶
Entrypoints are validated the moment they are registered:
- Hooks: The entrypoint will check the the required combination of
step,pre_stepandpost_stepleads to a valid hook that will actually be run. - EasyBlocks: The entrypoint will check that the easyblock is a subclass of the
EasyBlockclass (provided byeasybuild.framework.easyblock). Furthermore, to avoid conflicts, an error will be raised if two separate modules attempt to register the same easyblock name. If an entrypoint easyblock has the same name as an existing easyblock, the entrypoint will take precedence over the existing easyblock. - Toolchains: The entrypoint will check that the toolchain is a subclass of
Toolchain(provided byeasybuild.tools.toolchain). Furthermore, to avoid conflicts, an error will be raised if two separate module attempts to register the same toolchain name (tc.NAME). Theprependargument used for the decorator class will determine whether the entrypoint toolchain is prepended to the list of available toolchains. This allows to override an existing toolchain, or to only add a new one to the list of available toolchains.
Examples¶
Defining entrypoints can be done both through the setup.py file of a Python package, or through the pyproject.toml file.
In the case of the pyproject.toml, the entrypoints can be defined using the following syntax:
...
[project.entry-points."GROUP_NAME"]
"ENTRYPOINT_NAME1" = "import_path1:object1"
"ENTRYPOINT_NAME2" = "import_path2:object2"
...
Here, GROUP_NAME is the name of the entrypoint group (e.g. easybuild.hooks, easybuild.easyblocks, easybuild.toolchains),
ENTRYPOINT_NAME is the name of the entrypoint, and import_path:object is the import path and object name that are being registered.
For example: myblock = "my_module.sub_module:MyBlock" requires that from my_module.sub_module import MyBlock is possible.
For the following examples we are going to assume a package with the following folder structure:
my_package/
├── my_module
│ ├── __init__.py
│ ├── easyblock.py
│ ├── hook.py
| └── toolchain.py
└── pyproject.toml
Hooks¶
pyproject.toml example:
...
[project.entry-points."easybuild.hooks"]
"my_hook" = "my_module.hook:my_hook_start_func"
"my_hook2" = "my_module.hook:my_hook_config_func"
...
And the my_module/hook.py file would look like this:
from easybuild.tools.entrypoints import EntrypointHook
from easybuild.tools.hooks import CONFIGURE_STEP, START
@EntrypointHook(step=START, priority=10)
def my_hook_start_func(eb):
# Do something at the start of the EasyBuild process
@EntrypointHook(step=CONFIGURE_STEP, pre_step=True, priority=10)
def my_hook_config_func(eb):
# Do something at the configure step of the EasyBuild process
EasyBlocks¶
pyproject.toml example:
...
[project.entry-points."easybuild.easyblocks"]
"my_easyblock" = "my_module.easyblock:MyEasyBlock"
...
And the my_module/easyblock.py file would look like this:
from easybuild.framework.easyblock import EasyBlock
from easybuild.tools.entrypoints import EntrypointEasyblock
@EntrypointEasyblock()
class MyEasyBlock(EasyBlock):
"""My custom easyblock."""
# Define the methods and properties of your easyblock here
The custom easyblock needs only to be a subclass of easybuild.framework.easyblock.EasyBlock, which means it does not need to inherit directly from EasyBlock but can also be used on top of other existing easyblocks (e.g. PythonPackage, CMake, etc.).
Toolchain¶
pyproject.toml example:
...
[project.entry-points."easybuild.toolchains"]
"my_toolchain" = "my_module.toolchain:MyToolchain"
...
And the my_module/toolchain.py file would look like this:
from easybuild.tools.toolchain.toolchain import import Toolchain
from easybuild.tools.entrypoints import EntrypointToolchain
@EntrypointToolchain(prepend=True)
class MyToolchain(Toolchain):
"""My custom toolchain."""
# Define the methods and properties of your toolchain here
The custom toolchain needs only to be a subclass of easybuild.tools.toolchain.Toolchain, which means it does not need to inherit directly from Toolchain but can also be used on top of other existing toolchain objects (e.g. GCC, OpenMPI, etc.).