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.
What EasyBuild writes.¶
What other solvers write. EasyBuild does not.¶
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.
That mapping is still not a range here; see A wrapper is the range EasyBuild will not write.
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.
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 is %(pyshortver)s rather than 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¶
A software stack built by one organisation for one year must be reproducible.
A version range undermines reproducibility.
When 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.
A resolver that allows ranges would produce one HDF5 per distinct
range the solver picked.
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.
The move can result from a user locked onto the older stack.
The move can result from a site performing backfilling.
The easyconfig specifies HDF5 1.14.6.
foss-2024a provides HDF5 1.14.5.
The build fails.
No HDF5 1.14.6 exists in that generation.
No such version will appear later.
The content of the easyconfig states “1.14.6”.
Version 1.14.6 is what that generation contained when the easyconfig was authored.
The number records the build environment.
The software itself stated no such requirement.
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.
Why retargeting is not a text edit¶
The first move is to open the easyconfig and replace 2025a with 2024a.
The build fails on HDF5.
The version changes from 1.14.6 to 1.14.5.
The build then fails on netCDF.
The version provided by foss-2024a is looked up and inserted.
Twenty minutes later the easyconfig builds.
The recipe does not record which numbers were requirements and which were incidental.
The right shape is different.
The old easyconfig’s versions are evidence about what the package
was once seen to work with, in that generation.
The target generation’s available modules are the candidates.
Look up what that generation provides. Do not carry the old numbers
across as floors a solver must satisfy.
Where the real lower bound lives¶
The minimum, if it exists, is written down somewhere else, 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. It is worth having, and it is 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: a pip
specifier or a Spack @1.14.6: range. Spack @1.14.6 is the
closed range @1.14.6:1.14.6. 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.5 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.5 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.5 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.
“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. The easyconfig has no dependencies. The easyconfig pins a version with no dependencies. The pin is still a pin. The easyconfig 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.
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.
Both statements are 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”.
An easyconfig cannot tell the difference.
One thing to do differently¶
When an easyconfig moves to a new generation, record each changed version’s source in the commit message.
Possible sources include the project’s own build system, another packaging format that can express a condition, or the shelf.
The next person moving this file will see which entry is which.
The following page 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.
What to remember
A version beside a dependency is a pin for one generation, not a minimum.
Spack
@1.14.6is the closed range@1.14.6:1.14.6. Or-newer is@1.14.6:.The same number on another generation is a different module.