One easyconfig, many installs

What you will be able to do

  1. Say what exts_list installs, and which earlier chapters then apply per entry.

  2. Say what %(name)s means inside an extension’s options, and what it does not.

  3. Read a failure inside a bundle back to the entry that caused it.

Each easyconfig to date has installed a single component. Below is the template that installs hundreds, forming the majority of a real software stack:

easyblock = 'Bundle'

name = 'ExampleRSet'
version = '1.0'
versionsuffix = '-R-%(rver)s'

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

dependencies = [
    ('R', '4.4.2'),
]

exts_defaultclass = 'RPackage'
exts_filter = ("R -q --no-save", "library(%(ext_name)s)")

exts_default_options = {
    'source_urls': ['https://cran.r-project.org/src/contrib/'],
    'source_tmpl': '%(name)s_%(version)s.tar.gz',
}

exts_list = [
    ('directlabels', '2024.1.21', {
        'checksums': ['9b2e780410bc7812fe155afba9181653293ca657b0b10e1618242dad4d619775'],
    }),
    ('latex2exp', '0.9.6', {
        'checksums': ['fc03bfa663231b60e1edace7b3001a7eb395b4ca468c6a983673845ddd63cb8a'],
    }),
]

Two extensions are present here, and a genuine one of them contains two hundred. Each entry involves its own download, its own build, and its own check. The final module consists of a single module.

The An easyconfig is Python rule inverts here

easyblock = 'Bundle' An easyconfig is Python states that a missing easyblock line claims a software-specific easyblock exists and that naming a generic one is the only way to obtain it. This easyconfig names one. Then look at exts_defaultclass. That is the easyblock for the extensions and it is a default; an entry in exts_list can override it with its own easyblock key. An easyconfig is Python also states that EasyBuild does not guess and that the two parameters which could soften that rule both default to true. There was one exception, named and then set aside:

The one caller that does pass false is the **extensions** machinery,
where falling through to a generic extension handler is the right
behaviour.

The extensions machinery is the caller. Extensions receive a default. The strictness described in An easyconfig is Python arises.

Templates rebind inside an entry

Look at source_tmpl in exts_default_options:

'source_tmpl': '%(name)s_%(version)s.tar.gz'

%(name)s is not ExampleRSet, and %(version)s is not 1.0. Within an extension, those templates resolve to that extension’s own name and version. the line becomes directlabels_2024.1.21.tar.gz for the first entry and latex2exp_0.9.6.tar.gz for the second. That rebinding makes exts_default_options useful. A single source_urls, a single source_tmpl, two hundred extensions, and no repetition result. It also causes confusion, since both scopes share the same spelling. %(version)s inside an extension option does not reach the bundle’s version. Note that %(ext_name)s in exts_filter is a distinct third element, intentionally spelled differently. This occurs.

EB-Extensions-1 — Which name is this

A bundle is named ExampleRSet, version 1.0, and its exts_default_options carries:

'source_tmpl': '%(name)s_%(version)s.tar.gz',

Its exts_list has an entry for latex2exp at 0.9.6.

  1. What filename does that entry download?

  2. A colleague asks for the bundle’s own version in an extension’s option and writes %(version)s there. What do they get?

  3. They then try %(ext_version)s for the extension’s version in the bundle’s own versionsuffix. Why is that a different kind of question?

Solution

latex2exp_0.9.6.tar.gz. Inside an entry, those templates bind to the extension, which is exactly what lets one source_tmpl serve two hundred rows.

They get 0.9.6, the extension’s version, not 1.0. The rebinding is not optional and there is no template inside an entry that reaches back out to the bundle. If they need 1.0 there, they have to write it.

The third is different one of its extensions, and the bundle is evaluated before any of them. %(ext_name)s exists for exts_filter, which is a template the bundle expands once per extension at check time; a versionsuffix is resolved long before that and has no extension in scope to ask about.

Chapters 6 and 7 now apply once per extension

The exts_filter function acts as the sanity check for each extension referenced in What “installed” means. It has an unusual shape.

("R -q --no-save", "library(%(ext_name)s)")

A pair consists of a command and the standard input to feed it. The check for directlabels runs R and asks it to load the library. The Python equivalent is ("python -c 'import %(ext_name)s'", ''), with an empty second element. That is the strong kind of check described in What “installed” means, executed rather than existence‑tested, and a bundle obtains it for every extension at once. Two hundred extensions result in two hundred loads, each providing a real verification. Every entry carries its own checksums, which is A checksum is a claim about bytes two hundred times over. Each extension has its own bytes, its own source URL, and its own opportunity for a checksum that is right about the wrong artifact.

What that costs, and why it matters when reading a failure

A bundle consists of one module and hundreds of builds. The consequences are practical. Two hundred serial downloads and two hundred serial compiles take hours. The useful mental model is “running two hundred small builds in one process” rather than “installing R packages”. A failure can occur in the middle. When extension 140 of 200 fails, the requested package does not fail. One extension fails, and the error identifies that extension. Reading that failure follows An easyconfig is Python’s unchanged discipline. The error names the consequence, and the question is which entry produced it and what that entry declared. The answer is a line in exts_list, plus any exts_default_options key it inherited implicitly. The inheritance is the part worth watching. An entry that fails may have declared almost nothing, taking its source URL, its template, and its easyblock from three different defaults above it. None of those appear in the failure.

What scales, and what does not

exts_list enables a single easyconfig to install multiple packages. Earlier three chapters apply per entry rather than per file. Each extension defines its own sources, checksums, easyblock, and executes a check via exts_filter.

Two aspects do not scale. An easyconfig is Python’s rule is inverted. Templates are rebound: %(name)s inside an entry expands to the extension’s name. This allows a single source_tmpl to generate two hundred rows and causes attempts to retrieve the bundle’s own version to fail.

When a bundle fails, the failure occurs in a single entry. Inspect that entry together with the defaults defined above it.

The next chapter covers hooks, the supported method to modify EasyBuild behavior without editing an easyconfig.