Skip to content

Added documentation for entrypoints #331

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

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
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
149 changes: 149 additions & 0 deletions docs/entrypoints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Entrypoints {: #entrypoints }

[Entrypoints](https://packaging.python.org/en/latest/specifications/entry-points/) 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][available-hooks].
Furthermore, 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 hooks detected using `--hooks` where 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.
- **toolchain** (`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.

- **Hooks**: `EntrypointHook`
- **EasyBlocks**: `EntrypointEasyblock`
- **Toolchains**: `EntrypointToolchain`

## Validation {: #entrypoints_validation }

Entrypoints are validated the moment they are registered:

- **Hooks**: The entrypoint will check the the required combination of `step`, `pre_step` and `post_step` leads to a valid hook that will actually be ran.
- **EasyBlocks**: The entrypoint will check that the easyblock is a subclass of `easybuild.framework.easyblock.EasyBlock`.
Furthermore, to avoid conflicts, an error will be raised if 2 separate modules attempts to register the same easyblock name.
For 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 `easybuild.tools.toolchain.Toolchain`.
Furthermore, to avoid conflicts, an error will be raised if 2 separate modules attempts to register the same toolchain name (tc.NAME).
The `prepend` argument used for the decorator class, will determine whether the entrypoint toolchain is prepended to the list of available toolchains or not.
This allows to override an existing toolchain, or to only add a new one to the list of available toolchains.


## Examples {: #entrypoints_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:

```toml
...

[project.entry-points."GROUP_NAME"]
"ENTRYPOINT_NAME1" = "import_path1:object1"
"ENTRYPOINT_NAME2" = "import_path2:object2"

...
```

Where `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.
EG: `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:

```text
my_package/
├── my_module
│ ├── __init__.py
│ ├── easyblock.py
│ ├── hook.py
| └── toolchain.py
└── pyproject.toml

```

### Hooks {: #entrypoints_examples_hooks }

`pyproject.toml` example:

```toml
...

[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:

```python
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 {: #entrypoints_examples_easyblocks }

`pyproject.toml` example:

```toml
...

[project.entry-points."easybuild.easyblocks"]
"my_easyblock" = "my_module.easyblock:MyEasyBlock"

...
```

And the `my_module/easyblock.py` file would look like this:

```python
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 {: #entrypoints_examples_toolchain }

`pyproject.toml` example:

```toml
...

[project.entry-points."easybuild.toolchains"]
"my_toolchain" = "my_module.toolchain:MyToolchain"
...
```

And the `my_module/toolchain.py` file would look like this:

```python
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.).

1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ nav:
- Using external modules: using-external-modules.md
- Wrapping dependencies: wrapping-dependencies.md
- Easystack files: easystack-files.md
- Entrypoints: entrypoints.md
- Installing extensions in parallel: installing-extensions-in-parallel.md
- For developers/contributors:
- Archived easyconfigs: archived-easyconfigs.md
Expand Down