A pin is for one generation¶
Questions
What does
('HDF5', '1.14.6')claim?Does that claim survive a move to another toolchain generation?
Where is the real lower bound, if it is not that pin?
What you will be able to do
Say what a version beside a dependency claims, and what it does not.
Say why that claim stops holding the moment the easyconfig moves to another generation.
Find the real lower bound when there is one, and say where it was found.
Here is a dependency list:
The tuple ('HDF5', '1.14.6') is commonly interpreted as requiring HDF5 version 1.14.6 or newer.
Other packaging systems operate similarly.
In Python, install_requires accepts >=1.14.6.
Cargo interprets 1.14.6 as a compatible version constraint.
Conda uses >=1.14.6,<2 as its constraint syntax.
EasyBuild lacks operators, ranges, carets, and tildes in its syntax.
A dependency consists solely of a name and a specific version.
Resolution requires that exact module; otherwise the build fails.
A site wrap or a ModuleRC .modulerc can map an old name to a newer module; see A wrapper is the range EasyBuild will not write, and this does not constitute a range here.
The official common-toolchain table now includes foss/2026.1 and omits 2024b.
The 2026 maintainer summit decided on a single common generation annually.
The pin law remains unchanged: a version adjacent to a dependency denotes that specific module and generation.
The function _parse_dependency in easybuild/framework/easyconfig/easyconfig.py defines a dependency in its docstring.
dep can be a dict, a tuple or a list. if it is a tuple or a list the
attributes are expected to be in the following order: ('name',
'version', 'versionsuffix', 'toolchain'). of these attributes, 'name'
and 'version' are mandatory
Four positional attributes are defined, two of them are required, and the resulting dict contains a single version string.
A comparator has no place to operate.
The version may be templated. The literal text in the file to be %(pyshortver)s instead of a numeric value.
A template still resolves to a single exact version.
The format does not express a set.
Why that is fine, and then why it is not¶
Within a single generation, exactness is required.
A software stack built by one organisation for one year must be reproducible.
A version range undermines reproducibility.
If foss-2025a provides HDF5 1.14.6, every package on foss-2025a that depends on HDF5 receives 1.14.6.
The stack then contains one HDF5 instance instead of four.
A resolver that allows ranges would produce four instances.
Thus, exactness justifies its role.
Problems arise when the easyconfig outlives the generation for which it was written.
The thing that breaks¶
An easyconfig that works on foss-2025a is moved to foss-2024a, possibly due to a user being locked onto the older stack or a site performing backfilling.
The easyconfig specifies HDF5 1.14.6, while foss-2024a provides HDF5 1.14.3.
The build fails.
No HDF5 1.14.6 exists in that generation, and no such version will appear later.
Examine the content of the easyconfig.
It states “1.14.6”.
The software does not necessarily require 1.14.6.
That is highly unlikely.
It required a feature that was present in an earlier version.
Version 1.14.6 reflects what that generation happened to contain when the easyconfig was authored.
The version number records the build environment.
It provides no information about the package’s actual requirements.
This constitutes the core insight.
A dependency version in an easyconfig is an exact pin for the
generation the easyconfig was written against. Carried across generations,
it states a requirement the software never made.
This is why retargeting is not a text edit¶
The first move is to open the easyconfig and replace 2025a with 2024a.
Then the build fails on HDF5, so the version changes from 1.14.6 to 1.14.3.
Then it fails on netCDF, so the version provided by foss-2024a is looked up and inserted.
Twenty minutes later the easyconfig builds and there is no record of which numbers were requirements and which were incidental.
The right shape is different.
Treat the old easyconfig’s versions as floors: evidence about what the package was once seen to work with.
The target generation’s available modules are the candidates.
Something then must choose a set of candidates that satisfies the floors and remains internally consistent.
That is a solver’s job, not a text editor’s.
The floors themselves only hold within one generation, for the reason above.
When moving to a new generation, the floors must be dropped, and something must decide what the target generation provides instead.
Where the real lower bound lives¶
Dropping the floor is a fallback. The answer lies elsewhere: if the minimum is needed, it is written down, just not in the easyconfig.
The project’s own build system says it.
find_package(HDF5 1.10 REQUIRED)in CMake is a real lower bound, stated by the people who wrote the software.So is a
requires-pythonor a dependency specifier inpyproject.toml.Other packaging ecosystems record it.
A conda-forge recipe or a Spack
package.pykeeps the constraint and the condition it applies under.
Either of those is evidence. The following is not: “the older generation carried an older version, and the build passed”. That tells that a version was sufficient on one machine on one day. Worth having, and a different claim.
When an easyconfig is retargeted and the bound has to be right, the commit message should say which of the two the bound came from.
EB-Pin-mcq-1 — What the second field claims
In dependencies = [('HDF5', '1.14.6')], the second field means:
HDF5 1.14.6 or newer will satisfy the package.
HDF5 1.14.6 is preferred; the robot may pick another 1.14.x.
This recipe, on this toolchain generation, uses HDF5 1.14.6.
The package’s authors require exactly 1.14.6.
Solution
The field is a pin for this generation. EasyBuild does not treat
it as a minimum (1), a preference (2), or a statement about the
upstream authors (4). 1 is the most common wrong model: it is how
conda and Spack read a version. 2 is how a solver with a range
would read it. 4 is what a required_version in the package’s own
docs would say, and that is a different document.
EB-Pin-1 — Two numbers, two different claims
You are moving an easyconfig from foss-2025a to foss-2024a. It pins
two dependencies:
('HDF5', '1.14.6'),
('netCDF', '4.9.3'),
foss-2024a carries HDF5 1.14.3 and netCDF 4.9.2. You change both
numbers and the build passes.
What have you established?
What have you not?
Where would you look to find out whether HDF5 1.14.3 is actually enough for this package, and what would count as an answer?
Solution
You have established that the package builds against those two versions, on that machine, on that day. That is a data point and it is worth having.
You have not established that 1.14.3 satisfies what the software needs. A passing build is consistent with the requirement being 1.10. It is equally consistent with the requirement being 1.14.3 and you having been lucky about which symbols the build exercised.
The real bound is in the project’s own build system, or in a packaging
format that can express a condition. find_package(HDF5 1.10 REQUIRED)
in CMake is an answer. A requires-python or a dependency specifier in
pyproject.toml is an answer. A conda-forge recipe or a Spack
package.py is an answer
constraint.
“The older generation carried an older one and the build passed” is not an answer to that question, though it answers a different one perfectly well.
A worked case¶
Take the code-server easyconfig from An easyconfig is Python. It has no dependencies at all, which makes it a poor example of pinning. It is a perfect example of the other half of this problem: what a generation contains. code-server 4.130.0 is a real, released, upstream easyconfig. EasyBuild’s own 5.2.0 and 5.2.1 releases carried these versions:
4.9.1 4.16.1 4.21.1 4.22.1 4.89.1 4.90.2 4.93.1 4.97.2
4.100.2 4.103.2 4.104.0 4.104.1 4.104.2 4.104.3 4.105.1
The list stops at 4.105.1.
So a site running EasyBuild 5.2 can have a perfectly valid easyconfig for 4.130.0 in front of it and still not build it.
“The easyconfig exists” and “the easyconfig exists in the release in use” are different statements, and both held at once here.
The same distinction applies to pinning.
“HDF5 1.14.6 exists” is not “HDF5 1.14.6 exists in foss-2024a”, and an easyconfig cannot tell the difference.
One thing to do differently¶
When an easyconfig moves to a new generation, record in the commit message the source of each changed version. Possible sources include the project’s own build system, another packaging format that can express a condition, or the shelf. The first two entries represent bounds. The third entry denotes a build environment that happened to be present. The next person who moves this file will appreciate the author for indicating which entry is which. The following chapter describes the machinery that converts a filename into an install plan. It also explains what occurs when the lookup is performed in the wrong location.