One easyconfig, many installs

one easyconfig yields an install per architecture and per extension

One file is many installs. Pages on pins, checksums, and sanity apply once per extension.

one easyconfig, two architectures, two modules

One easyconfig is many installs. Architecture is one axis. Extensions are another.

one recipe times toolchain and arch yields many prefixes

The file is not the install. The same easyconfig is written into as many prefixes as there are (toolchain, arch) pairs.

What you will be able to do

  1. Say what exts_list installs, and which earlier pages 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.

Every easyconfig up to now installs one component. The following template installs hundreds of components. It is most of a real software stack: the applications, with the toolchain left out.

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 appear. One genuine extension contains two hundred. Each entry has its own download, its own build, and its own check. The final module is a single module.

The An easyconfig is Python rule inverts here

easyblock = 'Bundle'. An easyconfig, written in Python, states that a missing easyblock line claims a software-specific easyblock exists. 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. It is a default. An entry in exts_list can override it with its own easyblock key. An easyconfig, written in Python, also states that EasyBuild does not guess. The two parameters that 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 does not equal ExampleRSet. %(version)s does not equal 1.0. Within an extension, those templates resolve to the extension’s own name and version. For the first entry, the line becomes directlabels_2024.1.21.tar.gz. For the second entry, the line becomes latex2exp_0.9.6.tar.gz. That rebinding renders exts_default_options useful. One source_urls and one source_tmpl apply to two hundred extensions without repetition. Both scopes share the same spelling. This causes confusion. %(version)s inside an extension option does not reach the bundle’s version. %(ext_name)s in exts_filter is a distinct third element. It is 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 question is about the bundle, not an extension. 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.

The installed-means checks run once per extension

The exts_filter function checks each extension referenced in What “installed” means. It has an unusual shape.

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

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

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

Each bundle contains one module and hundreds of builds. The implications are practical. Two hundred serial downloads and two hundred serial compiles require hours. The mental model is “running two hundred small builds in one process” rather than “installing R packages”. Failures can occur in the middle. If extension 140 of 200 fails, the requested package continues. One extension fails and the error identifies that extension. Reading that failure follows the unchanged discipline of an easyconfig in Python. The error states the consequence. The question is which entry produced it and what that entry declared. The entry is identified by a line in exts_list. Any exts_default_options key it inherited implicitly also identifies it. The inheritance is the aspect to monitor. An entry that fails may declare almost nothing. It may take its source URL, its template, and its easyblock from three different defaults above it. Those defaults do not appear in the failure.

What scales, and what does not

exts_list allows one easyconfig to install several packages. The previous three pages apply per entry instead of per file. Each extension specifies its own sources. Each extension specifies its own checksums. Each extension specifies its own easyblock. Each extension runs a check using exts_filter. Two aspects fail to scale. The rule that an easyconfig is Python’s is inverted. %(name)s within an entry expands to the extension’s name. A single source_tmpl can produce two hundred rows. Attempts to retrieve the bundle’s own version fail. When a bundle fails, the failure appears in a single entry. Inspect that entry along with the defaults defined above it. The next page discusses hooks. Hooks are the supported way to modify EasyBuild behavior without editing an easyconfig.

toolchain plus arch yields an install prefix

A generation is the set of (toolchain, arch) pairs that move together.

What to remember

  • The file is not the install. The same easyconfig is written into as many prefixes as there are (toolchain, arch) pairs.

  • exts_list is more installs inside one file, not one install with extras.

  • A generation is the set of those pairs that move together.