An easyconfig is Python¶
Questions
Why is one name quoted and the other not?
When is
%(version)ssubstituted: while the file runs, or after?What is an easyconfig without an
easyblockline claiming?
What you will be able to do
Say why
SYSTEMcarries no quotes and'tools'does.Predict the easyblock class EasyBuild will look for, from a software name alone, and read one backwards to the name.
Say what an easyconfig with no
easyblockline is claiming.Read an EasyBuild error for the assumption behind it rather than the state it reports.
The file installs VS Code in a browser on a supercomputer. It consists of twenty lines. Recipes of this shape are merged weekly.
The file reads as YAML would: names on the left, values on the right, no functions, no if.
Line seven breaks that reading.
SYSTEM has no quotes.
moduleclass does.
That is not a house style.
EasyBuild executed this file as Python, and SYSTEM was already a variable in the evaluation namespace.
Once that is visible, a lot of the rest of EasyBuild stops being folklore.
The templating is Python’s own %.
The missing easyblock line is a claim that a class exists.
The quoting is not inconsistent.
One of those names was supplied.
The other was typed.
Why that distinction earns its place in chapter one¶
EasyBuild executes an easyconfig in a namespace it has prepared, which carries the set of names the file is allowed to use.
SYSTEM on line seven is not a keyword and not a string.
It is a value EasyBuild put in the namespace before evaluating the file: {'name': 'system', 'version': 'system'}.
That is why it has no quotes around it while moduleclass does.
One is a variable, the other is a literal.
Run the sample at the top and read the model it gives back.
toolchain comes out as a structure with a name and a version.
The text SYSTEM is gone by then.
Look at the last two lines of that output. The file names no easyblock, and the model reports the one EasyBuild will derive from the name anyway. That is the rest of this chapter.
The same fact explains a thing that trips people up later.
Since the file is Python, it can compute.
A list comprehension in an easyconfig is legal.
A loop that builds exts_list from a table is legal.
Almost no easyconfig does either, and the convention against it is a good one.
But it is a convention.
Nothing in the format forbids computation.
The things in angle brackets are not in angle brackets¶
Look at sources:
%(version)s is old‑style Python string formatting applied after the file runs, to a dictionary that EasyBuild assembles from the easyconfig.
The file stores the literal.
%(version)s works even when version is set below sources.
EasyBuild adds that second pass, plus names such as SOURCE_TAR_GZ that are in the eval namespace.
Run the sample and observe %(version)s resolve.
Then change the version and observe it move.
Every value derived from it moves with it, which constitutes the entirety of templating.
The one that does not resolve is %(mapped_arch)s, and nothing in the file sets mapped_arch.
On a cluster it resolves anyway, to amd64 on x86-64.
It resolves. That something is the easyblock, and it is the subject of the remainder of this chapter.
EasyBuild derives the easyblock from the name, and the derivation is a string transformation¶
The easyconfig above never states how to install anything.
No easyblock line, no configure flags, no make invocation, no install prefix.
It lists what to download and what the checksums are, then stops.
So how does it get installed?
EasyBuild searches for an easyblock named after the software, using a name mangling.
The package is code-server.
The module it searches for is code_server, and the class inside it is:
EB_code_minus_server.
The hyphen became the literal word minus.
That is not a joke at EasyBuild’s expense. It answers a real problem. Class names have to be valid Python identifiers and package names are not, so something has to bridge the two. The bridge also has to be reversible: without that, two differently-named packages could collide on one class. Spelling the punctuation out gives both.
The framework’s own justification names c++ and C#, and both appear in the sample above.
Run it and watch c++ become EB_c_plus__plus_.
The doubled separator is the tell: the substitution runs per character and collapses nothing.
The consequence people trip over stays invisible in code-server.
Try Python-bundle-PyPI, then try a name with _ in it.
The character is itself encoded, to _underscore_.
That is what keeps the mapping reversible.
Left alone, _minus_ would be ambiguous: an encoded hyphen, or a name that really did spell out minus between two of them.
The class name is longer and stranger than expected.
That is the moment people decide the lookup is broken rather than mechanical.
Surprising exactly once, and then it explains a whole category of “why is the easyblock not being found”.
EB-Python-1 — Read a class name backwards
A traceback names a class you have never seen:
EB_Python_minus_bundle_minus_PyPI
What software is it?
Two different packages are called
a-banda_minus_b. Work out both class names. Does the encoding keep them apart?
Use the sample above for the second one rather than doing it in your head.
Solution
Python-bundle-PyPI. Each _minus_ is a hyphen, and nothing else in the
name was transformed.
a-b gives EB_a_minus_b. a_minus_b gives
EB_a_underscore_minus_underscore_b
too. So they stay apart, and that is the entire reason the underscore is
not left alone: without encoding it, both names would collide on
EB_a_minus_b and one package would silently get the other’s easyblock.
The class for
code-serverexists and performs three actions:It computes
mapped_arch, the source of that template value.Any architecture that is neither x86-64 nor aarch64 raises an error.
It defaults the install step to copying the unpacked tree into place.
It runs
code-server --helpas a sanity check.The install is not finished until the binary has run.
None of that appears in the easyconfig.
All of it is implied by the name.
What happens when the class does not exist¶
Guess before reading on.
The first answer is wrong, and it is the wrong answer that gets carried around.
That answer: EasyBuild falls back to something generic, ConfigureMake being the usual guess, and tries ./configure && make && make install.
It does not.
EasyBuild has no default.
If no easyblock is given and no software-specific class can be imported, the build stops:
No software-specific easyblock 'EB_example' found for example
The function get_easyblock_class in easybuild/framework/easyconfig/easyconfig.py raises the exit code MISSING_EASYBLOCK.
The parameters error_on_missing_easyblock and error_on_failed_import both default to true and are not overridden by the build path.
The only caller that passes false is the extensions machinery, where falling through to a generic extension handler is the correct behaviour.
Reversing that description yields a tool.
A missing easyblock line does not indicate a shrug.
It positively claims that a software-specific easyblock exists.
Thus, in the sample at the top of this chapter, the missing line reports that code_server.py is present before any search occurs.
An easyconfig that does specify easyblock = 'ConfigureMake' is not verbose; naming a generic easyblock is the only way to obtain one.
A second, more severe failure is hidden behind the same code.
An existing easyblock module with a broken internal import also raises ImportError.
EasyBuild separates the two cases by regex‑matching the error message against the requested module name.
A match indicates “no such easyblock”.
Any other case is reported as Failed to import... easyblock.
That second message indicates that the easyblock is present but its own dependencies cause the problem, and editing the easyconfig will not help.
An aside on reading the error¶
All content in this chapter concerns the inferences made by EasyBuild. An example of a wrong inference from a real run on a real cluster is presented. The package is QMCPACK, a quantum Monte Carlo code, and the easyconfig was moved from a pinned development commit to the 4.4.0 release.
A build that got everything right and failed anyway
== processing EasyBuild easyconfig
/sw/eb/easyconfigs-surf/q/QMCPACK/QMCPACK-4.4.0-foss-2025a.eb
== building and installing QMCPACK/4.4.0-foss-2025a...
== fetching files and verifying checksums...
>> download succeeded:
https://github.com/QMCPACK/QMCPACK/archive/v4.4.0.tar.gz
>> sources:
>> qmcpack-4.4.0.tar.gz
[SHA256: 72accd955b59f9040ac8643059bc26c031e94b355299eecd293d10a872b6fc15]
== unpacking...
>> running shell command:
tar xzf qmcpack-4.4.0.tar.gz
>> command completed: exit 0, ran in 00h00m02s
== patching...
== preparing...
>> loading toolchain module: foss/2025a
>> loading modules for build dependencies:
>> * CMake/3.31.3-GCCcore-14.2.0
>> * Ninja/1.12.1-GCCcore-14.2.0
>> * pkgconf/2.3.0-GCCcore-14.2.0
== FAILED: Installation ended unsuccessfully: Specified start dir
/tmp/jenkins/build/QMCPACK/4.4.0/foss-2025a/qmcpack-4.4.0/qmcpack does not exist
Recorded: SURF Snellius, production run 20260909_063530, snel-zen4_h100, EasyBuild 5.3.1
The earlier steps were read until they stopped.
The correct URL was used.
The checksum was verified against the archive.
tar xzf returned exit code 0.
Patching, the toolchain, and every build dependency were all fine.
Every check that would be expected to run had already succeeded.
A required path does not exist.
The error names the consequence.
Something looked for qmcpack-4.4.0/qmcpack and did not find it.
The message says that and nothing else.
The identity of the lookup and the expectation are missing.
Answering those two questions constitutes the work.
The lever is already provided in this chapter.
The easyconfig is a Python file, so the expected path is a value assigned in it.
The value was start_dir, and it was set to qmcpack.
Five minutes earlier that value had been correct.
The easyconfig was built from a git checkout, and EasyBuild archives a clone under the repository’s name, so the sources resided one level down inside qmcpack/.
A release tarball unpacks to qmcpack-4.4.0/ with the sources at the top level.
The line was correct for the old source and incorrect for the new one.
The change from one to the other says nothing about start_dir.
A complete recipe contains one hole.
The lint sample names the hole; it does not fill it.
EB-Python-fade-1 — Fill the hole the linter names
This recipe is otherwise complete. The lint widget reports a missing checksums list. What is the smallest addition that makes the linter quiet, and what does that addition still not prove?
Solution
A checksums list with one sha256, aligned with sources. The
in-page linter then stops reporting the missing list. That does not
prove the hash is the hash of the archive: verification is a later
step, on a machine that can fetch the file.
EB-Python-2 — The smallest change that fixes it
The recipe above fails with:
Specified start dir /tmp/.../qmcpack-4.4.0/qmcpack does not exist
You have the released tarball, and tar tzf on it shows every path
beginning qmcpack-4.4.0/.
What is the smallest change to the easyconfig that fixes this, and what would you have to check before making it?
Solution
Delete the start_dir line.
start_dir is relative to the unpacked source directory. The tarball
already puts the sources at the top of qmcpack-4.4.0/, which is where
EasyBuild starts by default, so the parameter has nothing left to say.
What to check first: that no other parameter assumes the old layout. Patch strip levels and any path naming a subdirectory were written against a git checkout archived under the repository’s name, and they are quiet in exactly the same way. A diff against the previous recipe will report them unchanged, which is the wrong question.
Two observations are relevant, and neither concerns QMCPACK.
First: An EasyBuild error reports the state it discovered.
The assumption underlying that state is absent from the message.
“Does not exist” is accurate but not the interesting fact.
What expected the path to exist is the interesting part, and it resides in the easyconfig or in the EasyBlock selected by the easyconfig’s name.
Second: The parameters that break on a version bump describe the source layout.
The software’s own requirements remain unchanged.
The archive’s layout changes.
A checksum change is prominent and cannot be ignored.
start_dir, patch strip levels, and any subdirectory name are quiet.
They were correct previously, and a diff against the prior easyconfig shows them unchanged.
Unchanged is not the relevant question.
The question is whether they still apply to the archive currently being unpacked.
Three consequences¶
An easyconfig is a Python file evaluated in a prepared namespace, and all three of these appear constantly.
Unquoted names like SYSTEM are variables EasyBuild supplied.
That is why the quoting looks inconsistent and is not.
%(...)s is Python’s own string formatting, over a dictionary built from the easyconfig plus whatever the easyblock adds.
So a template can resolve to something the file never mentions.
The easyblock comes from transforming the software’s name.
With no specific one available the build stops rather than guessing, which makes a missing easyblock line a claim that one exists.
The next chapter is about toolchain.
It is the one line in the sample still unread, and it causes more confusion than the other nineteen together.