Build dependencies are not dependencies¶
Two lists, almost the same syntax. Only dependencies appear in the generated module.¶
Two lists. Mixing them ships CMake to users or hides HDF5 from them.¶
Two lists. A builddependency is gone after install. A dependency is what the module still needs.¶
What you will be able to do
Say which list a given dependency belongs in.
Say what each list does to the generated module.
Recognise both directions of getting it wrong from the symptom.
Two lists have nearly identical syntax. The distinction determines what a user receives upon loading the module.
A four-tuple is legal too: ('CMake', '2.8.12', '', ('GCC', '4.8.2')).
The empty third field is the versionsuffix. The fourth field is a
per-dependency toolchain, used when that one item is not on the
parent’s toolchain. The four-tuple form is the one in
the official dependency specs.
hiddendependencies is a third list: the robot still walks it.
The generated module loads those modules as hidden: they do not
appear in module avail, and they still load.
During prepare, both lists are loaded into the build environment.
Reading the easyconfig shows the lists interchangeable.
The walk below treats both lists as robot items to locate.
Swapping a name from dependencies to builddependencies still makes the walk list it.
The generated module marks the divergence.
The listing is a recording of a real run.
They are not. The modulefile shows where the difference appears.
The module is the difference¶
Seven dependencies went in; four came out
$ sed -n '/^depends_on/p' \
> /sw/arch/.../modulefiles/chem/QMCPACK/4.4.0-foss-2025a.lua
depends_on("foss/2025a")
depends_on("Boost/1.88.0-GCC-14.2.0")
depends_on("HDF5/1.14.6-gompi-2025a")
depends_on("Python/3.13.1-GCCcore-14.2.0")
depends_on("libxml2/2.13.4-GCCcore-14.2.0")
Recorded: SURF Snellius, generated modulefile, 2026-09-10, EasyBuild 5.3.1
The toolchain and the four dependencies are listed.
CMake, Ninja and pkgconf are absent.
This absence is intentional.
The rule: ``dependencies`` go into the module, and ``builddependencies`` do not. Each entry becomes a runtime question rather than a build question.
The issue is not whether the build needs it.
The question: when a user runs this software, does the software still need it?.
QMCPACK needs HDF5 at run time.
CMake is not needed.
Getting it wrong in each direction¶
The two mistakes fail differently.
Neither error message names the list.
A runtime dependency in ``builddependencies``. The build succeeds.
The sanity check may pass.
The module lacks a depends_on.
A user loads it.
The binary cannot locate a shared library.
The binary locates a different version that was already loaded.
The failure occurs at runtime for another user.
The failure resembles What “installed” means’s third check if that check is executed.
A build tool in ``dependencies``. Everything works.
This mistake never announces itself.
It incurs costs in two ways.
Every user of the module now loads CMake regardless of need.
That can result in a user having a CMake older than the one they installed themselves.
The user does not know its source.
It also constrains the module.
Anything that conflicts with that CMake now conflicts with the package without reason.
Neither issue is caught by a build.
Both are detected by asking the runtime question for each entry once.
The awkward cases¶
Four cases where the answer is not obvious.
Python is in dependencies rather than builddependencies in the sample above.
A package that merely uses Python to generate a header at build time puts it in builddependencies.
The same package name appears in a different list depending on what survives into the install.
A header-only library is illustrated by Boost.
Nothing links against a header‑only library.
A strict reading classifies it as build‑time.
It is in dependencies here.
Parts of Boost are not header‑only.
Being wrong in this direction adds a needless module load.
Being wrong in the other direction causes a runtime failure.
A code generator whose output is compiled in.
The classification is build‑time always.
The generated code resides in the binary.
The generator does not reside in the binary.
When the list cannot be decided the tie‑break uses ldd on the installed binaries and grep through the installed scripts.
If nothing in the install mentions it, it is a build dependency.
The assembler is the case that hides best.
binutils provides as and ld.
The compiler writes assembly, and as turns it into object code.
A newer GCC emits directives that an older as does not know.
So a GCCcore easyconfig that compiles anything lists binutils in builddependencies, at the version its GCCcore was built with.
Leaving it out still builds on any host whose own as is new enough.
easyconfigs PR 26973 shows the failure.
It added the Rust tool rsx for GCCcore/15.2.0 without binutils.
The build passed in EESSI and failed on a Rocky Linux 9 test node.
A crate, zstd-sys, compiled C through the cc crate.
GCC 15 wrote a .base64 directive, and the older system as stopped at it:
Error: unknown pseudo-op: `.base64'
The fix was one line, ('binutils', '2.45'), and the next test passed.
The same stack somewhere else explains why EESSI never saw the error.
And the hierarchy still applies¶
Another constraint comes from A toolchain is a hierarchy.
The modulefile lists the versions Boost/1.88.0-GCC-14.2.0, HDF5/1.14.6-gompi-2025a, and Python/3.13.1-GCCcore-14.2.0.
These three toolchains belong to the hierarchy of foss-2025a.
None of them equals foss.
The easyconfig specifies only ('Boost', '1.88.0').
The robot resolves each entry to the hierarchy member that actually provides it.
The resolution shortens the dependency list.
The resolution makes the modulefile specific.
Both lists resolve in this manner.
The build dependency follows the same hierarchy rule.
The build dependency fails to resolve under the same conditions.
EB-Builddeps-1 — Which list, and how to check
You are writing an easyconfig for a C++ application. It needs:
CMake, to configure.
Catch2, a header-only test framework, used only by its test suite.
HDF5, which it links against.
Python, which its build uses to generate a lookup table, and which it also installs three analysis scripts for.
pkgconf, so CMake can find things.binutils, since the toolchain isGCCcore.
Put each in a list. Then say what single command on the finished install would confirm you got HDF5 right, and what would confirm Python.
Solution
builddependencies: CMake, Catch2, pkgconf, binutils. Catch2 is used
during the build and is not installed; if the easyconfig installed the
test binary for users to run, the answer would change. binutils assembles
what GCCcore emits and ends in no installed file.
dependencies: HDF5, Python. Python is in both roles and the runtime one
wins. The build-time Python is long gone.
For HDF5: ldd on the installed binaries, looking for
libhdf5.so. If the binary links it, the module must declare it.
For Python: read the installed scripts. A #! line naming an interpreter,
or an import of anything, means the install needs Python at run time.
If the only Python in the whole install was a build-time generator whose
output is a compiled-in table, it would be a build dependency.
Both checks look at the install rather than the easyconfig, which is the point: the lists are a claim about the finished thing, and the finished thing can be asked.
What to remember
builddependenciesare not in the module.dependenciesare.Python can appear in both lists. The runtime one wins after the build is gone.
Confirm the claim by reading the install rather than the easyconfig.
A
GCCcoreeasyconfig that compiles code listsbinutilsinbuilddependencies; a host with a new enoughashides the omission.