Writing an easyblock

What you will be able to do

  1. Decide whether a build needs an easyblock or a parameter.

  2. Write one: which class to derive from, which methods to override, and how to add a parameter of your own.

  3. Load it without installing it anywhere, and test it without building anything.

Before this chapter

An easyblock is Python code executed by EasyBuild. Therefore it is the most powerful element in this book and the one most frequently used prematurely.

The decision, before the code

EasyBuild 5.3.1 provides 82 generic easyblock classes across 47 modules and 282 software‑specific classes. The generic classes exist. Three questions are posed in sequence, and the first two normally resolve the issue. Can ``configopts`` say it? A build that requires an unusual flag, an environment variable, or a command before configure or after install does not need additional code. preconfigopts, configopts, prebuildopts, buildopts, preinstallopts, installopts and postinstallcmds are strings inserted into the commands described in How a build executes, and they handle more cases than expected. Is there a generic easyblock for this build system? CMakeMake, MesonNinja, PythonPackage, Cargo, Bundle, Tarball, Binary, MakeCp, PerlModule are available. Deriving from the appropriate class and overriding a single method requires only a fraction of the effort of building from scratch, and this approach is recommended in EasyBuild documentation. Will more than one easyconfig need it? This question determines the true threshold. An easyblock represents code maintained by a site across EasyBuild versions. A single package with one unusual step can be addressed with a postinstallcmds line and a comment. Multiple versions of the same package each requiring an unusual step justify creating an easyblock.

What to derive from

EasyBlock, located in easybuild.framework.easyblock, is abstract. It is not abstract by convention:

Three methods every easyblock must supply

$ python3 -c 'import inspect
> from easybuild.framework.easyblock import EasyBlock
> print(inspect.getsource(EasyBlock.install_step))'
    def install_step(self):
        """Dummy install step, should be implemented in derived classes."""
        raise NotImplementedError

Recorded: EasyBuild 5.3.1 from PyPI, local workstation, 2026-09-10

The base class raises NotImplementedError for configure_step, build_step and install_step. All other steps provide a functional default. An easyblock that inherits from ConfigureMake to be only twenty lines long. Mechanical naming rules must be applied correctly, since EasyBuild locates an easyblock by computing its name instead of performing a lookup.

That encoding (see ) is what An easyconfig is Python’s widget solves. The book has one.

The methods that actually get overridden

easyblock = 'EB_example'

name = 'example'
version = '1.0'
toolchain = {'name': 'foss', 'version': '2025a'}

And beside it, the easyblock:

import os

from easybuild.easyblocks.generic.configuremake import ConfigureMake
from easybuild.framework.easyconfig import CUSTOM
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.environment import setvar
from easybuild.tools.filetools import apply_regex_substitutions


class EB_example(ConfigureMake):
    """Support for building/installing example."""

    @staticmethod
    def extra_options(extra_vars=None):
        """Custom easyconfig parameters for example."""
        extra_vars = ConfigureMake.extra_options(extra_vars)
        extra_vars.update({
            'with_widgets': [False, "Build the widget backend", CUSTOM],
        })
        return extra_vars

    def configure_step(self):
        """Patch the makefile template, then configure as usual."""
        apply_regex_substitutions('Makefile.in',
                                  [(r'^CC\s*=.*', 'CC = %s' % os.getenv('CC'))])
        if self.cfg['with_widgets']:
            self.cfg.update('configopts', '--enable-widgets')
        super(EB_example, self).configure_step()

Four things in that are the whole idiom.

``self.cfg[…]`` is the easyconfig. Every parameter is readable, and self.cfg.update appends to a string or list parameter rather than replacing it, which matters when the easyconfig also set it. self.name, self.version, self.toolchain and self.all_dependencies are shortcuts to the ones used constantly.

``extra_options`` is how a parameter is born. A @staticmethod returning a dict of name: [default, help, category], merged with the parent’s. The category is CUSTOM for an optional parameter and MANDATORY for one the easyconfig must set. Once declared, it is a real parameter: eb -a -e EB_example lists it, and an easyconfig that misspells it gets the unknown-parameter warning from Writing one from nothing.

Call the parent, and know where. super().configure_step() last means “run the subclass preparation, then configure normally”. First means “configure, then fix up what it produced”. Getting that order wrong produces a build that works on your machine and fails on a clean one.

Use ``setvar``, not ``os.environ``. EasyBuild tracks environment changes so they can be logged and reported under Reading a failed build’s extended dry run. A direct write to os.environ happens invisibly and does not appear in -x output at all.

One correction to make before copying an example from the upstream documentation: that page still documents run_cmd and run_cmd_qa, which EasyBuild 5 replaced with run_shell_cmd. The framework’s own step methods use run_shell_cmd, and there is a migration page for the change. An easyblock written from that page will import a function that is no longer there.

Loading one without installing it

An easyblock does not need to be installed into EasyBuild to try it.

eb example-1.0.eb --include-easyblocks=$HOME/myeb/*.py

--include-easyblocks accepts a comma‑separated list of paths, which may be absolute, relative, or globbed, and expects generic easyblocks in a subdirectory named generic. The option builds a temporary directory with the correct Python package layout, symlinks the modules into it, and places that directory first on the Python search path, thereby defining precedence. an included easyblock overrides the shipped one, which is desirable for testing but must not remain in a site configuration. eb --list-easyblocks=detailed reports the exact file that was loaded, including its path. This check is useful when an easyblock seems ineffective. The mutually exclusive options --include-easyblocks-from-pr and --include-easyblocks-from-commit provide the same functionality for upstream work in progress.

Testing it without building anything

The documented order is a good one. ``eb -x``, the extended dry run, walks the whole procedure and reports what each step would do, including commands and environment changes. It ends with (no ignored errors during dry run) when nothing went wrong, and that line is the indicator to look for instead of the absence of a traceback. ``eb –module-only –force``, skips to the module step and generates the modulefile. The easyblock’s exports are readable before any compilation. A trap associated with the second command is that under --module-only, most \*_step methods do not run. an easyblock whose configure_step sets self.foo and whose sanity_check_step reads it will encounter None. The fix is a helper method that both steps call, avoiding state carried between steps. A real build follows, then Testing, and the four things it can mean.

EB-Easyblock-1 — Four requests, and only one of them is code

For each, say whether you would write an easyblock, and what you would do instead if not.

  1. A package whose configure needs --with-hdf5 pointing at the HDF5 module’s root.

  2. A package that ships a Makefile.in with CC = gcc hardcoded, and has done for six releases.

  3. A package that installs correctly but leaves its binaries in libexec/, so the module exports nothing on $PATH.

  4. A package whose build must be run twice, with different flags, and the second run needs a file the first one produced.

Solution
  1. No easyblock. configopts = '--with-hdf5=$EBROOTHDF5', using the variable from What a module exports that the dependency’s module set during prepare.

  2. An easyblock, and this is the case that justifies one. Six releases means the substitution will be needed again, so apply_regex_substitutions in configure_step is worth maintaining. A patch would work for one version and conflict on the next, which is Patches, and what a patch is relative to’s argument.

  3. No easyblock. modextrapaths = {'PATH': 'libexec'}, which is What a module exports. Nothing about the build changed; only what the module says about it.

  4. Probably no easyblock. A list-valued configopts makes EasyBuild iterate the whole ready-to-extensions block once per entry, which is exactly two builds with different flags, and it is How a build executes’s postiter step putting the environment back between them. Reach for an easyblock only if the second run needs to find the first one’s output in a way no flag can express, and then say so in a comment person will assume the iteration would have worked.

What to remember

  • Reach for configopts first, a generic easyblock second, and your own code only when more than one easyconfig will need it.

  • configure_step, build_step and install_step are the three the abstract class does not implement.

  • extra_options is how a parameter is born; setvar is how an environment variable is set so the dry run can see it.

  • --include-easyblocks shadows the shipped easyblock, and --list-easyblocks=detailed is how to confirm which file loaded.

  • Under --module-only most steps do not run, so state carried between steps arrives as None.