A hook changes a file you are not reading¶
A hook changes a file that is not the one on the page. It starts with a guard. An overlay is a different tool.¶
A hook runs on a file the reader is not looking at. The easyconfig on the page is not the last word.¶
A hook runs on a stage. The easyconfig on the page is not the file the hook rewrites.¶
What you will be able to do
Say what a hook is and when it runs.
Say what condition a hook puts on An easyconfig is Python’s way of reading a failure.
Spot the one-line hook bug that silently discards an easyconfig’s own parameter.
Each preceding page read an easyconfig and derived reasoning from it. This page discusses the mechanism that renders that reasoning incomplete. The page shows why a site still adopts it.
The problem a hook solves¶
Upstream provides the easyconfig. A site defines policies for each build it runs. These policies include installation locations, module naming, required compiler flags, and local machine requirements. Such information does not belong in the easyconfig. Placing it there causes the site to fork the upstream file. Each version bump becomes a merge. Each policy change triggers a sweep across thousands of files. Thus EasyBuild takes a file of Python functions and calls them at defined points.
eb GROMACS-2025.2-foss-2025a.eb -r --hooks=/my/site/hooks.py
Functions are located by their names.
parse_hook runs after an easyconfig is parsed.
pre_configure_hook runs before the configure step.
post_module_hook runs after the modulefile is written.
Each step has exactly one name.
Registration or decorator is not required; the name provides the subscription.
parse_hook is the most significant.
A modification there changes what is built rather than only how.
A hook sees everything, so it starts with a guard¶
The outline of an example, reduced to its structure:
Three guard lines precede one work line.
The guards are the interesting part.
The first guard tests the package.
A hook is called for every easyconfig the site builds.
Without that test the guard would fire on all of them.
The second guard tests the hostname.
The second guard is a hook that performs the purpose of a hook.
It is a workaround for a specific set of machines.
It is expressed once.
It is applied to any easyconfig that meets it.
Both guards are ordinary Python.
They are evaluated at build time.
Neither appears in any easyconfig.
The line of work is where hooks go wrong¶
Look at the assignment:
ec['postinstallcmds'] = [...]
The parameter is replaced.
Anything declared in postinstallcmds by the easyconfig disappears without diagnostic.
Compare:
ec['postinstallcmds'] = ec['postinstallcmds'] + [...]
Two versions of a single hook vary by a few characters.
The difference decides if an easyconfig’s own post-install steps survive.
The hazard remains silent in the ordinary case.
Most easyconfigs declare no postinstallcmds.
Replacing an empty list with a one-element list matches the intent.
It becomes a defect when postinstallcmds are added to an easyconfig.
The hook fires on that easyconfig.
The easyconfig is correct.
The hook is unchanged.
The commands do not run.
This applies to every list- or dictionary-valued parameter a hook touches.
dependencies, configopts, modextravars, patches: assignment discards.
No warning is issued.
Which qualifies An easyconfig is Python¶
An easyconfig is written in Python. The easyconfig resolved a failure using this inference:
The easyconfig is a Python file, so whatever expected that path is a
value somebody assigned in it.
A hook makes the statement false.
The value may be assigned in the hook after parsing the file.
The file on disk does not mention it.
The inference requires an attached condition.
The condition is mandatory at sites that use hooks.
Reading a failure requires knowing whether a hook is loaded and what it touches.
It must be known before concluding that an easyconfig says what it appears to say.
The practical approach is to find out what --hooks is set to in the configuration or the wrapper script that invoked eb.
Read that file once.
Most are short.
The guards report immediately whether the hook can have applied to your build.
EB-Hook-1 — The commands that never ran
A site hook has worked for two years:
def parse_hook(ec, *args, **kwargs):
if ec.name != 'NVHPC':
return
ec['postinstallcmds'] = ['echo "set LIBDIR=/usr/lib64;" >> ...']
Somebody adds a postinstallcmds line to the NVHPC easyconfig, to fix up
a set of symlinks. The build passes. The symlinks are not there.
Why not?
Why did nobody notice this in the two years before?
What is the one-line change, and what would you grep for to find the same bug elsewhere in the hook?
Solution
The hook assigns postinstallcmds, so whatever the easyconfig declared
is replaced. Assignment to a list parameter is not an error and produces
no diagnostic, so the build is green and the commands never ran.
Nobody noticed
postinstallcmds, and replacing an empty list with a one-element list is
indistinguishable from appending to it. The defect was there the whole
time, dormant until the value it discards stopped being empty.
The change is ec['postinstallcmds'] = ec['postinstallcmds'] + [...].
To find the rest, grep the hook for assignment to any parameter holding a
list or a dictionary: dependencies, builddependencies, configopts,
patches, modextravars, sanity_check_commands. Every one has the
same failure mode and the same silence.
Hooks and overlays solve different problems¶
The robot’s robot path and the hooks described in this page are the two methods a site uses to alter EasyBuild’s behaviour. They are not interchangeable. An overlay modifies which easyconfig is discovered. An overlay is appropriate when the site requires a different file, such as a newer version, a local patch, or a variant not provided upstream. A hook alters what happens to the easyconfig that was found. A hook is appropriate when the same modification must affect files the site does not own and prefers not to adopt. Choosing the incorrect method yields a characteristic outcome in each case. An overlay employed for policy creates a fork to maintain, with one copy per package, each diverging from upstream at its own pace. A hook used to apply a package-specific change provides a global mechanism whose behaviour depends on a guard. The subsequent reader examines the easyconfig and observes no change.
The condition this puts on an easyconfig as Python¶
Python states that any expected path originates from a value assigned in the easyconfig.
On a site employing hooks, the statement is true only after confirming that no hook has modified it.
Determining this requires reading a short Python file and its guards.
Two rules follow.
The second is problematic.
A hook is invoked for each easyconfig.
Its guards are integral to its behavior as its body.
Assigning a list parameter overwrites the existing value.
ec['postinstallcmds'] = [...] = discards any declaration from the easyconfig without diagnostic output. The effect remains hidden until =postinstallcmds is added to a package on which the hook runs.
Append.
Replace only when replacement is intended.
The subsequent three pages cover the actual implementation steps.
Install into a prefix owned by the operator.
Apply it atop EESSI.
Examine how the engine behind these examples determines its established state.
What to remember
A hook runs in the site’s EasyBuild process, outside the easyconfig being read.
The robot path and hooks are the two ways a site changes behaviour without editing upstream files.
A hook that is not in the log is still what ran.